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

on:
push:
branches: [main]
pull_request:
branches: [main]
workflow_dispatch:

permissions:
contents: read

# Deployments queue rather than cancel: a half-published site is worse than a
# slightly stale one.
concurrency:
group: pages
cancel-in-progress: false

env:
# Pinned rather than 'latest': the site depends on Hugo's mount and render-hook
# behaviour, and hugo.toml documents a v0.165.0 quirk in module.mounts.
HUGO_VERSION: 0.165.0

jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
with:
# The theme is a submodule; fetch-depth 0 gives every page its
# last-modified date from git history (enableGitInfo).
submodules: recursive
fetch-depth: 0

- name: Install Hugo
run: |
curl -sSLo hugo.deb "https://github.com/gohugoio/hugo/releases/download/v${HUGO_VERSION}/hugo_extended_${HUGO_VERSION}_linux-amd64.deb"
sudo dpkg -i hugo.deb

# Runs on pull requests too. docs/*.md keep plain relative links so they
# stay readable on github.com, which means a renamed page only shows up as
# a broken link here — the build is the check.
- name: Build
run: hugo --gc --minify

- name: Check every internal link resolves
run: ./scripts/check-site-links.py

- uses: actions/upload-pages-artifact@7b1f4a764d45c48632c6b24a0339c27f5614fb0b # v4
if: github.event_name != 'pull_request'
with:
path: ./public

deploy:
needs: build
if: github.event_name != 'pull_request'
runs-on: ubuntu-latest
permissions:
contents: read
pages: write
id-token: write
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@d6db90164ac5ed86f2b6aed7e0febac5b3c0c03e # v4
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,11 @@ __pycache__/
*.pyo
.venv/

# Hugo (docs site build output and asset cache)
/public/
/resources/
.hugo_build.lock

# Worktrees
.worktrees/

Expand Down
4 changes: 4 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
[submodule "themes/hugo-book"]
path = themes/hugo-book
url = https://github.com/alex-shpak/hugo-book
branch = main
17 changes: 15 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
.PHONY: build test codegen run clean test-compat docker-build docker-run changelog stats
.PHONY: build test codegen run clean test-compat docker-build docker-run changelog stats docs-serve docs-build

build:
go build -o dist/devcloud ./cmd/devcloud
Expand All @@ -25,8 +25,21 @@ docker-build:
docker-run:
docker run -p 4747:4747 -v $(PWD)/data:/app/data devcloud/devcloud

# Serves docs/ as the site published to skyoo2003.github.io/devcloud.
# Requires the extended edition (the theme compiles SCSS) and the theme
# submodule: git submodule update --init
docs-serve:
hugo server --buildDrafts

# What .github/workflows/pages.yml runs. The link check is not optional: the
# render hook resolves unknown links to GitHub URLs instead of failing, so a
# renamed page breaks quietly without it.
docs-build:
hugo --gc --minify
./scripts/check-site-links.py

clean:
rm -rf dist/ data/
rm -rf dist/ data/ public/ resources/

changelog:
@if [ -z "$(VERSION)" ]; then \
Expand Down
5 changes: 5 additions & 0 deletions changes/unreleased/Documentation-20260907-101500.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
kind: Documentation
body: The documentation is now published as a searchable site at https://skyoo2003.github.io/devcloud/, built with Hugo from the same `docs/*.md` files GitHub renders — no front matter added and no links rewritten.
time: 2026-09-07T10:15:00.000000+09:00
custom:
Issue: "154"
28 changes: 27 additions & 1 deletion docs/contributing.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
- Python 3.10+ with pip (compatibility tests)
- Docker (optional — Lambda runtime and integration tests)
- `pre-commit` (optional but recommended)
- Hugo extended 0.165+ (optional — only to preview this documentation as a site)

No C toolchain and no SQLite headers: the SQLite driver is pure Go, which is why
everything here builds with `CGO_ENABLED=0`.
Expand Down Expand Up @@ -33,7 +34,9 @@ make run # starts the server on port 4747
| `make codegen-s3` | Same, restricted to S3 — fast loop while editing templates |
| `make docker-build` | Build the image from `docker/Dockerfile` as `devcloud/devcloud` |
| `make docker-run` | Run that image on port 4747 with `./data` mounted |
| `make clean` | Delete `dist/` and `data/` |
| `make docs-serve` | Preview this documentation locally with Hugo |
| `make docs-build` | Build the documentation site and check every internal link |
| `make clean` | Delete `dist/`, `data/`, and the Hugo output in `public/` and `resources/` |
| `make changelog VERSION=vX.Y.Z` | Batch and merge Changie fragments; `VERSION` is required |
| `make stats` | Print registered service and operation counts |

Expand Down Expand Up @@ -139,6 +142,29 @@ internal/
└── register.go # Plugin registration
```

## Documentation

Everything under `docs/` is read two ways: as plain markdown on github.com, and
as the site at <https://skyoo2003.github.io/devcloud/>. Hugo mounts the directory
rather than copying it, so both come from the same files.

That dual audience sets one rule: **no front matter**. GitHub renders a YAML
block as a metadata table above the page, so titles come from each document's
H1 instead, and links stay plain and relative (`[coverage](coverage.md#tiers)`)
rather than Hugo `ref` shortcodes.

Adding a page takes two steps:

1. Write `docs/<name>.md` starting with an `# H1` — that becomes its title.
2. Add it to `[menu.before]` in `hugo.toml`. The sidebar is defined there
because Hugo would otherwise sort pages alphabetically, and every entry needs
its own `identifier` — entries without one collapse into a single link.

Then run `make docs-build`. Links that leave `docs/` — to source files or to
root files like `CONTRIBUTING.md` — are rewritten to point at GitHub, which also
means an unresolvable link fails silently; `scripts/check-site-links.py`, which
`make docs-build` runs, is what catches it.

## Code Style

- Standard Go conventions (`gofmt`, `go vet`)
Expand Down
149 changes: 149 additions & 0 deletions hugo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
baseURL = 'https://skyoo2003.github.io/devcloud/'
title = 'DevCloud'
theme = 'hugo-book'
enableGitInfo = true

# docs/ is mounted rather than copied: the same files serve github.com readers
# (README.md links straight into them) and this site, so they must stay free of
# front matter and stay where the repository links expect them.
#
# README.md becomes the home page instead of a separate _index.md — it is
# already the documentation index, and duplicating it would guarantee the two
# drift apart.
[[module.mounts]]
source = 'docs'
target = 'content'
# Without this, README.md is built a second time as /readme/, duplicating the
# home page. Hugo deprecated `excludeFiles` in v0.153.0 in favour of `files`,
# but as of v0.165.0 `files` does not filter mounts — every pattern tried
# ('!README.md', '{**,!README.md}', list forms) was silently ignored and let
# README.md through. Reordering the mounts so the file mount claims it first
# does not work either. Revisit when `files` actually filters; until then this
# is the only key that does the job, at the cost of one deprecation warning.
excludeFiles = ['README.md']

[[module.mounts]]
source = 'docs/README.md'
target = 'content/_index.md'

[markup]
[markup.goldmark.renderer]
unsafe = true
[markup.tableOfContents]
startLevel = 2
endLevel = 3

# Sidebar order is defined here rather than by per-page `weight` front matter.
# Hugo's default page sort is alphabetical, which buries Getting Started at
# position ten; the alternative — adding front matter to all twenty documents —
# makes github.com render a metadata table above every page.
# The file-tree menu is disabled in layouts/_partials/docs/menu-filetree.html.
#
# Every entry needs an explicit `identifier`. Entries carrying only `pageRef`
# all share the empty identifier and collapse into a single surviving link.
# Names are omitted on purpose: the theme falls back to the page title, which
# layouts/_partials/docs/title.html reads from each document's H1.
[menu]
[[menu.before]]
identifier = 'getting-started'
pageRef = '/getting-started'
weight = 10
[[menu.before]]
identifier = 'configuration'
pageRef = '/configuration'
weight = 20
[[menu.before]]
identifier = 'architecture'
pageRef = '/architecture'
weight = 30
[[menu.before]]
identifier = 'coverage'
pageRef = '/coverage'
weight = 40
[[menu.before]]
identifier = 'compatibility-policy'
pageRef = '/compatibility-policy'
weight = 50
[[menu.before]]
identifier = 'fidelity-manifest'
pageRef = '/fidelity-manifest'
weight = 60
[[menu.before]]
identifier = 'crud-engine'
pageRef = '/crud-engine'
weight = 70

[[menu.before]]
identifier = 'services-matrix'
pageRef = '/services-matrix'
weight = 80
[[menu.before]]
identifier = 's3'
parent = 'services-matrix'
pageRef = '/services/s3'
weight = 81
[[menu.before]]
identifier = 'sqs'
parent = 'services-matrix'
pageRef = '/services/sqs'
weight = 82
[[menu.before]]
identifier = 'dynamodb'
parent = 'services-matrix'
pageRef = '/services/dynamodb'
weight = 83
[[menu.before]]
identifier = 'lambda'
parent = 'services-matrix'
pageRef = '/services/lambda'
weight = 84
[[menu.before]]
identifier = 'iam-sts'
parent = 'services-matrix'
pageRef = '/services/iam-sts'
weight = 85

[[menu.before]]
identifier = 'demand'
pageRef = '/demand'
weight = 90
[[menu.before]]
identifier = 'roadmap'
pageRef = '/roadmap'
weight = 100
[[menu.before]]
identifier = 'faq'
pageRef = '/faq'
weight = 110
[[menu.before]]
identifier = 'troubleshooting'
pageRef = '/troubleshooting'
weight = 120
[[menu.before]]
identifier = 'contributing'
pageRef = '/contributing'
weight = 130
[[menu.before]]
identifier = 'plugin-api'
pageRef = '/plugin-api'
weight = 140

[[menu.after]]
name = 'GitHub'
url = 'https://github.com/skyoo2003/devcloud'
weight = 10
[[menu.after]]
name = 'Changelog'
url = 'https://github.com/skyoo2003/devcloud/blob/main/CHANGELOG.md'
weight = 20

[params]
BookTheme = 'auto'
BookSearch = true
BookComments = false
BookSection = '/'
BookRepo = 'https://github.com/skyoo2003/devcloud'
# .Path already arrives as docs/<file>.md — it is derived from the file's
# path relative to the working directory, not to the mount target.
BookEditLink = '{{ .Site.Params.BookRepo }}/edit/main/{{ .Path }}'
BookLastChangeLink = '{{ .Site.Params.BookRepo }}/commit/{{ .GitInfo.Hash }}'
44 changes: 44 additions & 0 deletions layouts/_markup/render-link.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
{{- /*
Overrides the theme's link hook so docs/*.md can keep the relative links that
make them readable on github.com.

Two kinds arrive here:
- 83 links inside docs/, e.g. `](coverage.md#tiers)` — resolved to the built
page, so the site is self-contained.
- 46 links that escape docs/, e.g. `](../internal/gateway/router.go)` and
`](../CONTRIBUTING.md)` — nothing outside the mount is built, so these
point at the file on GitHub rather than 404.

Anything unresolved falls through to the GitHub URL rather than failing the
build: a link to a source file is a normal thing for these docs to contain.
*/ -}}
{{- $destination := .Destination -}}
{{- $url := urls.Parse $destination -}}

{{- if and (not $url.IsAbs) (not (strings.HasPrefix $destination "#")) -}}
{{- $ref := strings.TrimPrefix "./" $url.Path -}}
{{- $ref = strings.TrimSuffix ".md" $ref -}}
{{- $ref = strings.TrimSuffix "/_index" $ref -}}

{{- with .Page.GetPage $ref -}}
{{- $destination = .RelPermalink -}}
{{- else -}}
{{- with $.Page.File -}}
{{- /* .Path is relative to the mount target, so re-add the docs/ prefix
before resolving the `../` segments against the repository root. */ -}}
{{- $repoPath := path.Join "docs" (path.Dir .Path) $url.Path -}}
{{- $kind := cond (strings.HasSuffix $url.Path "/") "tree" "blob" -}}
{{- $destination = printf "%s/%s/main/%s" $.Page.Site.Params.BookRepo $kind $repoPath -}}
{{- end -}}
{{- end -}}

{{- with $url.RawQuery -}}
{{- $destination = printf "%s?%s" $destination . -}}
{{- end -}}
{{- with $url.Fragment -}}
{{- $destination = printf "%s#%s" $destination . -}}
{{- end -}}
{{- end -}}

<a href="{{ $destination | safeURL }}"{{ with .Title }} title="{{ . }}"{{ end }}>{{ .Text | safeHTML }}</a>
{{- /**/ -}}
8 changes: 8 additions & 0 deletions layouts/_partials/docs/menu-filetree.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{{- /*
Deliberately empty. The theme builds its sidebar from the content tree, which
Hugo sorts alphabetically when pages carry no `weight` — and docs/*.md carry
no front matter at all, on purpose, so they render cleanly on github.com.

The sidebar is defined by [menu.before] in hugo.toml instead, rendered by the
theme's docs/menu-hugo partial. Add new pages there, not here.
*/ -}}
Loading