|
1 | | -Hi |
| 1 | +# sqlc docs site |
| 2 | + |
| 3 | +This repository builds and deploys <https://docs.sqlc.dev>. It contains **no |
| 4 | +documentation content**. Content lives in |
| 5 | +[sqlc-dev/sqlc](https://github.com/sqlc-dev/sqlc) under `docs/` as plain |
| 6 | +GitHub-flavored Markdown; this repo ingests it, renders it with |
| 7 | +[Fumadocs](https://fumadocs.dev) on Next.js, and publishes a fully static |
| 8 | +site. |
| 9 | + |
| 10 | +``` |
| 11 | +sqlc-dev/sqlc this repo |
| 12 | +┌─────────────────────────┐ ┌──────────────────────────────┐ |
| 13 | +│ docs/*.md (content) │ checkout │ ingest: md → content tree │ |
| 14 | +│ docs/toc.yaml (nav) │ ─────────► │ build: Fumadocs / Next.js │ |
| 15 | +│ internal/docs (Go lint) │ │ deploy: static files → CDN │ |
| 16 | +└─────────────────────────┘ └──────────────────────────────┘ |
| 17 | +``` |
| 18 | + |
| 19 | +The split exists so that sqlc contributors never need a JavaScript toolchain: |
| 20 | +the Go linter in `sqlc/internal/docs` (runs in sqlc's `go test ./...`) |
| 21 | +enforces the content contract, and anything that passes it must render here. |
| 22 | +If a change to this repo would reject content the linter accepts, that's a |
| 23 | +bug in this repo — or a proposed contract change that goes to the linter |
| 24 | +first. |
| 25 | + |
| 26 | +## The content contract |
| 27 | + |
| 28 | +Enforced upstream by `sqlc/internal/docs`: |
| 29 | + |
| 30 | +- Plain CommonMark + GFM. No MDX, no JSX, no raw HTML except HTML comments. |
| 31 | +- Every page starts with exactly one `#` heading — the page title. |
| 32 | +- Admonitions use GitHub alert syntax: `> [!NOTE]`, `> [!TIP]`, `> [!WARNING]`. |
| 33 | +- Relative links (`../reference/config.md#database`) resolve to real files; |
| 34 | + anchors match GitHub-style heading slugs. |
| 35 | +- `docs/toc.yaml` lists every page exactly once: an `index`, titled |
| 36 | + `sections`, and `unlisted` (published, not in the sidebar). |
| 37 | + |
| 38 | +Two implementation details here are load-bearing for that contract: |
| 39 | + |
| 40 | +- Ingested pages keep their `.md` extension, so fumadocs-mdx compiles them as |
| 41 | + **Markdown, not MDX** — literal `{...}` and `<...>` in prose (which the |
| 42 | + linter rightly does not reject) render fine. |
| 43 | +- `lib/remark-github-alerts.ts` maps `> [!NOTE]` blockquotes to Fumadocs |
| 44 | + `<Callout>` components, and `lib/relative-link.tsx` resolves relative `.md` |
| 45 | + links (bare ones included) to page routes at render time. |
| 46 | + |
| 47 | +## Local development |
| 48 | + |
| 49 | +```sh |
| 50 | +npm ci |
| 51 | +npm run ingest # sparse-clones sqlc-dev/sqlc@main into .cache/ |
| 52 | +npm run dev |
| 53 | +``` |
| 54 | + |
| 55 | +`npm run ingest -- --ref v1.30.0` builds another ref; |
| 56 | +`npm run ingest -- --src ../sqlc/docs` uses a local checkout (CI does this). |
| 57 | + |
| 58 | +The ingest step (`scripts/ingest.mjs`) writes `content/docs/`: it derives |
| 59 | +each page's frontmatter `title` from its `#` heading and strips it, copies |
| 60 | +the body verbatim, and converts `toc.yaml` into a Fumadocs `meta.json` |
| 61 | +(sections become sidebar separators; `unlisted` pages get routes but stay out |
| 62 | +of the sidebar). Anything unexpected — an unknown `toc.yaml` field, a page |
| 63 | +without a title — fails the build loudly: that's the contract-drift alarm. |
| 64 | +`content/` is generated output; never commit or hand-edit it. |
| 65 | + |
| 66 | +`npm run build` emits the static site to `out/` (plain HTML/CSS/JS, servable |
| 67 | +from any static host that resolves `/howto/select` to `howto/select.html`), |
| 68 | +then `scripts/redirects.mjs` adds redirect stubs for legacy URLs. |
| 69 | + |
| 70 | +## URL scheme and redirects |
| 71 | + |
| 72 | +Pages are mounted at the domain root: `docs.sqlc.dev/howto/select`. The old |
| 73 | +Read the Docs URLs redirect: |
| 74 | + |
| 75 | +``` |
| 76 | +/en/latest/<path>.html → /<path> (301) |
| 77 | +/en/latest/ → / |
| 78 | +/howto/upload → /howto/push (carried over from the old rediraffe config) |
| 79 | +``` |
| 80 | + |
| 81 | +These should be real 301s at the hosting edge (e.g. Cloudflare Bulk |
| 82 | +Redirects). As a belt-and-braces fallback the build also emits meta-refresh |
| 83 | +stubs (with canonical links and `noindex`) under `out/en/latest/`, so legacy |
| 84 | +links work on any static host even before edge rules exist. Old RTD tag URLs |
| 85 | +(`/en/v1.29.0/...`) can redirect to `/` until versioned builds cover them. |
| 86 | + |
| 87 | +## Versioning |
| 88 | + |
| 89 | +Versions are immutable build artifacts, not branches: |
| 90 | + |
| 91 | +- The root serves latest, rebuilt on every `main` docs change. |
| 92 | +- The release workflow builds a tag with `NEXT_PUBLIC_BASE_PATH=/v1.32.0` |
| 93 | + (static export bakes absolute asset/link/search paths, so the prefix is a |
| 94 | + build-time setting) and uploads it to the `/v1.32.0/` prefix, once, forever. |
| 95 | +- `versions.json` at the domain root, appended by each release, drives the |
| 96 | + version-switcher dropdown; every snapshot fetches it at runtime so old |
| 97 | + snapshots list new versions. |
| 98 | +- Versioned builds set `noindex` so stale versions never outrank current |
| 99 | + docs in search engines. |
| 100 | +- Versioning starts at the first tag that contains `docs/toc.yaml`; older |
| 101 | + tags are not backfilled. |
| 102 | + |
| 103 | +## CI |
| 104 | + |
| 105 | +- **CI** (`ci.yml`): ingest + build + typecheck on every PR and push to main. |
| 106 | +- **Deploy latest docs** (`deploy.yml`): runs on `repository_dispatch` |
| 107 | + (type `docs-updated`), manual dispatch, and a daily cron as a safety net. |
| 108 | + Ingests sqlc@main, builds, syncs `out/` to the R2 bucket root — excluding |
| 109 | + `v*/` and `versions.json`, which belong to releases. |
| 110 | +- **Deploy versioned docs** (`release.yml`): takes a tag (manual input or |
| 111 | + `repository_dispatch` type `docs-release` with `{"tag": "v1.32.0"}`), |
| 112 | + builds it under its version prefix, syncs to that prefix, and appends the |
| 113 | + tag to `versions.json`. |
| 114 | + |
| 115 | +Deploys need these repository secrets: `R2_ACCOUNT_ID`, `R2_ACCESS_KEY_ID`, |
| 116 | +`R2_SECRET_ACCESS_KEY`, `R2_BUCKET`. Until they exist, the deploy step is |
| 117 | +skipped and the workflows just prove the build is green. |
| 118 | + |
| 119 | +The sqlc repo side is a tiny workflow that notifies this repo when docs |
| 120 | +change. `DOCS_DISPATCH_TOKEN` is a fine-grained PAT scoped to this repo only, |
| 121 | +with Contents read/write permission (what `repository_dispatch` requires): |
| 122 | + |
| 123 | +```yaml |
| 124 | +# .github/workflows/docs-dispatch.yml in sqlc-dev/sqlc |
| 125 | +name: Notify docs site |
| 126 | +on: |
| 127 | + push: |
| 128 | + branches: [main] |
| 129 | + paths: ['docs/**'] |
| 130 | +jobs: |
| 131 | + dispatch: |
| 132 | + runs-on: ubuntu-latest |
| 133 | + steps: |
| 134 | + - run: | |
| 135 | + curl -sS -X POST \ |
| 136 | + -H "Authorization: Bearer ${{ secrets.DOCS_DISPATCH_TOKEN }}" \ |
| 137 | + -H "Accept: application/vnd.github+json" \ |
| 138 | + https://api.github.com/repos/sqlc-dev/docs/dispatches \ |
| 139 | + -d '{"event_type": "docs-updated"}' |
| 140 | +``` |
| 141 | +
|
| 142 | +and the release equivalent sending |
| 143 | +`{"event_type": "docs-release", "client_payload": {"tag": "${{ github.ref_name }}"}}` |
| 144 | +on tag push. |
0 commit comments