godber 6f6590a714
release / release (push) Successful in 28s
release v0.0.4
2026-10-24 13:53:13 -07:00
2026-10-24 13:39:33 -07:00
2026-10-24 13:45:35 -07:00
2026-10-24 13:45:35 -07:00
2026-10-24 13:53:12 -07:00
2026-10-24 13:30:04 -07:00
2026-10-24 13:30:04 -07:00
2026-10-24 13:45:35 -07:00
2026-10-24 13:30:04 -07:00
2026-10-24 13:39:33 -07:00
2026-10-24 13:53:13 -07:00
2026-10-24 13:30:04 -07:00
2026-10-24 13:45:35 -07:00
2026-10-24 13:45:35 -07:00
2026-10-24 13:30:04 -07:00
2026-10-24 13:30:04 -07:00
2026-10-24 13:30:04 -07:00

srcsite

Turn a source tree into a fully static website: a syntax-highlighted page for every file with stable line anchors, a file tree, and an optional architecture page with pre-rendered Mermaid/C4 diagrams whose links into the source are validated.

  • Static: plain HTML, CSS and inline SVG. No CDN, no fonts, no network. Works from file://, and every page works without JavaScript.
  • Deterministic: same tree and tool version give byte-identical output. No timestamps, sorted traversal, LF-normalized input. It never calls an LLM.
  • Checked: srcsite check fails when a link points at a missing file, a line past the end of a file, an inverted range, or an unknown heading or directory anchor.

Status: pre-release. It is distributed as a tarball attached to each release on the Gitea project, not through npm. SPEC.md is the source of truth; its phase list is in section 14.

Install

Requires Node >= 26.

From a release (pick the version from the releases page):

pnpm add -D https://git.hawt.cloud/godber/srcsite/releases/download/v<version>/srcsite-<version>.tgz
pnpm exec srcsite build --out site

From a checkout, as a global command:

pnpm install && pnpm build
pnpm link --global
srcsite build --root /path/to/repo --out /path/to/site

A git-URL install (pnpm add -D git+https://git.hawt.cloud/godber/srcsite.git) also works: the prepare script compiles the package on install.

Quick start

From a checkout (no install needed beyond the dependencies):

pnpm install
pnpm srcsite build --root /path/to/repo --out /path/to/site
open /path/to/site/index.html

build runs every stage and exits non-zero if any link is broken. Output layout:

site/
├── index.html              # architecture page, or the file tree if there is no architecture.md
├── tree.html               # collapsible file tree + flat file index
├── assets/srcsite.css
├── src/<path>.html         # one page per source file
└── .srcsite-manifest.json  # contract between stages; index of files and links

Commands

srcsite build   Run all stages (discover, highlight, docs, check)
srcsite src     Highlight source files; write tree.html and the manifest
srcsite docs    Render the architecture page (needs a prior `src`)
srcsite check   Validate links in the generated site
srcsite watch   Rebuild on change, optionally serving the site

Common flags: --config <file>, --root <dir>, --out <dir>, --strict (diagram render errors are fatal), --no-git (walk the filesystem instead of git ls-files).

Command Flags
check --json machine-readable report; --git-diff <ref> warn about links into lines changed since <ref>; --check-external also probe http(s) links
watch --serve serve --out on http://127.0.0.1; --port <n> (default 4173, 0 picks a free port); --debounce <ms> (default 200)

Exit codes: 0 ok, 1 build error, 2 link-check failures, 3 bad config. When several apply, 3 beats 1 beats 2.

The architecture page

Write architecture.md in the source root (the name is configurable). It is CommonMark plus GFM tables, with Mermaid diagrams as fenced blocks. Link into the source with the src: shorthand, in prose and inside diagrams:

# Architecture

The entry point is [`main`](src:backend/app/main.py#L48-L60); see the
[services](src:backend/app/services) directory for the rest, or {{file:backend/app/config.py}}.

```mermaid
flowchart LR
  API --> Worker
  click API "src:backend/app/main.py#L48"
```
Authored Resolves to
src:path/to/file.py src/path/to/file.py.html
src:path/to/file.py#L48 or #L142-L190 the same page, with the fragment
src:path/to/dir tree.html#dir-path/to/dir

Diagrams are rendered at build time with Pele into a single theme-adaptive inline SVG. A diagram that fails to render shows an error banner with its source and is reported, or fails the build under --strict. Raw HTML in Markdown is dropped with a warning.

Configuration

srcsite.config.json (or a srcsite.config.js default-exporting an object). Everything is optional; CLI flags beat the config file, which beats the defaults. An unknown key is an error with a did-you-mean hint.

{
  "root": ".",
  "out": "site",
  "include": ["**/*"],
  "exclude": ["**/*.snap", "frontend/dist/**"],
  "useDefaultExcludes": true,
  "maxFileBytes": 524288,
  "maxLineLength": 2000,
  "maxHighlightLines": 10000,
  "languageMap": { "**/*.tsx": "tsx", "Dockerfile*": "dockerfile" },
  "themes": { "light": "github-light", "dark": "github-dark" },
  "tabSize": 4,
  "wrap": false,
  "docs": {
    "input": "architecture.md",
    "format": "markdown",
    "title": "Architecture",
    "diagramMaxWidth": "100%"
  },
  "strict": false
}

Binary files, files over maxFileBytes, and files that would only differ from another by case are handled at discovery: the first two are skipped and listed in tree.html, and the case collision is a build error because the output pages would overwrite each other on macOS and Windows. Files with an unknown grammar, a very long line, or too many lines are rendered as plain text with a warning.

Programmatic API

import { build, check, type BuildResult, type CheckReport, type Config } from "srcsite";

const result: BuildResult = await build({ root: "/path/to/repo", out: "site" }, { git: true });
if (result.exitCode !== 0) console.error(result.check.pages);
  • build(config?, options?) runs every stage (discover, highlight, docs, check). config is a Partial<Config> merged over the defaults and validated like a config file (unknown keys throw ConfigError); config files are not read, use loadConfig() for that. options.cwd is the base for relative root / out; options.git: false walks the filesystem instead of git ls-files. It throws BuildError on a build failure and does not call process.exit.
  • BuildResult has exitCode (0, or 2 when the link check found problems), outDir (absolute), src, docs and check (the per-stage results), and warnings (highlight and docs stages combined).
  • check(config?) validates an existing site (config.out) and returns a CheckReport (ok, plus per-page problems).
  • Also exported: loadConfig, validateConfig, DEFAULT_CONFIG, ConfigError, discover and its types, and readManifest / writeManifest with the manifest types.

The API holds no global state and may be called repeatedly in one process.

Incremental builds and watch

A page is rewritten only when the file's sha256, the tool version, the rendering-relevant config and the commit label all match the previous manifest and the page still exists. Otherwise it is rebuilt. srcsite watch uses the same mechanism, so a one-file edit rebuilds in well under a second even on a large tree.

Builds with at least 20,000 lines to highlight spread the work over worker threads. On a synthetic 5,000-file / 500k-line tree (14-core laptop) a cold build takes about 16 s and a no-op rebuild 0.3 s. Details and caveats are in SPEC.md section 14.

Development

pnpm install
pnpm build          # tsc -> dist/
pnpm typecheck
pnpm test           # vitest run
pnpm test -- -u     # update golden files (review the diff first)
node scripts/perf.ts <work-dir>   # synthetic 5,000-file benchmark
node scripts/smoke.ts [repo-dir]  # pack, install the tarball, build a site for repo-dir (needs network)

Releasing

Move the CHANGELOG.md entries under the new version, then run scripts/bump.sh <version>: it sets version in package.json (the single source; TOOL_VERSION reads it), commits, and creates the v<version> tag. Pushing that tag runs .gitea/workflows/release.yml: it checks the tag against package.json, runs typecheck, tests and the smoke test, packs, and creates a Gitea release with srcsite-<version>.tgz attached. It needs a RELEASE_TOKEN repository secret (an access token that can create releases). scripts/release.sh performs the same steps by hand.

pnpm typecheck && pnpm test must pass before a change is done. Read CLAUDE.md for the toolchain rules and the invariants that are easy to break.

S
Description
No description provided
Readme MIT
250 KiB
0 Stars 1 Watchers 0 Forks
srcsite 0.0.4
Latest
2026-10-24 14:06:24 -07:00
Languages
TypeScript 81.4%
HTML 9.9%
CSS 4.6%
JavaScript 3%
Shell 1.1%