CLI: Add Waku template

This commit is contained in:
Fuma Nama
2025-08-01 16:42:03 +08:00
parent 224f25970a
commit dd4d8fcf40
21 changed files with 331 additions and 36 deletions
+5
View File
@@ -0,0 +1,5 @@
---
'create-fumadocs-app': patch
---
Support Waku template
+2 -2
View File
@@ -3,10 +3,10 @@ import type { ReactNode } from 'react';
import { WakuProvider } from 'fumadocs-core/framework/waku';
import { RootProvider } from 'fumadocs-ui/provider/base';
export const Provider = ({ children }: { children: ReactNode }) => {
export function Provider({ children }: { children: ReactNode }) {
return (
<WakuProvider>
<RootProvider>{children}</RootProvider>
</WakuProvider>
);
};
}
+33 -31
View File
@@ -12,6 +12,7 @@ export const templates = [
'+next+fuma-docs-mdx',
'react-router',
'tanstack-start',
'waku',
] as const;
export type Template = (typeof templates)[number];
@@ -137,7 +138,9 @@ export async function create(options: Options): Promise<void> {
);
}
const packageJson = await createPackageJson(projectName, dest, options);
const packageJson = isNext
? await createNextPackageJson(projectName, options)
: await createPackageJson(projectName, dest);
await fs.writeFile(
path.join(dest, 'package.json'),
JSON.stringify(packageJson, null, 2),
@@ -189,39 +192,10 @@ async function copy(
}
}
async function createPackageJson(
async function createNextPackageJson(
projectName: string,
dir: string,
options: Options,
): Promise<object> {
function replaceWorkspaceDeps(deps: Record<string, string>) {
for (const k in deps) {
if (deps[k].startsWith('workspace:') && k in localVersions) {
deps[k] = localVersions[k as keyof typeof localVersions];
}
}
return deps;
}
if (
options.template === 'tanstack-start' ||
options.template === 'react-router'
) {
const packageJson = JSON.parse(
await fs
.readFile(path.join(dir, 'package.json'))
.then((res) => res.toString()),
);
return {
name: projectName,
...packageJson,
dependencies: replaceWorkspaceDeps(packageJson.dependencies),
devDependencies: replaceWorkspaceDeps(packageJson.devDependencies),
};
}
return {
name: projectName,
version: '0.0.0',
@@ -278,6 +252,34 @@ async function createPackageJson(
};
}
async function createPackageJson(
projectName: string,
dir: string,
): Promise<object> {
function replaceWorkspaceDeps(deps: Record<string, string>) {
for (const k in deps) {
if (deps[k].startsWith('workspace:') && k in localVersions) {
deps[k] = localVersions[k as keyof typeof localVersions];
}
}
return deps;
}
const packageJson = JSON.parse(
await fs
.readFile(path.join(dir, 'package.json'))
.then((res) => res.toString()),
);
return {
name: projectName,
...packageJson,
dependencies: replaceWorkspaceDeps(packageJson.dependencies),
devDependencies: replaceWorkspaceDeps(packageJson.devDependencies),
};
}
function pick<T extends object, K extends keyof T>(
obj: T,
keys: K[],
+6 -3
View File
@@ -96,14 +96,17 @@ async function main(config: Options): Promise<void> {
value: '+next+content-collections',
label: 'Next.js: Content Collections',
},
{
value: 'waku',
label: 'Waku: Fumadocs MDX',
},
{
value: 'react-router',
label: 'React Router: MDX Remote',
label: 'React Router: Fumadocs MDX (not RSC)',
},
{
value: 'tanstack-start',
label: 'Tanstack Start: MDX Remote',
hint: 'Experimental',
label: 'Tanstack Start: Fumadocs MDX (not RSC)',
},
],
});
@@ -0,0 +1,11 @@
This is a Waku application generated with [Create Fumadocs](https://github.com/fuma-nama/fumadocs).
Run development server:
```bash
npm run dev
# or
pnpm dev
# or
yarn dev
```
@@ -0,0 +1,31 @@
---
title: Hello World
description: Your favourite docs framework.
icon: Rocket
---
Hey there! Fumadocs is a docs framework that works on Waku!
## Heading
Hello World!
<Cards>
<Card title="Learn more about Waku" href="https://waku.gg" />
<Card title="Learn more about Fumadocs" href="https://fumadocs.vercel.app" />
</Cards>
### CodeBlock
```ts
console.log('Hello World');
```
#### Table
| Head | Description |
| ------------------------------- | ----------------------------------- |
| `hello` | Hello World |
| very **important** | Hey |
| _Surprisingly_ | Fumadocs |
| very long text that looks weird | hello world hello world hello world |
@@ -0,0 +1,12 @@
---
title: Test
description: This is another page
---
Hello World again!
## Installation
```npm
npm i fumadocs-core fumadocs-ui
```
@@ -0,0 +1,4 @@
.DS_Store
/node_modules/
dist
@@ -0,0 +1,28 @@
{
"name": "example-waku",
"private": true,
"sideEffects": false,
"type": "module",
"scripts": {
"dev": "waku dev",
"build": "waku build",
"start": "waku start"
},
"dependencies": {
"fumadocs-core": "workspace:*",
"fumadocs-mdx": "workspace:*",
"fumadocs-ui": "workspace:*",
"react": "^19.1.1",
"react-dom": "^19.1.1",
"react-server-dom-webpack": "^19.1.1",
"waku": "^0.23.6"
},
"devDependencies": {
"@types/node": "^24.1.0",
"@types/react": "^19.1.9",
"@types/react-dom": "^19.1.7",
"tailwindcss": "^4.1.11",
"typescript": "^5.7.2",
"vite": "7.0.5"
}
}
@@ -0,0 +1,5 @@
export default {
plugins: {
'@tailwindcss/postcss': {},
},
};
@@ -0,0 +1,7 @@
import { defineConfig, defineDocs } from 'fumadocs-mdx/config';
export const docs = defineDocs({
dir: 'content/docs',
});
export default defineConfig();
@@ -0,0 +1,12 @@
'use client';
import type { ReactNode } from 'react';
import { WakuProvider } from 'fumadocs-core/framework/waku';
import { RootProvider } from 'fumadocs-ui/provider/base';
export function Provider({ children }: { children: ReactNode }) {
return (
<WakuProvider>
<RootProvider>{children}</RootProvider>
</WakuProvider>
);
}
@@ -0,0 +1,18 @@
import '../styles/globals.css';
import type { ReactNode } from 'react';
import { Provider } from '../components/provider';
export default function RootLayout({
children,
}: {
children: ReactNode;
path: string;
}) {
return <Provider>{children}</Provider>;
}
export const getConfig = async () => {
return {
render: 'static',
};
};
@@ -0,0 +1,4 @@
import { createFromSource } from 'fumadocs-core/search/server';
import { source } from '../../source';
export const { GET } = createFromSource(source);
@@ -0,0 +1,50 @@
import { source } from '../../source';
import { PageProps } from 'waku/router';
import defaultMdxComponents from 'fumadocs-ui/mdx';
import {
DocsBody,
DocsDescription,
DocsPage,
DocsTitle,
} from 'fumadocs-ui/page';
export default function DocPage({ slugs }: PageProps<'/docs/[...slugs]'>) {
const page = source.getPage(slugs);
if (!page) {
return (
<div className="text-center py-12">
<h1 className="text-3xl font-bold mb-4 text-gray-900 dark:text-gray-100">
Page Not Found
</h1>
<p className="text-gray-600 dark:text-gray-400">
The page you are looking for does not exist.
</p>
</div>
);
}
const MDX = page.data.default;
return (
<DocsPage toc={page.data.toc}>
<DocsTitle>{page.data.title}</DocsTitle>
<DocsDescription>{page.data.description}</DocsDescription>
<DocsBody>
<MDX
components={{
...defaultMdxComponents,
}}
/>
</DocsBody>
</DocsPage>
);
}
export async function getConfig() {
const pages = source.getPages().map((page) => page.slugs);
return {
render: 'static' as const,
staticPaths: pages,
} as const;
}
@@ -0,0 +1,27 @@
import type { ReactNode } from 'react';
import { DocsLayout } from 'fumadocs-ui/layouts/docs';
import { source } from '../../source';
export default function Layout({
children,
}: {
children: ReactNode;
path: string;
}) {
return (
<DocsLayout
tree={source.pageTree}
nav={{
title: 'Waku',
}}
>
{children}
</DocsLayout>
);
}
export const getConfig = async () => {
return {
render: 'static',
};
};
@@ -0,0 +1,27 @@
import { HomeLayout } from 'fumadocs-ui/layouts/home';
import { Link } from 'waku';
export default function Home() {
return (
<HomeLayout
nav={{
title: 'Waku',
}}
className="text-center py-32 justify-center"
>
<h1 className="font-medium text-xl mb-4">Fumadocs on Waku.</h1>
<Link
to="/docs"
className="px-3 py-2 rounded-lg bg-fd-primary text-fd-primary-foreground font-medium text-sm mx-auto"
>
Open Docs
</Link>
</HomeLayout>
);
}
export const getConfig = async () => {
return {
render: 'static',
};
};
@@ -0,0 +1,7 @@
import { loader } from 'fumadocs-core/source';
import { create, docs } from '../source.generated.js';
export const source = loader({
source: await create.sourceAsync(docs.doc, docs.meta),
baseUrl: '/docs',
});
@@ -0,0 +1,9 @@
@import 'tailwindcss';
@import 'fumadocs-ui/css/neutral.css';
@import 'fumadocs-ui/css/preset.css';
body {
display: flex;
flex-direction: column;
min-height: 100vh;
}
@@ -0,0 +1,19 @@
{
"compilerOptions": {
"target": "ES2022",
"lib": ["dom", "dom.iterable", "ES2022"],
"jsx": "react-jsx",
"module": "ESNext",
"moduleResolution": "bundler",
"resolveJsonModule": true,
"allowJs": true,
"checkJs": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"strict": true,
"skipLibCheck": true,
"noUncheckedIndexedAccess": true,
"noEmit": true
},
"exclude": ["node_modules", "dist"]
}
@@ -0,0 +1,14 @@
import { defineConfig } from 'waku/config';
import mdx from 'fumadocs-mdx/vite';
import * as MdxConfig from './source.config.js';
import type { UserConfig } from 'vite';
export default defineConfig({
unstable_viteConfigs: {
common() {
return {
plugins: [mdx(MdxConfig)],
} as UserConfig;
},
},
});