Skip to content
Open
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
118 changes: 13 additions & 105 deletions .github/workflows/continuous-deployment.yml
Original file line number Diff line number Diff line change
@@ -1,59 +1,27 @@
# Workflow for building Next.js site and downloading DocumentDB packages, then deploying to GitHub Pages
name: Deploy Next.js site and DocumentDB packages to Pages
# Fork-demo deployment: builds the Next.js site (including versioned docs
# snapshots) and deploys to GitHub Pages as a project page under
# /documentdb.github.io/. The upstream workflow additionally builds the
# APT/RPM package repositories; those steps are omitted here because the fork
# demo only showcases the documentation site.
name: Deploy Next.js site to Pages (fork demo)
on:
# Runs on pushes targeting the default branch
push:
branches:
- main
# Allows you to run this workflow manually from the Actions tab
workflow_dispatch:
# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued.
# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete.
concurrency:
group: pages
cancel-in-progress: false
jobs:
# Build job
build:
name: Build Next.js static site
# Sets permissions of the GITHUB_TOKEN to allow reading of repository content
permissions:
contents: read
runs-on: ubuntu-22.04
# Without an explicit timeout a stalled step runs against GitHub's 6-hour
# default before failing, which for `pages` concurrency means blocking
# every deployment queued behind it.
timeout-minutes: 30
steps:
- name: Checkout source
uses: actions/checkout@v7
- name: Install required packages
run: |
until sudo apt-get update; do sleep 1; done
sudo apt-get install -y createrepo-c dpkg-dev dpkg-sig gnupg2 python3
- name: Setup GPG
id: import_gpg
uses: crazy-max/ghaction-import-gpg@v7
with:
gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }}
continue-on-error: true
- name: Set GPG fingerprint and version config
run: |
# Configure GPG signing
if [ -n "${{ steps.import_gpg.outputs.fingerprint }}" ]; then
echo "GPG_FINGERPRINT=${{ steps.import_gpg.outputs.fingerprint }}" >> $GITHUB_ENV
echo "✅ GPG key loaded successfully"
echo " Fingerprint: ${{ steps.import_gpg.outputs.fingerprint }}"
echo " Key ID: ${{ steps.import_gpg.outputs.keyid }}"
echo " User ID: ${{ steps.import_gpg.outputs.name }} <${{ steps.import_gpg.outputs.email }}>"
else
echo "⚠️ No GPG key configured - packages will not be signed"
echo " To enable signing, add GPG_PRIVATE_KEY to repository secrets"
fi

# Configure DocumentDB version (can be overridden by repository variables)
echo "DOCUMENTDB_VERSION=${{ vars.DOCUMENTDB_VERSION || 'latest' }}" >> $GITHUB_ENV
echo "MULTI_VERSION=${{ vars.MULTI_VERSION || 'true' }}" >> $GITHUB_ENV
- name: Setup Node.js
uses: actions/setup-node@v7
with:
Expand All @@ -69,93 +37,38 @@ jobs:
with:
path: |
.next/cache
# Generate a new cache whenever packages or source files change.
key: ${{ runner.os }}-nextjs-${{ hashFiles('**/package-lock.json', '**/yarn.lock') }}-${{ hashFiles('**.[jt]s', '**.[jt]sx') }}
# If source files changed but packages didn't, rebuild from a prior cache.
restore-keys: |
${{ runner.os }}-nextjs-${{ hashFiles('**/package-lock.json', '**/yarn.lock') }}-
- name: Install dependencies
run: npm ci
- name: Build with Next.js
# This repository is the organization Pages site served at the root of
# the custom domain (documentdb.io), so the build must NOT set
# NEXT_BASE_PATH. Setting it to the repository name (the usual trick
# for project pages) prefixes every internal link and asset URL with
# /documentdb.github.io/, which GitHub Pages then 301-redirects back
# to the root on every request and leaves the prefixed URL visible in
# the address bar after client-side navigation.
# This fork deploys as a PROJECT page at
# https://guanzhousong.github.io/documentdb.github.io/, so unlike the
# upstream org-domain deployment the base path MUST be set.
env:
JEKYLL_BASE_PATH: /blogs
NEXT_BASE_PATH: documentdb.github.io
JEKYLL_BASE_PATH: /documentdb.github.io/blogs
run: npm run build
- name: Verify exported documentation pages
# A partially failed content compile must never reach production as a
# docs-less site. compile-content fails the build on clone/copy errors;
# this is the independent belt-and-braces check on the final artifact.
run: |
set -euo pipefail
for page in out/index.html out/docs/index.html out/docs/getting-started/index.html out/docs/reference/index.html; do
for page in out/index.html out/docs/index.html out/docs/getting-started/index.html out/docs/reference/index.html out/docs/versions/index.html out/docs/versions/v0.114-0/getting-started/index.html; do
if [ ! -f "$page" ]; then
echo "Missing expected page: $page"
exit 1
fi
done
reference_count=$(find out/docs/reference -name index.html | wc -l)
echo "Reference pages exported: $reference_count"
# The docs repo currently holds ~240 reference entries; well under
# half of that means the compile silently lost content.
if [ "$reference_count" -lt 100 ]; then
echo "Only $reference_count reference pages exported - documentation content looks incomplete."
exit 1
fi
- name: Download DocumentDB packages from latest release
run: .github/scripts/download_packages.sh
- name: Verify generated package components
run: |
set -euo pipefail
python3 - <<'PY'
import json
from pathlib import Path

release_info = Path("out/packages/release-info.json")
if not release_info.exists():
raise SystemExit("release-info.json was not generated")

data = json.loads(release_info.read_text())
assets = [asset["name"] for asset in data.get("assets", [])]
components = ("deb11", "deb12", "deb13", "ubuntu22", "ubuntu24")

for component in components:
has_assets = any(
name.endswith(".deb")
and (
name.startswith(f"{component}-")
or name.startswith(f"{component}.04-")
)
for name in assets
)
if not has_assets:
continue

for arch in ("amd64", "arm64"):
packages = Path(f"out/deb/dists/stable/{component}/binary-{arch}/Packages")
packages_gz = Path(f"out/deb/dists/stable/{component}/binary-{arch}/Packages.gz")
if not packages.exists() or not packages_gz.exists():
raise SystemExit(
f"Missing APT metadata for {component} {arch}: "
f"{packages} / {packages_gz}"
)

release_file = Path("out/deb/dists/stable/Release")
if release_file.exists() and any(name.startswith("deb13-") and name.endswith(".deb") for name in assets):
release_text = release_file.read_text()
if "deb13" not in release_text:
raise SystemExit("deb13 assets exist but deb13 is missing from the APT Release file")
PY
- name: Upload artifact
uses: actions/upload-pages-artifact@v5
with:
path: ./out
# Deployment job
deploy:
name: Publish site to GitHub Pages
environment:
Expand All @@ -165,19 +78,14 @@ jobs:
timeout-minutes: 10
needs:
- build
# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
permissions:
pages: write
id-token: write
steps:
- name: Setup Pages
uses: actions/configure-pages@v6
with:
# Automatically inject basePath in your Next.js configuration file and disable
# server side image optimization (https://nextjs.org/docs/api-reference/next/image#unoptimized).
#
# You may remove this line if you want to manage the configuration yourself.
static_site_generator: next
enablement: true
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v5
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@
# Temporary content cloning directory
_tmp/

# Compiled versioned documentation snapshots
/versioned/

# Reference files (compiled into the repo root from documentdb/docs; anchored
# so the patterns cannot swallow tracked paths like app/docs/reference/)
/api-reference/
Expand Down
4 changes: 4 additions & 0 deletions app/components/Breadcrumb.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ export default function Breadcrumb({ type, category, name }: {
}) {
return (
<nav className="mb-6 text-sm text-gray-400">
<Link href="/docs" className="hover:text-blue-400 transition-colors">
Docs
</Link>
<span className="mx-2">/</span>
<Link href="/docs/reference" className="hover:text-blue-400 transition-colors">
Reference
</Link>
Expand Down
30 changes: 30 additions & 0 deletions app/components/DocsBreadcrumb.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import Link from "next/link";

export interface Crumb {
title: string;
href?: string;
}

/**
* Breadcrumb trail for documentation pages:
* Docs / [version] / Section / Page
* The last crumb (no href) is the page being viewed.
*/
export default function DocsBreadcrumb({ crumbs }: { crumbs: Crumb[] }) {
return (
<nav className="mb-6 text-sm text-gray-400">
{crumbs.map((crumb, index) => (
<span key={`${crumb.title}-${index}`}>
{index > 0 && <span className="mx-2">/</span>}
{crumb.href ? (
<Link href={crumb.href} className="hover:text-blue-400 transition-colors">
{crumb.title}
</Link>
) : (
<span className="text-white">{crumb.title}</span>
)}
</span>
))}
</nav>
);
}
145 changes: 145 additions & 0 deletions app/components/DocsSidebar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
import Link from "next/link";
import VersionSwitcher from "./VersionSwitcher";
import { VersionSwitcherEntry } from "../services/versionService";

export interface SidebarNavItem {
title: string;
href: string;
active?: boolean;
}

export interface DocsSidebarProps {
/** null when viewing current docs; the version label on archived pages. */
version: string | null;
/** Where the top back link goes: /docs for current, /docs/versions/<v> for a version. */
backHref: string;
backLabel: string;
sectionTitle: string;
/** Pages of the section being viewed. */
nav: SidebarNavItem[];
/** All sections in the SAME version context (never leaves the version). */
sections: SidebarNavItem[];
switcherEntries: VersionSwitcherEntry[];
}

function navLinkClass(active?: boolean): string {
return `block w-full text-left px-4 py-2.5 rounded-lg text-sm transition-all duration-200 ${active
? "bg-blue-500/20 text-blue-300 border border-blue-500/30"
: "text-gray-300 hover:text-white hover:bg-neutral-700/50"
}`;
}

/**
* Shared sidebar for current and versioned documentation pages. Every link in
* it stays inside the version being viewed, so readers never fall out of an
* archived version by navigating.
*/
export function DocsSidebarContent({
version,
backHref,
backLabel,
sectionTitle,
nav,
sections,
switcherEntries,
}: DocsSidebarProps) {
return (
<>
{/* Header: back link + section title + version state */}
<div className="p-6 border-b border-neutral-700/50">
<Link
href={backHref}
className="text-blue-400 hover:text-blue-300 text-sm mb-3 flex items-center transition-colors"
>
<svg className="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 19l-7-7 7-7" />
</svg>
{backLabel}
</Link>
<p className="text-2xl font-bold text-white">{sectionTitle}</p>
{version && (
<p className="mt-1 inline-flex items-center rounded-full bg-amber-500/15 border border-amber-500/40 px-2.5 py-0.5 text-xs font-semibold text-amber-300">
{version} · archived
</p>
)}
<div className="mt-4">
<VersionSwitcher entries={switcherEntries} />
</div>
</div>

{/* Section pages */}
<div className="flex-1 p-4 overflow-y-auto">
<nav className="space-y-1">
{nav.map((item) => (
<Link key={item.href} href={item.href} className={navLinkClass(item.active)}>
{item.title}
</Link>
))}
</nav>

{/* Other sections, same version context */}
{sections.length > 0 && (
<div className="mt-6 border-t border-neutral-700/50 pt-4">
<p className="px-4 pb-2 text-xs font-semibold uppercase tracking-wider text-gray-500">
All sections
</p>
<nav className="space-y-1">
{sections.map((item) => (
<Link
key={item.href}
href={item.href}
className={`block w-full px-4 py-2 rounded-lg text-sm transition-colors ${item.active
? "text-white font-medium"
: "text-gray-400 hover:text-white hover:bg-neutral-700/40"
}`}
>
{item.title}
</Link>
))}
</nav>
</div>
)}
</div>

{/* Footer meta links */}
<div className="p-4 border-t border-neutral-700/50 space-y-1">
<Link
href="/docs/release-notes"
className="block px-4 py-1.5 text-xs text-gray-400 hover:text-white transition-colors"
>
Release notes
</Link>
<Link
href="/docs/versions"
className="block px-4 py-1.5 text-xs text-gray-400 hover:text-white transition-colors"
>
All documentation versions
</Link>
</div>
</>
);
}

/** Desktop sidebar wrapper. */
export default function DocsSidebar(props: DocsSidebarProps) {
return (
<div className="hidden w-80 bg-neutral-800/50 backdrop-blur-sm border-r border-neutral-700/50 md:flex flex-col">
<DocsSidebarContent {...props} />
</div>
);
}

/** Mobile disclosure variant of the same navigation. */
export function DocsSidebarMobile(props: DocsSidebarProps) {
return (
<details className="mb-6 rounded-lg border border-neutral-700/50 bg-neutral-800/50 md:hidden">
<summary className="cursor-pointer px-4 py-3 text-sm font-semibold text-gray-200">
{props.sectionTitle} navigation
{props.version ? ` (${props.version})` : ""}
</summary>
<div className="border-t border-neutral-700/50 flex flex-col">
<DocsSidebarContent {...props} />
</div>
</details>
);
}
Loading