diff --git a/.github/workflows/website.yml b/.github/workflows/website.yml new file mode 100644 index 0000000..9c6e93b --- /dev/null +++ b/.github/workflows/website.yml @@ -0,0 +1,71 @@ +name: Deploy website + +on: + push: + branches: [ main ] + paths: + - "website/**" + - "package.json" + - "pnpm-lock.yaml" + - "pnpm-workspace.yaml" + - ".github/workflows/website.yml" + workflow_dispatch: + +permissions: + contents: read + pages: write + id-token: write + +concurrency: + group: github-pages + cancel-in-progress: false + +jobs: + build: + name: Build website + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v7 + + - name: Setup pnpm + uses: pnpm/action-setup@v6 + with: + version: 10.24.0 + + - name: Setup Node.js + uses: actions/setup-node@v6 + with: + node-version: 22 + cache: pnpm + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Configure GitHub Pages + id: pages + uses: actions/configure-pages@v6 + + - name: Build website + env: + SITE_BASE_PATH: ${{ steps.pages.outputs.base_path }}/ + run: pnpm run website:build + + - name: Upload GitHub Pages artifact + uses: actions/upload-pages-artifact@v4 + with: + path: website/dist + + deploy: + name: Deploy website + needs: build + runs-on: ubuntu-latest + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + + steps: + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 diff --git a/website/api/index.html b/website/api/index.html index 8d5d487..d132e2d 100644 --- a/website/api/index.html +++ b/website/api/index.html @@ -4,8 +4,8 @@ - - + + zig-napi API diff --git a/website/index.html b/website/index.html index 1c5c18e..accb59d 100644 --- a/website/index.html +++ b/website/index.html @@ -7,8 +7,8 @@ name="description" content="Documentation website for zig-napi, a Zig toolkit for OpenHarmony and Node.js native addons." /> - - + + zig-napi diff --git a/website/src/api-docs.tsx b/website/src/api-docs.tsx index e4fc7a0..c49ce60 100644 --- a/website/src/api-docs.tsx +++ b/website/src/api-docs.tsx @@ -1,5 +1,6 @@ import { lastUpdatedByApiSection } from "virtual:api-last-updated"; import { apiMarkdownGroups, apiMarkdownSections, getApiSection } from "./content/api"; +import { siteHref, siteRelativePath } from "./site-paths"; export function ApiDocsPage() { const activeId = resolveActiveApiSectionId(); @@ -110,12 +111,12 @@ export function ApiDocsPage() { } function resolveActiveApiSectionId() { - const parts = window.location.pathname.split("/").filter(Boolean); + const parts = siteRelativePath().split("/").filter(Boolean); return parts[0] === "api" && parts[1] ? parts[1] : "overview"; } function apiHref(id: string) { - return id === "overview" ? "/api/" : `/api/${id}/`; + return siteHref(id === "overview" ? "api/" : `api/${id}/`); } function apiNavLinkClass(active: boolean, mode: "desktop" | "mobile" = "desktop") { diff --git a/website/src/main.tsx b/website/src/main.tsx index ed2ae9b..d35f634 100644 --- a/website/src/main.tsx +++ b/website/src/main.tsx @@ -6,6 +6,7 @@ import InstallSnippet from "./content/snippets/install.md"; import NodeSnippet from "./content/snippets/node.md"; import OpenHarmonySnippet from "./content/snippets/openharmony.md"; import TypesSnippet from "./content/snippets/types.md"; +import { siteHref, siteRelativePath } from "./site-paths"; import "./styles.css"; type Snippet = { @@ -140,9 +141,8 @@ function App() { } function resolveRoute(): SiteRoute { - return window.location.pathname === "/api" || window.location.pathname.startsWith("/api/") - ? "api" - : "home"; + const path = siteRelativePath(); + return path === "api" || path.startsWith("api/") ? "api" : "home"; } function HomePage({ route }: { route: SiteRoute }) { @@ -197,9 +197,9 @@ function SiteHeader({ route }: { route: SiteRoute }) {
- + zig-napi
@@ -207,7 +207,7 @@ function SiteHeader({ route }: { route: SiteRoute }) { className="flex items-center gap-5 text-sm text-(--color-muted)" aria-label="Primary navigation" > - + API @@ -233,7 +233,7 @@ function Hero() {

OpenHarmony and Node.js native addons

@@ -248,7 +248,7 @@ function Hero() { Start with ZON - + API Reference
@@ -256,7 +256,7 @@ function Hero() {
Zig source flowing through zig-napi into OpenHarmony libraries, Node addons, and TypeScript definitions
@@ -439,7 +439,7 @@ function ApiOverviewSection() { Use the API page when you need exact Zig signatures and wrapper behavior instead of the overview flow.

- + Open API Reference
diff --git a/website/src/site-paths.ts b/website/src/site-paths.ts new file mode 100644 index 0000000..b2d0b4b --- /dev/null +++ b/website/src/site-paths.ts @@ -0,0 +1,13 @@ +const siteBasePath = import.meta.env.BASE_URL; + +export function siteHref(path = "") { + return `${siteBasePath}${path.replace(/^\/+/, "")}`; +} + +export function siteRelativePath(pathname = window.location.pathname) { + const relativePath = pathname.startsWith(siteBasePath) + ? pathname.slice(siteBasePath.length) + : pathname; + + return relativePath.replace(/^\/+|\/+$/g, ""); +} diff --git a/website/vite.config.ts b/website/vite.config.ts index 87ba637..6dd2ca3 100644 --- a/website/vite.config.ts +++ b/website/vite.config.ts @@ -1,5 +1,5 @@ import { execFileSync } from "node:child_process"; -import { statSync } from "node:fs"; +import { copyFileSync, mkdirSync, statSync } from "node:fs"; import { dirname, resolve } from "node:path"; import { fileURLToPath } from "node:url"; import { defineConfig, type Plugin } from "vite"; @@ -8,6 +8,7 @@ import { voidReact } from "@void/react/plugin"; import tailwindcss from "@tailwindcss/vite"; const rootDir = dirname(fileURLToPath(import.meta.url)); +const siteBasePath = normalizeBasePath(process.env.SITE_BASE_PATH); const API_LAST_UPDATED_ID = "virtual:api-last-updated"; const RESOLVED_API_LAST_UPDATED_ID = `\0${API_LAST_UPDATED_ID}`; const apiMarkdownFiles = [ @@ -68,6 +69,29 @@ function apiLastUpdated(): Plugin { }; } +function apiStaticRoutes(): Plugin { + return { + name: "api-static-routes", + apply: "build", + closeBundle() { + const apiEntry = resolve(rootDir, "dist/api/index.html"); + + for (const [sectionId] of apiMarkdownFiles) { + if (sectionId === "overview") continue; + + const routeDir = resolve(rootDir, "dist/api", sectionId); + mkdirSync(routeDir, { recursive: true }); + copyFileSync(apiEntry, resolve(routeDir, "index.html")); + } + }, + }; +} + +function normalizeBasePath(value: string | undefined) { + const path = value?.trim().replace(/^\/+|\/+$/g, ""); + return path ? `/${path}/` : "/"; +} + function lastUpdatedIso(filePath: string) { try { const committed = execFileSync("git", ["log", "-1", "--format=%cI", "--", filePath], { @@ -84,6 +108,7 @@ function lastUpdatedIso(filePath: string) { } export default defineConfig({ + base: siteBasePath, plugins: [ voidReact(), voidMarkdown({ @@ -96,6 +121,7 @@ export default defineConfig({ }), apiFallback(), apiLastUpdated(), + apiStaticRoutes(), tailwindcss(), ], build: {