chore: add helix-api and workspace config to monorepo

This commit is contained in:
siujamo committed 2026-09-01 10:11:07 +08:00
1 parent c732624580
commit 112d0f3be4
40 files changed
+7145

No files matched your search

+2
View File
@@ -0,0 +1,2 @@
# Claude Code local tooling
.claude/
+4
View File
@@ -0,0 +1,4 @@
{
"java.compile.nullAnalysis.mode": "disabled",
"java.configuration.updateBuildConfiguration": "automatic"
}
+9
View File
@@ -0,0 +1,9 @@
# Secrets
.env*
# Dependencies
node_modules
# OS files
.DS_Store
Thumbs.db
@@ -0,0 +1,22 @@
info:
name: Login
type: http
seq: 1
http:
method: POST
url: "{{BaseURL}}/auth/login"
body:
type: json
data: |-
{
"username": "helix",
"password": "123456"
}
auth: inherit
settings:
encodeUrl: true
timeout: 0
followRedirects: true
maxRedirects: 5
@@ -0,0 +1,7 @@
info:
name: Authentication
type: folder
seq: 1
request:
auth: inherit
@@ -0,0 +1,24 @@
info:
name: Add Department
type: http
seq: 1
http:
method: POST
url: "{{BaseURL}}/departments"
body:
type: json
data: |-
{
"name": "Domestic Marketing",
"parentId": 10,
"sort": null,
"status": null
}
auth: inherit
settings:
encodeUrl: true
timeout: 0
followRedirects: true
maxRedirects: 5
@@ -0,0 +1,7 @@
info:
name: Departments
type: folder
seq: 2
request:
auth: inherit
@@ -0,0 +1,6 @@
name: Development
variables:
- name: BaseURL
value: http://localhost:5000
- name: language
value: zh-CN
+26
View File
@@ -0,0 +1,26 @@
opencollection: 1.0.0
info:
name: Helix
config:
proxy:
inherit: true
config:
protocol: http
hostname: ""
port: ""
auth:
username: ""
password: ""
bypassProxy: ""
request:
headers:
- name: Accept-Language
value: "{{language}}"
bundled: false
extensions:
bruno:
ignore:
- node_modules
- .git
+7
View File
@@ -0,0 +1,7 @@
.DS_Store
/node_modules/
# React Router
/.react-router/
/build/
.source
+23
View File
@@ -0,0 +1,23 @@
# Helix API Docs
Vite + React Router + Fumadocs documentation app with OpenAPI support.
## Run
```bash
pnpm install
pnpm dev
```
Open these pages:
- `/api-reference`: OpenAPI interactive reference
- `/docs`: MDX docs pages
## Edit OpenAPI
Manually maintain:
- `openapi/openapi.yaml`
After changes, refresh the `/api-reference` page.
+4
View File
@@ -0,0 +1,4 @@
@import 'tailwindcss';
@import 'fumadocs-ui/css/neutral.css';
@import 'fumadocs-ui/css/preset.css';
@import 'fumadocs-openapi/css/preset.css';
@@ -0,0 +1,4 @@
import { openapi } from '@/lib/openapi';
import { createAPIPage } from 'fumadocs-openapi/ui';
export const APIPage = createAPIPage(openapi);
+15
View File
@@ -0,0 +1,15 @@
import defaultMdxComponents from 'fumadocs-ui/mdx';
import type { MDXComponents } from 'mdx/types';
export function getMDXComponents(components?: MDXComponents) {
return {
...defaultMdxComponents,
...components,
} satisfies MDXComponents;
}
export const useMDXComponents = getMDXComponents;
declare global {
type MDXProvidedComponents = ReturnType<typeof getMDXComponents>;
}
+45
View File
@@ -0,0 +1,45 @@
'use client';
import {
SearchDialog,
SearchDialogClose,
SearchDialogContent,
SearchDialogHeader,
SearchDialogIcon,
SearchDialogInput,
SearchDialogList,
SearchDialogOverlay,
type SharedProps,
} from 'fumadocs-ui/components/dialog/search';
import { useDocsSearch } from 'fumadocs-core/search/client';
import { create } from '@orama/orama';
import { useI18n } from 'fumadocs-ui/contexts/i18n';
function initOrama() {
return create({
schema: { _: 'string' },
language: 'english',
});
}
export default function DefaultSearchDialog(props: SharedProps) {
const { locale } = useI18n();
const { search, setSearch, query } = useDocsSearch({
type: 'static',
initOrama,
locale,
});
return (
<SearchDialog search={search} onSearchChange={setSearch} isLoading={query.isLoading} {...props}>
<SearchDialogOverlay />
<SearchDialogContent>
<SearchDialogHeader>
<SearchDialogIcon />
<SearchDialogInput />
<SearchDialogClose />
</SearchDialogHeader>
<SearchDialogList items={query.data !== 'empty' ? query.data : null} />
</SearchDialogContent>
</SearchDialog>
);
}
+1
View File
@@ -0,0 +1 @@
export { twMerge as cn } from 'tailwind-merge';
+17
View File
@@ -0,0 +1,17 @@
import type { BaseLayoutProps } from 'fumadocs-ui/layouts/shared';
// fill this with your actual GitHub info, for example:
export const gitConfig = {
user: 'fuma-nama',
repo: 'fumadocs',
branch: 'main',
};
export function baseOptions(): BaseLayoutProps {
return {
nav: {
title: 'React Router',
},
githubUrl: `https://github.com/${gitConfig.user}/${gitConfig.repo}`,
};
}
+8
View File
@@ -0,0 +1,8 @@
import path from 'node:path';
import { createOpenAPI } from 'fumadocs-openapi/server';
export const openapi = createOpenAPI({
input: () => ({
helix: path.resolve('./openapi/openapi.yaml'),
}),
});
+15
View File
@@ -0,0 +1,15 @@
import { loader, type InferPageType } from 'fumadocs-core/source';
import { docs } from 'collections/server';
export const source = loader({
source: docs.toFumadocsSource(),
baseUrl: '/docs',
});
export async function getLLMText(page: InferPageType<typeof source>) {
const processed = await page.data.getText('processed');
return `# ${page.data.title} (${page.url})
${processed}`;
}
+8
View File
@@ -0,0 +1,8 @@
import { getLLMText, source } from '@/lib/source';
export async function loader() {
const scan = source.getPages().map(getLLMText);
const scanned = await Promise.all(scan);
return new Response(scanned.join('\n\n'));
}
+6
View File
@@ -0,0 +1,6 @@
import { source } from '@/lib/source';
import { llms } from 'fumadocs-core/source';
export function loader() {
return new Response(llms(source).index());
}
+17
View File
@@ -0,0 +1,17 @@
import type { Route } from './+types/mdx';
import { getLLMText, source } from '@/lib/source';
export async function loader({ params }: Route.LoaderArgs) {
const slugs = params['*'].split('/').filter((v) => v.length > 0);
// remove the appended "index.mdx" that's added to avoid React Router issues
slugs.pop();
const page = source.getPage(slugs);
if (!page) {
return new Response('not found', { status: 404 });
}
return new Response(await getLLMText(page), {
headers: {
'Content-Type': 'text/markdown',
},
});
}
+75
View File
@@ -0,0 +1,75 @@
import {
isRouteErrorResponse,
Links,
Meta,
Outlet,
Scripts,
ScrollRestoration,
} from 'react-router';
import { RootProvider } from 'fumadocs-ui/provider/react-router';
import type { Route } from './+types/root';
import './app.css';
import SearchDialog from '@/components/search';
import NotFound from './routes/not-found';
export const links: Route.LinksFunction = () => [
{ rel: 'preconnect', href: 'https://fonts.googleapis.com' },
{
rel: 'preconnect',
href: 'https://fonts.gstatic.com',
crossOrigin: 'anonymous',
},
{
rel: 'stylesheet',
href: 'https://fonts.googleapis.com/css2?family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&display=swap',
},
];
export function Layout({ children }: { children: React.ReactNode }) {
return (
<html lang="en" suppressHydrationWarning>
<head>
<meta charSet="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<Meta />
<Links />
</head>
<body className="flex flex-col min-h-screen">
<RootProvider search={{ SearchDialog }}>{children}</RootProvider>
<ScrollRestoration />
<Scripts />
</body>
</html>
);
}
export default function App() {
return <Outlet />;
}
export function ErrorBoundary({ error }: Route.ErrorBoundaryProps) {
let message = 'Oops!';
let details = 'An unexpected error occurred.';
let stack: string | undefined;
if (isRouteErrorResponse(error)) {
if (error.status === 404) return <NotFound />;
message = 'Error';
details = error.statusText;
} else if (import.meta.env.DEV && error && error instanceof Error) {
details = error.message;
stack = error.stack;
}
return (
<main className="pt-16 p-4 w-full max-w-[1400px] mx-auto">
<h1>{message}</h1>
<p>{details}</p>
{stack && (
<pre className="w-full p-4 overflow-x-auto">
<code>{stack}</code>
</pre>
)}
</main>
);
}
+15
View File
@@ -0,0 +1,15 @@
import { index, route, type RouteConfig } from '@react-router/dev/routes';
export default [
index('routes/home.tsx'),
route('docs/*', 'routes/docs.tsx'),
route('api-reference', 'routes/api-reference.tsx'),
route('api/search', 'routes/search.ts'),
// LLM integration:
route('llms.txt', 'llms/index.ts'),
route('llms-full.txt', 'llms/full.ts'),
route('llms.mdx/docs/*', 'llms/mdx.ts'),
route('*', 'routes/not-found.tsx'),
] satisfies RouteConfig;
@@ -0,0 +1,29 @@
import type { Route } from './+types/api-reference';
import { DocsLayout } from 'fumadocs-ui/layouts/docs';
import { DocsBody, DocsDescription, DocsPage, DocsTitle } from 'fumadocs-ui/layouts/docs/page';
import { source } from '@/lib/source';
import { baseOptions } from '@/lib/layout.shared';
import { APIPage } from '@/components/api-page';
export function meta({}: Route.MetaArgs) {
return [
{ title: 'API Reference' },
{ name: 'description', content: 'OpenAPI reference for Helix API.' },
];
}
export default function ApiReference() {
return (
<DocsLayout {...baseOptions()} tree={source.getPageTree()}>
<DocsPage full>
<DocsTitle>API Reference</DocsTitle>
<DocsDescription>
Edit openapi/openapi.yaml to maintain this API documentation.
</DocsDescription>
<DocsBody>
<APIPage document="helix" />
</DocsBody>
</DocsPage>
</DocsLayout>
);
}
+74
View File
@@ -0,0 +1,74 @@
import type { Route } from './+types/docs';
import { DocsLayout } from 'fumadocs-ui/layouts/docs';
import {
DocsBody,
DocsDescription,
DocsPage,
DocsTitle,
MarkdownCopyButton,
ViewOptionsPopover,
} from 'fumadocs-ui/layouts/docs/page';
import { source } from '@/lib/source';
import browserCollections from 'collections/browser';
import { baseOptions, gitConfig } from '@/lib/layout.shared';
import { useFumadocsLoader } from 'fumadocs-core/source/client';
import { useMDXComponents } from '@/components/mdx';
export async function loader({ params }: Route.LoaderArgs) {
const slugs = params['*'].split('/').filter((v) => v.length > 0);
const page = source.getPage(slugs);
if (!page) throw new Response('Not found', { status: 404 });
return {
slugs: page.slugs,
path: page.path,
pageTree: await source.serializePageTree(source.getPageTree()),
};
}
const clientLoader = browserCollections.docs.createClientLoader({
component(
{ toc, frontmatter, default: Mdx },
// you can define props for the component
{
markdownUrl,
path,
}: {
markdownUrl: string;
path: string;
},
) {
return (
<DocsPage toc={toc}>
<title>{frontmatter.title}</title>
<meta name="description" content={frontmatter.description} />
<DocsTitle>{frontmatter.title}</DocsTitle>
<DocsDescription>{frontmatter.description}</DocsDescription>
<div className="flex flex-row gap-2 items-center border-b -mt-4 pb-6">
<MarkdownCopyButton markdownUrl={markdownUrl} />
<ViewOptionsPopover
markdownUrl={markdownUrl}
githubUrl={`https://github.com/${gitConfig.user}/${gitConfig.repo}/blob/${gitConfig.branch}/content/docs/${path}`}
/>
</div>
<DocsBody>
<Mdx components={useMDXComponents()} />
</DocsBody>
</DocsPage>
);
},
});
export default function Page({ loaderData }: Route.ComponentProps) {
const { pageTree, slugs, path } = useFumadocsLoader(loaderData);
const markdownUrl = `/llms.mdx/docs/${[...slugs, 'index.mdx'].join('/')}`;
return (
<DocsLayout {...baseOptions()} tree={pageTree}>
{clientLoader.useContent(loaderData.path, {
markdownUrl,
path,
})}
</DocsLayout>
);
}
+35
View File
@@ -0,0 +1,35 @@
import type { Route } from './+types/home';
import { HomeLayout } from 'fumadocs-ui/layouts/home';
import { Link } from 'react-router';
import { baseOptions } from '@/lib/layout.shared';
export function meta({}: Route.MetaArgs) {
return [
{ title: 'New React Router App' },
{ name: 'description', content: 'Welcome to React Router!' },
];
}
export default function Home() {
return (
<HomeLayout {...baseOptions()}>
<div className="p-4 flex flex-col items-center justify-center text-center flex-1">
<h1 className="text-xl font-bold mb-2">Helix API Documentation</h1>
<p className="text-fd-muted-foreground mb-4">
Powered by Vite, React Router and Fumadocs OpenAPI.
</p>
<div className="flex items-center gap-3">
<Link
className="text-sm bg-fd-primary text-fd-primary-foreground rounded-full font-medium px-4 py-2.5"
to="/api-reference"
>
Open API Reference
</Link>
<Link className="text-sm rounded-full font-medium px-4 py-2.5 border" to="/docs">
Open MDX Docs
</Link>
</div>
</div>
</HomeLayout>
);
}
+25
View File
@@ -0,0 +1,25 @@
import type { Route } from './+types/not-found';
import { HomeLayout } from 'fumadocs-ui/layouts/home';
import { Link } from 'react-router';
import { baseOptions } from '@/lib/layout.shared';
export function meta({}: Route.MetaArgs) {
return [{ title: 'Not Found' }];
}
export default function NotFound() {
return (
<HomeLayout {...baseOptions()}>
<div className="p-4 flex flex-col items-center justify-center text-center flex-1">
<h1 className="text-xl font-bold mb-2">Not Found</h1>
<p className="text-fd-muted-foreground mb-4">This page could not be found.</p>
<Link
className="text-sm bg-fd-primary text-fd-primary-foreground rounded-full font-medium px-4 py-2.5"
to="/docs"
>
Back to Docs
</Link>
</div>
</HomeLayout>
);
}
+10
View File
@@ -0,0 +1,10 @@
import { createFromSource } from 'fumadocs-core/search/server';
import { source } from '@/lib/source';
const server = createFromSource(source, {
language: 'english',
});
export async function loader() {
return server.staticGET();
}
+32
View File
@@ -0,0 +1,32 @@
---
title: Hello World
description: |
Your first `document`
You'll love it!
---
Hey there! Fumadocs is the docs framework that also works on React Router!
## Heading
Hello World
<Cards>
<Card title="Learn more about React Router" href="https://reactrouter.com" />
<Card title="Learn more about Fumadocs" href="https://fumadocs.dev" />
</Cards>
```ts
console.log('I love React!');
```
### Heading
#### Heading
| Head | Description |
| ------------------------------- | ----------------------------------- |
| `hello` | Hello World |
| very **important** | Hey |
| _Surprisingly_ | Fumadocs |
| very long text that looks weird | hello world hello world hello world |
+24
View File
@@ -0,0 +1,24 @@
---
title: Test
description: A document to test Fumadocs
---
Hey there!
## Cards
<Cards>
<Card title="Learn more about Next.js" href="https://nextjs.org/docs" />
<Card title="Learn more about Fumadocs" href="https://fumadocs.dev" />
</Cards>
### CodeBlock
```js
console.log('Hello World');
```
#### List
- Hello
- World
+32
View File
@@ -0,0 +1,32 @@
openapi: 3.1.0
info:
title: Helix API
version: 0.1.0
description: |
Initial OpenAPI skeleton.
Maintain this file manually.
servers:
- url: http://localhost:5000
description: Local development
paths:
/health:
get:
summary: Health check
operationId: healthCheck
tags:
- System
responses:
'200':
description: Service is available
content:
application/json:
schema:
type: object
properties:
status:
type: string
example: ok
required:
- status
components:
securitySchemes: {}
+39
View File
@@ -0,0 +1,39 @@
{
"name": "docs",
"private": true,
"type": "module",
"scripts": {
"build": "react-router build",
"dev": "react-router dev",
"start": "serve ./build/client --config ../../serve.json",
"types:check": "react-router typegen && fumadocs-mdx && tsc --noEmit",
"postinstall": "fumadocs-mdx"
},
"dependencies": {
"@orama/orama": "^3.1.18",
"@react-router/node": "^7.13.1",
"fumadocs-core": "16.7.5",
"fumadocs-mdx": "14.2.11",
"fumadocs-openapi": "^10.4.1",
"fumadocs-ui": "16.7.5",
"isbot": "^5.1.36",
"lucide-react": "^0.577.0",
"react": "^19.2.4",
"react-dom": "^19.2.4",
"react-router": "^7.13.1",
"tailwind-merge": "^3.5.0"
},
"devDependencies": {
"@react-router/dev": "^7.13.1",
"@tailwindcss/vite": "^4.2.2",
"@types/mdx": "^2.0.13",
"@types/node": "^25.5.0",
"@types/react": "^19.2.14",
"@types/react-dom": "^19.2.3",
"react-router-devtools": "^6.2.0",
"serve": "^14.2.6",
"tailwindcss": "^4.2.2",
"typescript": "^5.9.3",
"vite": "^8.0.1"
}
}
+6401
View File
File diff suppressed because it is too large. Load diff
Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

+27
View File
@@ -0,0 +1,27 @@
import type { Config } from '@react-router/dev/config';
import { glob } from 'node:fs/promises';
import { createGetUrl, getSlugs } from 'fumadocs-core/source';
const getUrl = createGetUrl('/docs');
export default {
ssr: false,
future: {
v8_middleware: true,
},
async prerender({ getStaticPaths }) {
const paths: string[] = [];
const excluded: string[] = ['/api-reference', '/api-reference/'];
for (const path of getStaticPaths()) {
if (!excluded.includes(path)) paths.push(path);
}
for await (const entry of glob('**/*.mdx', { cwd: 'content/docs' })) {
const slugs = getSlugs(entry);
paths.push(getUrl(slugs), `/llms.mdx/docs/${[...slugs, 'index.mdx'].join('/')}`);
}
return paths;
},
} satisfies Config;
+3
View File
@@ -0,0 +1,3 @@
{
"rewrites": [{ "source": "/**", "destination": "/__spa-fallback.html" }]
}
+12
View File
@@ -0,0 +1,12 @@
import { defineConfig, defineDocs } from 'fumadocs-mdx/config';
export const docs = defineDocs({
dir: 'content/docs',
docs: {
postprocess: {
includeProcessedMarkdown: true,
},
},
});
export default defineConfig();
+23
View File
@@ -0,0 +1,23 @@
{
"include": ["**/*", "**/.server/**/*", "**/.client/**/*", ".react-router/types/**/*"],
"compilerOptions": {
"lib": ["DOM", "DOM.Iterable", "ES2022"],
"types": ["node", "vite/client"],
"target": "esnext",
"module": "esnext",
"moduleResolution": "bundler",
"jsx": "react-jsx",
"rootDirs": [".", "./.react-router/types"],
"baseUrl": ".",
"paths": {
"@/*": ["./app/*"],
"collections/*": [".source/*"]
},
"esModuleInterop": true,
"verbatimModuleSyntax": true,
"noEmit": true,
"resolveJsonModule": true,
"skipLibCheck": true,
"strict": true
}
}
+12
View File
@@ -0,0 +1,12 @@
import { reactRouter } from '@react-router/dev/vite';
import tailwindcss from '@tailwindcss/vite';
import { defineConfig } from 'vite';
import mdx from 'fumadocs-mdx/vite';
import * as MdxConfig from './source.config';
export default defineConfig({
plugins: [mdx(MdxConfig), tailwindcss(), reactRouter()],
resolve: {
tsconfigPaths: true,
},
});