mirror of
https://github.com/hanzo-docs/docs.git
synced 2026-10-24 20:09:02 +00:00
feat(docs): integrate project sync into build pipeline, add dark theme and CF Pages deploy
Wire project docs sync into pre-build step, add comprehensive dark theme CSS variables, integrate ProjectSwitcher into sidebar, and add custom Cloudflare Pages deployment script with batched uploads and retry logic.
This commit is contained in:
@@ -51,3 +51,44 @@ html:has(body[data-scroll-locked]) {
|
||||
body {
|
||||
background-color: var(--color-fd-background);
|
||||
}
|
||||
|
||||
:root {
|
||||
color-scheme: dark;
|
||||
--color-fd-background: hsl(0, 0%, 2%);
|
||||
--color-fd-foreground: hsl(0, 0%, 96%);
|
||||
--color-fd-muted: hsl(0, 0%, 10%);
|
||||
--color-fd-muted-foreground: hsla(0, 0%, 70%, 0.85);
|
||||
--color-fd-popover: hsl(0, 0%, 4%);
|
||||
--color-fd-popover-foreground: hsl(0, 0%, 90%);
|
||||
--color-fd-card: hsl(0, 0%, 4%);
|
||||
--color-fd-card-foreground: hsl(0, 0%, 96%);
|
||||
--color-fd-border: hsla(0, 0%, 40%, 0.2);
|
||||
--color-fd-primary: hsl(0, 0%, 96%);
|
||||
--color-fd-primary-foreground: hsl(0, 0%, 8%);
|
||||
--color-fd-secondary: hsl(0, 0%, 12%);
|
||||
--color-fd-secondary-foreground: hsl(0, 0%, 92%);
|
||||
--color-fd-accent: hsla(0, 0%, 40%, 0.3);
|
||||
--color-fd-accent-foreground: hsl(0, 0%, 92%);
|
||||
--color-fd-ring: hsl(0, 0%, 55%);
|
||||
--color-fd-overlay: hsla(0, 0%, 0%, 0.3);
|
||||
}
|
||||
|
||||
.dark {
|
||||
--color-fd-background: hsl(0, 0%, 2%);
|
||||
--color-fd-foreground: hsl(0, 0%, 96%);
|
||||
--color-fd-muted: hsl(0, 0%, 10%);
|
||||
--color-fd-muted-foreground: hsla(0, 0%, 70%, 0.85);
|
||||
--color-fd-popover: hsl(0, 0%, 4%);
|
||||
--color-fd-popover-foreground: hsl(0, 0%, 90%);
|
||||
--color-fd-card: hsl(0, 0%, 4%);
|
||||
--color-fd-card-foreground: hsl(0, 0%, 96%);
|
||||
--color-fd-border: hsla(0, 0%, 40%, 0.2);
|
||||
--color-fd-primary: hsl(0, 0%, 96%);
|
||||
--color-fd-primary-foreground: hsl(0, 0%, 8%);
|
||||
--color-fd-secondary: hsl(0, 0%, 12%);
|
||||
--color-fd-secondary-foreground: hsl(0, 0%, 92%);
|
||||
--color-fd-accent: hsla(0, 0%, 40%, 0.3);
|
||||
--color-fd-accent-foreground: hsl(0, 0%, 92%);
|
||||
--color-fd-ring: hsl(0, 0%, 55%);
|
||||
--color-fd-overlay: hsla(0, 0%, 0%, 0.3);
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ const mono = Geist_Mono({
|
||||
export const viewport: Viewport = {
|
||||
themeColor: [
|
||||
{ media: '(prefers-color-scheme: dark)', color: '#0A0A0A' },
|
||||
{ media: '(prefers-color-scheme: light)', color: '#fff' },
|
||||
{ media: '(prefers-color-scheme: light)', color: '#0A0A0A' },
|
||||
],
|
||||
};
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ import { TooltipProvider } from '@radix-ui/react-tooltip';
|
||||
|
||||
export function Provider({ children }: { children: ReactNode }) {
|
||||
return (
|
||||
<RootProvider>
|
||||
<RootProvider theme={{ defaultTheme: 'dark', enableSystem: false }}>
|
||||
<TooltipProvider>
|
||||
{children}
|
||||
</TooltipProvider>
|
||||
|
||||
@@ -23,6 +23,6 @@ const create = browser<typeof Config, import("@hanzo/docs-mdx/runtime/types").In
|
||||
}
|
||||
}>();
|
||||
const browserCollections = {
|
||||
docs: create.doc("docs", {"faq.mdx": () => import("../content/docs/faq.mdx?collection=docs"), "index.mdx": () => import("../content/docs/index.mdx?collection=docs"), "architecture/agent-mesh.mdx": () => import("../content/docs/architecture/agent-mesh.mdx?collection=docs"), "architecture/index.mdx": () => import("../content/docs/architecture/index.mdx?collection=docs"), "architecture/parity.mdx": () => import("../content/docs/architecture/parity.mdx?collection=docs"), "architecture/platform.mdx": () => import("../content/docs/architecture/platform.mdx?collection=docs"), "configuration/example-config.mdx": () => import("../content/docs/configuration/example-config.mdx?collection=docs"), "configuration/index.mdx": () => import("../content/docs/configuration/index.mdx?collection=docs"), "configuration/platform-sandboxing.mdx": () => import("../content/docs/configuration/platform-sandboxing.mdx?collection=docs"), "configuration/sandbox.mdx": () => import("../content/docs/configuration/sandbox.mdx?collection=docs"), "contributing/cla.mdx": () => import("../content/docs/contributing/cla.mdx?collection=docs"), "contributing/index.mdx": () => import("../content/docs/contributing/index.mdx?collection=docs"), "getting-started/authentication.mdx": () => import("../content/docs/getting-started/authentication.mdx?collection=docs"), "getting-started/homebrew.mdx": () => import("../content/docs/getting-started/homebrew.mdx?collection=docs"), "getting-started/index.mdx": () => import("../content/docs/getting-started/index.mdx?collection=docs"), "getting-started/install.mdx": () => import("../content/docs/getting-started/install.mdx?collection=docs"), "integrations/copilot.mdx": () => import("../content/docs/integrations/copilot.mdx?collection=docs"), "integrations/index.mdx": () => import("../content/docs/integrations/index.mdx?collection=docs"), "integrations/zed.mdx": () => import("../content/docs/integrations/zed.mdx?collection=docs"), "platform/components.mdx": () => import("../content/docs/platform/components.mdx?collection=docs"), "platform/compute-rewards.mdx": () => import("../content/docs/platform/compute-rewards.mdx?collection=docs"), "platform/index.mdx": () => import("../content/docs/platform/index.mdx?collection=docs"), "platform/pools-design.mdx": () => import("../content/docs/platform/pools-design.mdx?collection=docs"), "reference/cli.mdx": () => import("../content/docs/reference/cli.mdx?collection=docs"), "reference/index.mdx": () => import("../content/docs/reference/index.mdx?collection=docs"), "usage/advanced.mdx": () => import("../content/docs/usage/advanced.mdx?collection=docs"), "usage/agents.mdx": () => import("../content/docs/usage/agents.mdx?collection=docs"), "usage/auto-drive.mdx": () => import("../content/docs/usage/auto-drive.mdx?collection=docs"), "usage/exec.mdx": () => import("../content/docs/usage/exec.mdx?collection=docs"), "usage/execpolicy.mdx": () => import("../content/docs/usage/execpolicy.mdx?collection=docs"), "usage/index.mdx": () => import("../content/docs/usage/index.mdx?collection=docs"), "usage/prompts.mdx": () => import("../content/docs/usage/prompts.mdx?collection=docs"), "usage/settings.mdx": () => import("../content/docs/usage/settings.mdx?collection=docs"), "usage/skills.mdx": () => import("../content/docs/usage/skills.mdx?collection=docs"), "usage/slash-commands.mdx": () => import("../content/docs/usage/slash-commands.mdx?collection=docs"), }),
|
||||
docs: create.doc("docs", {"faq.mdx": () => import("../content/docs/faq.mdx?collection=docs"), "index.mdx": () => import("../content/docs/index.mdx?collection=docs"), "configuration/example-config.mdx": () => import("../content/docs/configuration/example-config.mdx?collection=docs"), "configuration/index.mdx": () => import("../content/docs/configuration/index.mdx?collection=docs"), "configuration/platform-sandboxing.mdx": () => import("../content/docs/configuration/platform-sandboxing.mdx?collection=docs"), "configuration/sandbox.mdx": () => import("../content/docs/configuration/sandbox.mdx?collection=docs"), "contributing/cla.mdx": () => import("../content/docs/contributing/cla.mdx?collection=docs"), "contributing/index.mdx": () => import("../content/docs/contributing/index.mdx?collection=docs"), "architecture/agent-mesh.mdx": () => import("../content/docs/architecture/agent-mesh.mdx?collection=docs"), "architecture/index.mdx": () => import("../content/docs/architecture/index.mdx?collection=docs"), "architecture/parity.mdx": () => import("../content/docs/architecture/parity.mdx?collection=docs"), "architecture/platform.mdx": () => import("../content/docs/architecture/platform.mdx?collection=docs"), "integrations/copilot.mdx": () => import("../content/docs/integrations/copilot.mdx?collection=docs"), "integrations/index.mdx": () => import("../content/docs/integrations/index.mdx?collection=docs"), "integrations/zed.mdx": () => import("../content/docs/integrations/zed.mdx?collection=docs"), "getting-started/authentication.mdx": () => import("../content/docs/getting-started/authentication.mdx?collection=docs"), "getting-started/homebrew.mdx": () => import("../content/docs/getting-started/homebrew.mdx?collection=docs"), "getting-started/index.mdx": () => import("../content/docs/getting-started/index.mdx?collection=docs"), "getting-started/install.mdx": () => import("../content/docs/getting-started/install.mdx?collection=docs"), "platform/components.mdx": () => import("../content/docs/platform/components.mdx?collection=docs"), "platform/compute-rewards.mdx": () => import("../content/docs/platform/compute-rewards.mdx?collection=docs"), "platform/index.mdx": () => import("../content/docs/platform/index.mdx?collection=docs"), "platform/pools-design.mdx": () => import("../content/docs/platform/pools-design.mdx?collection=docs"), "reference/cli.mdx": () => import("../content/docs/reference/cli.mdx?collection=docs"), "reference/index.mdx": () => import("../content/docs/reference/index.mdx?collection=docs"), "usage/advanced.mdx": () => import("../content/docs/usage/advanced.mdx?collection=docs"), "usage/agents.mdx": () => import("../content/docs/usage/agents.mdx?collection=docs"), "usage/auto-drive.mdx": () => import("../content/docs/usage/auto-drive.mdx?collection=docs"), "usage/exec.mdx": () => import("../content/docs/usage/exec.mdx?collection=docs"), "usage/execpolicy.mdx": () => import("../content/docs/usage/execpolicy.mdx?collection=docs"), "usage/index.mdx": () => import("../content/docs/usage/index.mdx?collection=docs"), "usage/prompts.mdx": () => import("../content/docs/usage/prompts.mdx?collection=docs"), "usage/settings.mdx": () => import("../content/docs/usage/settings.mdx?collection=docs"), "usage/skills.mdx": () => import("../content/docs/usage/skills.mdx?collection=docs"), "usage/slash-commands.mdx": () => import("../content/docs/usage/slash-commands.mdx?collection=docs"), }),
|
||||
};
|
||||
export default browserCollections;
|
||||
File diff suppressed because one or more lines are too long
@@ -6,6 +6,7 @@ import { getSection } from '@/lib/source/navigation';
|
||||
import { MessageCircleIcon } from 'lucide-react';
|
||||
import { cn } from '@/lib/cn';
|
||||
import { buttonVariants } from '@hanzo/docs-base-ui/components/ui/button';
|
||||
import { ProjectSwitcher } from '@/components/projects/project-switcher';
|
||||
import 'katex/dist/katex.min.css';
|
||||
|
||||
export default function Layout({ children }: LayoutProps<'/docs'>) {
|
||||
@@ -27,6 +28,7 @@ export default function Layout({ children }: LayoutProps<'/docs'>) {
|
||||
),
|
||||
}}
|
||||
sidebar={{
|
||||
banner: <ProjectSwitcher />,
|
||||
tabs: {
|
||||
transform(option, node) {
|
||||
const meta = source.getNodeMeta(node);
|
||||
|
||||
@@ -102,6 +102,47 @@ body {
|
||||
background-color: var(--color-fd-background);
|
||||
}
|
||||
|
||||
:root {
|
||||
color-scheme: dark;
|
||||
--color-fd-background: hsl(0, 0%, 2%);
|
||||
--color-fd-foreground: hsl(0, 0%, 96%);
|
||||
--color-fd-muted: hsl(0, 0%, 10%);
|
||||
--color-fd-muted-foreground: hsla(0, 0%, 70%, 0.85);
|
||||
--color-fd-popover: hsl(0, 0%, 4%);
|
||||
--color-fd-popover-foreground: hsl(0, 0%, 90%);
|
||||
--color-fd-card: hsl(0, 0%, 4%);
|
||||
--color-fd-card-foreground: hsl(0, 0%, 96%);
|
||||
--color-fd-border: hsla(0, 0%, 40%, 0.2);
|
||||
--color-fd-primary: hsl(0, 0%, 96%);
|
||||
--color-fd-primary-foreground: hsl(0, 0%, 8%);
|
||||
--color-fd-secondary: hsl(0, 0%, 12%);
|
||||
--color-fd-secondary-foreground: hsl(0, 0%, 92%);
|
||||
--color-fd-accent: hsla(0, 0%, 40%, 0.3);
|
||||
--color-fd-accent-foreground: hsl(0, 0%, 92%);
|
||||
--color-fd-ring: hsl(0, 0%, 55%);
|
||||
--color-fd-overlay: hsla(0, 0%, 0%, 0.3);
|
||||
}
|
||||
|
||||
.dark {
|
||||
--color-fd-background: hsl(0, 0%, 2%);
|
||||
--color-fd-foreground: hsl(0, 0%, 96%);
|
||||
--color-fd-muted: hsl(0, 0%, 10%);
|
||||
--color-fd-muted-foreground: hsla(0, 0%, 70%, 0.85);
|
||||
--color-fd-popover: hsl(0, 0%, 4%);
|
||||
--color-fd-popover-foreground: hsl(0, 0%, 90%);
|
||||
--color-fd-card: hsl(0, 0%, 4%);
|
||||
--color-fd-card-foreground: hsl(0, 0%, 96%);
|
||||
--color-fd-border: hsla(0, 0%, 40%, 0.2);
|
||||
--color-fd-primary: hsl(0, 0%, 96%);
|
||||
--color-fd-primary-foreground: hsl(0, 0%, 8%);
|
||||
--color-fd-secondary: hsl(0, 0%, 12%);
|
||||
--color-fd-secondary-foreground: hsl(0, 0%, 92%);
|
||||
--color-fd-accent: hsla(0, 0%, 40%, 0.3);
|
||||
--color-fd-accent-foreground: hsl(0, 0%, 92%);
|
||||
--color-fd-ring: hsl(0, 0%, 55%);
|
||||
--color-fd-overlay: hsla(0, 0%, 0%, 0.3);
|
||||
}
|
||||
|
||||
.dark {
|
||||
--headless-color: hsl(250 100% 80%);
|
||||
--framework-color: #fd4444;
|
||||
|
||||
@@ -30,6 +30,10 @@ export function Provider({ children }: { children: ReactNode }) {
|
||||
search={{
|
||||
SearchDialog,
|
||||
}}
|
||||
theme={{
|
||||
defaultTheme: 'dark',
|
||||
enableSystem: false,
|
||||
}}
|
||||
>
|
||||
<TooltipProvider>
|
||||
<script suppressHydrationWarning dangerouslySetInnerHTML={{ __html: inject }} />
|
||||
|
||||
@@ -11,6 +11,8 @@
|
||||
"dev",
|
||||
"zap",
|
||||
"---Platform---",
|
||||
"services"
|
||||
"services",
|
||||
"---Projects---",
|
||||
"projects"
|
||||
]
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
+256
-176
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,12 @@
|
||||
import { buildRegistry } from '@/scripts/build-registry';
|
||||
import { syncProjectDocs } from './sync-project-docs';
|
||||
|
||||
async function main() {
|
||||
await Promise.all([buildRegistry()]);
|
||||
const tasks = [buildRegistry()];
|
||||
if (process.env.HANZO_DOCS_SYNC !== '0') {
|
||||
tasks.push(syncProjectDocs());
|
||||
}
|
||||
await Promise.all(tasks);
|
||||
}
|
||||
|
||||
await main().catch((e) => {
|
||||
|
||||
@@ -0,0 +1,166 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Custom CF Pages deployment script that handles large file counts
|
||||
* by batching uploads in smaller chunks to avoid EPIPE errors.
|
||||
*/
|
||||
import { createHash } from 'crypto';
|
||||
import { readdir, readFile, stat } from 'fs/promises';
|
||||
import { join, relative } from 'path';
|
||||
|
||||
const ACCOUNT_ID = '94a3e3f299092abb1feda2a7481ea845';
|
||||
const PROJECT_NAME = 'hanzo-docs';
|
||||
const API_TOKEN = process.env.CLOUDFLARE_API_TOKEN || 'bBEtGaPXpGSmvC5w2WbpE2V7zx08VDWspCS-E30g';
|
||||
const BASE_URL = `https://api.cloudflare.com/client/v4/accounts/${ACCOUNT_ID}/pages/projects/${PROJECT_NAME}`;
|
||||
const BATCH_SIZE = 50; // files per upload batch
|
||||
const MAX_RETRIES = 3;
|
||||
|
||||
async function getAllFiles(dir) {
|
||||
const files = [];
|
||||
const entries = await readdir(dir, { withFileTypes: true });
|
||||
for (const entry of entries) {
|
||||
const fullPath = join(dir, entry.name);
|
||||
if (entry.isDirectory()) {
|
||||
files.push(...await getAllFiles(fullPath));
|
||||
} else {
|
||||
files.push(fullPath);
|
||||
}
|
||||
}
|
||||
return files;
|
||||
}
|
||||
|
||||
async function hashFile(filePath) {
|
||||
const content = await readFile(filePath);
|
||||
const hash = createHash('sha256').update(content).digest('hex');
|
||||
return { path: filePath, hash, content, size: content.length };
|
||||
}
|
||||
|
||||
async function apiCall(url, options = {}) {
|
||||
const resp = await fetch(url, {
|
||||
...options,
|
||||
headers: {
|
||||
'Authorization': `Bearer ${API_TOKEN}`,
|
||||
...options.headers,
|
||||
},
|
||||
});
|
||||
const data = await resp.json();
|
||||
if (!data.success) {
|
||||
throw new Error(`CF API error: ${JSON.stringify(data.errors)}`);
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
async function createDeployment(hashes) {
|
||||
// Step 1: Create deployment with manifest
|
||||
const manifest = {};
|
||||
for (const { relativePath, hash } of hashes) {
|
||||
manifest[`/${relativePath}`] = hash;
|
||||
}
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append('manifest', JSON.stringify(manifest));
|
||||
formData.append('branch', 'dev');
|
||||
|
||||
console.log(`Creating deployment with ${Object.keys(manifest).length} files...`);
|
||||
const data = await apiCall(`${BASE_URL}/deployments`, {
|
||||
method: 'POST',
|
||||
body: formData,
|
||||
});
|
||||
|
||||
return data.result;
|
||||
}
|
||||
|
||||
async function uploadBatch(files, jwt) {
|
||||
const formData = new FormData();
|
||||
for (const file of files) {
|
||||
formData.append(file.hash, new Blob([file.content]), file.relativePath);
|
||||
}
|
||||
|
||||
const resp = await fetch('https://api.cloudflare.com/client/v4/pages/assets/upload', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${jwt}`,
|
||||
},
|
||||
body: formData,
|
||||
});
|
||||
|
||||
if (!resp.ok) {
|
||||
const text = await resp.text();
|
||||
throw new Error(`Upload failed (${resp.status}): ${text}`);
|
||||
}
|
||||
|
||||
return resp.json();
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const outDir = process.argv[2] || 'apps/docs/out';
|
||||
|
||||
console.log(`Scanning ${outDir}...`);
|
||||
const allFiles = await getAllFiles(outDir);
|
||||
console.log(`Found ${allFiles.length} files`);
|
||||
|
||||
// Hash all files
|
||||
console.log('Hashing files...');
|
||||
const fileInfos = await Promise.all(
|
||||
allFiles.map(async (filePath) => {
|
||||
const info = await hashFile(filePath);
|
||||
return {
|
||||
...info,
|
||||
relativePath: relative(outDir, filePath),
|
||||
};
|
||||
})
|
||||
);
|
||||
|
||||
// Create deployment (gets JWT token and list of missing files)
|
||||
const deployment = await createDeployment(fileInfos);
|
||||
console.log(`Deployment ID: ${deployment.id}`);
|
||||
console.log(`URL: ${deployment.url}`);
|
||||
|
||||
const jwt = deployment.jwt;
|
||||
if (!jwt) {
|
||||
console.log('No JWT returned - all files already uploaded!');
|
||||
return;
|
||||
}
|
||||
|
||||
// Find which files need uploading
|
||||
const missingHashes = new Set(deployment.required_hashes || []);
|
||||
const filesToUpload = missingHashes.size > 0
|
||||
? fileInfos.filter(f => missingHashes.has(f.hash))
|
||||
: fileInfos; // upload all if no required_hashes returned
|
||||
|
||||
console.log(`Need to upload ${filesToUpload.length} files (${fileInfos.length - filesToUpload.length} already cached)`);
|
||||
|
||||
// Upload in batches
|
||||
const batches = [];
|
||||
for (let i = 0; i < filesToUpload.length; i += BATCH_SIZE) {
|
||||
batches.push(filesToUpload.slice(i, i + BATCH_SIZE));
|
||||
}
|
||||
|
||||
let uploaded = 0;
|
||||
for (let i = 0; i < batches.length; i++) {
|
||||
const batch = batches[i];
|
||||
let retries = 0;
|
||||
while (retries < MAX_RETRIES) {
|
||||
try {
|
||||
await uploadBatch(batch, jwt);
|
||||
uploaded += batch.length;
|
||||
process.stdout.write(`\rUploaded ${uploaded}/${filesToUpload.length} (batch ${i + 1}/${batches.length})`);
|
||||
break;
|
||||
} catch (err) {
|
||||
retries++;
|
||||
if (retries >= MAX_RETRIES) {
|
||||
console.error(`\nFailed batch ${i + 1} after ${MAX_RETRIES} retries: ${err.message}`);
|
||||
throw err;
|
||||
}
|
||||
console.warn(`\nRetrying batch ${i + 1} (attempt ${retries + 1})...`);
|
||||
await new Promise(r => setTimeout(r, 1000 * retries));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`\nDeployment complete: ${deployment.url}`);
|
||||
}
|
||||
|
||||
main().catch(err => {
|
||||
console.error('Deploy failed:', err.message);
|
||||
process.exit(1);
|
||||
});
|
||||
Reference in New Issue
Block a user