diff --git a/install-and-build.sh b/install-and-build.sh index bf37de316..ce5d11196 100644 --- a/install-and-build.sh +++ b/install-and-build.sh @@ -31,4 +31,5 @@ curl -LJO "https://github.com/sass/dart-sass/releases/download/${DART_SASS_VERSI tar -xf "${SASS_ARCHIVE}" && \ rm "${SASS_ARCHIVE}" && \ export PATH="${CURRENT_DIR}/dart-sass:${PATH}" && \ - cd qdrant-landing && npm install && hugo --gc --minify --config config.toml,config-theme.toml --buildFuture -b ${DEPLOY_PRIME_URL} + cd qdrant-landing && npm install && hugo --gc --minify --config config.toml,config-theme.toml --buildFuture -b ${DEPLOY_PRIME_URL} && \ + ./scripts/emit-md-aliases.sh public diff --git a/qdrant-landing/scripts/emit-md-aliases.sh b/qdrant-landing/scripts/emit-md-aliases.sh new file mode 100755 index 000000000..6669c1027 --- /dev/null +++ b/qdrant-landing/scripts/emit-md-aliases.sh @@ -0,0 +1,29 @@ +#!/usr/bin/env bash +# +# Publish every /path/index.md a second time as /path.md, the guessable form a +# client can build from a URL without fetching the HTML first. Additions, never +# moves: /llms.txt advertises the index.md URLs. +# +# A build step rather than a Netlify redirect because a splat has to be +# terminal, so /articles/*.md is not a pattern Netlify accepts. +set -euo pipefail + +PUBLIC="${1:-public}" +[ -d "$PUBLIC" ] || { echo "md-aliases: no such directory: $PUBLIC" >&2; exit 1; } + +written=0 +skipped=0 +while IFS= read -r -d '' src; do + dir=$(dirname "$src") + # The site root would write a sibling of the publish directory, not a page. + [ "$dir" = "$PUBLIC" ] && continue + dest="${dir}.md" + if [ -e "$dest" ]; then + skipped=$((skipped + 1)) + continue + fi + cp "$src" "$dest" + written=$((written + 1)) +done < <(find "$PUBLIC" -type f -name index.md -print0) + +echo "md-aliases: wrote ${written}, skipped ${skipped} (a file was already there)" diff --git a/qdrant-landing/test/markdown/aliases.test.mjs b/qdrant-landing/test/markdown/aliases.test.mjs new file mode 100644 index 000000000..63c5dca1a --- /dev/null +++ b/qdrant-landing/test/markdown/aliases.test.mjs @@ -0,0 +1,40 @@ +import { test } from 'node:test'; +import assert from 'node:assert/strict'; +import { execFileSync } from 'node:child_process'; +import { mkdtempSync, mkdirSync, writeFileSync, existsSync, rmSync } from 'node:fs'; +import { tmpdir } from 'node:os'; +import { join } from 'node:path'; + +// node --test test/markdown/aliases.test.mjs +const run = (dir) => execFileSync('bash', ['scripts/emit-md-aliases.sh', dir], { encoding: 'utf8' }); + +function fixture() { + const out = mkdtempSync(join(tmpdir(), 'md-alias-')); + mkdirSync(join(out, 'articles', 'foo'), { recursive: true }); + writeFileSync(join(out, 'articles', 'foo', 'index.md'), 'page\n'); + writeFileSync(join(out, 'index.md'), 'home\n'); + return out; +} + +test('the site root does not write outside the publish directory', () => { + const out = fixture(); + try { + run(out); + assert.ok(existsSync(join(out, 'articles', 'foo.md')), 'a page should gain its alias'); + assert.ok(!existsSync(`${out}.md`), 'the root must not produce a sibling of the publish dir'); + } finally { + rmSync(out, { recursive: true, force: true }); + } +}); + +test('an existing file is never overwritten', () => { + const out = fixture(); + try { + writeFileSync(join(out, 'articles', 'foo.md'), 'hand written\n'); + run(out); + assert.equal(execFileSync('cat', [join(out, 'articles', 'foo.md')], { encoding: 'utf8' }), + 'hand written\n', 'an existing alias must survive'); + } finally { + rmSync(out, { recursive: true, force: true }); + } +});