Add VitePress documentation site setup

Introduces VitePress configuration and theme files for documentation, updates the GitHub Pages workflow to build and deploy the new docs, and updates .gitignore for VitePress and Node artifacts. Adds necessary dependencies and scripts to package.json, and updates the ONVIF client example README title.
This commit is contained in:
Sergey Krashevich
2026-01-19 01:23:45 +03:00
parent 159fb4675c
commit d98059f069
8 changed files with 208 additions and 9 deletions
+20 -5
View File
@@ -2,7 +2,9 @@
name: Deploy static content to Pages
on:
# Allows you to run this workflow manually from the Actions tab
push:
branches:
- main
workflow_dispatch:
# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
@@ -14,7 +16,7 @@ permissions:
# Allow one concurrent deployment
concurrency:
group: "pages"
cancel-in-progress: true
cancel-in-progress: false
jobs:
# Single deploy job since we're just deploying
@@ -25,13 +27,26 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v5
with:
fetch-depth: 0
- name: Setup Node
uses: actions/setup-node@v6
with:
node-version: 24
package-manager-cache: false
- name: Install dependencies
run: npm install --no-package-lock
- name: Build docs
env:
BASE_URL: /${{ github.event.repository.name }}/
run: npm run docs:build
- name: Setup Pages
uses: actions/configure-pages@v4
uses: actions/configure-pages@v5
- name: Upload artifact
uses: actions/upload-pages-artifact@v3
with:
path: './website'
path: './.vitepress/dist'
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4
+6
View File
@@ -15,3 +15,9 @@ go2rtc_win*
0_test.go
.DS_Store
.vitepress/cache
.vitepress/dist
node_modules
package-lock.json
+160
View File
@@ -0,0 +1,160 @@
import fs from 'fs';
import path from 'path';
import { defineConfig } from 'vitepress';
const repoRoot = path.resolve(__dirname, '..');
const srcDir = repoRoot;
const skipDirs = new Set(['.git', 'node_modules', '.vitepress', 'dist', 'scripts']);
function walkForReadmes(dir: string, results: string[]) {
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
if (entry.isDirectory()) {
if (skipDirs.has(entry.name)) {
continue;
}
walkForReadmes(path.join(dir, entry.name), results);
continue;
}
if (entry.isFile() && entry.name === 'README.md') {
results.push(path.join(dir, entry.name));
}
}
}
function extractTitle(filePath: string) {
const content = fs.readFileSync(filePath, 'utf8');
const match = content.match(/^#\s+(.+)$/m);
return match ? match[1].trim() : '';
}
function toTitleCase(value: string) {
return value
.replace(/[-_]/g, ' ')
.replace(/\b\w/g, (char) => char.toUpperCase());
}
function toLink(routePath: string) {
const clean = routePath.replace(/index\.md$/, '');
return clean ? `/${clean}` : '/';
}
const readmeFiles: string[] = [];
walkForReadmes(srcDir, readmeFiles);
const readmePaths = readmeFiles
.map((filePath) => path.relative(srcDir, filePath).replace(/\\/g, '/'))
.sort();
const rewrites = Object.fromEntries(
readmePaths.map((relPath) => [relPath, relPath.replace(/README\.md$/, 'index.md')])
);
const groupOrder = ['', 'docker', 'api', 'pkg', 'internal', 'examples', 'www'];
const groupTitles = new Map([
['', 'Overview'],
['api', 'API'],
['pkg', 'Packages'],
['internal', 'Internal'],
['examples', 'Examples'],
['docker', 'Docker'],
['www', 'WWW'],
]);
const groupedItems = new Map<string, Array<{ text: string; link: string }>>();
for (const relPath of readmePaths) {
const filePath = path.join(srcDir, relPath);
const segments = relPath.split('/');
const groupKey = segments.length > 1 ? segments[0] : '';
const routePath = rewrites[relPath];
const link = toLink(routePath);
const title = extractTitle(filePath);
const fallback = segments.length > 1 ? segments[segments.length - 2] : 'Overview';
const text = title || toTitleCase(fallback);
if (!groupedItems.has(groupKey)) {
groupedItems.set(groupKey, []);
}
groupedItems.get(groupKey)?.push({ text, link });
}
for (const items of groupedItems.values()) {
items.sort((a, b) => a.text.localeCompare(b.text));
}
const orderedGroups = [...groupedItems.entries()].sort((a, b) => {
const indexA = groupOrder.indexOf(a[0]);
const indexB = groupOrder.indexOf(b[0]);
if (indexA !== -1 || indexB !== -1) {
return (indexA === -1 ? Number.POSITIVE_INFINITY : indexA) -
(indexB === -1 ? Number.POSITIVE_INFINITY : indexB);
}
return a[0].localeCompare(b[0]);
});
const sidebar = orderedGroups.flatMap(([groupKey, items]) => {
const groupTitle = groupTitles.get(groupKey) || toTitleCase(groupKey || 'Overview');
if (items.length === 1) {
const [item] = items;
return [{ text: groupTitle, link: item.link }];
}
return [{
text: groupTitle,
collapsed: groupKey !== '',
items,
}];
});
const nav = orderedGroups
.filter(([, items]) => items.length > 0)
.map(([groupKey, items]) => {
if (groupKey === '') {
return { text: groupTitles.get(groupKey) || 'Overview', link: '/' };
}
const landing = items.find((item) => item.link === `/${groupKey}/`) ?? items[0];
return {
text: groupTitles.get(groupKey) || toTitleCase(groupKey),
link: landing.link,
};
});
export default defineConfig({
lang: 'en-US',
title: 'go2rtc Docs',
description: 'go2rtc documentation',
srcDir,
base: process.env.BASE_URL || '/',
cleanUrls: true,
ignoreDeadLinks: true,
rewrites,
head: [
['link', { rel: 'preconnect', href: 'https://fonts.googleapis.com' }],
['link', { rel: 'preconnect', href: 'https://fonts.gstatic.com', crossorigin: '' }],
[
'link',
{
rel: 'stylesheet',
href:
'https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;600&family=IBM+Plex+Sans:wght@400;500;600;700&display=swap',
},
],
],
markdown: {
theme: {
light: "catppuccin-latte",
dark: "catppuccin-mocha",
},
},
themeConfig: {
nav,
sidebar: {
'/': sidebar,
},
outline: [2, 3],
search: {
provider: 'local',
},
socialLinks: [{ icon: "github", link: "https://github.com/AlexxIT/go2rtc" }],
},
});
+4
View File
@@ -0,0 +1,4 @@
.VPSidebarItem.level-1 {
font-weight: 700;
}
+5
View File
@@ -0,0 +1,5 @@
import DefaultTheme from 'vitepress/theme';
import "@catppuccin/vitepress/theme/mocha/mauve.css";
import './custom.css';
export default DefaultTheme;
+1 -1
View File
@@ -1,4 +1,4 @@
## Example
## ONVIF Client
```shell
go run examples/onvif_client/main.go http://admin:password@192.168.10.90 GetAudioEncoderConfigurations
+2 -2
View File
@@ -37,8 +37,8 @@ Settings > Stream:
- Service: Custom
- Server: rtmp://192.168.10.101/tmp
- Stream Key: <empty>
- Use auth: <disabled>
- Stream Key: `<empty>`
- Use auth: `<disabled>`
**OpenIPC**
+10 -1
View File
@@ -1,7 +1,13 @@
{
"devDependencies": {
"eslint": "^8.44.0",
"eslint-plugin-html": "^7.1.0"
"eslint-plugin-html": "^7.1.0",
"vitepress": "^2.0.0-alpha.15"
},
"scripts": {
"docs:dev": "vitepress dev .",
"docs:build": "vitepress build .",
"docs:preview": "vitepress preview ."
},
"eslintConfig": {
"env": {
@@ -36,5 +42,8 @@
}
}
]
},
"dependencies": {
"@catppuccin/vitepress": "^0.1.2"
}
}