create-fumadocs-app 16.0.3 → 16.0.4
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.
- package/dist/bin.d.ts +1 -0
- package/dist/bin.js +215 -0
- package/dist/chunk-BEZTHMLF.js +51 -0
- package/dist/chunk-JCFTHRDR.js +50 -0
- package/dist/chunk-UXPTMGXV.js +130 -0
- package/dist/{chunk-HLHY7KAF.js → chunk-ZWEHS3HT.js} +29 -19
- package/dist/index.d.ts +63 -1
- package/dist/index.js +6 -208
- package/dist/plugins/biome.base.d.ts +45 -0
- package/dist/plugins/biome.base.js +18 -0
- package/dist/plugins/biome.d.ts +5 -0
- package/dist/plugins/biome.js +43 -0
- package/dist/plugins/biome.next.d.ts +49 -0
- package/dist/plugins/biome.next.js +18 -0
- package/dist/plugins/eslint.d.ts +5 -0
- package/dist/plugins/eslint.js +61 -0
- package/dist/plugins/next-use-src.d.ts +8 -0
- package/dist/plugins/next-use-src.js +39 -0
- package/dist/plugins/orama-cloud.d.ts +5 -0
- package/dist/plugins/orama-cloud.js +359 -0
- package/package.json +17 -7
- package/dist/chunk-3VWJJEHU.js +0 -204
- package/dist/create-app.d.ts +0 -49
- package/dist/create-app.js +0 -7
- package/dist/orama-cloud-VQUOOEZ2.js +0 -313
- package/template/+next+biome/biome.json +0 -34
- package/template/+next+eslint/eslint.config.mjs +0 -26
|
@@ -0,0 +1,359 @@
|
|
|
1
|
+
import {
|
|
2
|
+
copy,
|
|
3
|
+
depVersions,
|
|
4
|
+
pick,
|
|
5
|
+
sourceDir,
|
|
6
|
+
writeFile
|
|
7
|
+
} from "../chunk-ZWEHS3HT.js";
|
|
8
|
+
|
|
9
|
+
// src/plugins/orama-cloud.ts
|
|
10
|
+
import path2 from "path";
|
|
11
|
+
import fs3 from "fs/promises";
|
|
12
|
+
|
|
13
|
+
// src/transform/shared.ts
|
|
14
|
+
import { IndentationText, Project, QuoteKind } from "ts-morph";
|
|
15
|
+
import fs from "fs/promises";
|
|
16
|
+
var project = new Project({
|
|
17
|
+
manipulationSettings: {
|
|
18
|
+
indentationText: IndentationText.TwoSpaces,
|
|
19
|
+
quoteKind: QuoteKind.Single
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
async function createSourceFile(path3) {
|
|
23
|
+
return project.createSourceFile(path3, (await fs.readFile(path3)).toString(), {
|
|
24
|
+
overwrite: true
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
function getCodeValue(v) {
|
|
28
|
+
return new Function(`return ${v}`)();
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// src/transform/index.ts
|
|
32
|
+
import path from "path";
|
|
33
|
+
|
|
34
|
+
// src/transform/react-router.ts
|
|
35
|
+
import {
|
|
36
|
+
ts
|
|
37
|
+
} from "ts-morph";
|
|
38
|
+
var SyntaxKind = ts.SyntaxKind;
|
|
39
|
+
function filterReactRouterPrerenderArray(sourceFile, array, filter) {
|
|
40
|
+
const methodBody = getPrerenderMethod(sourceFile)?.getBody();
|
|
41
|
+
if (!methodBody) return;
|
|
42
|
+
const initializer = methodBody.getDescendantsOfKind(SyntaxKind.VariableDeclaration).find((item) => item.getName() === array)?.getInitializerIfKind(SyntaxKind.ArrayLiteralExpression);
|
|
43
|
+
if (!initializer) return;
|
|
44
|
+
for (const element of initializer.getElements()) {
|
|
45
|
+
if (!filter(getCodeValue(element.getText()))) {
|
|
46
|
+
initializer.removeElement(element);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
function addReactRouterRoute(sourceFile, routes) {
|
|
51
|
+
modifyReactRouterRoutes(sourceFile, (arr) => {
|
|
52
|
+
for (const { path: path3, entry } of routes) {
|
|
53
|
+
arr.addElement(`route('${path3}', '${entry}')`);
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
function filterReactRouterRoute(sourceFile, filter) {
|
|
58
|
+
modifyReactRouterRoutes(sourceFile, (arr) => {
|
|
59
|
+
for (const element of arr.getElements()) {
|
|
60
|
+
if (!element.isKind(SyntaxKind.CallExpression) || element.getFirstChildByKind(SyntaxKind.Identifier)?.getText() !== "route")
|
|
61
|
+
continue;
|
|
62
|
+
const args = element.getArguments();
|
|
63
|
+
if (filter({
|
|
64
|
+
path: getCodeValue(args[0].getText()),
|
|
65
|
+
entry: getCodeValue(args[1].getText())
|
|
66
|
+
}))
|
|
67
|
+
continue;
|
|
68
|
+
arr.removeElement(element);
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
function modifyReactRouterRoutes(sourceFile, mod) {
|
|
73
|
+
const initializer = sourceFile.getDefaultExportSymbol()?.getValueDeclaration()?.getFirstDescendantByKind(SyntaxKind.ArrayLiteralExpression);
|
|
74
|
+
if (initializer) mod(initializer);
|
|
75
|
+
}
|
|
76
|
+
function getPrerenderMethod(sourceFile) {
|
|
77
|
+
return sourceFile.getDefaultExportSymbol()?.getValueDeclaration()?.getFirstDescendantByKind(SyntaxKind.ObjectLiteralExpression)?.getProperty("prerender")?.asKind(SyntaxKind.MethodDeclaration) ?? null;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// src/transform/index.ts
|
|
81
|
+
import fs2 from "fs/promises";
|
|
82
|
+
|
|
83
|
+
// src/transform/tanstack-start.ts
|
|
84
|
+
import { SyntaxKind as SyntaxKind2 } from "ts-morph";
|
|
85
|
+
function addTanstackPrerender(sourceFile, paths) {
|
|
86
|
+
const optionsArg = getTanstackStartCall(sourceFile)?.getArguments()[0]?.asKind(SyntaxKind2.ObjectLiteralExpression);
|
|
87
|
+
if (!optionsArg) {
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
const pagesProperty = optionsArg.getProperty("pages")?.asKind(SyntaxKind2.PropertyAssignment);
|
|
91
|
+
function toItem(path3) {
|
|
92
|
+
return `{ path: '${path3}' }`;
|
|
93
|
+
}
|
|
94
|
+
if (pagesProperty) {
|
|
95
|
+
const initializer = pagesProperty.getInitializerIfKindOrThrow(
|
|
96
|
+
SyntaxKind2.ArrayLiteralExpression
|
|
97
|
+
);
|
|
98
|
+
const existingPaths = /* @__PURE__ */ new Set();
|
|
99
|
+
for (const element of initializer.getElements()) {
|
|
100
|
+
const value = element.asKind(SyntaxKind2.ObjectLiteralExpression)?.getProperty("path")?.asKind(SyntaxKind2.PropertyAssignment)?.getInitializer()?.getText();
|
|
101
|
+
if (value) {
|
|
102
|
+
existingPaths.add(getCodeValue(value));
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
for (const path3 of paths) {
|
|
106
|
+
if (existingPaths.has(path3)) continue;
|
|
107
|
+
initializer.addElement(toItem(path3));
|
|
108
|
+
}
|
|
109
|
+
} else {
|
|
110
|
+
optionsArg.addProperty(
|
|
111
|
+
`pages: [
|
|
112
|
+
${paths.map((path3) => ` ${toItem(path3)}`).join(",\n")}
|
|
113
|
+
]`
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
function getTanstackStartCall(sourceFile) {
|
|
118
|
+
const pluginsProperty = sourceFile.getDefaultExportSymbol()?.getValueDeclaration()?.getFirstDescendantByKind(SyntaxKind2.ObjectLiteralExpression)?.getProperty("plugins")?.getFirstChildByKind(SyntaxKind2.ArrayLiteralExpression);
|
|
119
|
+
if (!pluginsProperty) return;
|
|
120
|
+
for (const element of pluginsProperty.getElements()) {
|
|
121
|
+
const expression = element.asKind(SyntaxKind2.CallExpression);
|
|
122
|
+
if (expression?.getFirstChildByKind(SyntaxKind2.Identifier)?.getText() === "tanstackStart") {
|
|
123
|
+
return expression;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// src/transform/index.ts
|
|
129
|
+
import { StructureKind, SyntaxKind as SyntaxKind3 } from "ts-morph";
|
|
130
|
+
async function rootProvider({ appDir, template }, fn) {
|
|
131
|
+
const file = await createSourceFile(
|
|
132
|
+
path.join(appDir, template.rootProviderPath)
|
|
133
|
+
);
|
|
134
|
+
fn({
|
|
135
|
+
addSearchDialog(specifier) {
|
|
136
|
+
const elements = file.getDescendantsOfKind(SyntaxKind3.JsxElement);
|
|
137
|
+
for (const element of elements) {
|
|
138
|
+
const provider = element.getFirstChildByKind(
|
|
139
|
+
SyntaxKind3.JsxOpeningElement
|
|
140
|
+
);
|
|
141
|
+
if (provider?.getTagNameNode().getText() !== "RootProvider") continue;
|
|
142
|
+
if (provider.getAttributes().some(
|
|
143
|
+
(attr) => attr.isKind(SyntaxKind3.JsxAttribute) && attr.getNameNode().getText() === "search"
|
|
144
|
+
))
|
|
145
|
+
continue;
|
|
146
|
+
provider.addAttribute({
|
|
147
|
+
kind: StructureKind.JsxAttribute,
|
|
148
|
+
name: "search",
|
|
149
|
+
initializer: "{{ SearchDialog }}"
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
file.addImportDeclaration({
|
|
153
|
+
moduleSpecifier: specifier,
|
|
154
|
+
defaultImport: "SearchDialog"
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
});
|
|
158
|
+
await file.save();
|
|
159
|
+
}
|
|
160
|
+
async function reactRouterRoutes({ dest, appDir }, fn) {
|
|
161
|
+
const configFile = await createSourceFile(
|
|
162
|
+
path.join(dest, "react-router.config.ts")
|
|
163
|
+
);
|
|
164
|
+
const routesFile = await createSourceFile(path.join(appDir, "routes.ts"));
|
|
165
|
+
const tasks = [];
|
|
166
|
+
function normalizePath(v) {
|
|
167
|
+
return v.split("/").filter(Boolean).join("/");
|
|
168
|
+
}
|
|
169
|
+
fn({
|
|
170
|
+
addRoute: (p, entry, code) => {
|
|
171
|
+
addReactRouterRoute(routesFile, [{ path: p, entry }]);
|
|
172
|
+
if (code) {
|
|
173
|
+
tasks.push(fs2.writeFile(path.join(appDir, entry), code));
|
|
174
|
+
}
|
|
175
|
+
},
|
|
176
|
+
removeRoute: (p) => {
|
|
177
|
+
const normalizedPath = normalizePath(p);
|
|
178
|
+
filterReactRouterRoute(routesFile, (item) => {
|
|
179
|
+
if (normalizePath(item.path) !== normalizedPath) return true;
|
|
180
|
+
tasks.push(fs2.unlink(path.join(appDir, item.entry)).catch(() => null));
|
|
181
|
+
return false;
|
|
182
|
+
});
|
|
183
|
+
filterReactRouterPrerenderArray(
|
|
184
|
+
configFile,
|
|
185
|
+
"excluded",
|
|
186
|
+
(item) => normalizePath(item) !== normalizedPath
|
|
187
|
+
);
|
|
188
|
+
filterReactRouterPrerenderArray(
|
|
189
|
+
configFile,
|
|
190
|
+
"paths",
|
|
191
|
+
(item) => normalizePath(item) !== normalizedPath
|
|
192
|
+
);
|
|
193
|
+
}
|
|
194
|
+
});
|
|
195
|
+
await Promise.all([...tasks, routesFile.save(), configFile.save()]);
|
|
196
|
+
}
|
|
197
|
+
async function tanstackStartRoutes({ appDir, dest }, fn) {
|
|
198
|
+
const configFile = await createSourceFile(path.join(dest, "vite.config.ts"));
|
|
199
|
+
const tasks = [];
|
|
200
|
+
fn({
|
|
201
|
+
addRoute(options) {
|
|
202
|
+
if (options.code) {
|
|
203
|
+
tasks.push(
|
|
204
|
+
fs2.writeFile(path.join(appDir, "routes", options.path), options.code)
|
|
205
|
+
);
|
|
206
|
+
}
|
|
207
|
+
if (options.prerender) {
|
|
208
|
+
addTanstackPrerender(configFile, [options.route]);
|
|
209
|
+
}
|
|
210
|
+
},
|
|
211
|
+
removeRoute(options) {
|
|
212
|
+
tasks.push(
|
|
213
|
+
fs2.unlink(path.join(appDir, "routes", options.path)).catch(() => null)
|
|
214
|
+
);
|
|
215
|
+
}
|
|
216
|
+
});
|
|
217
|
+
await Promise.all([...tasks, configFile.save()]);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// src/plugins/orama-cloud.ts
|
|
221
|
+
function oramaCloud() {
|
|
222
|
+
return {
|
|
223
|
+
packageJson(packageJson) {
|
|
224
|
+
return {
|
|
225
|
+
...packageJson,
|
|
226
|
+
scripts: {
|
|
227
|
+
...packageJson.scripts,
|
|
228
|
+
build: `${packageJson.scripts.build} && bun scripts/sync-content.ts`
|
|
229
|
+
},
|
|
230
|
+
dependencies: {
|
|
231
|
+
...packageJson.dependencies,
|
|
232
|
+
...pick(depVersions, ["@orama/core"])
|
|
233
|
+
}
|
|
234
|
+
};
|
|
235
|
+
},
|
|
236
|
+
readme(content) {
|
|
237
|
+
return `${content}
|
|
238
|
+
|
|
239
|
+
## Orama Cloud
|
|
240
|
+
|
|
241
|
+
This project uses Orama Cloud for 3rd party search solution.
|
|
242
|
+
|
|
243
|
+
See https://fumadocs.dev/docs/headless/search/orama-cloud for integrating Orama Cloud to Fumadocs.`;
|
|
244
|
+
},
|
|
245
|
+
async afterWrite() {
|
|
246
|
+
const { dest, appDir, template } = this;
|
|
247
|
+
await copy(path2.join(sourceDir, "template/+orama-cloud/@root"), dest);
|
|
248
|
+
await copy(path2.join(sourceDir, "template/+orama-cloud/@app"), appDir);
|
|
249
|
+
await rootProvider(
|
|
250
|
+
this,
|
|
251
|
+
(mod) => mod.addSearchDialog("@/components/search")
|
|
252
|
+
);
|
|
253
|
+
if (template.value === "tanstack-start") {
|
|
254
|
+
await tanstackStartRoutes(this, (mod) => {
|
|
255
|
+
mod.addRoute({
|
|
256
|
+
path: "static[.]json.ts",
|
|
257
|
+
route: "/static.json",
|
|
258
|
+
code: route.tanstack,
|
|
259
|
+
prerender: true
|
|
260
|
+
});
|
|
261
|
+
mod.removeRoute({
|
|
262
|
+
path: "api/search.ts",
|
|
263
|
+
route: "/api/search"
|
|
264
|
+
});
|
|
265
|
+
});
|
|
266
|
+
} else if (template.value.startsWith("react-router")) {
|
|
267
|
+
await reactRouterRoutes(this, (mod) => {
|
|
268
|
+
mod.addRoute(
|
|
269
|
+
"static.json",
|
|
270
|
+
"routes/static.ts",
|
|
271
|
+
route["react-router"]
|
|
272
|
+
);
|
|
273
|
+
mod.removeRoute("api/search");
|
|
274
|
+
});
|
|
275
|
+
} else if (template.value.startsWith("+next")) {
|
|
276
|
+
await Promise.all([
|
|
277
|
+
fs3.unlink(path2.join(appDir, "app/api/search/route.ts")).catch(() => null),
|
|
278
|
+
writeFile(path2.join(appDir, "app/static.json/route.ts"), route.next)
|
|
279
|
+
]);
|
|
280
|
+
} else {
|
|
281
|
+
await Promise.all([
|
|
282
|
+
fs3.unlink(path2.join(appDir, "pages/api/search.ts")).catch(() => null),
|
|
283
|
+
writeFile(path2.join(appDir, "pages/static.json.ts"), route.waku)
|
|
284
|
+
]);
|
|
285
|
+
}
|
|
286
|
+
const filePath = {
|
|
287
|
+
"+next+fuma-docs-mdx": ".next/server/app/static.json.body",
|
|
288
|
+
"tanstack-start": ".output/public/static.json",
|
|
289
|
+
"react-router": "build/client/static.json",
|
|
290
|
+
"react-router-spa": "build/client/static.json",
|
|
291
|
+
waku: "dist/public/static.json"
|
|
292
|
+
}[template.value];
|
|
293
|
+
await writeFile(
|
|
294
|
+
path2.join(dest, "scripts/sync-content.ts"),
|
|
295
|
+
`import { type OramaDocument, sync } from 'fumadocs-core/search/orama-cloud';
|
|
296
|
+
import * as fs from 'node:fs/promises';
|
|
297
|
+
import { OramaCloud } from '@orama/core';
|
|
298
|
+
|
|
299
|
+
// the path of pre-rendered \`static.json\`
|
|
300
|
+
const filePath = '${filePath}';
|
|
301
|
+
|
|
302
|
+
async function main() {
|
|
303
|
+
const orama = new OramaCloud({
|
|
304
|
+
projectId: process.env.NEXT_PUBLIC_ORAMA_PROJECT_ID,
|
|
305
|
+
apiKey: process.env.ORAMA_PRIVATE_API_KEY,
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
const content = await fs.readFile(filePath);
|
|
309
|
+
const records = JSON.parse(content.toString()) as OramaDocument[];
|
|
310
|
+
|
|
311
|
+
await sync(orama, {
|
|
312
|
+
index: process.env.NEXT_PUBLIC_ORAMA_DATASOURCE_ID,
|
|
313
|
+
documents: records,
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
console.log(\`search updated: \${records.length} records\`);
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
void main();`
|
|
320
|
+
);
|
|
321
|
+
}
|
|
322
|
+
};
|
|
323
|
+
}
|
|
324
|
+
var route = {
|
|
325
|
+
next: `import { exportSearchIndexes } from '@/lib/export-search-indexes';
|
|
326
|
+
|
|
327
|
+
export const revalidate = false;
|
|
328
|
+
|
|
329
|
+
export async function GET() {
|
|
330
|
+
return Response.json(await exportSearchIndexes());
|
|
331
|
+
}`,
|
|
332
|
+
"react-router": `import { exportSearchIndexes } from '@/lib/export-search-indexes';
|
|
333
|
+
|
|
334
|
+
export async function loader() {
|
|
335
|
+
return Response.json(await exportSearchIndexes());
|
|
336
|
+
}`,
|
|
337
|
+
tanstack: `import { createFileRoute } from '@tanstack/react-router';
|
|
338
|
+
import { exportSearchIndexes } from '@/lib/export-search-indexes';
|
|
339
|
+
|
|
340
|
+
export const Route = createFileRoute('/static.json')({
|
|
341
|
+
server: {
|
|
342
|
+
handlers: {
|
|
343
|
+
GET: async () => Response.json(await exportSearchIndexes()),
|
|
344
|
+
},
|
|
345
|
+
},
|
|
346
|
+
});`,
|
|
347
|
+
waku: `import { exportSearchIndexes } from '@/lib/export-search-indexes';
|
|
348
|
+
|
|
349
|
+
export async function GET() {
|
|
350
|
+
return Response.json(await exportSearchIndexes());
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
export const getConfig = () => ({
|
|
354
|
+
render: 'static',
|
|
355
|
+
});`
|
|
356
|
+
};
|
|
357
|
+
export {
|
|
358
|
+
oramaCloud
|
|
359
|
+
};
|
package/package.json
CHANGED
|
@@ -1,27 +1,37 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-fumadocs-app",
|
|
3
|
-
"version": "16.0.
|
|
3
|
+
"version": "16.0.4",
|
|
4
4
|
"description": "Create a new documentation site with Fumadocs",
|
|
5
5
|
"keywords": [
|
|
6
|
-
"NextJs",
|
|
7
|
-
"next",
|
|
8
6
|
"react",
|
|
9
|
-
"Docs"
|
|
7
|
+
"Docs",
|
|
8
|
+
"Fumadocs"
|
|
10
9
|
],
|
|
11
10
|
"homepage": "https://fumadocs.dev",
|
|
12
11
|
"repository": "github:fuma-nama/fumadocs",
|
|
13
12
|
"license": "MIT",
|
|
14
13
|
"author": "Fuma Nama",
|
|
15
14
|
"type": "module",
|
|
16
|
-
"bin": "./dist/
|
|
17
|
-
"module": "./dist/
|
|
18
|
-
"types": "./dist/
|
|
15
|
+
"bin": "./dist/bin.js",
|
|
16
|
+
"module": "./dist/index.js",
|
|
17
|
+
"types": "./dist/index.d.ts",
|
|
18
|
+
"exports": {
|
|
19
|
+
".": {
|
|
20
|
+
"import": "./dist/index.js",
|
|
21
|
+
"types": "./dist/index.d.ts"
|
|
22
|
+
},
|
|
23
|
+
"./plugins/*": {
|
|
24
|
+
"import": "./dist/plugins/*.js",
|
|
25
|
+
"types": "./dist/plugins/*.d.ts"
|
|
26
|
+
}
|
|
27
|
+
},
|
|
19
28
|
"files": [
|
|
20
29
|
"template/*",
|
|
21
30
|
"dist/*"
|
|
22
31
|
],
|
|
23
32
|
"dependencies": {
|
|
24
33
|
"@clack/prompts": "^0.11.0",
|
|
34
|
+
"@commander-js/extra-typings": "^14.0.0",
|
|
25
35
|
"commander": "^14.0.1",
|
|
26
36
|
"picocolors": "^1.1.1",
|
|
27
37
|
"tinyexec": "^1.0.1",
|
package/dist/chunk-3VWJJEHU.js
DELETED
|
@@ -1,204 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
copy,
|
|
3
|
-
cwd,
|
|
4
|
-
depVersions,
|
|
5
|
-
pick,
|
|
6
|
-
sourceDir,
|
|
7
|
-
templates,
|
|
8
|
-
tryGitInit
|
|
9
|
-
} from "./chunk-HLHY7KAF.js";
|
|
10
|
-
|
|
11
|
-
// src/create-app.ts
|
|
12
|
-
import path from "path";
|
|
13
|
-
import fs from "fs/promises";
|
|
14
|
-
|
|
15
|
-
// src/auto-install.ts
|
|
16
|
-
import { x } from "tinyexec";
|
|
17
|
-
var managers = ["npm", "yarn", "bun", "pnpm"];
|
|
18
|
-
function getPackageManager() {
|
|
19
|
-
const userAgent = process.env.npm_config_user_agent ?? "";
|
|
20
|
-
if (userAgent.startsWith("yarn")) {
|
|
21
|
-
return "yarn";
|
|
22
|
-
}
|
|
23
|
-
if (userAgent.startsWith("pnpm")) {
|
|
24
|
-
return "pnpm";
|
|
25
|
-
}
|
|
26
|
-
if (userAgent.startsWith("bun")) {
|
|
27
|
-
return "bun";
|
|
28
|
-
}
|
|
29
|
-
return "npm";
|
|
30
|
-
}
|
|
31
|
-
async function autoInstall(manager, dest) {
|
|
32
|
-
await x(manager, ["install"], {
|
|
33
|
-
throwOnError: true,
|
|
34
|
-
nodeOptions: {
|
|
35
|
-
env: {
|
|
36
|
-
...process.env,
|
|
37
|
-
NODE_ENV: "development",
|
|
38
|
-
DISABLE_OPENCOLLECTIVE: "1"
|
|
39
|
-
},
|
|
40
|
-
cwd: dest
|
|
41
|
-
}
|
|
42
|
-
});
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
// src/create-app.ts
|
|
46
|
-
function defaults(options) {
|
|
47
|
-
return {
|
|
48
|
-
...options,
|
|
49
|
-
plugins: options.plugins ?? [],
|
|
50
|
-
useSrcDir: options.template.startsWith("+next") && options.useSrcDir === true,
|
|
51
|
-
lint: options.lint ?? false,
|
|
52
|
-
initializeGit: options.initializeGit ?? false,
|
|
53
|
-
installDeps: options.installDeps ?? false,
|
|
54
|
-
log: console.log
|
|
55
|
-
};
|
|
56
|
-
}
|
|
57
|
-
function isRelative(dir, file) {
|
|
58
|
-
return !path.relative(dir, file).startsWith(`..${path.sep}`);
|
|
59
|
-
}
|
|
60
|
-
async function create(createOptions) {
|
|
61
|
-
const options = defaults(createOptions);
|
|
62
|
-
const {
|
|
63
|
-
outputDir,
|
|
64
|
-
useSrcDir,
|
|
65
|
-
log,
|
|
66
|
-
installDeps,
|
|
67
|
-
template,
|
|
68
|
-
lint,
|
|
69
|
-
initializeGit,
|
|
70
|
-
packageManager,
|
|
71
|
-
plugins
|
|
72
|
-
} = options;
|
|
73
|
-
const projectName = path.basename(outputDir);
|
|
74
|
-
const dest = path.resolve(cwd, outputDir);
|
|
75
|
-
const isNext = options.template.startsWith("+next");
|
|
76
|
-
const pluginContext = {
|
|
77
|
-
template: templates.find((item) => item.value === template),
|
|
78
|
-
dest,
|
|
79
|
-
options
|
|
80
|
-
};
|
|
81
|
-
await copy(path.join(sourceDir, `template/${template}`), dest, {
|
|
82
|
-
rename(file) {
|
|
83
|
-
file = file.replace("example.gitignore", ".gitignore");
|
|
84
|
-
if (useSrcDir && (path.basename(file) === "mdx-components.tsx" || isRelative(path.join(dest, "app"), file) || isRelative(path.join(dest, "lib"), file))) {
|
|
85
|
-
return path.join(dest, "src", path.relative(dest, file));
|
|
86
|
-
}
|
|
87
|
-
return file;
|
|
88
|
-
}
|
|
89
|
-
});
|
|
90
|
-
if (isNext && lint) {
|
|
91
|
-
await copy(path.join(sourceDir, `template/+next+${lint}`), dest);
|
|
92
|
-
log("Configured Linter");
|
|
93
|
-
}
|
|
94
|
-
if (isNext && useSrcDir) {
|
|
95
|
-
const tsconfigPath = path.join(dest, "tsconfig.json");
|
|
96
|
-
const content = (await fs.readFile(tsconfigPath)).toString();
|
|
97
|
-
const config = JSON.parse(content);
|
|
98
|
-
if (config.compilerOptions?.paths) {
|
|
99
|
-
Object.assign(config.compilerOptions.paths, {
|
|
100
|
-
"@/*": ["./src/*"]
|
|
101
|
-
});
|
|
102
|
-
}
|
|
103
|
-
await fs.writeFile(tsconfigPath, JSON.stringify(config, null, 2));
|
|
104
|
-
}
|
|
105
|
-
let packageJson = await createPackageJson(projectName, dest, options);
|
|
106
|
-
for (const plugin of plugins) {
|
|
107
|
-
const result = await plugin.packageJson?.call(pluginContext, packageJson);
|
|
108
|
-
if (result) packageJson = result;
|
|
109
|
-
}
|
|
110
|
-
await fs.writeFile(
|
|
111
|
-
path.join(dest, "package.json"),
|
|
112
|
-
JSON.stringify(packageJson, null, 2)
|
|
113
|
-
);
|
|
114
|
-
let readme = await getReadme(dest, projectName);
|
|
115
|
-
for (const plugin of plugins) {
|
|
116
|
-
readme = await plugin.readme?.call(pluginContext, readme) ?? readme;
|
|
117
|
-
}
|
|
118
|
-
await fs.writeFile(path.join(dest, "README.md"), readme);
|
|
119
|
-
for (const plugin of plugins) {
|
|
120
|
-
await plugin.afterWrite?.call(pluginContext);
|
|
121
|
-
}
|
|
122
|
-
if (installDeps) {
|
|
123
|
-
try {
|
|
124
|
-
await autoInstall(packageManager, dest);
|
|
125
|
-
log("Installed dependencies");
|
|
126
|
-
} catch (err) {
|
|
127
|
-
log(`Failed to install dependencies: ${err}`);
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
if (initializeGit && await tryGitInit(dest)) {
|
|
131
|
-
log("Initialized Git repository");
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
async function getReadme(dest, projectName) {
|
|
135
|
-
const template = await fs.readFile(path.join(dest, "README.md")).then((res) => res.toString());
|
|
136
|
-
return `# ${projectName}
|
|
137
|
-
|
|
138
|
-
${template}`;
|
|
139
|
-
}
|
|
140
|
-
async function createPackageJson(projectName, dir, { template, lint }) {
|
|
141
|
-
const isNext = template.startsWith("+next");
|
|
142
|
-
function replaceWorkspaceDeps(deps) {
|
|
143
|
-
for (const k in deps) {
|
|
144
|
-
if (deps[k].startsWith("workspace:") && k in depVersions) {
|
|
145
|
-
deps[k] = depVersions[k];
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
return deps;
|
|
149
|
-
}
|
|
150
|
-
let packageJson = JSON.parse(
|
|
151
|
-
await fs.readFile(path.join(dir, "package.json")).then((res) => res.toString())
|
|
152
|
-
);
|
|
153
|
-
packageJson = {
|
|
154
|
-
name: projectName,
|
|
155
|
-
...packageJson,
|
|
156
|
-
dependencies: replaceWorkspaceDeps(packageJson.dependencies),
|
|
157
|
-
devDependencies: replaceWorkspaceDeps(packageJson.devDependencies)
|
|
158
|
-
};
|
|
159
|
-
if (isNext) {
|
|
160
|
-
packageJson = {
|
|
161
|
-
...packageJson,
|
|
162
|
-
scripts: {
|
|
163
|
-
...packageJson.scripts,
|
|
164
|
-
postinstall: "fumadocs-mdx"
|
|
165
|
-
}
|
|
166
|
-
};
|
|
167
|
-
}
|
|
168
|
-
if (isNext && lint === "biome") {
|
|
169
|
-
packageJson = {
|
|
170
|
-
...packageJson,
|
|
171
|
-
scripts: {
|
|
172
|
-
...packageJson.scripts,
|
|
173
|
-
lint: "biome check",
|
|
174
|
-
format: "biome format --write"
|
|
175
|
-
},
|
|
176
|
-
devDependencies: {
|
|
177
|
-
...packageJson.devDependencies,
|
|
178
|
-
...pick(depVersions, ["@biomejs/biome"])
|
|
179
|
-
}
|
|
180
|
-
};
|
|
181
|
-
}
|
|
182
|
-
if (isNext && lint === "eslint") {
|
|
183
|
-
packageJson = {
|
|
184
|
-
...packageJson,
|
|
185
|
-
scripts: {
|
|
186
|
-
...packageJson.scripts,
|
|
187
|
-
lint: "eslint"
|
|
188
|
-
},
|
|
189
|
-
devDependencies: {
|
|
190
|
-
...packageJson.devDependencies,
|
|
191
|
-
eslint: "^9",
|
|
192
|
-
"eslint-config-next": depVersions.next,
|
|
193
|
-
"@eslint/eslintrc": "^3"
|
|
194
|
-
}
|
|
195
|
-
};
|
|
196
|
-
}
|
|
197
|
-
return packageJson;
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
export {
|
|
201
|
-
managers,
|
|
202
|
-
getPackageManager,
|
|
203
|
-
create
|
|
204
|
-
};
|
package/dist/create-app.d.ts
DELETED
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
type PackageManager = (typeof managers)[number];
|
|
2
|
-
declare const managers: readonly ["npm", "yarn", "bun", "pnpm"];
|
|
3
|
-
|
|
4
|
-
interface TemplateInfo {
|
|
5
|
-
value: '+next+fuma-docs-mdx' | 'waku' | 'react-router' | 'react-router-spa' | 'tanstack-start';
|
|
6
|
-
label: string;
|
|
7
|
-
hint?: string;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
type Template = TemplateInfo['value'];
|
|
11
|
-
interface Options {
|
|
12
|
-
outputDir: string;
|
|
13
|
-
template: Template;
|
|
14
|
-
/**
|
|
15
|
-
* the package manager to use
|
|
16
|
-
*/
|
|
17
|
-
packageManager: PackageManager;
|
|
18
|
-
/**
|
|
19
|
-
* (Next.js only) Create files inside `src`
|
|
20
|
-
* @defaultValue false
|
|
21
|
-
*/
|
|
22
|
-
useSrcDir?: boolean;
|
|
23
|
-
/**
|
|
24
|
-
* (Next.js Only) Configure Lint
|
|
25
|
-
* @defaultValue false
|
|
26
|
-
*/
|
|
27
|
-
lint?: 'eslint' | 'biome' | false;
|
|
28
|
-
installDeps?: boolean;
|
|
29
|
-
initializeGit?: boolean;
|
|
30
|
-
log?: (message: string) => void;
|
|
31
|
-
plugins?: TemplatePlugin[];
|
|
32
|
-
}
|
|
33
|
-
interface TemplatePluginContext {
|
|
34
|
-
template: TemplateInfo;
|
|
35
|
-
options: Required<Options>;
|
|
36
|
-
/**
|
|
37
|
-
* output directory
|
|
38
|
-
*/
|
|
39
|
-
dest: string;
|
|
40
|
-
}
|
|
41
|
-
type Awaitable<T> = T | Promise<T>;
|
|
42
|
-
interface TemplatePlugin {
|
|
43
|
-
packageJson?: (this: TemplatePluginContext, packageJson: any) => Awaitable<void | any>;
|
|
44
|
-
afterWrite?: (this: TemplatePluginContext) => Awaitable<void>;
|
|
45
|
-
readme?: (this: TemplatePluginContext, content: string) => Awaitable<void | string>;
|
|
46
|
-
}
|
|
47
|
-
declare function create(createOptions: Options): Promise<void>;
|
|
48
|
-
|
|
49
|
-
export { type Options, type Template, type TemplatePlugin, type TemplatePluginContext, create };
|