From d98059f0699aaa58c266c0a720e2f53a1b8f0c1f Mon Sep 17 00:00:00 2001 From: Sergey Krashevich Date: Mon, 19 Jan 2026 01:23:45 +0300 Subject: [PATCH] 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. --- .github/workflows/gh-pages.yml | 25 ++++- .gitignore | 6 ++ .vitepress/config.mts | 160 ++++++++++++++++++++++++++++++++ .vitepress/theme/custom.css | 4 + .vitepress/theme/index.ts | 5 + examples/onvif_client/README.md | 2 +- internal/rtmp/README.md | 4 +- package.json | 11 ++- 8 files changed, 208 insertions(+), 9 deletions(-) create mode 100644 .vitepress/config.mts create mode 100644 .vitepress/theme/custom.css create mode 100644 .vitepress/theme/index.ts diff --git a/.github/workflows/gh-pages.yml b/.github/workflows/gh-pages.yml index 4d0e2e67..caa91fb8 100644 --- a/.github/workflows/gh-pages.yml +++ b/.github/workflows/gh-pages.yml @@ -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 diff --git a/.gitignore b/.gitignore index 272fb7f0..de784c79 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,9 @@ go2rtc_win* 0_test.go .DS_Store + +.vitepress/cache +.vitepress/dist + +node_modules +package-lock.json \ No newline at end of file diff --git a/.vitepress/config.mts b/.vitepress/config.mts new file mode 100644 index 00000000..09c46e70 --- /dev/null +++ b/.vitepress/config.mts @@ -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>(); + +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" }], + }, +}); diff --git a/.vitepress/theme/custom.css b/.vitepress/theme/custom.css new file mode 100644 index 00000000..1a501f75 --- /dev/null +++ b/.vitepress/theme/custom.css @@ -0,0 +1,4 @@ + +.VPSidebarItem.level-1 { + font-weight: 700; +} \ No newline at end of file diff --git a/.vitepress/theme/index.ts b/.vitepress/theme/index.ts new file mode 100644 index 00000000..b4754c50 --- /dev/null +++ b/.vitepress/theme/index.ts @@ -0,0 +1,5 @@ +import DefaultTheme from 'vitepress/theme'; +import "@catppuccin/vitepress/theme/mocha/mauve.css"; +import './custom.css'; + +export default DefaultTheme; diff --git a/examples/onvif_client/README.md b/examples/onvif_client/README.md index b4ae3383..1fda07f1 100644 --- a/examples/onvif_client/README.md +++ b/examples/onvif_client/README.md @@ -1,4 +1,4 @@ -## Example +## ONVIF Client ```shell go run examples/onvif_client/main.go http://admin:password@192.168.10.90 GetAudioEncoderConfigurations diff --git a/internal/rtmp/README.md b/internal/rtmp/README.md index 662efa19..ce340e63 100644 --- a/internal/rtmp/README.md +++ b/internal/rtmp/README.md @@ -37,8 +37,8 @@ Settings > Stream: - Service: Custom - Server: rtmp://192.168.10.101/tmp -- Stream Key: -- Use auth: +- Stream Key: `` +- Use auth: `` **OpenIPC** diff --git a/package.json b/package.json index 649791f6..af5c7a70 100644 --- a/package.json +++ b/package.json @@ -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" } } \ No newline at end of file