CLI: Support auto root layout transform for i18n

This commit is contained in:
fuma-nama
2024-09-26 23:50:17 +08:00
parent 5fd30c2878
commit bae14afefe
21 changed files with 437 additions and 101 deletions
@@ -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
+3 -3
View File
@@ -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"
+1 -1
View File
@@ -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"
+60 -44
View File
@@ -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;
}
}
+5 -4
View File
@@ -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);
}
}
+1 -1
View File
@@ -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();
+16 -4
View File
@@ -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 }`,
});
}
+2 -2
View File
@@ -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,
-28
View File
@@ -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);
});
}
+22
View File
@@ -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>
);
}
+21
View File
@@ -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',
);
});
+9
View File
@@ -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') {
+2 -4
View File
@@ -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;
+155 -3
View File
@@ -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]: {}