mirror of
https://github.com/hanzo-docs/docs.git
synced 2026-10-24 20:09:02 +00:00
CLI: Support auto root layout transform for i18n
This commit is contained in:
@@ -7,7 +7,10 @@ import { File, Files, Folder } from 'fumadocs-ui/components/files';
|
||||
|
||||
## Overview
|
||||
|
||||
This is a shared convention for organizing your pages, followed by all file-system based content sources including Fumadocs MDX.
|
||||
This is a shared convention for organizing your documents.
|
||||
|
||||
Fumadocs MDX, and other file-system based content sources generate slugs and page trees depending on your file structure.
|
||||
It's very similar to Next.js App Router, but is specialized for documents.
|
||||
|
||||
## File
|
||||
|
||||
@@ -37,6 +40,15 @@ full: false
|
||||
## Learn More
|
||||
```
|
||||
|
||||
#### Slugs
|
||||
|
||||
The generated slugs will be same as your file path.
|
||||
|
||||
| path (relative to root folder) | slugs |
|
||||
| ------------------------------ | ----------------- |
|
||||
| `./dir/page.mdx` | `['dir', 'page']` |
|
||||
| `./dir/index.mdx` | `['dir']` |
|
||||
|
||||
## Folder
|
||||
|
||||
Organize multiple pages. When not specified, the display name will be generated from its folder name.
|
||||
@@ -192,9 +204,9 @@ You can add an icon too.
|
||||
|
||||
## Icons
|
||||
|
||||
Since Fumadocs doesn't include an icon library, you have to convert the icon names to JSX elements so that it can be rendered as a component.
|
||||
Since Fumadocs doesn't include an icon library, you have to convert the icon names to JSX elements in runtime, and render it as a component.
|
||||
|
||||
For built-in content sources like Fumadocs MDX, you can use [the `icon` handler](/docs/headless/source-api#icons) from Source API.
|
||||
You can use [the `icon` handler](/docs/headless/source-api#icons) from Source API.
|
||||
|
||||
## Multiple Page Trees
|
||||
|
||||
|
||||
@@ -241,9 +241,9 @@ If you find anything confusing, please give your feedback on [Github Discussion]
|
||||
description="Learn how to organize your pages"
|
||||
/>
|
||||
<Card
|
||||
href="/docs/ui/internationalization"
|
||||
title="Learn i18n"
|
||||
description="Learn how to implement i18n"
|
||||
href="/docs/ui/navigation"
|
||||
title="Navigation Links"
|
||||
description="Learn how to customise navigation links in Fumadocs"
|
||||
/>
|
||||
<Card
|
||||
href="/docs/ui/theme"
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
"dependencies": {
|
||||
"@clack/prompts": "^0.7.0",
|
||||
"commander": "^12.1.0",
|
||||
"cross-spawn": "^7.0.3",
|
||||
"execa": "^9.4.0",
|
||||
"package-manager-detector": "^0.2.0",
|
||||
"picocolors": "^1.1.0",
|
||||
"ts-morph": "^23.0.0"
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import path from 'node:path';
|
||||
import fs from 'node:fs/promises';
|
||||
import { sync as spawnSync } from 'cross-spawn';
|
||||
import { log, confirm, isCancel, outro, spinner, intro } from '@clack/prompts';
|
||||
import { type Project } from 'ts-morph';
|
||||
import picocolors from 'picocolors';
|
||||
import { execa } from 'execa';
|
||||
import { createEmptyProject } from '@/utils/typescript';
|
||||
import { getPackageManager } from '@/utils/get-package-manager';
|
||||
import { exists } from '@/utils/fs';
|
||||
@@ -15,6 +15,7 @@ import {
|
||||
} from '@/utils/transform-references';
|
||||
import { isSrc } from '@/utils/is-src';
|
||||
import { typescriptExtensions } from '@/constants';
|
||||
import { getDependencies } from '@/utils/add/get-dependencies';
|
||||
|
||||
interface Context {
|
||||
config: Config;
|
||||
@@ -24,9 +25,9 @@ interface Context {
|
||||
}
|
||||
|
||||
/**
|
||||
* A set of downloaded paths (without ext)
|
||||
* A set of downloaded files
|
||||
*/
|
||||
const downloadedFiles = new Map<string, FindResult>();
|
||||
const downloadedFiles = new Map<string, DownloadFileResult>();
|
||||
const excludedDeps = ['react', 'next', 'fumadocs-ui', 'fumadocs-core'];
|
||||
|
||||
export async function add(
|
||||
@@ -53,53 +54,60 @@ export async function add(
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
const deps = result.deps.filter((v) => !excludedDeps.includes(v));
|
||||
const value = await confirm({
|
||||
message: `This component requires extra dependencies (${deps.join(' ')}), do you want to install?`,
|
||||
});
|
||||
const installed = await getDependencies();
|
||||
const deps = Array.from(result.deps).filter(
|
||||
(v) => !installed.has(v) && !excludedDeps.includes(v),
|
||||
);
|
||||
|
||||
if (isCancel(value)) {
|
||||
outro(picocolors.bold(picocolors.greenBright('Component downloaded')));
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
if (value) {
|
||||
const spin = spinner();
|
||||
spin.start('Installing dependencies...');
|
||||
spawnSync(`${await getPackageManager()} install ${deps.join(' ')}`, {
|
||||
stdio: 'ignore',
|
||||
if (deps.length > 0) {
|
||||
const value = await confirm({
|
||||
message: `This component requires extra dependencies (${deps.join(' ')}), do you want to install?`,
|
||||
});
|
||||
spin.stop('Dependencies installed.');
|
||||
|
||||
if (isCancel(value)) {
|
||||
outro(picocolors.bold(picocolors.greenBright('Component downloaded')));
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
if (value) {
|
||||
const spin = spinner();
|
||||
spin.start('Installing dependencies...');
|
||||
await execa(await getPackageManager(), ['install', ...deps]);
|
||||
spin.stop('Dependencies installed.');
|
||||
}
|
||||
}
|
||||
|
||||
outro(picocolors.bold(picocolors.greenBright('Component installed')));
|
||||
}
|
||||
|
||||
interface DownloadFileResult {
|
||||
deps: string[];
|
||||
found: FindResult;
|
||||
deps: Set<string>;
|
||||
found: FetchResult;
|
||||
}
|
||||
|
||||
async function downloadFile(
|
||||
downloadPath: string,
|
||||
referencePath: string,
|
||||
outputPath: string,
|
||||
ctx: Context,
|
||||
): Promise<DownloadFileResult | undefined> {
|
||||
const rootUrl = `https://raw.githubusercontent.com/fuma-nama/fumadocs/${ctx.branch}/packages/ui`;
|
||||
const found = await find(rootUrl, downloadPath);
|
||||
const found = await fetchReference(rootUrl, referencePath);
|
||||
if (!found) return;
|
||||
|
||||
const cached = downloadedFiles.get(found.filePath);
|
||||
if (cached) return cached;
|
||||
|
||||
const outPath =
|
||||
path.extname(outputPath).length > 0
|
||||
? outputPath
|
||||
: `${outputPath}${path.extname(found.filePath)}`;
|
||||
log.step(`downloaded ${outPath}`);
|
||||
const [output, deps] = typescriptExtensions.includes(path.extname(outPath))
|
||||
? await transformTypeScript(outPath, found.code, ctx)
|
||||
: [found.code, []];
|
||||
const [output, deps] = typescriptExtensions.includes(
|
||||
path.extname(found.filePath),
|
||||
)
|
||||
? await transformTypeScript(found.filePath, found.code, outPath, ctx)
|
||||
: [found.code, new Set<string>()];
|
||||
|
||||
let canWrite = true;
|
||||
|
||||
if (await exists(outPath)) {
|
||||
const value = await confirm({
|
||||
message: `Do you want to override ${outPath}?`,
|
||||
@@ -116,12 +124,16 @@ async function downloadFile(
|
||||
if (canWrite) {
|
||||
await fs.mkdir(path.dirname(outPath), { recursive: true });
|
||||
await fs.writeFile(outPath, output);
|
||||
log.step(`downloaded ${outPath}`);
|
||||
}
|
||||
|
||||
return {
|
||||
const result: DownloadFileResult = {
|
||||
found,
|
||||
deps,
|
||||
};
|
||||
|
||||
downloadedFiles.set(found.filePath, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
const transformMap: Record<string, string> = {
|
||||
@@ -132,9 +144,10 @@ const transformMap: Record<string, string> = {
|
||||
async function transformTypeScript(
|
||||
filePath: string,
|
||||
code: string,
|
||||
outputPath: string,
|
||||
ctx: Context,
|
||||
): Promise<[code: string, deps: string[]]> {
|
||||
const deps: string[] = [];
|
||||
): Promise<[code: string, deps: Set<string>]> {
|
||||
const deps = new Set<string>();
|
||||
const file = ctx.project.createSourceFile(filePath, code, {
|
||||
overwrite: true,
|
||||
});
|
||||
@@ -147,12 +160,13 @@ async function transformTypeScript(
|
||||
},
|
||||
async (resolved, original) => {
|
||||
if (resolved.type === 'dep') {
|
||||
deps.push(resolved.name);
|
||||
deps.add(resolved.name);
|
||||
return;
|
||||
}
|
||||
|
||||
// image-zoom component requires CSS file
|
||||
if (original === '../../dist/image-zoom.css') {
|
||||
const cssOut = path.join(path.dirname(filePath), 'image-zoom.css');
|
||||
const cssOut = path.join(path.dirname(outputPath), 'image-zoom.css');
|
||||
await downloadFile('css/image-zoom.css', cssOut, ctx);
|
||||
|
||||
return './image-zoom.css';
|
||||
@@ -169,15 +183,15 @@ async function transformTypeScript(
|
||||
if (!downloaded)
|
||||
throw new Error(`Cannot download file: ${resolved.path}`);
|
||||
|
||||
deps.push(...downloaded.deps);
|
||||
return toReferencePath(filePath, downloadPath);
|
||||
downloaded.deps.forEach((dep) => deps.add(dep));
|
||||
return toReferencePath(outputPath, downloadPath);
|
||||
},
|
||||
);
|
||||
|
||||
return [file.getFullText(), deps];
|
||||
}
|
||||
|
||||
interface FindResult {
|
||||
interface FetchResult {
|
||||
filePath: string;
|
||||
code: string;
|
||||
}
|
||||
@@ -185,7 +199,7 @@ interface FindResult {
|
||||
async function fetchFile(
|
||||
rootUrl: string,
|
||||
filePath: string,
|
||||
): Promise<FindResult | undefined> {
|
||||
): Promise<FetchResult | undefined> {
|
||||
const res = await fetch(`${rootUrl}/${filePath}`);
|
||||
|
||||
if (res.ok)
|
||||
@@ -195,23 +209,25 @@ async function fetchFile(
|
||||
};
|
||||
}
|
||||
|
||||
async function find(
|
||||
const referenceMap = new Map<string, FetchResult>();
|
||||
|
||||
async function fetchReference(
|
||||
rootUrl: string,
|
||||
pathWithoutExt: string,
|
||||
): Promise<FindResult | undefined> {
|
||||
const cached = downloadedFiles.get(pathWithoutExt);
|
||||
referencePath: string,
|
||||
): Promise<FetchResult | undefined> {
|
||||
const cached = referenceMap.get(referencePath);
|
||||
if (cached) return cached;
|
||||
|
||||
if (path.extname(pathWithoutExt).length > 0) {
|
||||
return fetchFile(rootUrl, pathWithoutExt);
|
||||
if (path.extname(referencePath).length > 0) {
|
||||
return fetchFile(rootUrl, referencePath);
|
||||
}
|
||||
|
||||
// Only look for TypeScript extensions
|
||||
for (const ext of ['tsx', 'ts']) {
|
||||
const res = await fetchFile(rootUrl, `${pathWithoutExt}.${ext}`);
|
||||
const res = await fetchFile(rootUrl, `${referencePath}.${ext}`);
|
||||
if (!res) continue;
|
||||
|
||||
downloadedFiles.set(pathWithoutExt, res);
|
||||
referenceMap.set(referencePath, res);
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
note,
|
||||
} from '@clack/prompts';
|
||||
import picocolors from 'picocolors';
|
||||
import { sync } from 'cross-spawn';
|
||||
import { execa } from 'execa';
|
||||
import { getPackageManager } from '@/utils/get-package-manager';
|
||||
import { exists } from '@/utils/fs';
|
||||
import { isSrc } from '@/utils/is-src';
|
||||
@@ -51,6 +51,7 @@ export interface Plugin {
|
||||
>;
|
||||
|
||||
transform?: (ctx: PluginContext) => Awaitable<void>;
|
||||
transformRejected?: (ctx: PluginContext) => Awaitable<void>;
|
||||
}
|
||||
|
||||
export async function init(plugin: Plugin, config: Config = {}): Promise<void> {
|
||||
@@ -122,9 +123,7 @@ export async function init(plugin: Plugin, config: Config = {}): Promise<void> {
|
||||
if (value) {
|
||||
const spin = spinner();
|
||||
spin.start('Installing dependencies');
|
||||
sync(`${manager} install ${plugin.dependencies.join(' ')}`, {
|
||||
stdio: 'ignore',
|
||||
});
|
||||
await execa(manager, ['install', ...plugin.dependencies]);
|
||||
spin.stop('Successfully installed.');
|
||||
}
|
||||
}
|
||||
@@ -147,6 +146,8 @@ export async function init(plugin: Plugin, config: Config = {}): Promise<void> {
|
||||
prettier . --write`,
|
||||
picocolors.bold(picocolors.green('Changes Applied')),
|
||||
);
|
||||
} else {
|
||||
await plugin.transformRejected?.(ctx);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ import {
|
||||
treeToJavaScript,
|
||||
treeToMdx,
|
||||
} from '@/commands/file-tree';
|
||||
import { runTree } from '@/utils/run-tree';
|
||||
import { runTree } from '@/utils/file-tree/run-tree';
|
||||
import packageJson from '../package.json';
|
||||
|
||||
const program = new Command();
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
import path from 'node:path';
|
||||
import picocolors from 'picocolors';
|
||||
import { log } from '@clack/prompts';
|
||||
import { type Plugin } from '@/commands/init';
|
||||
import { generated } from '@/generated';
|
||||
import { transformLayoutConfig } from '@/utils/transform-layout-config';
|
||||
import { transformLayoutConfig } from '@/utils/i18n/transform-layout-config';
|
||||
import { resolveAppPath } from '@/utils/is-src';
|
||||
import { moveFiles } from '@/utils/move-files';
|
||||
import { isRelative } from '@/utils/fs';
|
||||
import { transformSourceI18n } from '@/utils/transform-source-i18n';
|
||||
import { transformSourceI18n } from '@/utils/i18n/transform-source-i18n';
|
||||
import { defaultConfig } from '@/config';
|
||||
import { createEmptyProject } from '@/utils/typescript';
|
||||
import { transformRootLayout } from '@/utils/i18n/transform-root-layout';
|
||||
|
||||
export const i18nPlugin: Plugin = {
|
||||
files: () => ({
|
||||
@@ -19,8 +21,7 @@ export const i18nPlugin: Plugin = {
|
||||
instructions: () => [
|
||||
{
|
||||
type: 'text',
|
||||
text: `Moved the ./app files to a [lang] route group.
|
||||
Make sure to update the params of page.tsx and route.ts (if necessary):`,
|
||||
text: 'Make sure to update the params of page.tsx and route.ts (if necessary):',
|
||||
},
|
||||
{
|
||||
type: 'code',
|
||||
@@ -57,6 +58,7 @@ const pages = source.getPage(params.lang);`,
|
||||
project,
|
||||
resolveAppPath('./app/layout.config.tsx', ctx.src),
|
||||
),
|
||||
transformRootLayout(project, resolveAppPath('./app/layout.tsx', ctx.src)),
|
||||
transformSourceI18n(
|
||||
project,
|
||||
path.join(
|
||||
@@ -79,5 +81,15 @@ const pages = source.getPage(params.lang);`,
|
||||
project,
|
||||
ctx.src,
|
||||
);
|
||||
|
||||
log.success(
|
||||
'Moved the ./app files to a [lang] route group, and modified your root layout to add `<I18nProvider />`.',
|
||||
);
|
||||
},
|
||||
transformRejected() {
|
||||
log.info(
|
||||
`Please create a [lang] route group and move all special files into the folder.
|
||||
See https://nextjs.org/docs/app/building-your-application/routing/internationalization for more info.`,
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
import fs from 'node:fs/promises';
|
||||
import { exists } from '@/utils/fs';
|
||||
|
||||
/**
|
||||
* Get dependencies from `package.json`
|
||||
*/
|
||||
export async function getDependencies(): Promise<Map<string, string>> {
|
||||
const dependencies = new Map<string, string>();
|
||||
|
||||
if (!(await exists('package.json'))) return dependencies;
|
||||
|
||||
const content = await fs.readFile('package.json');
|
||||
const parsed = JSON.parse(content.toString()) as object;
|
||||
|
||||
if ('dependencies' in parsed && typeof parsed.dependencies === 'object') {
|
||||
const records = parsed.dependencies as Record<string, string>;
|
||||
|
||||
Object.entries(records).forEach(([k, v]) => {
|
||||
dependencies.set(k, v);
|
||||
});
|
||||
}
|
||||
|
||||
if (
|
||||
'devDependencies' in parsed &&
|
||||
typeof parsed.devDependencies === 'object'
|
||||
) {
|
||||
const records = parsed.devDependencies as Record<string, string>;
|
||||
|
||||
Object.entries(records).forEach(([k, v]) => {
|
||||
dependencies.set(k, v);
|
||||
});
|
||||
}
|
||||
|
||||
return dependencies;
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
import { execa } from 'execa';
|
||||
import type { JsonTreeNode } from '@/commands/file-tree';
|
||||
|
||||
export async function runTree(args: string): Promise<JsonTreeNode[]> {
|
||||
const out = await execa('tree', [args, '--gitignore', '--prune', '-J']);
|
||||
|
||||
try {
|
||||
return JSON.parse(out.stdout) as JsonTreeNode[];
|
||||
} catch (e) {
|
||||
throw new Error('failed to run `tree` command', {
|
||||
cause: e,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
import fs from 'node:fs/promises';
|
||||
import { type Project, type SourceFile, StructureKind, ts } from 'ts-morph';
|
||||
import ScriptKind = ts.ScriptKind;
|
||||
import SyntaxKind = ts.SyntaxKind;
|
||||
|
||||
export async function transformRootLayout(
|
||||
project: Project,
|
||||
filePath: string,
|
||||
): Promise<void> {
|
||||
let content: string;
|
||||
|
||||
try {
|
||||
content = await fs.readFile(filePath).then((res) => res.toString());
|
||||
} catch (_) {
|
||||
return;
|
||||
}
|
||||
|
||||
const sourceFile = project.createSourceFile(filePath, content, {
|
||||
overwrite: true,
|
||||
scriptKind: ScriptKind.TSX,
|
||||
});
|
||||
|
||||
runTransform(sourceFile);
|
||||
|
||||
return sourceFile.save();
|
||||
}
|
||||
|
||||
export function runTransform(sourceFile: SourceFile): void {
|
||||
const rootProvider = sourceFile
|
||||
.getDescendantsOfKind(SyntaxKind.JsxElement)
|
||||
.find(
|
||||
(node) =>
|
||||
node.getOpeningElement().getTagNameNode().getFullText() ===
|
||||
'RootProvider',
|
||||
);
|
||||
|
||||
if (!rootProvider) return;
|
||||
|
||||
const parent = rootProvider.getParentIfKind(SyntaxKind.JsxElement);
|
||||
|
||||
if (parent) {
|
||||
const inner = parent
|
||||
.getJsxChildren()
|
||||
.map((v) => v.getFullText())
|
||||
.filter((v) => v.length > 0)
|
||||
.join('\n');
|
||||
|
||||
parent.setBodyText(
|
||||
`<I18nProvider locale={params.lang} locales={[
|
||||
{ locale: 'en', name: 'English' }
|
||||
]}>
|
||||
${inner.trim()}
|
||||
</I18nProvider>`,
|
||||
);
|
||||
|
||||
sourceFile.addImportDeclaration({
|
||||
kind: StructureKind.ImportDeclaration,
|
||||
moduleSpecifier: 'fumadocs-ui/i18n',
|
||||
namedImports: ['I18nProvider'],
|
||||
});
|
||||
}
|
||||
|
||||
// add types for `params`
|
||||
const func = sourceFile
|
||||
.getDescendantsOfKind(SyntaxKind.FunctionDeclaration)
|
||||
.find((v) => v.isDefaultExport());
|
||||
const param = func?.getParameters().at(0);
|
||||
param?.setType(`{ params: { lang: string }, children: ReactNode }`);
|
||||
param?.set({
|
||||
name: `{ params, children }`,
|
||||
});
|
||||
}
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
transformReferences,
|
||||
} from '@/utils/transform-references';
|
||||
|
||||
const transformExts = ['.js', '.ts', '.tsx', '.jsx'];
|
||||
const transformExtensions = ['.js', '.ts', '.tsx', '.jsx'];
|
||||
|
||||
/**
|
||||
* Move files from directory to another directory
|
||||
@@ -62,7 +62,7 @@ export async function moveFiles(
|
||||
const allowed = await filter(path.resolve(from));
|
||||
if (!allowed) return;
|
||||
|
||||
if (transformExts.includes(path.extname(from))) {
|
||||
if (transformExtensions.includes(path.extname(from))) {
|
||||
const content = await fs.readFile(from);
|
||||
const sourceFile = project.createSourceFile(from, content.toString(), {
|
||||
overwrite: true,
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
import { spawn } from 'cross-spawn';
|
||||
import type { JsonTreeNode } from '@/commands/file-tree';
|
||||
|
||||
export function runTree(args: string): Promise<JsonTreeNode[]> {
|
||||
return new Promise((res, reject) => {
|
||||
const child = spawn(`tree`, [args, '--gitignore', '--prune', '-J'], {
|
||||
cwd: process.cwd(),
|
||||
});
|
||||
|
||||
let out: string | undefined;
|
||||
|
||||
child.stdout.on('data', (d) => {
|
||||
out ??= '';
|
||||
out = out + String(d);
|
||||
});
|
||||
|
||||
child.stderr.on('data', (d) => {
|
||||
reject(new Error(String(d)));
|
||||
});
|
||||
|
||||
child.on('close', () => {
|
||||
if (!out) reject(new Error('failed to run `tree` command'));
|
||||
else res(JSON.parse(out) as JsonTreeNode[]);
|
||||
});
|
||||
|
||||
child.on('error', reject);
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
import './global.css';
|
||||
import { RootProvider } from 'fumadocs-ui/provider';
|
||||
import { Inter } from 'next/font/google';
|
||||
import type { ReactNode } from 'react';
|
||||
|
||||
const inter = Inter({
|
||||
subsets: ['latin'],
|
||||
});
|
||||
|
||||
export default function Layout({
|
||||
children,
|
||||
}: {
|
||||
children: ReactNode;
|
||||
}) {
|
||||
return (
|
||||
<html lang="en" className={inter.className} suppressHydrationWarning>
|
||||
<body>
|
||||
<RootProvider>{children}</RootProvider>
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import path from 'node:path';
|
||||
import fs from 'node:fs/promises';
|
||||
import { expect, test } from 'vitest';
|
||||
import { runTransform } from '@/utils/i18n/transform-root-layout';
|
||||
import { createEmptyProject } from '@/utils/typescript';
|
||||
|
||||
const project = createEmptyProject();
|
||||
|
||||
test('transform layout: i18n', async () => {
|
||||
const sourceFile = project.createSourceFile(
|
||||
'layout.tsx',
|
||||
await fs
|
||||
.readFile(path.join(__dirname, './fixture/layout'))
|
||||
.then((r) => r.toString()),
|
||||
);
|
||||
|
||||
runTransform(sourceFile);
|
||||
await expect(sourceFile.getFullText()).toMatchFileSnapshot(
|
||||
'./fixture/layout.out',
|
||||
);
|
||||
});
|
||||
@@ -0,0 +1,9 @@
|
||||
import { defineProject } from 'vitest/config';
|
||||
|
||||
export default defineProject({
|
||||
resolve: {
|
||||
alias: {
|
||||
'@/': new URL('./src/', import.meta.url).pathname,
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -10,7 +10,7 @@
|
||||
"noEmit": true,
|
||||
"esModuleInterop": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "node",
|
||||
"moduleResolution": "bundler",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"jsx": "preserve",
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import path from 'node:path';
|
||||
import { type EmitArgs, type FSWatcher, watch as watchFn } from 'chokidar';
|
||||
import { type FSWatcher, watch as watchFn } from 'chokidar';
|
||||
import { type EventName } from 'chokidar/handler.js';
|
||||
|
||||
export interface WatchOptions {
|
||||
files: string[];
|
||||
@@ -29,8 +30,7 @@ export function watch(options: WatchOptions): FSWatcher {
|
||||
console.error('Development Server failed to start', e);
|
||||
});
|
||||
|
||||
watcher.on('all', (...args: EmitArgs) => {
|
||||
const [event, relativePath] = args;
|
||||
watcher.on('all', (event: EventName, relativePath: string) => {
|
||||
const absolutePath = path.resolve(cwd, relativePath);
|
||||
|
||||
if (event === 'add') {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import path from 'node:path';
|
||||
import fs from 'node:fs';
|
||||
import { type EmitArgs } from 'chokidar';
|
||||
import { type EventName } from 'chokidar/handler.js';
|
||||
import { watcher } from '@/map/watcher';
|
||||
import { getConfigHash, loadConfigCached } from '@/config/cached';
|
||||
import { generateJS, generateTypes } from '@/map/generate';
|
||||
@@ -24,9 +24,7 @@ export async function start(
|
||||
console.log('[MDX] started dev server');
|
||||
});
|
||||
|
||||
instance.on('all', (...args: EmitArgs) => {
|
||||
const [event, file] = args;
|
||||
|
||||
instance.on('all', (event: EventName, file: string) => {
|
||||
const onUpdate = async (): Promise<void> => {
|
||||
const isConfigFile = path.resolve(file) === configPath;
|
||||
|
||||
|
||||
Generated
+155
-3
@@ -300,6 +300,52 @@ importers:
|
||||
specifier: ^5.6.2
|
||||
version: 5.6.2
|
||||
|
||||
examples/my-app:
|
||||
dependencies:
|
||||
fumadocs-core:
|
||||
specifier: workspace:*
|
||||
version: link:../../packages/core
|
||||
fumadocs-mdx:
|
||||
specifier: workspace:*
|
||||
version: link:../../packages/mdx
|
||||
fumadocs-ui:
|
||||
specifier: workspace:*
|
||||
version: link:../../packages/ui
|
||||
next:
|
||||
specifier: ^14.2.13
|
||||
version: 14.2.13(@babel/[email protected])([email protected]([email protected]))([email protected])
|
||||
react:
|
||||
specifier: ^18.3.1
|
||||
version: 18.3.1
|
||||
react-dom:
|
||||
specifier: ^18.3.1
|
||||
version: 18.3.1([email protected])
|
||||
devDependencies:
|
||||
'@types/mdx':
|
||||
specifier: ^2.0.13
|
||||
version: 2.0.13
|
||||
'@types/node':
|
||||
specifier: 22.7.0
|
||||
version: 22.7.0
|
||||
'@types/react':
|
||||
specifier: ^18.3.9
|
||||
version: 18.3.9
|
||||
'@types/react-dom':
|
||||
specifier: ^18.3.0
|
||||
version: 18.3.0
|
||||
autoprefixer:
|
||||
specifier: ^10.4.20
|
||||
version: 10.4.20([email protected])
|
||||
postcss:
|
||||
specifier: ^8.4.47
|
||||
version: 8.4.47
|
||||
tailwindcss:
|
||||
specifier: ^3.4.13
|
||||
version: 3.4.13
|
||||
typescript:
|
||||
specifier: ^5.6.2
|
||||
version: 5.6.2
|
||||
|
||||
examples/next-mdx:
|
||||
dependencies:
|
||||
fumadocs-core:
|
||||
@@ -428,9 +474,9 @@ importers:
|
||||
commander:
|
||||
specifier: ^12.1.0
|
||||
version: 12.1.0
|
||||
cross-spawn:
|
||||
specifier: ^7.0.3
|
||||
version: 7.0.3
|
||||
execa:
|
||||
specifier: ^9.4.0
|
||||
version: 9.4.0
|
||||
package-manager-detector:
|
||||
specifier: ^0.2.0
|
||||
version: 0.2.0
|
||||
@@ -2892,6 +2938,9 @@ packages:
|
||||
'@rushstack/[email protected]':
|
||||
resolution: {integrity: sha512-RbhOOTCNoCrbfkRyoXODZp75MlpiHMgbE5MEBZAnnnLyQNgrigEj4p0lzsMDyc1zVsJDLrivB58tgg3emX0eEA==}
|
||||
|
||||
'@sec-ant/[email protected]':
|
||||
resolution: {integrity: sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==}
|
||||
|
||||
'@shikijs/[email protected]':
|
||||
resolution: {integrity: sha512-VK4BNVCd2leY62Nm2JjyxtRLkyrZT/tv104O81eyaCjHq4Adceq2uJVFJJAIof6lT1mBwZrEo2qT/T+grv3MQQ==}
|
||||
|
||||
@@ -2920,6 +2969,10 @@ packages:
|
||||
resolution: {integrity: sha512-rUV5WyJrJLoloD4NDN1V1+LDMDWOa4OTsT4yYJwQNpTU6FWxkxHpL7eu4w+DmiH8x/EAM1otkPE1+LaspIbplw==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
'@sindresorhus/[email protected]':
|
||||
resolution: {integrity: sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
'@swc/[email protected]':
|
||||
resolution: {integrity: sha512-B2Bh2W+C7ALdGwDxRWAJ+UtNExfozvwyayGiNkbR3wmDKXXeQfhGM5MK+QYUWKu7UQ6ATq69OyZrxofDobKUug==}
|
||||
engines: {node: '>=10'}
|
||||
@@ -4380,6 +4433,10 @@ packages:
|
||||
resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
[email protected]:
|
||||
resolution: {integrity: sha512-yKHlle2YGxZE842MERVIplWwNH5VYmqqcPFgtnlU//K8gxuFFXu0pwd/CrfXTumFpeEiufsP7+opT/bPJa1yVw==}
|
||||
engines: {node: ^18.19.0 || >=20.5.0}
|
||||
|
||||
[email protected]:
|
||||
resolution: {integrity: sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
@@ -4425,6 +4482,10 @@ packages:
|
||||
picomatch:
|
||||
optional: true
|
||||
|
||||
[email protected]:
|
||||
resolution: {integrity: sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
[email protected]:
|
||||
resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==}
|
||||
engines: {node: ^10.12.0 || >=12.0.0}
|
||||
@@ -4549,6 +4610,10 @@ packages:
|
||||
resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
[email protected]:
|
||||
resolution: {integrity: sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
[email protected]:
|
||||
resolution: {integrity: sha512-g0QYk1dZBxGwk+Ngc+ltRH2IBp2f7zBkBMBJZCDerh6EhlhSR6+9irMCuT/09zD6qkarHUSn529sK/yL4S27mg==}
|
||||
engines: {node: '>= 0.4'}
|
||||
@@ -4773,6 +4838,10 @@ packages:
|
||||
resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==}
|
||||
engines: {node: '>=10.17.0'}
|
||||
|
||||
[email protected]:
|
||||
resolution: {integrity: sha512-/1/GPCpDUCCYwlERiYjxoczfP0zfvZMU/OWgQPMya9AbAE24vseigFdhAMObpc8Q4lc/kjutPfUddDYyAmejnA==}
|
||||
engines: {node: '>=18.18.0'}
|
||||
|
||||
[email protected]:
|
||||
resolution: {integrity: sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==}
|
||||
|
||||
@@ -4952,6 +5021,10 @@ packages:
|
||||
resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
[email protected]:
|
||||
resolution: {integrity: sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
[email protected]:
|
||||
resolution: {integrity: sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==}
|
||||
engines: {node: '>= 0.4'}
|
||||
@@ -5674,6 +5747,10 @@ packages:
|
||||
resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
[email protected]:
|
||||
resolution: {integrity: sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
[email protected]:
|
||||
resolution: {integrity: sha512-76YnmsbfrYp0tMsWxM0RNX0Vs+x8JxpJGu6B/jDn4lW8+laiTcKmKi9MeMh4UikO4RkJ1oqURoDy9bXJmMXS6A==}
|
||||
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
|
||||
@@ -5819,6 +5896,10 @@ packages:
|
||||
resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
[email protected]:
|
||||
resolution: {integrity: sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
[email protected]:
|
||||
resolution: {integrity: sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==}
|
||||
|
||||
@@ -5837,6 +5918,10 @@ packages:
|
||||
resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
[email protected]:
|
||||
resolution: {integrity: sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
[email protected]:
|
||||
resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==}
|
||||
|
||||
@@ -6036,6 +6121,10 @@ packages:
|
||||
resolution: {integrity: sha512-66hKPCr+72mlfiSjlEB1+45IjXSqvVAIy6mocupoww4tBFE9R9IhwwUGoI4G++Tc9Aq+2rxOt0RFU6gPcrte0A==}
|
||||
engines: {node: '>= 0.8'}
|
||||
|
||||
[email protected]:
|
||||
resolution: {integrity: sha512-o1piW0n3tgKIKCwk2vpM/vOV13zjJzvP37Ioze54YlTHE06m4tjEbzg9WsKkvTuyYln2DHjo5pY4qrZGI0otpw==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
[email protected]:
|
||||
resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==}
|
||||
|
||||
@@ -6553,6 +6642,10 @@ packages:
|
||||
resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
[email protected]:
|
||||
resolution: {integrity: sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
[email protected]:
|
||||
resolution: {integrity: sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==}
|
||||
engines: {node: '>=8'}
|
||||
@@ -6886,6 +6979,10 @@ packages:
|
||||
resolution: {integrity: sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
[email protected]:
|
||||
resolution: {integrity: sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
[email protected]:
|
||||
resolution: {integrity: sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==}
|
||||
|
||||
@@ -7197,6 +7294,10 @@ packages:
|
||||
resolution: {integrity: sha512-b4JR1PFR10y1mKjhHY9LaGo6tmrgjit7hxVIeAmyMw3jegXR4dhYqLaQF5zMXZxY7tLpMyJeLjr1C4rLmkVe8g==}
|
||||
engines: {node: '>=12.20'}
|
||||
|
||||
[email protected]:
|
||||
resolution: {integrity: sha512-GQHQqAopRhwU8Kt1DDM8NjibDXHC8eoh1erhGAJPEyveY9qqVeXvVikNKrDz69sHowPMorbPUrH/mx8c50eiBQ==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
[email protected]:
|
||||
resolution: {integrity: sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==}
|
||||
|
||||
@@ -8863,6 +8964,8 @@ snapshots:
|
||||
|
||||
'@rushstack/[email protected]': {}
|
||||
|
||||
'@sec-ant/[email protected]': {}
|
||||
|
||||
'@shikijs/[email protected]':
|
||||
dependencies:
|
||||
'@shikijs/engine-javascript': 1.18.0
|
||||
@@ -8914,6 +9017,8 @@ snapshots:
|
||||
|
||||
'@sindresorhus/[email protected]': {}
|
||||
|
||||
'@sindresorhus/[email protected]': {}
|
||||
|
||||
'@swc/[email protected]':
|
||||
optional: true
|
||||
|
||||
@@ -10716,6 +10821,21 @@ snapshots:
|
||||
signal-exit: 3.0.7
|
||||
strip-final-newline: 2.0.0
|
||||
|
||||
[email protected]:
|
||||
dependencies:
|
||||
'@sindresorhus/merge-streams': 4.0.0
|
||||
cross-spawn: 7.0.3
|
||||
figures: 6.1.0
|
||||
get-stream: 9.0.1
|
||||
human-signals: 8.0.0
|
||||
is-plain-obj: 4.1.0
|
||||
is-stream: 4.0.1
|
||||
npm-run-path: 6.0.0
|
||||
pretty-ms: 9.1.0
|
||||
signal-exit: 4.1.0
|
||||
strip-final-newline: 4.0.0
|
||||
yoctocolors: 2.1.1
|
||||
|
||||
[email protected]:
|
||||
dependencies:
|
||||
is-extendable: 0.1.1
|
||||
@@ -10761,6 +10881,10 @@ snapshots:
|
||||
optionalDependencies:
|
||||
picomatch: 4.0.2
|
||||
|
||||
[email protected]:
|
||||
dependencies:
|
||||
is-unicode-supported: 2.0.0
|
||||
|
||||
[email protected]:
|
||||
dependencies:
|
||||
flat-cache: 3.2.0
|
||||
@@ -10885,6 +11009,11 @@ snapshots:
|
||||
|
||||
[email protected]: {}
|
||||
|
||||
[email protected]:
|
||||
dependencies:
|
||||
'@sec-ant/readable-stream': 0.4.1
|
||||
is-stream: 4.0.1
|
||||
|
||||
[email protected]:
|
||||
dependencies:
|
||||
call-bind: 1.0.7
|
||||
@@ -11242,6 +11371,8 @@ snapshots:
|
||||
|
||||
[email protected]: {}
|
||||
|
||||
[email protected]: {}
|
||||
|
||||
[email protected]:
|
||||
dependencies:
|
||||
ms: 2.1.3
|
||||
@@ -11396,6 +11527,8 @@ snapshots:
|
||||
|
||||
[email protected]: {}
|
||||
|
||||
[email protected]: {}
|
||||
|
||||
[email protected]:
|
||||
dependencies:
|
||||
has-tostringtag: 1.0.2
|
||||
@@ -12350,6 +12483,11 @@ snapshots:
|
||||
dependencies:
|
||||
path-key: 3.1.1
|
||||
|
||||
[email protected]:
|
||||
dependencies:
|
||||
path-key: 4.0.0
|
||||
unicorn-magic: 0.3.0
|
||||
|
||||
[email protected]: {}
|
||||
|
||||
[email protected]: {}
|
||||
@@ -12528,6 +12666,8 @@ snapshots:
|
||||
json-parse-even-better-errors: 2.3.1
|
||||
lines-and-columns: 1.2.4
|
||||
|
||||
[email protected]: {}
|
||||
|
||||
[email protected]:
|
||||
dependencies:
|
||||
entities: 4.5.0
|
||||
@@ -12540,6 +12680,8 @@ snapshots:
|
||||
|
||||
[email protected]: {}
|
||||
|
||||
[email protected]: {}
|
||||
|
||||
[email protected]: {}
|
||||
|
||||
[email protected]:
|
||||
@@ -12704,6 +12846,10 @@ snapshots:
|
||||
|
||||
[email protected]: {}
|
||||
|
||||
[email protected]:
|
||||
dependencies:
|
||||
parse-ms: 4.0.0
|
||||
|
||||
[email protected]: {}
|
||||
|
||||
[email protected]:
|
||||
@@ -13342,6 +13488,8 @@ snapshots:
|
||||
|
||||
[email protected]: {}
|
||||
|
||||
[email protected]: {}
|
||||
|
||||
[email protected]:
|
||||
dependencies:
|
||||
min-indent: 1.0.1
|
||||
@@ -13701,6 +13849,8 @@ snapshots:
|
||||
|
||||
[email protected]: {}
|
||||
|
||||
[email protected]: {}
|
||||
|
||||
[email protected]:
|
||||
dependencies:
|
||||
'@types/unist': 3.0.2
|
||||
@@ -14093,6 +14243,8 @@ snapshots:
|
||||
|
||||
[email protected]: {}
|
||||
|
||||
[email protected]: {}
|
||||
|
||||
[email protected]: {}
|
||||
|
||||
[email protected]: {}
|
||||
|
||||
Reference in New Issue
Block a user