mirror of
https://github.com/hanzo-docs/docs.git
synced 2026-10-26 10:55:57 +00:00
CLI: Use plugins for create() to configure linters & app dir
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'create-fumadocs-app': patch
|
||||
---
|
||||
|
||||
Use plugins for `create()` to configure linters & app dir
|
||||
@@ -16,6 +16,16 @@
|
||||
"bin": "./dist/index.js",
|
||||
"module": "./dist/create-app.js",
|
||||
"types": "./dist/create-app.d.ts",
|
||||
"exports": {
|
||||
".": {
|
||||
"import": "./dist/create-app.js",
|
||||
"types": "./dist/create-app.d.ts"
|
||||
},
|
||||
"./plugins/*": {
|
||||
"import": "./dist/plugins/*.js",
|
||||
"types": "./dist/plugins/*.d.ts"
|
||||
}
|
||||
},
|
||||
"files": [
|
||||
"template/*",
|
||||
"dist/*"
|
||||
|
||||
@@ -3,7 +3,6 @@ import { versions as localVersions } from '@/versions';
|
||||
import versionPkg from '../../create-app-versions/package.json';
|
||||
|
||||
export const sourceDir = fileURLToPath(new URL(`../`, import.meta.url).href);
|
||||
export const cwd = process.cwd();
|
||||
|
||||
export interface TemplateInfo {
|
||||
value:
|
||||
@@ -13,7 +12,13 @@ export interface TemplateInfo {
|
||||
| 'react-router-spa'
|
||||
| 'tanstack-start';
|
||||
label: string;
|
||||
appDir: string;
|
||||
rootProviderPath: string;
|
||||
hint?: string;
|
||||
/**
|
||||
* rename files when copying from template
|
||||
*/
|
||||
rename?: (name: string) => string;
|
||||
}
|
||||
|
||||
export const templates: TemplateInfo[] = [
|
||||
@@ -21,23 +26,33 @@ export const templates: TemplateInfo[] = [
|
||||
value: '+next+fuma-docs-mdx',
|
||||
label: 'Next.js: Fumadocs MDX',
|
||||
hint: 'recommended',
|
||||
appDir: '',
|
||||
rootProviderPath: 'app/layout.tsx',
|
||||
},
|
||||
{
|
||||
value: 'waku',
|
||||
label: 'Waku: Fumadocs MDX',
|
||||
appDir: 'src',
|
||||
rootProviderPath: 'src/components/provider.tsx',
|
||||
},
|
||||
{
|
||||
value: 'react-router',
|
||||
label: 'React Router: Fumadocs MDX (not RSC)',
|
||||
appDir: 'app',
|
||||
rootProviderPath: 'app/root.tsx',
|
||||
},
|
||||
{
|
||||
value: 'react-router-spa',
|
||||
label: 'React Router SPA: Fumadocs MDX (not RSC)',
|
||||
hint: 'SPA mode allows you to host the site statically, compatible with a CDN.',
|
||||
appDir: 'app',
|
||||
rootProviderPath: 'app/root.tsx',
|
||||
},
|
||||
{
|
||||
value: 'tanstack-start',
|
||||
label: 'Tanstack Start: Fumadocs MDX (not RSC)',
|
||||
appDir: 'src',
|
||||
rootProviderPath: 'src/routes/__root.tsx',
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import path from 'node:path';
|
||||
import fs from 'node:fs/promises';
|
||||
import { copy, pick, tryGitInit } from '@/utils';
|
||||
import { copy, tryGitInit } from '@/utils';
|
||||
import type { PackageManager } from './auto-install';
|
||||
import { autoInstall } from './auto-install';
|
||||
import {
|
||||
cwd,
|
||||
depVersions,
|
||||
sourceDir,
|
||||
type TemplateInfo,
|
||||
@@ -18,20 +17,10 @@ export interface Options {
|
||||
|
||||
/**
|
||||
* the package manager to use
|
||||
*
|
||||
* @defaultValue 'npm'
|
||||
*/
|
||||
packageManager: PackageManager;
|
||||
|
||||
/**
|
||||
* (Next.js only) Create files inside `src`
|
||||
* @defaultValue false
|
||||
*/
|
||||
useSrcDir?: boolean;
|
||||
|
||||
/**
|
||||
* (Next.js Only) Configure Lint
|
||||
* @defaultValue false
|
||||
*/
|
||||
lint?: 'eslint' | 'biome' | false;
|
||||
packageManager?: PackageManager;
|
||||
|
||||
installDeps?: boolean;
|
||||
initializeGit?: boolean;
|
||||
@@ -41,21 +30,38 @@ export interface Options {
|
||||
|
||||
export interface TemplatePluginContext {
|
||||
template: TemplateInfo;
|
||||
|
||||
options: Required<Options>;
|
||||
log: (message: string) => void;
|
||||
/**
|
||||
* output directory
|
||||
*/
|
||||
dest: string;
|
||||
|
||||
/**
|
||||
* output directory for app code (e.g. under `/src`)
|
||||
*/
|
||||
appDir: string;
|
||||
}
|
||||
|
||||
export type PackageJsonType = {
|
||||
name?: string;
|
||||
version?: string;
|
||||
private?: boolean;
|
||||
scripts?: Record<string, string>;
|
||||
dependencies?: Record<string, string>;
|
||||
devDependencies?: Record<string, string>;
|
||||
} & Record<string, unknown>;
|
||||
|
||||
type Awaitable<T> = T | Promise<T>;
|
||||
|
||||
export interface TemplatePlugin {
|
||||
template?: (
|
||||
this: Pick<TemplatePluginContext, 'dest'>,
|
||||
info: TemplateInfo,
|
||||
) => Awaitable<void | TemplateInfo>;
|
||||
packageJson?: (
|
||||
this: TemplatePluginContext,
|
||||
packageJson: any,
|
||||
) => Awaitable<void | any>;
|
||||
packageJson: PackageJsonType,
|
||||
) => Awaitable<void | PackageJsonType>;
|
||||
afterWrite?: (this: TemplatePluginContext) => Awaitable<void>;
|
||||
readme?: (
|
||||
this: TemplatePluginContext,
|
||||
@@ -63,100 +69,56 @@ export interface TemplatePlugin {
|
||||
) => Awaitable<void | string>;
|
||||
}
|
||||
|
||||
function defaults(options: Options): Required<Options> {
|
||||
return {
|
||||
...options,
|
||||
plugins: options.plugins ?? [],
|
||||
useSrcDir:
|
||||
options.template.startsWith('+next') && options.useSrcDir === true,
|
||||
lint: options.lint ?? false,
|
||||
initializeGit: options.initializeGit ?? false,
|
||||
installDeps: options.installDeps ?? false,
|
||||
log: console.log,
|
||||
};
|
||||
}
|
||||
|
||||
function isRelative(dir: string, file: string) {
|
||||
return !path.relative(dir, file).startsWith(`..${path.sep}`);
|
||||
}
|
||||
|
||||
export async function create(createOptions: Options): Promise<void> {
|
||||
const options = defaults(createOptions);
|
||||
const {
|
||||
outputDir,
|
||||
useSrcDir,
|
||||
log,
|
||||
installDeps,
|
||||
template,
|
||||
lint,
|
||||
initializeGit,
|
||||
packageManager,
|
||||
plugins,
|
||||
} = options;
|
||||
plugins = [],
|
||||
packageManager = 'npm',
|
||||
initializeGit = false,
|
||||
installDeps = false,
|
||||
log = console.log,
|
||||
} = createOptions;
|
||||
|
||||
let template = templates.find(
|
||||
(item) => item.value === createOptions.template,
|
||||
)!;
|
||||
for (const plugin of plugins) {
|
||||
template =
|
||||
(await plugin.template?.call({ dest: outputDir }, template)) ?? template;
|
||||
}
|
||||
|
||||
const appDir = path.join(outputDir, template.appDir);
|
||||
const projectName = path.basename(outputDir);
|
||||
const dest = path.resolve(cwd, outputDir);
|
||||
const isNext = options.template.startsWith('+next');
|
||||
const pluginContext: TemplatePluginContext = {
|
||||
template: templates.find((item) => item.value === template)!,
|
||||
dest,
|
||||
options,
|
||||
template,
|
||||
dest: outputDir,
|
||||
log,
|
||||
appDir,
|
||||
};
|
||||
|
||||
await copy(path.join(sourceDir, `template/${template}`), dest, {
|
||||
await copy(path.join(sourceDir, 'template', template.value), outputDir, {
|
||||
rename(file) {
|
||||
file = file.replace('example.gitignore', '.gitignore');
|
||||
|
||||
if (
|
||||
useSrcDir &&
|
||||
(path.basename(file) === 'mdx-components.tsx' ||
|
||||
isRelative(path.join(dest, 'app'), file) ||
|
||||
isRelative(path.join(dest, 'lib'), file))
|
||||
) {
|
||||
return path.join(dest, 'src', path.relative(dest, file));
|
||||
}
|
||||
|
||||
return file;
|
||||
return template.rename?.(file) ?? file;
|
||||
},
|
||||
});
|
||||
|
||||
// optional ESLint configuration
|
||||
if (isNext && lint) {
|
||||
await copy(path.join(sourceDir, `template/+next+${lint}`), dest);
|
||||
log('Configured Linter');
|
||||
}
|
||||
|
||||
// update tsconfig.json for src dir
|
||||
if (isNext && useSrcDir) {
|
||||
const tsconfigPath = path.join(dest, 'tsconfig.json');
|
||||
const content = (await fs.readFile(tsconfigPath)).toString();
|
||||
const config = JSON.parse(content);
|
||||
|
||||
if (config.compilerOptions?.paths) {
|
||||
Object.assign(config.compilerOptions.paths, {
|
||||
'@/*': ['./src/*'],
|
||||
});
|
||||
}
|
||||
|
||||
await fs.writeFile(tsconfigPath, JSON.stringify(config, null, 2));
|
||||
}
|
||||
|
||||
let packageJson = await createPackageJson(projectName, dest, options);
|
||||
const packageJsonPath = path.join(outputDir, 'package.json');
|
||||
let packageJson = await initPackageJson(projectName, packageJsonPath);
|
||||
for (const plugin of plugins) {
|
||||
const result = await plugin.packageJson?.call(pluginContext, packageJson);
|
||||
|
||||
if (result) packageJson = result;
|
||||
packageJson =
|
||||
(await plugin.packageJson?.call(pluginContext, packageJson)) ??
|
||||
packageJson;
|
||||
}
|
||||
await fs.writeFile(
|
||||
path.join(dest, 'package.json'),
|
||||
JSON.stringify(packageJson, null, 2),
|
||||
);
|
||||
await fs.writeFile(packageJsonPath, JSON.stringify(packageJson, null, 2));
|
||||
|
||||
let readme = await getReadme(dest, projectName);
|
||||
const readmePath = path.join(outputDir, 'README.md');
|
||||
let readme = `# ${projectName}\n\n${await fs.readFile(readmePath)}`;
|
||||
for (const plugin of plugins) {
|
||||
readme = (await plugin.readme?.call(pluginContext, readme)) ?? readme;
|
||||
}
|
||||
await fs.writeFile(path.join(dest, 'README.md'), readme);
|
||||
await fs.writeFile(readmePath, readme);
|
||||
|
||||
for (const plugin of plugins) {
|
||||
await plugin.afterWrite?.call(pluginContext);
|
||||
@@ -164,33 +126,23 @@ export async function create(createOptions: Options): Promise<void> {
|
||||
|
||||
if (installDeps) {
|
||||
try {
|
||||
await autoInstall(packageManager, dest);
|
||||
await autoInstall(packageManager, outputDir);
|
||||
log('Installed dependencies');
|
||||
} catch (err) {
|
||||
log(`Failed to install dependencies: ${err}`);
|
||||
}
|
||||
}
|
||||
|
||||
if (initializeGit && (await tryGitInit(dest))) {
|
||||
if (initializeGit && (await tryGitInit(outputDir))) {
|
||||
log('Initialized Git repository');
|
||||
}
|
||||
}
|
||||
|
||||
async function getReadme(dest: string, projectName: string): Promise<string> {
|
||||
const template = await fs
|
||||
.readFile(path.join(dest, 'README.md'))
|
||||
.then((res) => res.toString());
|
||||
|
||||
return `# ${projectName}\n\n${template}`;
|
||||
}
|
||||
|
||||
async function createPackageJson(
|
||||
async function initPackageJson(
|
||||
projectName: string,
|
||||
dir: string,
|
||||
{ template, lint }: Required<Options>,
|
||||
): Promise<object> {
|
||||
const isNext = template.startsWith('+next');
|
||||
function replaceWorkspaceDeps(deps: Record<string, string>) {
|
||||
packageJsonPath: string,
|
||||
): Promise<PackageJsonType> {
|
||||
function replaceWorkspaceDeps(deps: Record<string, string> = {}) {
|
||||
for (const k in deps) {
|
||||
if (deps[k].startsWith('workspace:') && k in depVersions) {
|
||||
deps[k] = depVersions[k as keyof typeof depVersions];
|
||||
@@ -200,59 +152,18 @@ async function createPackageJson(
|
||||
return deps;
|
||||
}
|
||||
|
||||
let packageJson = JSON.parse(
|
||||
await fs
|
||||
.readFile(path.join(dir, 'package.json'))
|
||||
.then((res) => res.toString()),
|
||||
const packageJson: PackageJsonType = JSON.parse(
|
||||
(await fs.readFile(packageJsonPath)).toString(),
|
||||
);
|
||||
|
||||
packageJson = {
|
||||
name: projectName,
|
||||
return {
|
||||
...packageJson,
|
||||
name: projectName,
|
||||
scripts: {
|
||||
...packageJson.scripts,
|
||||
postinstall: 'fumadocs-mdx',
|
||||
},
|
||||
dependencies: replaceWorkspaceDeps(packageJson.dependencies),
|
||||
devDependencies: replaceWorkspaceDeps(packageJson.devDependencies),
|
||||
};
|
||||
|
||||
if (isNext) {
|
||||
packageJson = {
|
||||
...packageJson,
|
||||
scripts: {
|
||||
...packageJson.scripts,
|
||||
postinstall: 'fumadocs-mdx',
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
if (isNext && lint === 'biome') {
|
||||
packageJson = {
|
||||
...packageJson,
|
||||
scripts: {
|
||||
...packageJson.scripts,
|
||||
lint: 'biome check',
|
||||
format: 'biome format --write',
|
||||
},
|
||||
devDependencies: {
|
||||
...packageJson.devDependencies,
|
||||
...pick(depVersions, ['@biomejs/biome']),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
if (isNext && lint === 'eslint') {
|
||||
packageJson = {
|
||||
...packageJson,
|
||||
scripts: {
|
||||
...packageJson.scripts,
|
||||
lint: 'eslint',
|
||||
},
|
||||
devDependencies: {
|
||||
...packageJson.devDependencies,
|
||||
eslint: '^9',
|
||||
'eslint-config-next': depVersions.next,
|
||||
'@eslint/eslintrc': '^3',
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
return packageJson;
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ import {
|
||||
type PackageManager,
|
||||
} from './auto-install';
|
||||
import { create, type Template, TemplatePlugin } from './create-app';
|
||||
import { cwd, templates } from './constants';
|
||||
import { templates } from './constants';
|
||||
import { program } from 'commander';
|
||||
|
||||
program.argument('[name]', 'the project name');
|
||||
@@ -172,9 +172,7 @@ async function main(config: Options): Promise<void> {
|
||||
);
|
||||
|
||||
const projectName = options.name.toLowerCase().replace(/\s/, '-');
|
||||
const dest = path.resolve(cwd, projectName);
|
||||
|
||||
const destDir = await fs.readdir(dest).catch(() => null);
|
||||
const destDir = await fs.readdir(projectName).catch(() => null);
|
||||
if (destDir && destDir.length > 0) {
|
||||
const del = await confirm({
|
||||
message: `directory ${projectName} already exists, do you want to delete its files?`,
|
||||
@@ -191,7 +189,7 @@ async function main(config: Options): Promise<void> {
|
||||
|
||||
await Promise.all(
|
||||
destDir.map((item) => {
|
||||
return fs.rm(path.join(dest, item), {
|
||||
return fs.rm(path.join(projectName, item), {
|
||||
recursive: true,
|
||||
force: true,
|
||||
});
|
||||
@@ -206,18 +204,31 @@ async function main(config: Options): Promise<void> {
|
||||
info.start(`Generating Project`);
|
||||
const plugins: TemplatePlugin[] = [];
|
||||
|
||||
if (options.src) {
|
||||
const { nextUseSrc } = await import('./plugins/next-use-src');
|
||||
plugins.push(nextUseSrc());
|
||||
}
|
||||
|
||||
if (options.search === 'orama-cloud') {
|
||||
const { oramaCloud } = await import('./plugins/orama-cloud');
|
||||
plugins.push(oramaCloud());
|
||||
}
|
||||
|
||||
if (options.lint === 'eslint') {
|
||||
const { eslint } = await import('./plugins/eslint');
|
||||
plugins.push(eslint());
|
||||
}
|
||||
|
||||
if (options.lint === 'biome') {
|
||||
const { biome } = await import('./plugins/biome');
|
||||
plugins.push(biome());
|
||||
}
|
||||
|
||||
await create({
|
||||
packageManager: manager,
|
||||
template: options.template,
|
||||
outputDir: dest,
|
||||
outputDir: projectName,
|
||||
installDeps: options.installDeps,
|
||||
lint: options.lint as LintOption,
|
||||
useSrcDir: options.src as SrcOption,
|
||||
initializeGit: config.git ?? true,
|
||||
plugins,
|
||||
log: (message) => {
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
import { TemplatePlugin } from '@/create-app';
|
||||
import { copy, pick } from '@/utils';
|
||||
import path from 'node:path';
|
||||
import { depVersions, sourceDir } from '@/constants';
|
||||
|
||||
export function biome(): TemplatePlugin {
|
||||
return {
|
||||
packageJson(packageJson) {
|
||||
return {
|
||||
...packageJson,
|
||||
scripts: {
|
||||
...packageJson.scripts,
|
||||
lint: 'biome check',
|
||||
format: 'biome format --write',
|
||||
},
|
||||
devDependencies: {
|
||||
...packageJson.devDependencies,
|
||||
...pick(depVersions, ['@biomejs/biome']),
|
||||
},
|
||||
};
|
||||
},
|
||||
async afterWrite() {
|
||||
await copy(path.join(sourceDir, `template/+next+biome`), this.dest);
|
||||
this.log('Configured Biome');
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
import { TemplatePlugin } from '@/create-app';
|
||||
import { copy } from '@/utils';
|
||||
import path from 'node:path';
|
||||
import { depVersions, sourceDir } from '@/constants';
|
||||
|
||||
export function eslint(): TemplatePlugin {
|
||||
return {
|
||||
packageJson(packageJson) {
|
||||
return {
|
||||
...packageJson,
|
||||
scripts: {
|
||||
...packageJson.scripts,
|
||||
lint: 'eslint',
|
||||
},
|
||||
devDependencies: {
|
||||
...packageJson.devDependencies,
|
||||
eslint: '^9',
|
||||
'eslint-config-next': depVersions.next,
|
||||
'@eslint/eslintrc': '^3',
|
||||
},
|
||||
};
|
||||
},
|
||||
async afterWrite() {
|
||||
await copy(path.join(sourceDir, `template/+next+eslint`), this.dest);
|
||||
this.log('Configured ESLint');
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
import { TemplatePlugin } from '@/create-app';
|
||||
import path from 'node:path';
|
||||
import fs from 'node:fs/promises';
|
||||
|
||||
/**
|
||||
* Use `src` for app directory
|
||||
*/
|
||||
export function nextUseSrc(): TemplatePlugin {
|
||||
return {
|
||||
template(info) {
|
||||
return {
|
||||
...info,
|
||||
appDir: 'src',
|
||||
rename: (file) => {
|
||||
if (
|
||||
path.basename(file) === 'mdx-components.tsx' ||
|
||||
isRelative(path.join(this.dest, 'app'), file) ||
|
||||
isRelative(path.join(this.dest, 'lib'), file)
|
||||
) {
|
||||
return path.join(this.dest, 'src', path.relative(this.dest, file));
|
||||
}
|
||||
|
||||
return file;
|
||||
},
|
||||
};
|
||||
},
|
||||
// update tsconfig.json for src dir
|
||||
async afterWrite() {
|
||||
const tsconfigPath = path.join(this.dest, 'tsconfig.json');
|
||||
const content = (await fs.readFile(tsconfigPath)).toString();
|
||||
const config = JSON.parse(content);
|
||||
|
||||
if (config.compilerOptions?.paths) {
|
||||
Object.assign(config.compilerOptions.paths, {
|
||||
'@/*': ['./src/*'],
|
||||
});
|
||||
}
|
||||
|
||||
await fs.writeFile(tsconfigPath, JSON.stringify(config, null, 2));
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function isRelative(dir: string, file: string) {
|
||||
return !path.relative(dir, file).startsWith(`..${path.sep}`);
|
||||
}
|
||||
@@ -7,8 +7,8 @@ import { createSourceFile } from '@/transform/shared';
|
||||
import { addTanstackPrerender } from '@/transform/tanstack-start';
|
||||
import {
|
||||
addReactRouterRoute,
|
||||
filterReactRouterPrerenderArray,
|
||||
filterReactRouterRoute,
|
||||
removeReactRouterPrerenderExclude,
|
||||
} from '@/transform/react-router';
|
||||
import { addSearchDialog } from '@/transform/provider';
|
||||
|
||||
@@ -19,7 +19,7 @@ export function oramaCloud(): TemplatePlugin {
|
||||
...packageJson,
|
||||
scripts: {
|
||||
...packageJson.scripts,
|
||||
build: `${packageJson.scripts.build} && bun scripts/sync-content.ts`,
|
||||
build: `${packageJson.scripts!.build} && bun scripts/sync-content.ts`,
|
||||
},
|
||||
dependencies: {
|
||||
...packageJson.dependencies,
|
||||
@@ -35,8 +35,7 @@ This project uses Orama Cloud for 3rd party search solution.
|
||||
See https://fumadocs.dev/docs/headless/search/orama-cloud for integrating Orama Cloud to Fumadocs.`;
|
||||
},
|
||||
async afterWrite() {
|
||||
const { dest, template, options } = this;
|
||||
const appDir = path.join(dest, options.useSrcDir ? 'src' : '.');
|
||||
const { dest, appDir, template } = this;
|
||||
await copy(path.join(sourceDir, 'template/+orama-cloud/@root'), dest);
|
||||
await copy(path.join(sourceDir, 'template/+orama-cloud/@app'), appDir);
|
||||
|
||||
@@ -48,18 +47,18 @@ See https://fumadocs.dev/docs/headless/search/orama-cloud for integrating Orama
|
||||
(file) => file.save(),
|
||||
),
|
||||
fs
|
||||
.unlink(path.join(dest, 'src/routes/api/search.ts'))
|
||||
.unlink(path.join(appDir, 'routes/api/search.ts'))
|
||||
.catch(() => null),
|
||||
writeFile(
|
||||
path.join(dest, 'src/routes/static[.]json.ts'),
|
||||
path.join(appDir, 'routes/static[.]json.ts'),
|
||||
route.tanstack,
|
||||
),
|
||||
updateRootProvider(path.join(dest, 'src/routes/__root.tsx')),
|
||||
updateRootProvider(path.join(appDir, 'routes/__root.tsx')),
|
||||
]);
|
||||
} else if (template.value.startsWith('react-router')) {
|
||||
await Promise.all([
|
||||
fluent(
|
||||
createSourceFile(path.join(dest, 'app/routes.ts')),
|
||||
createSourceFile(path.join(appDir, 'routes.ts')),
|
||||
(file) =>
|
||||
filterReactRouterRoute(file, ({ path }) => path !== 'api/search'),
|
||||
(file) =>
|
||||
@@ -73,15 +72,20 @@ See https://fumadocs.dev/docs/headless/search/orama-cloud for integrating Orama
|
||||
),
|
||||
fluent(
|
||||
createSourceFile(path.join(dest, 'react-router.config.ts')),
|
||||
(file) => removeReactRouterPrerenderExclude(file, ['/api/search']),
|
||||
(file) =>
|
||||
filterReactRouterPrerenderArray(
|
||||
file,
|
||||
'excluded',
|
||||
(v) => v !== '/api/search',
|
||||
),
|
||||
(file) => file.save(),
|
||||
),
|
||||
fs.unlink(path.join(dest, 'app/docs/search.ts')).catch(() => null),
|
||||
fs.unlink(path.join(appDir, 'docs/search.ts')).catch(() => null),
|
||||
writeFile(
|
||||
path.join(dest, 'app/routes/static.ts'),
|
||||
path.join(appDir, 'routes/static.ts'),
|
||||
route['react-router'],
|
||||
),
|
||||
updateRootProvider(path.join(dest, 'app/root.tsx')),
|
||||
updateRootProvider(path.join(appDir, 'root.tsx')),
|
||||
]);
|
||||
} else if (template.value.startsWith('+next')) {
|
||||
await Promise.all([
|
||||
@@ -93,14 +97,9 @@ See https://fumadocs.dev/docs/headless/search/orama-cloud for integrating Orama
|
||||
]);
|
||||
} else {
|
||||
await Promise.all([
|
||||
fs
|
||||
.unlink(path.join(dest, 'src/pages/api/search.ts'))
|
||||
.catch(() => null),
|
||||
writeFile(
|
||||
path.join(dest, 'src/pages/api/static.json.ts'),
|
||||
route.waku,
|
||||
),
|
||||
updateRootProvider(path.join(dest, 'src/components/provider.tsx')),
|
||||
fs.unlink(path.join(appDir, 'pages/api/search.ts')).catch(() => null),
|
||||
writeFile(path.join(appDir, 'pages/api/static.json.ts'), route.waku),
|
||||
updateRootProvider(path.join(appDir, 'components/provider.tsx')),
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
@@ -8,25 +8,24 @@ import { getCodeValue } from '@/transform/shared';
|
||||
import SyntaxKind = ts.SyntaxKind;
|
||||
|
||||
/**
|
||||
* remove items from `excluded` in the prerender function
|
||||
* filter items in a specific array initializer in the prerender function
|
||||
*/
|
||||
export function removeReactRouterPrerenderExclude(
|
||||
export function filterReactRouterPrerenderArray(
|
||||
sourceFile: SourceFile,
|
||||
paths: string[],
|
||||
array: 'paths' | 'excluded',
|
||||
filter: (item: string) => boolean,
|
||||
) {
|
||||
const methodBody = getPrerenderMethod(sourceFile)?.getBody();
|
||||
if (!methodBody) return;
|
||||
|
||||
const initializer = methodBody
|
||||
.getDescendantsOfKind(SyntaxKind.VariableDeclaration)
|
||||
.find((item) => item.getName() === 'excluded')
|
||||
.find((item) => item.getName() === array)
|
||||
?.getInitializerIfKind(SyntaxKind.ArrayLiteralExpression);
|
||||
|
||||
if (!initializer) return;
|
||||
for (const element of initializer.getElements()) {
|
||||
const value = getCodeValue(element.getText());
|
||||
|
||||
if (paths.includes(value)) {
|
||||
if (!filter(getCodeValue(element.getText()))) {
|
||||
initializer.removeElement(element);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,10 @@ export function addTanstackPrerender(sourceFile: SourceFile, paths: string[]) {
|
||||
.getProperty('pages')
|
||||
?.asKind(SyntaxKind.PropertyAssignment);
|
||||
|
||||
function toItem(path: string) {
|
||||
return `{ path: '${path}' }`;
|
||||
}
|
||||
|
||||
if (pagesProperty) {
|
||||
const initializer = pagesProperty.getInitializerIfKindOrThrow(
|
||||
SyntaxKind.ArrayLiteralExpression,
|
||||
@@ -48,10 +52,6 @@ export function addTanstackPrerender(sourceFile: SourceFile, paths: string[]) {
|
||||
}
|
||||
}
|
||||
|
||||
function toItem(path: string) {
|
||||
return `{ path: '${path}' }`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the tanstackStart call expression
|
||||
*/
|
||||
@@ -70,9 +70,8 @@ function getTanstackStartCall(
|
||||
for (const element of pluginsProperty.getElements()) {
|
||||
const expression = element.asKind(SyntaxKind.CallExpression);
|
||||
if (
|
||||
expression &&
|
||||
expression.getFirstChildByKind(SyntaxKind.Identifier)?.getText() ===
|
||||
'tanstackStart'
|
||||
expression?.getFirstChildByKind(SyntaxKind.Identifier)?.getText() ===
|
||||
'tanstackStart'
|
||||
) {
|
||||
return expression;
|
||||
}
|
||||
|
||||
@@ -5,8 +5,8 @@ import fs from 'node:fs/promises';
|
||||
import path from 'node:path';
|
||||
import {
|
||||
addReactRouterRoute,
|
||||
filterReactRouterPrerenderArray,
|
||||
filterReactRouterRoute,
|
||||
removeReactRouterPrerenderExclude,
|
||||
} from '@/transform/react-router';
|
||||
|
||||
const project = new Project({
|
||||
@@ -64,7 +64,11 @@ test('transform react router routes: filter routes', async () => {
|
||||
|
||||
test('transform react router config: remove exclude', async () => {
|
||||
const sourceFile = await createSourceFile('fixtures/react-router-config.txt');
|
||||
removeReactRouterPrerenderExclude(sourceFile, ['/api/search']);
|
||||
filterReactRouterPrerenderArray(
|
||||
sourceFile,
|
||||
'excluded',
|
||||
(v) => v !== '/api/search',
|
||||
);
|
||||
await expect(sourceFile.getFullText()).toMatchFileSnapshot(
|
||||
'fixtures/react-router-config(remove-exclude).output.txt',
|
||||
);
|
||||
|
||||
@@ -22,8 +22,8 @@ writeFileSync(
|
||||
console.log('Create-Fumadocs-App: versions.json updated');
|
||||
|
||||
export default defineConfig({
|
||||
entry: ['./src/index.ts', './src/create-app.ts'],
|
||||
entry: ['./src/index.ts', './src/create-app.ts', './src/plugins/*'],
|
||||
format: 'esm',
|
||||
target: 'node18',
|
||||
target: 'node20',
|
||||
dts: true,
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user