Skip to content

Commit 2a31e8f

Browse files
authored
Merge pull request #112 from hyp3rd/feat/dist-mem-cache
docs: add MkDocs Material site deployed to GitHub Page
2 parents f526157 + 50b0f03 commit 2a31e8f

21 files changed

Lines changed: 1768 additions & 7 deletions

.github/workflows/docs.yml

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
---
2+
name: docs
3+
4+
# Build and deploy the MkDocs site to GitHub Pages.
5+
# * pull_request — build only (validates the docs render
6+
# without publishing).
7+
# * push to main — build + deploy to gh-pages.
8+
# * workflow_dispatch — same as push (lets operators
9+
# re-publish without a docs change).
10+
11+
on:
12+
pull_request:
13+
paths:
14+
- "docs/**"
15+
- "mkdocs.yml"
16+
- "CHANGELOG.md"
17+
- "cmd/hypercache-server/README.md"
18+
- ".github/workflows/docs.yml"
19+
push:
20+
branches: [ main ]
21+
paths:
22+
- "docs/**"
23+
- "mkdocs.yml"
24+
- "CHANGELOG.md"
25+
- "cmd/hypercache-server/README.md"
26+
- ".github/workflows/docs.yml"
27+
workflow_dispatch:
28+
29+
# Pages deployments require these permissions; the build-only
30+
# branch (PR) doesn't actually use the deploy steps so the
31+
# extra permissions are harmless.
32+
permissions:
33+
contents: read
34+
pages: write
35+
id-token: write
36+
37+
# A single in-flight deploy at a time. Newer pushes cancel
38+
# older ones to avoid an out-of-order publish.
39+
concurrency:
40+
group: pages
41+
cancel-in-progress: true
42+
43+
jobs:
44+
build:
45+
name: build
46+
runs-on: ubuntu-latest
47+
timeout-minutes: 10
48+
steps:
49+
- uses: actions/checkout@v6
50+
with:
51+
fetch-depth: 0 # docs/ may reference files via relative paths
52+
53+
- name: Setup Python
54+
uses: actions/setup-python@v6
55+
with:
56+
python-version: "3.13"
57+
cache: pip
58+
# `actions/setup-python` uses this file's hash as the
59+
# pip-cache key. docs/requirements.txt pins the MkDocs
60+
# + plugin versions so cache hits are reproducible and
61+
# the runner can find a key file at all.
62+
cache-dependency-path: docs/requirements.txt
63+
64+
- name: Install MkDocs + plugins
65+
run: |
66+
python -m pip install --upgrade pip
67+
pip install -r docs/requirements.txt
68+
69+
- name: Build site (strict)
70+
# Strict in CI catches broken links / missing pages on PR;
71+
# `mkdocs serve` locally relaxes this for fast iteration.
72+
run: mkdocs build --strict
73+
74+
- name: Upload Pages artifact
75+
if: github.event_name == 'push' || github.event_name == 'workflow_dispatch'
76+
uses: actions/upload-pages-artifact@v5
77+
with:
78+
path: ./site
79+
80+
deploy:
81+
name: deploy
82+
needs: build
83+
if: github.event_name == 'push' || github.event_name == 'workflow_dispatch'
84+
runs-on: ubuntu-latest
85+
timeout-minutes: 5
86+
environment:
87+
name: github-pages
88+
url: ${{ steps.deployment.outputs.page_url }}
89+
steps:
90+
- name: Deploy to GitHub Pages
91+
id: deployment
92+
uses: actions/deploy-pages@v5

.gitignore

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,3 +96,9 @@ tags
9696

9797
### Project ###
9898
.dccache
99+
100+
### MkDocs site build output (CI publishes; local builds shouldn't be committed) ###
101+
/site/
102+
103+
### Python bytecode caches from MkDocs hooks ###
104+
_mkdocs/__pycache__/

.gitleaksignore

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,11 @@ scripts/tests/10-test-cluster-api.sh:curl-auth-header:36
1010
cmd/hypercache-server/README.md:curl-auth-header:50
1111
cmd/hypercache-server/README.md:curl-auth-header:55
1212
cmd/hypercache-server/README.md:curl-auth-header:59
13+
cmd/hypercache-server/README.md:curl-auth-header:77
14+
cmd/hypercache-server/README.md:curl-auth-header:92
1315
cmd/hypercache-server/README.md:curl-auth-header:102
1416
cmd/hypercache-server/README.md:curl-auth-header:108
1517
cmd/hypercache-server/README.md:curl-auth-header:112
18+
cmd/hypercache-server/README.md:curl-auth-header:117
19+
cmd/hypercache-server/README.md:curl-auth-header:129
20+
cmd/hypercache-server/README.md:curl-auth-header:135

.mdl_style.rb

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,22 @@
1313
# under distinct parent headings — which is exactly the Keep-a-Changelog
1414
# shape, and still catches genuine duplicates within the same section.
1515
rule "MD024", :allow_different_nesting => true
16+
17+
# MkDocs pages start with YAML frontmatter (---\ntitle: ...\n---), so
18+
# the first line cannot be a top-level heading. MD041 fights that
19+
# convention; the alternative would be losing per-page metadata.
20+
exclude_rule 'MD041'
21+
22+
# Hard tabs in code blocks are valid — Go source uses tabs by
23+
# convention (gofmt enforces it), and MkDocs preserves them. The
24+
# default rule flags every Go example as broken, which would push
25+
# us to manually convert tabs in every code block.
26+
exclude_rule 'MD010'
27+
28+
# MkDocs Material's "grid cards" feature requires `<div class="grid cards">`
29+
# HTML wrappers around a markdown list. MD033 (no inline HTML) flags
30+
# every grid block. Ditto for the surrounding-blank-line rule (MD032)
31+
# which doesn't see the list inside the div as a list. Skipping both
32+
# is the standard Material-theme posture.
33+
exclude_rule 'MD033'
34+
exclude_rule 'MD032'

CHANGELOG.md

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,52 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
88

99
### Added
1010

11+
- **Documentation site on GitHub Pages**, built with MkDocs Material
12+
and published automatically on every push to `main`. Eight
13+
navigated pages — landing, quickstart, 5-node cluster tutorial,
14+
Helm chart guide, server-binary reference, distributed-backend
15+
architecture, operations runbook, RFC index — plus the
16+
CHANGELOG and the `cmd/hypercache-server/README.md` pulled in
17+
via the include-markdown plugin so they don't drift. A
18+
build-time hook at [`_mkdocs/hooks.py`](_mkdocs/hooks.py)
19+
rewrites repo-relative source-code references (`../pkg/foo.go`)
20+
into canonical GitHub URLs so the same markdown renders
21+
correctly both on github.com and on the rendered Pages site.
22+
Workflow at
23+
[`.github/workflows/docs.yml`](.github/workflows/docs.yml)
24+
builds with `--strict` on every PR (catches broken docs-internal
25+
links on submission) and deploys via `actions/deploy-pages@v4`
26+
on main pushes. The README now links to the rendered site.
27+
Polishing pass on the existing markdown surface: relaxed
28+
`mdl` rules that fight MkDocs/frontmatter idioms (MD041
29+
for YAML frontmatter pages, MD010 for Go's tab-in-code-blocks
30+
convention, MD033/MD032 for Material's grid-cards HTML).
31+
- **Richer client API — metadata inspection, JSON envelopes, batch
32+
operations.** Three additions to the
33+
`cmd/hypercache-server` HTTP surface:
34+
- `HEAD /v1/cache/:key` returns the value's metadata in
35+
`X-Cache-*` response headers (Version, Origin, Last-Updated,
36+
TTL-Ms, Expires-At, Owners, Node) with no body — fast
37+
existence + TTL inspection without paying the value-transfer
38+
cost. 200 if present, 404 if not.
39+
- `GET /v1/cache/:key` now honors `Accept: application/json`
40+
and returns an `itemEnvelope` with the same metadata as
41+
HEAD plus the base64-encoded value. The bare-`curl` default
42+
remains raw bytes via `application/octet-stream` — current
43+
clients are unaffected.
44+
- `POST /v1/cache/batch/{get,put,delete}` enable bulk operations
45+
in a single round-trip. Each request carries an array; the
46+
response carries one result entry per item with per-item
47+
status, owners, and error reporting. `batch-put` items
48+
accept either UTF-8 strings (default) or base64-encoded byte
49+
payloads via `value_encoding: "base64"`. Per-item errors are
50+
surfaced in `error` + `code` fields without failing the
51+
whole batch.
52+
Six unit tests at
53+
[cmd/hypercache-server/handlers_test.go](cmd/hypercache-server/handlers_test.go)
54+
pin the contracts: HEAD present/missing, Accept-JSON envelope
55+
shape, default-raw round-trip, mixed-encoding batch-put,
56+
batch-get found/missing, batch-delete cycle.
1157
- **SWIM self-refutation + cross-process gossip dissemination.**
1258
Closes the last `experimental` marker on the heartbeat path.
1359
Three pieces:

Makefile

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,15 @@ sec:
185185
@echo "\nRunning gosec..."
186186
gosec -exclude-generated -exclude-dir=__examples/size ./...
187187

188+
docs-build:
189+
PYENV_VERSION=mkdocs mkdocs build --strict
190+
191+
docs-publish: docs-build
192+
PYENV_VERSION=mkdocs mkdocs gh-deploy
193+
194+
docs-serve: docs-build
195+
PYENV_VERSION=mkdocs mkdocs serve
196+
188197
# check_command_exists is a helper function that checks if a command exists.
189198
define check_command_exists
190199
@which $(1) > /dev/null 2>&1 || (echo "$(1) command not found" && exit 1)
@@ -219,6 +228,11 @@ help:
219228
@echo " update-deps\t\t\tUpdate all dependencies and tidy go.mod"
220229
@echo
221230
@echo
231+
@echo "Documentation commands:"
232+
@echo " docs-build"
233+
@echo " docs-publish"
234+
@echo " docs-serve"
235+
@echo
222236
@echo "For more information, see the project README."
223237

224238
.PHONY: init prepare-toolchain prepare-base-tools update-toolchain test test-race typecheck build ci bench bench-baseline vet update-deps lint sec help

README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
# HyperCache
22

3-
[![Go](https://github.com/hyp3rd/hypercache/actions/workflows/go.yml/badge.svg)][build-link] [![CodeQL](https://github.com/hyp3rd/hypercache/actions/workflows/codeql.yml/badge.svg)][codeql-link]
3+
[![Go](https://github.com/hyp3rd/hypercache/actions/workflows/go.yml/badge.svg)][build-link] [![CodeQL](https://github.com/hyp3rd/hypercache/actions/workflows/codeql.yml/badge.svg)][codeql-link] [![Docs](https://img.shields.io/badge/docs-github--pages-blue)](https://hyp3rd.github.io/hypercache/)
4+
5+
> **📖 Full documentation**: <https://hyp3rd.github.io/hypercache/>
46
57
## Synopsis
68

_mkdocs/hooks.py

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
"""MkDocs hooks for the HyperCache site.
2+
3+
Rewrites repo-relative links to source files (`../pkg/foo.go`,
4+
`../../hypercache.go`, etc.) into canonical GitHub URLs, so the same
5+
markdown source renders correctly both on github.com and on the
6+
GitHub Pages MkDocs build.
7+
8+
Without this, the operations runbook and the RFCs reference dozens
9+
of source files via paths like `../pkg/backend/dist_memory.go`.
10+
GitHub renders those as in-repo links; MkDocs's strict mode flags
11+
them as broken because `pkg/` is not part of the documentation
12+
tree. Rewriting them at build time keeps the source markdown
13+
GitHub-friendly while letting strict mode actually enforce
14+
docs-internal correctness.
15+
"""
16+
17+
import os
18+
import re
19+
from typing import Any
20+
21+
GITHUB_REPO_BASE = "https://github.com/hyp3rd/hypercache/blob/main"
22+
23+
# File extensions that we treat as "source code, not docs" — links
24+
# to these get rewritten to GitHub URLs. .md is intentionally NOT
25+
# in this list because doc-to-doc links should stay intra-site so
26+
# MkDocs can validate them.
27+
SOURCE_EXTENSIONS = (
28+
".go",
29+
".yaml",
30+
".yml",
31+
".sh",
32+
".rb",
33+
".txt",
34+
".dockerignore",
35+
".gitignore",
36+
".env",
37+
"Dockerfile",
38+
"Makefile",
39+
)
40+
41+
# Paths that are entire directories the docs reference for context
42+
# (e.g. "see internal/cluster/"). These get rewritten to GitHub
43+
# tree URLs — clicking takes the reader to a directory listing.
44+
SOURCE_DIR_PREFIXES = (
45+
"pkg/",
46+
"internal/",
47+
"cmd/",
48+
"chart/",
49+
"scripts/",
50+
"tests/",
51+
"__examples/",
52+
".github/",
53+
"docker/",
54+
"_mkdocs/",
55+
)
56+
57+
LINK_RE = re.compile(r"\[([^\]]+)\]\(([^)]+)\)")
58+
59+
60+
def _is_source_link(target: str) -> bool:
61+
"""Return True when the link target looks like a repo source ref
62+
rather than an in-tree docs link."""
63+
# Strip anchor before extension/prefix checks.
64+
clean = target.split("#", 1)[0]
65+
66+
# Source files by extension or basename.
67+
if clean.endswith(SOURCE_EXTENSIONS):
68+
return True
69+
70+
# Directory references (no extension) that match known source
71+
# roots. We resolve `..` segments first so the prefix match
72+
# works against repo-rooted paths.
73+
parts = [p for p in clean.split("/") if p and p != "."]
74+
75+
# Drop leading `..` segments — they all collapse to repo root
76+
# for our purposes (rewrite-target side).
77+
while parts and parts[0] == "..":
78+
parts.pop(0)
79+
80+
if not parts:
81+
return False
82+
83+
repo_path = "/".join(parts)
84+
if any(repo_path.startswith(p) for p in SOURCE_DIR_PREFIXES):
85+
return True
86+
87+
return False
88+
89+
90+
def _resolve_to_repo_root(page_src_path: str, target: str) -> str:
91+
"""Translate a relative target into a repo-rooted path.
92+
93+
Page src_path is relative to docs/ (e.g. `rfcs/0001-foo.md`).
94+
Target is relative to the page (e.g. `../../pkg/foo.go`). The
95+
returned path is relative to the repo root.
96+
"""
97+
# `os.path.normpath` collapses `..` correctly; we anchor at
98+
# `docs/<page_dir>` and resolve from there.
99+
page_dir = os.path.dirname(page_src_path)
100+
docs_anchored = os.path.normpath(os.path.join("docs", page_dir, target))
101+
102+
# The result may still start with `../` if the relative target
103+
# walked above the repo root (it shouldn't in practice). Trim
104+
# any leading `../` defensively.
105+
while docs_anchored.startswith("../"):
106+
docs_anchored = docs_anchored[3:]
107+
108+
return docs_anchored
109+
110+
111+
def on_page_markdown(markdown: str, page: Any, **kwargs: Any) -> str:
112+
"""Rewrite source-code links on every page before MkDocs renders it."""
113+
page_src = page.file.src_path
114+
115+
def replace(match: re.Match[str]) -> str:
116+
link_text = match.group(1)
117+
link_target = match.group(2)
118+
119+
# Absolute URLs, mailtos, and pure anchors stay as-is.
120+
if link_target.startswith(("http://", "https://", "mailto:", "#")):
121+
return match.group(0)
122+
123+
if not _is_source_link(link_target):
124+
return match.group(0)
125+
126+
repo_path = _resolve_to_repo_root(page_src, link_target)
127+
128+
# Preserve any anchor on the target (e.g. line ranges like
129+
# `pkg/foo.go#L34-L58`).
130+
if "#" in link_target and "#" not in repo_path:
131+
anchor = "#" + link_target.split("#", 1)[1]
132+
repo_path += anchor
133+
134+
return f"[{link_text}]({GITHUB_REPO_BASE}/{repo_path})"
135+
136+
return LINK_RE.sub(replace, markdown)

0 commit comments

Comments
 (0)