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
92 changes: 92 additions & 0 deletions .github/workflows/docs.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
---
name: docs

# Build and deploy the MkDocs site to GitHub Pages.
# * pull_request — build only (validates the docs render
# without publishing).
# * push to main — build + deploy to gh-pages.
# * workflow_dispatch — same as push (lets operators
# re-publish without a docs change).

on:
pull_request:
paths:
- "docs/**"
- "mkdocs.yml"
- "CHANGELOG.md"
- "cmd/hypercache-server/README.md"
- ".github/workflows/docs.yml"
push:
branches: [ main ]
paths:
- "docs/**"
- "mkdocs.yml"
- "CHANGELOG.md"
- "cmd/hypercache-server/README.md"
- ".github/workflows/docs.yml"
workflow_dispatch:

# Pages deployments require these permissions; the build-only
# branch (PR) doesn't actually use the deploy steps so the
# extra permissions are harmless.
permissions:
contents: read
pages: write
id-token: write

# A single in-flight deploy at a time. Newer pushes cancel
# older ones to avoid an out-of-order publish.
concurrency:
group: pages
cancel-in-progress: true

jobs:
build:
name: build
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v6
with:
fetch-depth: 0 # docs/ may reference files via relative paths

- name: Setup Python
uses: actions/setup-python@v6
with:
python-version: "3.13"
cache: pip
# `actions/setup-python` uses this file's hash as the
# pip-cache key. docs/requirements.txt pins the MkDocs
# + plugin versions so cache hits are reproducible and
# the runner can find a key file at all.
cache-dependency-path: docs/requirements.txt

- name: Install MkDocs + plugins
run: |
python -m pip install --upgrade pip
pip install -r docs/requirements.txt

- name: Build site (strict)
# Strict in CI catches broken links / missing pages on PR;
# `mkdocs serve` locally relaxes this for fast iteration.
run: mkdocs build --strict

- name: Upload Pages artifact
if: github.event_name == 'push' || github.event_name == 'workflow_dispatch'
uses: actions/upload-pages-artifact@v5
with:
path: ./site

deploy:
name: deploy
needs: build
if: github.event_name == 'push' || github.event_name == 'workflow_dispatch'
runs-on: ubuntu-latest
timeout-minutes: 5
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v5
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -96,3 +96,9 @@ tags

### Project ###
.dccache

### MkDocs site build output (CI publishes; local builds shouldn't be committed) ###
/site/

### Python bytecode caches from MkDocs hooks ###
_mkdocs/__pycache__/
5 changes: 5 additions & 0 deletions .gitleaksignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,11 @@ scripts/tests/10-test-cluster-api.sh:curl-auth-header:36
cmd/hypercache-server/README.md:curl-auth-header:50
cmd/hypercache-server/README.md:curl-auth-header:55
cmd/hypercache-server/README.md:curl-auth-header:59
cmd/hypercache-server/README.md:curl-auth-header:77
cmd/hypercache-server/README.md:curl-auth-header:92
cmd/hypercache-server/README.md:curl-auth-header:102
cmd/hypercache-server/README.md:curl-auth-header:108
cmd/hypercache-server/README.md:curl-auth-header:112
cmd/hypercache-server/README.md:curl-auth-header:117
cmd/hypercache-server/README.md:curl-auth-header:129
cmd/hypercache-server/README.md:curl-auth-header:135
19 changes: 19 additions & 0 deletions .mdl_style.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,22 @@
# under distinct parent headings — which is exactly the Keep-a-Changelog
# shape, and still catches genuine duplicates within the same section.
rule "MD024", :allow_different_nesting => true

# MkDocs pages start with YAML frontmatter (---\ntitle: ...\n---), so
# the first line cannot be a top-level heading. MD041 fights that
# convention; the alternative would be losing per-page metadata.
exclude_rule 'MD041'

# Hard tabs in code blocks are valid — Go source uses tabs by
# convention (gofmt enforces it), and MkDocs preserves them. The
# default rule flags every Go example as broken, which would push
# us to manually convert tabs in every code block.
exclude_rule 'MD010'

# MkDocs Material's "grid cards" feature requires `<div class="grid cards">`
# HTML wrappers around a markdown list. MD033 (no inline HTML) flags
# every grid block. Ditto for the surrounding-blank-line rule (MD032)
# which doesn't see the list inside the div as a list. Skipping both
# is the standard Material-theme posture.
exclude_rule 'MD033'
exclude_rule 'MD032'
46 changes: 46 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,52 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

### Added

- **Documentation site on GitHub Pages**, built with MkDocs Material
and published automatically on every push to `main`. Eight
navigated pages — landing, quickstart, 5-node cluster tutorial,
Helm chart guide, server-binary reference, distributed-backend
architecture, operations runbook, RFC index — plus the
CHANGELOG and the `cmd/hypercache-server/README.md` pulled in
via the include-markdown plugin so they don't drift. A
build-time hook at [`_mkdocs/hooks.py`](_mkdocs/hooks.py)
rewrites repo-relative source-code references (`../pkg/foo.go`)
into canonical GitHub URLs so the same markdown renders
correctly both on github.com and on the rendered Pages site.
Workflow at
[`.github/workflows/docs.yml`](.github/workflows/docs.yml)
builds with `--strict` on every PR (catches broken docs-internal
links on submission) and deploys via `actions/deploy-pages@v4`
on main pushes. The README now links to the rendered site.
Polishing pass on the existing markdown surface: relaxed
`mdl` rules that fight MkDocs/frontmatter idioms (MD041
for YAML frontmatter pages, MD010 for Go's tab-in-code-blocks
convention, MD033/MD032 for Material's grid-cards HTML).
- **Richer client API — metadata inspection, JSON envelopes, batch
operations.** Three additions to the
`cmd/hypercache-server` HTTP surface:
- `HEAD /v1/cache/:key` returns the value's metadata in
`X-Cache-*` response headers (Version, Origin, Last-Updated,
TTL-Ms, Expires-At, Owners, Node) with no body — fast
existence + TTL inspection without paying the value-transfer
cost. 200 if present, 404 if not.
- `GET /v1/cache/:key` now honors `Accept: application/json`
and returns an `itemEnvelope` with the same metadata as
HEAD plus the base64-encoded value. The bare-`curl` default
remains raw bytes via `application/octet-stream` — current
clients are unaffected.
- `POST /v1/cache/batch/{get,put,delete}` enable bulk operations
in a single round-trip. Each request carries an array; the
response carries one result entry per item with per-item
status, owners, and error reporting. `batch-put` items
accept either UTF-8 strings (default) or base64-encoded byte
payloads via `value_encoding: "base64"`. Per-item errors are
surfaced in `error` + `code` fields without failing the
whole batch.
Six unit tests at
[cmd/hypercache-server/handlers_test.go](cmd/hypercache-server/handlers_test.go)
pin the contracts: HEAD present/missing, Accept-JSON envelope
shape, default-raw round-trip, mixed-encoding batch-put,
batch-get found/missing, batch-delete cycle.
- **SWIM self-refutation + cross-process gossip dissemination.**
Closes the last `experimental` marker on the heartbeat path.
Three pieces:
Expand Down
14 changes: 14 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,15 @@ sec:
@echo "\nRunning gosec..."
gosec -exclude-generated -exclude-dir=__examples/size ./...

docs-build:
PYENV_VERSION=mkdocs mkdocs build --strict

docs-publish: docs-build
PYENV_VERSION=mkdocs mkdocs gh-deploy

docs-serve: docs-build
PYENV_VERSION=mkdocs mkdocs serve

# check_command_exists is a helper function that checks if a command exists.
define check_command_exists
@which $(1) > /dev/null 2>&1 || (echo "$(1) command not found" && exit 1)
Expand Down Expand Up @@ -219,6 +228,11 @@ help:
@echo " update-deps\t\t\tUpdate all dependencies and tidy go.mod"
@echo
@echo
@echo "Documentation commands:"
@echo " docs-build"
@echo " docs-publish"
@echo " docs-serve"
@echo
@echo "For more information, see the project README."

.PHONY: init prepare-toolchain prepare-base-tools update-toolchain test test-race typecheck build ci bench bench-baseline vet update-deps lint sec help
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
# HyperCache

[![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]
[![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/)

> **📖 Full documentation**: <https://hyp3rd.github.io/hypercache/>

## Synopsis

Expand Down
136 changes: 136 additions & 0 deletions _mkdocs/hooks.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
"""MkDocs hooks for the HyperCache site.

Rewrites repo-relative links to source files (`../pkg/foo.go`,
`../../hypercache.go`, etc.) into canonical GitHub URLs, so the same
markdown source renders correctly both on github.com and on the
GitHub Pages MkDocs build.

Without this, the operations runbook and the RFCs reference dozens
of source files via paths like `../pkg/backend/dist_memory.go`.
GitHub renders those as in-repo links; MkDocs's strict mode flags
them as broken because `pkg/` is not part of the documentation
tree. Rewriting them at build time keeps the source markdown
GitHub-friendly while letting strict mode actually enforce
docs-internal correctness.
"""

import os
import re
from typing import Any

GITHUB_REPO_BASE = "https://github.com/hyp3rd/hypercache/blob/main"

# File extensions that we treat as "source code, not docs" — links
# to these get rewritten to GitHub URLs. .md is intentionally NOT
# in this list because doc-to-doc links should stay intra-site so
# MkDocs can validate them.
SOURCE_EXTENSIONS = (
".go",
".yaml",
".yml",
".sh",
".rb",
".txt",
".dockerignore",
".gitignore",
".env",
"Dockerfile",
"Makefile",
)

# Paths that are entire directories the docs reference for context
# (e.g. "see internal/cluster/"). These get rewritten to GitHub
# tree URLs — clicking takes the reader to a directory listing.
SOURCE_DIR_PREFIXES = (
"pkg/",
"internal/",
"cmd/",
"chart/",
"scripts/",
"tests/",
"__examples/",
".github/",
"docker/",
"_mkdocs/",
)

LINK_RE = re.compile(r"\[([^\]]+)\]\(([^)]+)\)")


def _is_source_link(target: str) -> bool:
"""Return True when the link target looks like a repo source ref
rather than an in-tree docs link."""
# Strip anchor before extension/prefix checks.
clean = target.split("#", 1)[0]

# Source files by extension or basename.
if clean.endswith(SOURCE_EXTENSIONS):
return True

# Directory references (no extension) that match known source
# roots. We resolve `..` segments first so the prefix match
# works against repo-rooted paths.
parts = [p for p in clean.split("/") if p and p != "."]

# Drop leading `..` segments — they all collapse to repo root
# for our purposes (rewrite-target side).
while parts and parts[0] == "..":
parts.pop(0)

if not parts:
return False

repo_path = "/".join(parts)
if any(repo_path.startswith(p) for p in SOURCE_DIR_PREFIXES):
return True

return False


def _resolve_to_repo_root(page_src_path: str, target: str) -> str:
"""Translate a relative target into a repo-rooted path.

Page src_path is relative to docs/ (e.g. `rfcs/0001-foo.md`).
Target is relative to the page (e.g. `../../pkg/foo.go`). The
returned path is relative to the repo root.
"""
# `os.path.normpath` collapses `..` correctly; we anchor at
# `docs/<page_dir>` and resolve from there.
page_dir = os.path.dirname(page_src_path)
docs_anchored = os.path.normpath(os.path.join("docs", page_dir, target))

# The result may still start with `../` if the relative target
# walked above the repo root (it shouldn't in practice). Trim
# any leading `../` defensively.
while docs_anchored.startswith("../"):
docs_anchored = docs_anchored[3:]

return docs_anchored


def on_page_markdown(markdown: str, page: Any, **kwargs: Any) -> str:
"""Rewrite source-code links on every page before MkDocs renders it."""
page_src = page.file.src_path

def replace(match: re.Match[str]) -> str:
link_text = match.group(1)
link_target = match.group(2)

# Absolute URLs, mailtos, and pure anchors stay as-is.
if link_target.startswith(("http://", "https://", "mailto:", "#")):
return match.group(0)

if not _is_source_link(link_target):
return match.group(0)

repo_path = _resolve_to_repo_root(page_src, link_target)

# Preserve any anchor on the target (e.g. line ranges like
# `pkg/foo.go#L34-L58`).
if "#" in link_target and "#" not in repo_path:
anchor = "#" + link_target.split("#", 1)[1]
repo_path += anchor

return f"[{link_text}]({GITHUB_REPO_BASE}/{repo_path})"

return LINK_RE.sub(replace, markdown)
Loading
Loading