create-fumadocs-app 16.0.2 → 16.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,372 +0,0 @@
1
- // src/create-app.ts
2
- import path2 from "path";
3
- import fs2 from "fs/promises";
4
-
5
- // src/utils.ts
6
- import fs from "fs/promises";
7
- import path, { join } from "path";
8
- import { x } from "tinyexec";
9
- async function copy(from, to, options = {}) {
10
- const {
11
- rename = (s) => s,
12
- filterDir = () => true,
13
- filter = () => true
14
- } = options;
15
- const stats = await fs.stat(from);
16
- if (stats.isDirectory() && filterDir(from)) {
17
- const files = await fs.readdir(from);
18
- await Promise.all(
19
- files.map(
20
- (file) => copy(path.join(from, file), path.join(to, file), options)
21
- )
22
- );
23
- }
24
- if (stats.isFile() && filter(from)) {
25
- to = rename(to);
26
- await fs.mkdir(path.dirname(to), { recursive: true });
27
- await fs.copyFile(from, to);
28
- }
29
- }
30
- async function isInGitRepository(cwd2) {
31
- const { exitCode } = await x("git", ["rev-parse", "--is-inside-work-tree"], {
32
- nodeOptions: { cwd: cwd2 }
33
- });
34
- return exitCode === 0;
35
- }
36
- async function isDefaultBranchSet(cwd2) {
37
- const { exitCode } = await x("git", ["config", "init.defaultBranch"], {
38
- nodeOptions: { cwd: cwd2 }
39
- });
40
- return exitCode === 0;
41
- }
42
- async function tryGitInit(cwd2) {
43
- const { exitCode } = await x("git", ["--version"]);
44
- if (exitCode !== 0) return false;
45
- if (await isInGitRepository(cwd2)) return false;
46
- try {
47
- await x("git", ["init"], {
48
- throwOnError: true,
49
- nodeOptions: { cwd: cwd2 }
50
- });
51
- if (!await isDefaultBranchSet(cwd2)) {
52
- await x("git", ["checkout", "-b", "main"], {
53
- throwOnError: true,
54
- nodeOptions: {
55
- cwd: cwd2
56
- }
57
- });
58
- }
59
- await x("git", ["add", "-A"], {
60
- throwOnError: true,
61
- nodeOptions: {
62
- cwd: cwd2
63
- }
64
- });
65
- await x(
66
- "git",
67
- ["commit", "-m", "Initial commit from Create Fumadocs App"],
68
- {
69
- throwOnError: true,
70
- nodeOptions: {
71
- cwd: cwd2
72
- }
73
- }
74
- );
75
- return true;
76
- } catch {
77
- await fs.rmdir(join(cwd2, ".git"), { recursive: true }).catch(() => null);
78
- return false;
79
- }
80
- }
81
- function pick(obj, keys) {
82
- const result = {};
83
- for (const key of keys) {
84
- if (key in obj) {
85
- result[key] = obj[key];
86
- }
87
- }
88
- return result;
89
- }
90
-
91
- // src/auto-install.ts
92
- import { x as x2 } from "tinyexec";
93
- var managers = ["npm", "yarn", "bun", "pnpm"];
94
- function getPackageManager() {
95
- const userAgent = process.env.npm_config_user_agent ?? "";
96
- if (userAgent.startsWith("yarn")) {
97
- return "yarn";
98
- }
99
- if (userAgent.startsWith("pnpm")) {
100
- return "pnpm";
101
- }
102
- if (userAgent.startsWith("bun")) {
103
- return "bun";
104
- }
105
- return "npm";
106
- }
107
- async function autoInstall(manager, dest) {
108
- await x2(manager, ["install"], {
109
- throwOnError: true,
110
- nodeOptions: {
111
- env: {
112
- ...process.env,
113
- NODE_ENV: "development",
114
- DISABLE_OPENCOLLECTIVE: "1"
115
- },
116
- cwd: dest
117
- }
118
- });
119
- }
120
-
121
- // src/constants.ts
122
- import { fileURLToPath } from "url";
123
-
124
- // src/versions.js
125
- var versions = { "fumadocs-core": "16.0.2", "fumadocs-ui": "16.0.2", "fumadocs-mdx": "13.0.0", "@fumadocs/mdx-remote": "1.4.3", "@fumadocs/content-collections": "1.2.4" };
126
-
127
- // ../create-app-versions/package.json
128
- var package_default = {
129
- name: "example-versions",
130
- version: "0.0.0",
131
- private: true,
132
- description: "Used to track dependency versions in create-fumadocs-app",
133
- dependencies: {
134
- "@biomejs/biome": "^2.2.6",
135
- "@content-collections/core": "^0.11.1",
136
- "@content-collections/mdx": "^0.2.2",
137
- "@content-collections/next": "^0.2.8",
138
- "@orama/core": "^1.2.13",
139
- "@react-router/dev": "^7.9.4",
140
- "@react-router/node": "^7.9.4",
141
- "@react-router/serve": "^7.9.4",
142
- "@tailwindcss/postcss": "^4.1.15",
143
- "@tailwindcss/vite": "^4.1.15",
144
- "@tanstack/react-router": "^1.133.21",
145
- "@tanstack/react-start": "^1.133.21",
146
- "@types/mdx": "^2.0.13",
147
- "@types/node": "24.9.1",
148
- "@types/react": "^19.2.2",
149
- "@types/react-dom": "^19.2.2",
150
- "@vitejs/plugin-react": "^5.0.4",
151
- "gray-matter": "^4.0.3",
152
- isbot: "^5.1.31",
153
- "lucide-react": "^0.546.0",
154
- next: "16.0.0",
155
- postcss: "^8.5.6",
156
- react: "^19.2.0",
157
- "react-dom": "^19.2.0",
158
- "react-router": "^7.9.4",
159
- "react-router-devtools": "^5.1.3",
160
- shiki: "^3.13.0",
161
- tailwindcss: "^4.1.15",
162
- tinyglobby: "^0.2.15",
163
- typescript: "^5.9.3",
164
- vinxi: "^0.5.8",
165
- vite: "^7.1.11",
166
- "vite-tsconfig-paths": "^5.1.4"
167
- }
168
- };
169
-
170
- // src/constants.ts
171
- var sourceDir = fileURLToPath(new URL(`../`, import.meta.url).href);
172
- var cwd = process.cwd();
173
- var templates = [
174
- {
175
- value: "+next+fuma-docs-mdx",
176
- label: "Next.js: Fumadocs MDX",
177
- hint: "recommended",
178
- componentsDir: "components"
179
- },
180
- {
181
- value: "waku",
182
- label: "Waku: Fumadocs MDX",
183
- componentsDir: "src/components"
184
- },
185
- {
186
- value: "react-router",
187
- label: "React Router: Fumadocs MDX (not RSC)",
188
- componentsDir: "app/components"
189
- },
190
- {
191
- value: "react-router-spa",
192
- label: "React Router SPA: Fumadocs MDX (not RSC)",
193
- hint: "SPA mode allows you to host the site statically, compatible with a CDN.",
194
- componentsDir: "app/components"
195
- },
196
- {
197
- value: "tanstack-start",
198
- label: "Tanstack Start: Fumadocs MDX (not RSC)",
199
- componentsDir: "src/components"
200
- }
201
- ];
202
- var depVersions = {
203
- ...versions,
204
- ...package_default.dependencies
205
- };
206
-
207
- // src/create-app.ts
208
- function defaults(options) {
209
- return {
210
- ...options,
211
- plugins: options.plugins ?? [],
212
- useSrcDir: options.template.startsWith("+next") && options.useSrcDir === true,
213
- lint: options.lint ?? false,
214
- initializeGit: options.initializeGit ?? false,
215
- installDeps: options.installDeps ?? false,
216
- log: console.log
217
- };
218
- }
219
- function isRelative(dir, file) {
220
- return !path2.relative(dir, file).startsWith(`..${path2.sep}`);
221
- }
222
- async function create(createOptions) {
223
- const options = defaults(createOptions);
224
- const {
225
- outputDir,
226
- useSrcDir,
227
- log,
228
- installDeps,
229
- template,
230
- lint,
231
- initializeGit,
232
- packageManager,
233
- plugins
234
- } = options;
235
- const projectName = path2.basename(outputDir);
236
- const dest = path2.resolve(cwd, outputDir);
237
- const isNext = options.template.startsWith("+next");
238
- const pluginContext = {
239
- template: templates.find((item) => item.value === template),
240
- dest,
241
- options
242
- };
243
- await copy(path2.join(sourceDir, `template/${template}`), dest, {
244
- rename(file) {
245
- file = file.replace("example.gitignore", ".gitignore");
246
- if (useSrcDir && (path2.basename(file) === "mdx-components.tsx" || isRelative(path2.join(dest, "app"), file) || isRelative(path2.join(dest, "lib"), file))) {
247
- return path2.join(dest, "src", path2.relative(dest, file));
248
- }
249
- return file;
250
- }
251
- });
252
- if (isNext && lint) {
253
- await copy(path2.join(sourceDir, `template/+next+${lint}`), dest);
254
- log("Configured Linter");
255
- }
256
- if (isNext && useSrcDir) {
257
- const tsconfigPath = path2.join(dest, "tsconfig.json");
258
- const content = (await fs2.readFile(tsconfigPath)).toString();
259
- const config = JSON.parse(content);
260
- if (config.compilerOptions?.paths) {
261
- Object.assign(config.compilerOptions.paths, {
262
- "@/*": ["./src/*"]
263
- });
264
- }
265
- await fs2.writeFile(tsconfigPath, JSON.stringify(config, null, 2));
266
- }
267
- let packageJson = await createPackageJson(projectName, dest, options);
268
- for (const plugin of plugins) {
269
- const result = await plugin.packageJson?.call(pluginContext, packageJson);
270
- if (result) packageJson = result;
271
- }
272
- await fs2.writeFile(
273
- path2.join(dest, "package.json"),
274
- JSON.stringify(packageJson, null, 2)
275
- );
276
- let readme = await getReadme(dest, projectName);
277
- for (const plugin of plugins) {
278
- readme = await plugin.readme?.call(pluginContext, readme) ?? readme;
279
- }
280
- await fs2.writeFile(path2.join(dest, "README.md"), readme);
281
- for (const plugin of plugins) {
282
- await plugin.afterWrite?.call(pluginContext);
283
- }
284
- if (installDeps) {
285
- try {
286
- await autoInstall(packageManager, dest);
287
- log("Installed dependencies");
288
- } catch (err) {
289
- log(`Failed to install dependencies: ${err}`);
290
- }
291
- }
292
- if (initializeGit && await tryGitInit(dest)) {
293
- log("Initialized Git repository");
294
- }
295
- }
296
- async function getReadme(dest, projectName) {
297
- const template = await fs2.readFile(path2.join(dest, "README.md")).then((res) => res.toString());
298
- return `# ${projectName}
299
-
300
- ${template}`;
301
- }
302
- async function createPackageJson(projectName, dir, { template, lint }) {
303
- const isNext = template.startsWith("+next");
304
- function replaceWorkspaceDeps(deps) {
305
- for (const k in deps) {
306
- if (deps[k].startsWith("workspace:") && k in depVersions) {
307
- deps[k] = depVersions[k];
308
- }
309
- }
310
- return deps;
311
- }
312
- let packageJson = JSON.parse(
313
- await fs2.readFile(path2.join(dir, "package.json")).then((res) => res.toString())
314
- );
315
- packageJson = {
316
- name: projectName,
317
- ...packageJson,
318
- dependencies: replaceWorkspaceDeps(packageJson.dependencies),
319
- devDependencies: replaceWorkspaceDeps(packageJson.devDependencies)
320
- };
321
- if (isNext) {
322
- packageJson = {
323
- ...packageJson,
324
- scripts: {
325
- ...packageJson.scripts,
326
- postinstall: "fumadocs-mdx"
327
- }
328
- };
329
- }
330
- if (isNext && lint === "biome") {
331
- packageJson = {
332
- ...packageJson,
333
- scripts: {
334
- ...packageJson.scripts,
335
- lint: "biome check",
336
- format: "biome format --write"
337
- },
338
- devDependencies: {
339
- ...packageJson.devDependencies,
340
- ...pick(depVersions, ["@biomejs/biome"])
341
- }
342
- };
343
- }
344
- if (isNext && lint === "eslint") {
345
- packageJson = {
346
- ...packageJson,
347
- scripts: {
348
- ...packageJson.scripts,
349
- lint: "eslint"
350
- },
351
- devDependencies: {
352
- ...packageJson.devDependencies,
353
- eslint: "^9",
354
- "eslint-config-next": depVersions.next,
355
- "@eslint/eslintrc": "^3"
356
- }
357
- };
358
- }
359
- return packageJson;
360
- }
361
-
362
- export {
363
- copy,
364
- pick,
365
- managers,
366
- getPackageManager,
367
- sourceDir,
368
- cwd,
369
- templates,
370
- depVersions,
371
- create
372
- };
@@ -1,18 +0,0 @@
1
- import { RootProvider } from 'fumadocs-ui/provider/next';
2
- import './global.css';
3
- import { Inter } from 'next/font/google';
4
- import SearchDialog from '@/components/search';
5
-
6
- const inter = Inter({
7
- subsets: ['latin'],
8
- });
9
-
10
- export default function Layout({ children }: LayoutProps<'/'>) {
11
- return (
12
- <html lang="en" className={inter.className} suppressHydrationWarning>
13
- <body className="flex flex-col min-h-screen">
14
- <RootProvider search={{ SearchDialog }}>{children}</RootProvider>
15
- </body>
16
- </html>
17
- );
18
- }
@@ -1,7 +0,0 @@
1
- import { exportSearchIndexes } from '@/lib/export-search-indexes';
2
-
3
- export const revalidate = false;
4
-
5
- export async function GET() {
6
- return Response.json(await exportSearchIndexes());
7
- }
@@ -1,76 +0,0 @@
1
- import {
2
- isRouteErrorResponse,
3
- Links,
4
- Meta,
5
- Outlet,
6
- Scripts,
7
- ScrollRestoration,
8
- } from 'react-router';
9
- import { RootProvider } from 'fumadocs-ui/provider/react-router';
10
- import type { Route } from './+types/root';
11
- import './app.css';
12
- import SearchDialog from '@/components/search';
13
-
14
- export const links: Route.LinksFunction = () => [
15
- { rel: 'preconnect', href: 'https://fonts.googleapis.com' },
16
- {
17
- rel: 'preconnect',
18
- href: 'https://fonts.gstatic.com',
19
- crossOrigin: 'anonymous',
20
- },
21
- {
22
- rel: 'stylesheet',
23
- href: 'https://fonts.googleapis.com/css2?family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&display=swap',
24
- },
25
- ];
26
-
27
- export function Layout({ children }: { children: React.ReactNode }) {
28
- return (
29
- <html lang="en" suppressHydrationWarning>
30
- <head>
31
- <meta charSet="utf-8" />
32
- <meta name="viewport" content="width=device-width, initial-scale=1" />
33
- <Meta />
34
- <Links />
35
- </head>
36
- <body className="flex flex-col min-h-screen">
37
- <RootProvider search={{ SearchDialog }}>{children}</RootProvider>
38
- <ScrollRestoration />
39
- <Scripts />
40
- </body>
41
- </html>
42
- );
43
- }
44
-
45
- export default function App() {
46
- return <Outlet />;
47
- }
48
-
49
- export function ErrorBoundary({ error }: Route.ErrorBoundaryProps) {
50
- let message = 'Oops!';
51
- let details = 'An unexpected error occurred.';
52
- let stack: string | undefined;
53
-
54
- if (isRouteErrorResponse(error)) {
55
- message = error.status === 404 ? '404' : 'Error';
56
- details =
57
- error.status === 404
58
- ? 'The requested page could not be found.'
59
- : error.statusText || details;
60
- } else if (import.meta.env.DEV && error && error instanceof Error) {
61
- details = error.message;
62
- stack = error.stack;
63
- }
64
-
65
- return (
66
- <main className="pt-16 p-4 container mx-auto">
67
- <h1>{message}</h1>
68
- <p>{details}</p>
69
- {stack && (
70
- <pre className="w-full p-4 overflow-x-auto">
71
- <code>{stack}</code>
72
- </pre>
73
- )}
74
- </main>
75
- );
76
- }
@@ -1,5 +0,0 @@
1
- import { exportSearchIndexes } from '@/lib/export-search-indexes';
2
-
3
- export async function loader() {
4
- return Response.json(await exportSearchIndexes());
5
- }
@@ -1,8 +0,0 @@
1
- import { index, route, type RouteConfig } from '@react-router/dev/routes';
2
-
3
- export default [
4
- index('routes/home.tsx'),
5
- route('docs/*', 'docs/page.tsx'),
6
- route('api/search', 'docs/search.ts'),
7
- route('static.json', 'routes/static.ts'),
8
- ] satisfies RouteConfig;
@@ -1,76 +0,0 @@
1
- import {
2
- isRouteErrorResponse,
3
- Links,
4
- Meta,
5
- Outlet,
6
- Scripts,
7
- ScrollRestoration,
8
- } from 'react-router';
9
- import { RootProvider } from 'fumadocs-ui/provider/react-router';
10
- import type { Route } from './+types/root';
11
- import './app.css';
12
- import SearchDialog from '@/components/search';
13
-
14
- export const links: Route.LinksFunction = () => [
15
- { rel: 'preconnect', href: 'https://fonts.googleapis.com' },
16
- {
17
- rel: 'preconnect',
18
- href: 'https://fonts.gstatic.com',
19
- crossOrigin: 'anonymous',
20
- },
21
- {
22
- rel: 'stylesheet',
23
- href: 'https://fonts.googleapis.com/css2?family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&display=swap',
24
- },
25
- ];
26
-
27
- export function Layout({ children }: { children: React.ReactNode }) {
28
- return (
29
- <html lang="en" suppressHydrationWarning>
30
- <head>
31
- <meta charSet="utf-8" />
32
- <meta name="viewport" content="width=device-width, initial-scale=1" />
33
- <Meta />
34
- <Links />
35
- </head>
36
- <body className="flex flex-col min-h-screen">
37
- <RootProvider search={{ SearchDialog }}>{children}</RootProvider>
38
- <ScrollRestoration />
39
- <Scripts />
40
- </body>
41
- </html>
42
- );
43
- }
44
-
45
- export default function App() {
46
- return <Outlet />;
47
- }
48
-
49
- export function ErrorBoundary({ error }: Route.ErrorBoundaryProps) {
50
- let message = 'Oops!';
51
- let details = 'An unexpected error occurred.';
52
- let stack: string | undefined;
53
-
54
- if (isRouteErrorResponse(error)) {
55
- message = error.status === 404 ? '404' : 'Error';
56
- details =
57
- error.status === 404
58
- ? 'The requested page could not be found.'
59
- : error.statusText || details;
60
- } else if (import.meta.env.DEV && error && error instanceof Error) {
61
- details = error.message;
62
- stack = error.stack;
63
- }
64
-
65
- return (
66
- <main className="pt-16 p-4 container mx-auto">
67
- <h1>{message}</h1>
68
- <p>{details}</p>
69
- {stack && (
70
- <pre className="w-full p-4 overflow-x-auto">
71
- <code>{stack}</code>
72
- </pre>
73
- )}
74
- </main>
75
- );
76
- }
@@ -1,5 +0,0 @@
1
- import { exportSearchIndexes } from '@/lib/export-search-indexes';
2
-
3
- export async function loader() {
4
- return Response.json(await exportSearchIndexes());
5
- }
@@ -1,8 +0,0 @@
1
- import { index, route, type RouteConfig } from '@react-router/dev/routes';
2
-
3
- export default [
4
- index('routes/home.tsx'),
5
- route('docs/*', 'docs/page.tsx'),
6
- route('api/search', 'docs/search.ts'),
7
- route('static.json', 'routes/static.ts'),
8
- ] satisfies RouteConfig;
@@ -1,51 +0,0 @@
1
- import {
2
- createRootRoute,
3
- HeadContent,
4
- Outlet,
5
- Scripts,
6
- } from '@tanstack/react-router';
7
- import * as React from 'react';
8
- import appCss from '@/styles/app.css?url';
9
- import { RootProvider } from 'fumadocs-ui/provider/tanstack';
10
- import SearchDialog from '@/components/search';
11
-
12
- export const Route = createRootRoute({
13
- head: () => ({
14
- meta: [
15
- {
16
- charSet: 'utf-8',
17
- },
18
- {
19
- name: 'viewport',
20
- content: 'width=device-width, initial-scale=1',
21
- },
22
- {
23
- title: 'Fumadocs on TanStack Start',
24
- },
25
- ],
26
- links: [{ rel: 'stylesheet', href: appCss }],
27
- }),
28
- component: RootComponent,
29
- });
30
-
31
- function RootComponent() {
32
- return (
33
- <RootDocument>
34
- <Outlet />
35
- </RootDocument>
36
- );
37
- }
38
-
39
- function RootDocument({ children }: { children: React.ReactNode }) {
40
- return (
41
- <html suppressHydrationWarning>
42
- <head>
43
- <HeadContent />
44
- </head>
45
- <body className="flex flex-col min-h-screen">
46
- <RootProvider search={{ SearchDialog }}>{children}</RootProvider>
47
- <Scripts />
48
- </body>
49
- </html>
50
- );
51
- }
@@ -1,10 +0,0 @@
1
- import { createFileRoute } from '@tanstack/react-router';
2
- import { exportSearchIndexes } from '@/lib/export-search-indexes';
3
-
4
- export const Route = createFileRoute('/static.json')({
5
- server: {
6
- handlers: {
7
- GET: async () => Response.json(await exportSearchIndexes()),
8
- },
9
- },
10
- });
@@ -1,27 +0,0 @@
1
- import react from '@vitejs/plugin-react';
2
- import { tanstackStart } from '@tanstack/react-start/plugin/vite';
3
- import { defineConfig } from 'vite';
4
- import tsConfigPaths from 'vite-tsconfig-paths';
5
- import tailwindcss from '@tailwindcss/vite';
6
- import mdx from 'fumadocs-mdx/vite';
7
-
8
- export default defineConfig({
9
- server: {
10
- port: 3000,
11
- },
12
- plugins: [
13
- mdx(await import('./source.config')),
14
- tailwindcss(),
15
- tsConfigPaths({
16
- projects: ['./tsconfig.json'],
17
- }),
18
- tanstackStart({
19
- prerender: {
20
- enabled: true,
21
- // pre-render the static file
22
- pages: [{ path: '/static.json' }],
23
- },
24
- }),
25
- react(),
26
- ],
27
- });
@@ -1,8 +0,0 @@
1
- 'use client';
2
- import type { ReactNode } from 'react';
3
- import { RootProvider } from 'fumadocs-ui/provider/waku';
4
- import SearchDialog from '@/components/search';
5
-
6
- export function Provider({ children }: { children: ReactNode }) {
7
- return <RootProvider search={{ SearchDialog }}>{children}</RootProvider>;
8
- }
@@ -1,9 +0,0 @@
1
- import { exportSearchIndexes } from '@/lib/export-search-indexes';
2
-
3
- export async function GET() {
4
- return Response.json(await exportSearchIndexes());
5
- }
6
-
7
- export const getConfig = () => ({
8
- render: 'static',
9
- });