diff --git a/.github/.hugo-version b/.github/.hugo-version new file mode 100644 index 0000000..b97a9dd --- /dev/null +++ b/.github/.hugo-version @@ -0,0 +1 @@ +0.156.0 diff --git a/.github/vendor/bin/hugo_extended_0.156.0_darwin-universal.pkg b/.github/vendor/bin/hugo_extended_0.156.0_darwin-universal.pkg new file mode 100644 index 0000000..f53ac9a Binary files /dev/null and b/.github/vendor/bin/hugo_extended_0.156.0_darwin-universal.pkg differ diff --git a/.github/vendor/bin/hugo_extended_0.156.0_linux-amd64.deb b/.github/vendor/bin/hugo_extended_0.156.0_linux-amd64.deb new file mode 100644 index 0000000..b2cbf85 Binary files /dev/null and b/.github/vendor/bin/hugo_extended_0.156.0_linux-amd64.deb differ diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..f460461 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,30 @@ +name: build + +on: + push: + branches: + - main + pull_request: + branches: + - main + +permissions: + contents: read + +jobs: + build: + name: build + strategy: + matrix: + os: [ubuntu-latest, macos-latest] + runs-on: ${{ matrix.os }} + + steps: + - name: checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # pin@v6.0.2 + + - name: bootstrap + run: script/bootstrap + + - name: build + run: script/build diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml new file mode 100644 index 0000000..5864342 --- /dev/null +++ b/.github/workflows/lint.yml @@ -0,0 +1,30 @@ +name: lint + +on: + push: + branches: + - main + pull_request: + branches: + - main + +permissions: + contents: read + +jobs: + lint: + name: lint + strategy: + matrix: + os: [ubuntu-latest, macos-latest] + runs-on: ${{ matrix.os }} + + steps: + - name: checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # pin@v6.0.2 + + - name: bootstrap + run: script/bootstrap + + - name: lint + run: script/lint diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..b1f5816 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,30 @@ +name: test + +on: + push: + branches: + - main + pull_request: + branches: + - main + +permissions: + contents: read + +jobs: + test: + name: test + strategy: + matrix: + os: [ubuntu-latest, macos-latest] + runs-on: ${{ matrix.os }} + + steps: + - name: checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # pin@v6.0.2 + + - name: bootstrap + run: script/bootstrap + + - name: test + run: script/test diff --git a/.gitignore b/.gitignore index 0e687e6..984b0c2 100644 --- a/.gitignore +++ b/.gitignore @@ -18,3 +18,8 @@ tmp/ # Local development files *.local + +# Hugo build artifacts +public/ +resources/_gen/ +.hugo_build.lock diff --git a/README.md b/README.md index eb80c05..4d6b3b2 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,9 @@ # dario 🖊️ +[![lint](https://github.com/GrantBirki/dario/actions/workflows/lint.yml/badge.svg)](https://github.com/GrantBirki/dario/actions/workflows/lint.yml) +[![test](https://github.com/GrantBirki/dario/actions/workflows/test.yml/badge.svg)](https://github.com/GrantBirki/dario/actions/workflows/test.yml) +[![build](https://github.com/GrantBirki/dario/actions/workflows/build.yml/badge.svg)](https://github.com/GrantBirki/dario/actions/workflows/build.yml) + A minimal hugo theme inspired by Dario Amodei's personal [website](https://darioamodei.com/). It is designed to be as minimal, performant, and as elegant as possible for reading. View the [live demo](https://log.birki.io) to see what it looks like ([source code](https://github.com/GrantBirki/dario)). @@ -12,6 +16,19 @@ This theme is designed to be minimal and the page speed insights are as follows: ![100](https://raw.githubusercontent.com/GrantBirki/dario/main/docs/assets/100.png) +## Development + +This repository follows the ["scripts to rule them all"](https://github.blog/engineering/scripts-to-rule-them-all/) pattern: + +```bash +script/bootstrap +script/lint +script/test +script/build +``` + +CI executes these same scripts directly. + ## Installation > To view a real example of a project using this theme, check out [grantbirki/log](https://github.com/GrantBirki/log) which is where I have the demo deployed. @@ -188,7 +205,7 @@ Set the `disableSocialMeta` parameter to turn off HTML tags related to [Open Gra ``` If you wish to add your own non-standard meta tags for things like Bitcoin, -PGP, and so on, you can add them in `layouts/partials/nonstdmeta.md`: +PGP, and so on, you can add them in `layouts/partials/nonstdmeta.html`: ```html diff --git a/assets/css/default.css b/assets/css/default.css index 9f77266..30c1942 100644 --- a/assets/css/default.css +++ b/assets/css/default.css @@ -2,16 +2,16 @@ font-family: "Newsreader"; font-style: normal; font-weight: 200 800; - font-display: block; - src: url("/fonts/Newsreader.woff2") format("woff2"); + font-display: swap; + src: url("../fonts/Newsreader.woff2") format("woff2"); } @font-face { font-family: "Newsreader"; font-style: italic; font-weight: 200 800; - font-display: block; - src: url("/fonts/Newsreader-italic.woff2") format("woff2"); + font-display: swap; + src: url("../fonts/Newsreader-italic.woff2") format("woff2"); } :root { @@ -22,6 +22,7 @@ --content-max-inline-size: 620px; --content-padding: 20px; --focus-color: #0066cc; + --muted-text-color: #666666; } body, @@ -55,6 +56,7 @@ header h1 a { --invert-percentage: 100%; --code-bg: rgb(55, 56, 62); --hljs-bg: rgb(46, 46, 51); + --muted-text-color: #b3b3b3; } .dark-mode-off:root { @@ -76,6 +78,7 @@ header h1 a { --invert-percentage: 0%; --code-bg: rgb(223, 223, 223); --hljs-bg: rgb(28, 29, 33); + --muted-text-color: #666666; } @media (prefers-color-scheme: dark) { @@ -98,6 +101,7 @@ header h1 a { --invert-percentage: 100%; --code-bg: rgb(55, 56, 62); --hljs-bg: rgb(46, 46, 51); + --muted-text-color: #b3b3b3; } } @@ -121,6 +125,7 @@ header h1 a { --invert-percentage: 0%; --code-bg: rgb(223, 223, 223); --hljs-bg: rgb(28, 29, 33); + --muted-text-color: #666666; } } @@ -196,6 +201,10 @@ h6:hover .anchor { opacity: 0.5; } +.anchor { + display: none; +} + a { color: inherit; text-decoration: underline; @@ -238,7 +247,7 @@ header > div { box-sizing: border-box; } -@media (min-inline-size: 1076px) { +@media (min-width: 1076px) { body.has-toc .content-wrapper, body.has-toc header > div { max-inline-size: var(--content-max-inline-size); @@ -356,6 +365,11 @@ p a:hover { background-color: var(--bg-color); } +.toggle-input:focus-visible + .toggle-label { + outline: 2px solid var(--focus-color); + outline-offset: 2px; +} + /* Typography */ h1, h2, @@ -407,7 +421,7 @@ center { } /* Mobile Typography */ -@media (max-inline-size: 1076px) { +@media (max-width: 1076px) { h1 { font-size: 2.4em; } @@ -424,7 +438,7 @@ center { .author-date { font-weight: 400; font-size: 0.9em; - color: #767676; + color: var(--muted-text-color); margin-block-start: 2em; margin-block-end: 0em; line-height: 1.4; @@ -453,7 +467,7 @@ hr:after { text-align: center; } -@media (min-inline-size: 1400px) { +@media (min-width: 1400px) { .content-wrapper, header > div { max-inline-size: var(--content-max-inline-size); diff --git a/layouts/_default/baseof.html b/layouts/_default/baseof.html index 8a4b16d..55c2612 100644 --- a/layouts/_default/baseof.html +++ b/layouts/_default/baseof.html @@ -7,7 +7,7 @@ {{ partial "head.html" . }} -{{ partial "header.html" . }} +{{ partialCached "header.html" . .Site.Language.Lang }}
@@ -17,7 +17,7 @@
-{{ partial "footer.html" . }} +{{ partialCached "footer.html" . .Site.Language.Lang now.Year }} diff --git a/layouts/_default/single.html b/layouts/_default/single.html index 24c1ad1..f2dff97 100644 --- a/layouts/_default/single.html +++ b/layouts/_default/single.html @@ -6,13 +6,5 @@

{{ .Params.description }}

{{ printf `` (.Date.Format "2006-01-02T15:04:05Z07:00") (.Date.Format "January 2, 2006") | safeHTML }}

{{- end }} - {{- if not (.Param "disableAnchoredHeadings") }} - -{{ partial "anchored_headings.html" .Content }} - - {{- else }} - {{ .Content }} - - {{- end }} {{- end }} diff --git a/layouts/_markup/render-heading.html b/layouts/_markup/render-heading.html new file mode 100644 index 0000000..3b0e3f4 --- /dev/null +++ b/layouts/_markup/render-heading.html @@ -0,0 +1,3 @@ +{{- $disableAnchoredHeadings := .Page.Param "disableAnchoredHeadings" -}} +{{- $anchor := .Anchor | safeURL -}} +{{ .Text }}{{- if not $disableAnchoredHeadings -}}{{- end -}} diff --git a/layouts/index.html b/layouts/index.html index c61af93..0c9581e 100644 --- a/layouts/index.html +++ b/layouts/index.html @@ -5,15 +5,7 @@

{{ .Title | .RenderString }}

{{ .Params.description }}

- {{ .Params.author }}

- {{- if not (.Param "disableAnchoredHeadings") }} - -{{ partial "anchored_headings.html" .Content }} - - {{- else }} - {{ .Content }} - - {{- end }} {{- else }} {{- $subtitle := .Params.subtitle }} {{- with .Content }} diff --git a/layouts/partials/anchored_headings.html b/layouts/partials/anchored_headings.html deleted file mode 100644 index 377af89..0000000 --- a/layouts/partials/anchored_headings.html +++ /dev/null @@ -1,2 +0,0 @@ -{{- /* formats .Content headings by adding an anchor */ -}} -{{ . | replaceRE "()" "${1}${3}" | safeHTML }} diff --git a/layouts/partials/head.html b/layouts/partials/head.html index 2ea18c3..71e9edc 100644 --- a/layouts/partials/head.html +++ b/layouts/partials/head.html @@ -2,11 +2,12 @@ - {{- with (partial "objects/author.html" .).name }} + {{- $author := partialCached "objects/author.html" . .Site.Language.Lang -}} + {{- with $author.name }} {{- end }} - {{- $page := partial "objects/page.html" . }} + {{- $page := partialCached "objects/page.html" . .RelPermalink -}} {{ $page.title }} @@ -22,7 +23,6 @@ {{- if not .Site.Params.disableFontPreload }} - {{- end }} {{- $defaultCSS := resources.Get "css/default.css" | minify | fingerprint }} @@ -33,16 +33,24 @@ {{- end }} {{- end }} + {{- if fileExists "static/favicon.ico" }} + {{- end }} + {{- if fileExists "static/apple-touch-icon.png" }} + {{- end }} {{- if or (not .Site.Params.colorScheme) (eq .Site.Params.colorScheme "toggle") }} {{- $darkmodeJS := resources.Get "js/darkmode.js" | minify | fingerprint }} {{- end }} - {{- $footnotesJS := resources.Get "js/footnotes.js" | minify | fingerprint }} + {{- with .Content }} + {{- if or (in . "id=\"fnref") (in . "class=\"footnote-ref\"") }} + {{- $footnotesJS := resources.Get "js/footnotes.js" | minify | fingerprint }} + {{- end }} + {{- end }} {{- if templates.Exists "partials/nonstdmeta.html" }} {{ partial "nonstdmeta.html" . }} diff --git a/layouts/partials/opengraph.html b/layouts/partials/opengraph.html index 8cd3e4f..6a34e0d 100644 --- a/layouts/partials/opengraph.html +++ b/layouts/partials/opengraph.html @@ -1,22 +1,12 @@ -{{- $page := partial "objects/page.html" . -}} +{{- $page := partialCached "objects/page.html" . .RelPermalink -}} {{- $title := .Params.ogTitle | default $page.title -}} {{- $description := .Params.ogDescription | default $page.description -}} -{{- /* Handle base URL for local dev vs production */ -}} -{{- $baseURL := .Site.BaseURL | strings.TrimSuffix "/" }} -{{- if not (strings.Contains $baseURL "localhost") }} - {{- if not (strings.HasPrefix $baseURL "https://") }} - {{- $baseURL = replace $baseURL "http://" "https://" }} - {{- end }} -{{- end }} - {{- $uniqueName := "" }} -{{- $isDefault := false }} {{- with .File }} {{- $uniqueName = printf "og/og-image-%s.svg" .UniqueID }} {{- else }} - {{- $uniqueName = "default.png" }} - {{- $isDefault = true }} + {{- $uniqueName = printf "og/og-image-%s.svg" (md5 .RelPermalink) }} {{- end }} {{- $svg := printf ` @@ -27,7 +17,7 @@ } @font-face { font-family: 'Newsreader'; - src: url('/fonts/Newsreader.woff2') format('woff2'); + src: url('../fonts/Newsreader.woff2') format('woff2'); } .title, .description { font-family: 'Newsreader', serif; @@ -44,38 +34,37 @@ - ` ($title | htmlEscape) $description | safeHTML }} + ` ($title | htmlEscape) ($description | htmlEscape) | safeHTML }} {{- $generatedSVG := resources.FromString $uniqueName $svg | resources.ExecuteAsTemplate $uniqueName . -}} {{- /* Determine which image URL to use */ -}} -{{- $ogImageURL := "" }} -{{- if $isDefault }} - {{- $ogImageURL = "/default.png" }} -{{- else }} - {{- with .Params.ogImage }} +{{- $ogImageURL := $generatedSVG.Permalink }} +{{- with .Params.ogImage }} + {{- if or (strings.HasPrefix . "http://") (strings.HasPrefix . "https://") (strings.HasPrefix . "//") }} {{- $ogImageURL = . }} {{- else }} - {{- $ogImageURL = $generatedSVG.RelPermalink }} + {{- $ogImageURL = . | absURL }} {{- end }} {{- end }} - + - + - -{{- with (partial "objects/author.html" .).twitter }} + +{{- $author := partialCached "objects/author.html" . .Site.Language.Lang -}} +{{- with $author.twitter }} {{- end }} diff --git a/script/bootstrap b/script/bootstrap new file mode 100755 index 0000000..01ba369 --- /dev/null +++ b/script/bootstrap @@ -0,0 +1,124 @@ +#! /usr/bin/env bash + +set -euo pipefail + +source script/env "$@" + +HUGO_VERSION_FILE="$DIR/.github/.hugo-version" +HUGO_VENDOR_BIN_DIR="$DIR/.github/vendor/bin" + +read_hugo_version() { + [[ -f "$HUGO_VERSION_FILE" ]] || die "missing Hugo version file: $HUGO_VERSION_FILE" + local version + version="$(tr -d '[:space:]' < "$HUGO_VERSION_FILE")" + [[ -n "$version" ]] || die "Hugo version file is empty: $HUGO_VERSION_FILE" + printf '%s' "$version" +} + +normalize_semver() { + local version="$1" + version="${version#v}" + version="${version%%+*}" + version="${version%%-*}" + printf '%s' "$version" +} + +hugo_installed_semver() { + if ! command -v hugo >/dev/null 2>&1; then + return 1 + fi + normalize_semver "$(hugo version | awk '{print $2}')" +} + +install_vendored_hugo_linux() { + require_cmd dpkg + + local version="$1" + local deb_pkg="$HUGO_VENDOR_BIN_DIR/hugo_extended_${version}_linux-amd64.deb" + [[ -f "$deb_pkg" ]] || die "missing vendored Hugo package: $deb_pkg" + + echo -e "${BLUE}Installing vendored Hugo package:${OFF} ${PURPLE}${deb_pkg}${OFF}" + as_root dpkg -i "$deb_pkg" +} + +install_vendored_hugo_macos() { + require_cmd installer + + local version="$1" + local pkg="$HUGO_VENDOR_BIN_DIR/hugo_extended_${version}_darwin-universal.pkg" + [[ -f "$pkg" ]] || die "missing vendored Hugo package: $pkg" + + echo -e "${BLUE}Installing vendored Hugo package:${OFF} ${PURPLE}${pkg}${OFF}" + as_root installer -pkg "$pkg" -target / +} + +install_hugo_default_linux() { + if command -v apt-get >/dev/null 2>&1; then + as_root apt-get update + as_root env DEBIAN_FRONTEND=noninteractive apt-get install -y hugo + return + fi + + die "unable to install hugo automatically on linux (apt-get not found)" +} + +install_hugo_default_macos() { + require_cmd brew + brew install hugo +} + +install_hugo_for_ci() { + local expected_version expected_semver installed_semver + expected_version="$(read_hugo_version)" + expected_semver="$(normalize_semver "$expected_version")" + installed_semver="$(hugo_installed_semver || true)" + echo -e "${BLUE}Installing vendored Hugo version:${OFF} ${PURPLE}${expected_semver}${OFF}" + if [[ -n "$installed_semver" ]]; then + echo -e "${BLUE}Detected preinstalled Hugo:${OFF} ${PURPLE}${installed_semver}${OFF}" + fi + + case "$OSTYPE" in + linux-gnu*) + install_vendored_hugo_linux "$expected_version" + ;; + darwin*) + install_vendored_hugo_macos "$expected_version" + ;; + *) + die "unsupported OS for CI Hugo install: $OSTYPE" + ;; + esac + + installed_semver="$(hugo_installed_semver || true)" + [[ "$installed_semver" == "$expected_semver" ]] || die "expected Hugo ${expected_semver}, but found ${installed_semver:-}" + echo -e "${GREEN}Hugo version:${OFF} $(hugo version)" +} + +install_hugo_default() { + if command -v hugo >/dev/null 2>&1; then + echo -e "${GREEN}Hugo version:${OFF} $(hugo version)" + return + fi + + warn "hugo not found; installing..." + case "$OSTYPE" in + linux-gnu*) + install_hugo_default_linux + ;; + darwin*) + install_hugo_default_macos + ;; + *) + die "unsupported OS for automatic hugo install: $OSTYPE" + ;; + esac + + require_cmd hugo + echo -e "${GREEN}Hugo version:${OFF} $(hugo version)" +} + +if [[ "${CI:-}" == "true" ]]; then + install_hugo_for_ci +else + install_hugo_default +fi diff --git a/script/build b/script/build new file mode 100755 index 0000000..f53ce77 --- /dev/null +++ b/script/build @@ -0,0 +1,34 @@ +#! /usr/bin/env bash + +set -euo pipefail + +source script/env "$@" +source script/lib/hugo-fixture.sh + +require_cmd hugo +require_cmd mktemp + +build_dir="$TMP_DIR/build" +rm -rf "$build_dir" +mkdir -p "$build_dir" + +work_dir="$(mktemp -d "$TMP_DIR/hugo-build-fixture.XXXXXX")" +site_dir="$work_dir/site" + +cleanup() { + rm -rf "$work_dir" +} +trap cleanup EXIT + +create_hugo_fixture_site "$site_dir" "$DIR" + +echo -e "${BLUE}Building fixture site to:${OFF} ${PURPLE}${build_dir}${OFF}" +HUGO_ENV=production hugo \ + --source "$site_dir" \ + --destination "$build_dir" \ + --cleanDestinationDir \ + --panicOnWarning \ + --printPathWarnings \ + --minify + +echo -e "${GREEN}Build complete${OFF}" diff --git a/script/env b/script/env new file mode 100755 index 0000000..5530e9c --- /dev/null +++ b/script/env @@ -0,0 +1,46 @@ +#! /usr/bin/env bash + +set -euo pipefail + +# COLORS +export OFF='\033[0m' +export RED='\033[0;31m' +export GREEN='\033[0;32m' +export BLUE='\033[0;34m' +export PURPLE='\033[0;35m' + +# set the working directory to the root of the project +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && cd .. && pwd )" +export DIR + +# The name of the repository is the name of the directory (usually) +REPO_NAME=$(basename "$DIR") +export REPO_NAME + +TMP_DIR="$DIR/tmp" +export TMP_DIR +mkdir -p "$TMP_DIR" + +die() { + echo -e "${RED}error:${OFF} $*" >&2 + exit 1 +} + +warn() { + echo -e "${RED}warning:${OFF} $*" >&2 +} + +require_cmd() { + local cmd="$1" + if ! command -v "$cmd" >/dev/null 2>&1; then + die "missing required tool: $cmd" + fi +} + +as_root() { + if command -v sudo >/dev/null 2>&1; then + sudo "$@" + else + "$@" + fi +} diff --git a/script/lib/hugo-fixture.sh b/script/lib/hugo-fixture.sh new file mode 100644 index 0000000..8ea3d59 --- /dev/null +++ b/script/lib/hugo-fixture.sh @@ -0,0 +1,80 @@ +#! /usr/bin/env bash + +set -euo pipefail + +create_hugo_fixture_site() { + local site_dir="$1" + local theme_dir="$2" + + mkdir -p "$site_dir/content/posts" "$site_dir/themes" + ln -s "$theme_dir" "$site_dir/themes/dario" + + cat > "$site_dir/hugo.toml" <<'EOF' +baseURL = "https://example.org/blog/" +languageCode = "en-us" +title = "Fixture Site" +theme = "dario" + +[params] + description = "Fixture description" + +[params.author] + name = "Fixture Author" +EOF + + cat > "$site_dir/content/_index.md" <<'EOF' ++++ +title = "Home *Post*" +subtitle = "Fixture subtitle" +description = "Fixture homepage description" +homePageIsPost = true +date = 2025-02-24T00:00:00Z +author = "Fixture Author" ++++ + +Fixture **content** for the homepage. +EOF + + cat > "$site_dir/content/posts/first.md" <<'EOF' ++++ +title = "First *Post*" +description = "Fixture post" +ogDescription = "A & B" +date = 2025-02-24T00:00:00Z ++++ + +Hello from the fixture post.[^1] + +## Details + +Some text under a heading. + +[^1]: Fixture footnote text. +EOF + + cat > "$site_dir/content/posts/absolute-og.md" <<'EOF' ++++ +title = "Absolute OG" +description = "Fixture post with absolute OG image" +ogImage = "https://cdn.example.com/og.png" +date = 2025-02-25T00:00:00Z ++++ + +## Absolute Image + +This post uses an absolute OG image URL. +EOF + + cat > "$site_dir/content/posts/no-anchors.md" <<'EOF' ++++ +title = "No Anchors" +description = "Fixture post with heading anchors disabled" +disableAnchoredHeadings = true +date = 2025-02-26T00:00:00Z ++++ + +## Without Anchor + +This post should not render a heading anchor. +EOF +} diff --git a/script/lint b/script/lint new file mode 100755 index 0000000..d300677 --- /dev/null +++ b/script/lint @@ -0,0 +1,39 @@ +#! /usr/bin/env bash + +set -euo pipefail + +source script/env "$@" +source script/lib/hugo-fixture.sh + +require_cmd hugo +require_cmd bash +require_cmd find +require_cmd mktemp + +while IFS= read -r -d '' file; do + bash -n "$file" +done < <(find "$DIR/script" -type f -print0) + +if grep -nE '@media \((min|max)-inline-size:' "$DIR/assets/css/default.css" >/dev/null; then + die "invalid media query feature in assets/css/default.css: use min-width/max-width for viewport queries" +fi + +work_dir="$(mktemp -d "$TMP_DIR/hugo-lint-fixture.XXXXXX")" +site_dir="$work_dir/site" + +cleanup() { + rm -rf "$work_dir" +} +trap cleanup EXIT + +create_hugo_fixture_site "$site_dir" "$DIR" + +echo -e "${BLUE}Running Hugo lint checks${OFF}" +hugo \ + --source "$site_dir" \ + --renderToMemory \ + --panicOnWarning \ + --printPathWarnings \ + --printI18nWarnings + +echo -e "${GREEN}Lint complete${OFF}" diff --git a/script/test b/script/test new file mode 100755 index 0000000..ca90eb0 --- /dev/null +++ b/script/test @@ -0,0 +1,126 @@ +#! /usr/bin/env bash + +set -euo pipefail + +source script/env "$@" +source script/lib/hugo-fixture.sh + +require_cmd hugo +require_cmd grep +require_cmd mktemp + +work_dir="$(mktemp -d "$TMP_DIR/hugo-fixture.XXXXXX")" +site_dir="$work_dir/site" +output_dir="$work_dir/public" + +cleanup() { + rm -rf "$work_dir" +} +trap cleanup EXIT + +create_hugo_fixture_site "$site_dir" "$DIR" + +echo -e "${BLUE}Building fixture site${OFF}" +hugo \ + --source "$site_dir" \ + --destination "$output_dir" \ + --panicOnWarning \ + --printPathWarnings + +home_html="$output_dir/index.html" +post_html="$output_dir/posts/first/index.html" +absolute_og_html="$output_dir/posts/absolute-og/index.html" +no_anchor_html="$output_dir/posts/no-anchors/index.html" +css_file="$(ls "$output_dir"/css/*.css | head -n1)" + +[[ -f "$home_html" ]] || die "expected fixture homepage output at $home_html" +[[ -f "$post_html" ]] || die "expected fixture post output at $post_html" +[[ -f "$absolute_og_html" ]] || die "expected fixture absolute-og output at $absolute_og_html" +[[ -f "$no_anchor_html" ]] || die "expected fixture no-anchors output at $no_anchor_html" +[[ -f "$css_file" ]] || die "expected processed CSS output under $output_dir/css" + +if ! grep -Fq "

Home Post

" "$home_html"; then + die "homepage title markdown did not render as expected" +fi + +if grep -Fq "First *Post*" "$home_html"; then + die "homepage post list title was not plainified as expected" +fi + +if ! grep -Fq "First Post" "$home_html"; then + die "homepage post list title text missing expected plainified value" +fi + +if ! grep -Fq "

First Post

" "$post_html"; then + die "single post title markdown did not render as expected" +fi + +if ! grep -Fq 'href="#details"' "$post_html"; then + die "expected heading anchor link for rendered markdown heading" +fi + +if grep -Fq 'href="#without-anchor"' "$no_anchor_html"; then + die "disableAnchoredHeadings should disable heading anchor links" +fi + +if ! grep -q "footnotes" "$post_html"; then + die "single post with footnotes should include footnotes script" +fi + +if grep -q "footnotes" "$home_html"; then + die "homepage without footnotes should not include footnotes script" +fi + +if ! grep -Fq "fonts/Newsreader.woff2" "$home_html"; then + die "expected preload for primary Newsreader font on homepage" +fi + +if grep -Fq "fonts/Newsreader-italic.woff2" "$home_html"; then + die "homepage should not preload italic Newsreader font by default" +fi + +if grep -Fq "/default.png" "$home_html"; then + die "homepage still references missing default OG image fallback" +fi + +if grep -Fq "https://example.org/blog/blog/" "$post_html"; then + die "OG URLs should not duplicate base path segments" +fi + +if ! grep -Fq '' "$post_html"; then + die "single post should have a canonical og:url based on .Permalink" +fi + +if ! grep -Eq '' "$post_html"; then + die "single post should reference generated OG image with a correct absolute URL" +fi + +if ! grep -Fq '' "$absolute_og_html"; then + die "absolute ogImage URL should be passed through without baseURL concatenation" +fi + +if ! grep -Fq "url(../fonts/Newsreader.woff2)" "$css_file"; then + die "CSS should reference Newsreader regular font via relative URL" +fi + +if ! grep -Fq "url(../fonts/Newsreader-italic.woff2)" "$css_file"; then + die "CSS should reference Newsreader italic font via relative URL" +fi + +if grep -Fq "url(/fonts/" "$css_file"; then + die "CSS should not use root-absolute font URLs" +fi + +if ! grep -Fq "../fonts/Newsreader.woff2" "$output_dir"/og/*.svg; then + die "generated OG SVG should use a relative font URL" +fi + +if grep -q "A & B" "$output_dir"/og/*.svg; then + die "OG SVG contains unescaped ampersand in description" +fi + +if ! grep -q "A & B" "$output_dir"/og/*.svg; then + die "OG SVG is missing escaped description content" +fi + +echo -e "${GREEN}Fixture tests passed${OFF}"