Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 71 additions & 0 deletions .github/workflows/website.yml
Original file line number Diff line number Diff line change
@@ -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
4 changes: 2 additions & 2 deletions website/api/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="description" content="API reference for zig-napi." />
<link rel="icon" type="image/png" sizes="256x256" href="/logo/icon-256.png" />
<link rel="apple-touch-icon" sizes="512x512" href="/logo/icon-512.png" />
<link rel="icon" type="image/png" sizes="256x256" href="%BASE_URL%logo/icon-256.png" />
<link rel="apple-touch-icon" sizes="512x512" href="%BASE_URL%logo/icon-512.png" />
<title>zig-napi API</title>
</head>
<body>
Expand Down
4 changes: 2 additions & 2 deletions website/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
name="description"
content="Documentation website for zig-napi, a Zig toolkit for OpenHarmony and Node.js native addons."
/>
<link rel="icon" type="image/png" sizes="256x256" href="/logo/icon-256.png" />
<link rel="apple-touch-icon" sizes="512x512" href="/logo/icon-512.png" />
<link rel="icon" type="image/png" sizes="256x256" href="%BASE_URL%logo/icon-256.png" />
<link rel="apple-touch-icon" sizes="512x512" href="%BASE_URL%logo/icon-512.png" />
<title>zig-napi</title>
</head>
<body>
Expand Down
5 changes: 3 additions & 2 deletions website/src/api-docs.tsx
Original file line number Diff line number Diff line change
@@ -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();
Expand Down Expand Up @@ -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") {
Expand Down
20 changes: 10 additions & 10 deletions website/src/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand Down Expand Up @@ -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 }) {
Expand Down Expand Up @@ -197,17 +197,17 @@ function SiteHeader({ route }: { route: SiteRoute }) {
<div className="container-page flex h-14 items-center justify-between gap-4">
<a
className="flex shrink-0 items-center gap-2.5 text-sm font-semibold text-(--color-fg)"
href="/"
href={siteHref()}
>
<img className="size-7 rounded-md" src="/logo/icon-256.png" alt="" />
<img className="size-7 rounded-md" src={siteHref("logo/icon-256.png")} alt="" />
zig-napi
</a>
<div className="ml-auto flex items-center justify-end gap-5">
<nav
className="flex items-center gap-5 text-sm text-(--color-muted)"
aria-label="Primary navigation"
>
<a className={apiClass} href="/api/">
<a className={apiClass} href={siteHref("api/")}>
API
</a>
</nav>
Expand All @@ -233,7 +233,7 @@ function Hero() {
<div className="reveal is-visible">
<img
className="mb-7 size-16 rounded-2xl border border-(--color-border-strong)"
src="/logo/icon-512.png"
src={siteHref("logo/icon-512.png")}
alt=""
/>
<p className="eyebrow">OpenHarmony and Node.js native addons</p>
Expand All @@ -248,15 +248,15 @@ function Hero() {
<a className="btn-primary" href="#install">
Start with ZON
</a>
<a className="btn-secondary" href="/api/">
<a className="btn-secondary" href={siteHref("api/")}>
API Reference
</a>
</div>
</div>
<figure className="reveal is-visible overflow-hidden rounded-lg border border-(--color-border-strong) bg-(--color-surface-1)/80 shadow-2xl shadow-black/30">
<img
className="block w-full"
src="/zig-napi-pipeline.svg"
src={siteHref("zig-napi-pipeline.svg")}
alt="Zig source flowing through zig-napi into OpenHarmony libraries, Node addons, and TypeScript definitions"
/>
</figure>
Expand Down Expand Up @@ -439,7 +439,7 @@ function ApiOverviewSection() {
Use the API page when you need exact Zig signatures and wrapper behavior instead of the
overview flow.
</p>
<a className="btn-primary mt-5 w-full" href="/api/">
<a className="btn-primary mt-5 w-full" href={siteHref("api/")}>
Open API Reference
</a>
</div>
Expand Down
13 changes: 13 additions & 0 deletions website/src/site-paths.ts
Original file line number Diff line number Diff line change
@@ -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, "");
}
28 changes: 27 additions & 1 deletion website/vite.config.ts
Original file line number Diff line number Diff line change
@@ -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";
Expand All @@ -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 = [
Expand Down Expand Up @@ -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], {
Expand All @@ -84,6 +108,7 @@ function lastUpdatedIso(filePath: string) {
}

export default defineConfig({
base: siteBasePath,
plugins: [
voidReact(),
voidMarkdown({
Expand All @@ -96,6 +121,7 @@ export default defineConfig({
}),
apiFallback(),
apiLastUpdated(),
apiStaticRoutes(),
tailwindcss(),
],
build: {
Expand Down
Loading