diff --git a/apps/website/docs-extractor.config.ts b/apps/website/docs-extractor.config.ts new file mode 100644 index 000000000..5d6938fa0 --- /dev/null +++ b/apps/website/docs-extractor.config.ts @@ -0,0 +1,33 @@ +import { defineConfig } from '@vapor-ui/ts-api-extractor'; + +export default defineConfig({ + global: { + outputDir: './public/components/generated', + languages: ['ko', 'en'], + defaultLanguage: 'ko', + filterExternal: true, + filterSprinkles: true, + filterHtml: true, + includeHtml: ['className', 'style'], + }, + + sprinkles: { + metaPath: '../../scripts/docs-extractor/generated/sprinkles-meta.json', + include: ['padding', 'paddingX', 'paddingY', 'margin', 'gap', 'color'], + }, + + components: { + 'box/box.tsx': { + sprinklesAll: true, + }, + 'flex/flex.tsx': { + sprinkles: ['gap', 'alignItems', 'justifyContent', 'flexDirection', 'alignContent'], + }, + 'v-stack/v-stack.tsx': { + sprinkles: ['gap', 'alignItems', 'justifyContent'], + }, + 'h-stack/h-stack.tsx': { + sprinkles: ['gap', 'alignItems', 'justifyContent'], + }, + }, +}); diff --git a/apps/website/package.json b/apps/website/package.json index c0b1da360..8c1367802 100644 --- a/apps/website/package.json +++ b/apps/website/package.json @@ -2,10 +2,12 @@ "name": "website", "version": "1.0.0-beta.6", "private": true, + "type": "module", "scripts": { "build": "next build", "clean": "rm -rf node_modules .next .turbo .source", "dev": "next dev --turbo", + "extract": "ts-api-extractor ../../packages/core/src/components", "format": "prettier --write \"./src/**/*.{ts,tsx,md}\"", "format:check": "prettier --check \"./src/**/*.{ts,tsx,md}\"", "postinstall": "fumadocs-mdx", @@ -59,6 +61,7 @@ "@types/node": "^22.15.4", "@types/react": "^18.3.27", "@types/react-dom": "^18.3.7", + "@vapor-ui/ts-api-extractor": "workspace:*", "eslint": "^9.20.1", "postcss": "^8.5.4", "shiki": "^3.7.0", diff --git a/apps/website/public/components/generated/ko/button.json b/apps/website/public/components/generated/ko/button.json new file mode 100644 index 000000000..01792349d --- /dev/null +++ b/apps/website/public/components/generated/ko/button.json @@ -0,0 +1,31 @@ +{ + "name": "Button", + "displayName": "Button", + "props": [ + { + "name": "colorPalette", + "type": ["primary", "secondary", "success", "warning", "danger", "contrast"], + "required": false, + "defaultValue": "primary" + }, + { + "name": "size", + "type": ["sm", "md", "lg", "xl"], + "required": false, + "defaultValue": "md" + }, + { + "name": "variant", + "type": ["outline", "fill", "ghost"], + "required": false, + "defaultValue": "fill" + }, + { + "name": "render", + "type": ["ReactElement", "(props: HTMLProps) => ReactElement"], + "required": false, + "description": "Allows you to replace the component’s HTML element\nwith a different tag, or compose it with another component.\n\nAccepts a `ReactElement` or a function that returns the element to render." + } + ], + "defaultElement": "button" +} diff --git a/apps/website/src/components/component-props-table/component-props-table.tsx b/apps/website/src/components/component-props-table/component-props-table.tsx index cf9799561..6d0ce5cc4 100644 --- a/apps/website/src/components/component-props-table/component-props-table.tsx +++ b/apps/website/src/components/component-props-table/component-props-table.tsx @@ -7,6 +7,9 @@ import { Badge, Flex, HStack, Text, VStack } from '@vapor-ui/core'; import { InfoPopover } from '~/components/info'; +// TODO: When i18n routing is implemented, derive language from URL path or context +const DEFAULT_LANGUAGE = 'ko'; + interface PropDefinition { name: string; type: string | string[]; @@ -36,7 +39,9 @@ export const ComponentPropsTable = ({ componentName }: ComponentPropsTableProps) React.useEffect(() => { const loadComponentData = async () => { try { - const response = await fetch(`/components/generated/${componentName}.json`); + const response = await fetch( + `/components/generated/${DEFAULT_LANGUAGE}/${componentName}.json`, + ); if (!response.ok) { throw new Error(`Failed to load component data for ${componentName}`); } diff --git a/apps/website/tsconfig.json b/apps/website/tsconfig.json index 569eb92de..4995c1b2e 100644 --- a/apps/website/tsconfig.json +++ b/apps/website/tsconfig.json @@ -26,6 +26,13 @@ } ] }, - "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts", "global.d.ts"], + "include": [ + "next-env.d.ts", + "**/*.ts", + "**/*.tsx", + ".next/types/**/*.ts", + "global.d.ts", + "docs-extractor.config.mjs" + ], "exclude": ["node_modules"] } diff --git a/package.json b/package.json index 3d0b9c8a3..0d7cc2e46 100644 --- a/package.json +++ b/package.json @@ -37,6 +37,7 @@ "test": "turbo run test", "test:regressions": "turbo run test:regressions", "test:update-snapshots": "turbo run test:regressions -- --update-snapshots", + "ts-api-extractor": "pnpm --filter @vapor-ui/ts-api-extractor", "typecheck": "turbo typecheck", "update-releases": "node scripts/update-releases.mjs", "version": "changeset version && pnpm version:changelog && pnpm install --lockfile-only", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c04e6706e..ac3020b07 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -76,7 +76,7 @@ importers: version: 0.25.11 eslint: specifier: ^9.33.0 - version: 9.36.0(jiti@2.6.0) + version: 9.36.0(jiti@2.6.1) typescript: specifier: ^5.9.2 version: 5.9.2 @@ -116,7 +116,7 @@ importers: version: link:../../packages/typescript-config '@tailwindcss/vite': specifier: ^4.1.12 - version: 4.1.13(vite@7.1.7(@types/node@22.18.6)(jiti@2.6.0)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5)) + version: 4.1.13(vite@7.1.7(@types/node@22.18.6)(jiti@2.6.1)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5)) '@types/culori': specifier: ^4.0.0 version: 4.0.1 @@ -128,13 +128,13 @@ importers: version: 18.3.7(@types/react@18.3.27) '@vitejs/plugin-react': specifier: ^5.0.0 - version: 5.0.3(vite@7.1.7(@types/node@22.18.6)(jiti@2.6.0)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5)) + version: 5.0.3(vite@7.1.7(@types/node@22.18.6)(jiti@2.6.1)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5)) concurrently: specifier: ^8.2.2 version: 8.2.2 eslint: specifier: ^9.33.0 - version: 9.36.0(jiti@2.6.0) + version: 9.36.0(jiti@2.6.1) tailwindcss: specifier: ^4.1.5 version: 4.1.13 @@ -143,10 +143,10 @@ importers: version: 5.9.2 vite: specifier: ^7.1.2 - version: 7.1.7(@types/node@22.18.6)(jiti@2.6.0)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5) + version: 7.1.7(@types/node@22.18.6)(jiti@2.6.1)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5) vite-plugin-singlefile: specifier: ^2.3.0 - version: 2.3.0(rollup@4.52.5)(vite@7.1.7(@types/node@22.18.6)(jiti@2.6.0)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5)) + version: 2.3.0(rollup@4.52.5)(vite@7.1.7(@types/node@22.18.6)(jiti@2.6.1)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5)) apps/storybook: dependencies: @@ -162,7 +162,7 @@ importers: version: 9.36.0 '@joshwooding/vite-plugin-react-docgen-typescript': specifier: ^0.5.0 - version: 0.5.0(typescript@5.8.3)(vite@7.1.7(@types/node@22.18.6)(jiti@2.6.0)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5)) + version: 0.5.0(typescript@5.8.3)(vite@7.1.7(@types/node@22.18.6)(jiti@2.6.1)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5)) '@repo/eslint-config': specifier: workspace:* version: link:../../packages/eslint-config @@ -171,13 +171,13 @@ importers: version: link:../../packages/typescript-config '@storybook/addon-docs': specifier: ^9.1.9 - version: 9.1.9(@types/react@19.2.10)(storybook@9.1.17(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@7.1.7(@types/node@22.18.6)(jiti@2.6.0)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5))) + version: 9.1.9(@types/react@19.2.10)(storybook@9.1.17(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@7.1.7(@types/node@22.18.6)(jiti@2.6.1)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5))) '@storybook/builder-vite': specifier: ^9.1.9 - version: 9.1.9(storybook@9.1.17(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@7.1.7(@types/node@22.18.6)(jiti@2.6.0)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5)))(vite@7.1.7(@types/node@22.18.6)(jiti@2.6.0)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5)) + version: 9.1.9(storybook@9.1.17(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@7.1.7(@types/node@22.18.6)(jiti@2.6.1)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5)))(vite@7.1.7(@types/node@22.18.6)(jiti@2.6.1)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5)) '@storybook/react-vite': specifier: ^9.1.9 - version: 9.1.9(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(rollup@4.52.5)(storybook@9.1.17(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@7.1.7(@types/node@22.18.6)(jiti@2.6.0)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5)))(typescript@5.8.3)(vite@7.1.7(@types/node@22.18.6)(jiti@2.6.0)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5)) + version: 9.1.9(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(rollup@4.52.5)(storybook@9.1.17(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@7.1.7(@types/node@22.18.6)(jiti@2.6.1)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5)))(typescript@5.8.3)(vite@7.1.7(@types/node@22.18.6)(jiti@2.6.1)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5)) '@types/react': specifier: ^19.2.9 version: 19.2.10 @@ -186,13 +186,13 @@ importers: version: 19.2.3(@types/react@19.2.10) '@vanilla-extract/vite-plugin': specifier: ^5.1.4 - version: 5.1.4(@types/node@22.18.6)(jiti@2.6.0)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5)(vite@7.1.7(@types/node@22.18.6)(jiti@2.6.0)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5)) + version: 5.1.4(@types/node@22.18.6)(jiti@2.6.1)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5)(vite@7.1.7(@types/node@22.18.6)(jiti@2.6.1)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5)) '@vitejs/plugin-react': specifier: ^5.0.3 - version: 5.0.3(vite@7.1.7(@types/node@22.18.6)(jiti@2.6.0)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5)) + version: 5.0.3(vite@7.1.7(@types/node@22.18.6)(jiti@2.6.1)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5)) eslint: specifier: ^9.36.0 - version: 9.36.0(jiti@2.6.0) + version: 9.36.0(jiti@2.6.1) globals: specifier: ^16.4.0 version: 16.4.0 @@ -201,16 +201,16 @@ importers: version: 3.6.2 storybook: specifier: ^9.1.17 - version: 9.1.17(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@7.1.7(@types/node@22.18.6)(jiti@2.6.0)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5)) + version: 9.1.17(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@7.1.7(@types/node@22.18.6)(jiti@2.6.1)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5)) typescript: specifier: ~5.8.3 version: 5.8.3 typescript-eslint: specifier: ^8.44.0 - version: 8.44.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.8.3) + version: 8.44.1(eslint@9.36.0(jiti@2.6.1))(typescript@5.8.3) vite: specifier: ^7.0.0 - version: 7.1.7(@types/node@22.18.6)(jiti@2.6.0)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5) + version: 7.1.7(@types/node@22.18.6)(jiti@2.6.1)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5) apps/website: dependencies: @@ -267,7 +267,7 @@ importers: version: 3.0.1(fumadocs-core@15.7.13(@oramacloud/client@2.1.4)(@types/react@18.3.27)(next@15.5.9(@babel/core@7.28.4)(@playwright/test@1.56.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.93.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)) fumadocs-mdx: specifier: ^11.6.3 - version: 11.10.1(@fumadocs/mdx-remote@1.4.0(@types/react@18.3.27)(fumadocs-core@15.7.13(@oramacloud/client@2.1.4)(@types/react@18.3.27)(next@15.5.9(@babel/core@7.28.4)(@playwright/test@1.56.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.93.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1))(fumadocs-core@15.7.13(@oramacloud/client@2.1.4)(@types/react@18.3.27)(next@15.5.9(@babel/core@7.28.4)(@playwright/test@1.56.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.93.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(next@15.5.9(@babel/core@7.28.4)(@playwright/test@1.56.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.93.1))(react@18.3.1)(vite@7.3.1(@types/node@22.18.6)(jiti@2.6.0)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5)) + version: 11.10.1(@fumadocs/mdx-remote@1.4.0(@types/react@18.3.27)(fumadocs-core@15.7.13(@oramacloud/client@2.1.4)(@types/react@18.3.27)(next@15.5.9(@babel/core@7.28.4)(@playwright/test@1.56.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.93.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1))(fumadocs-core@15.7.13(@oramacloud/client@2.1.4)(@types/react@18.3.27)(next@15.5.9(@babel/core@7.28.4)(@playwright/test@1.56.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.93.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(next@15.5.9(@babel/core@7.28.4)(@playwright/test@1.56.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.93.1))(react@18.3.1)(vite@7.3.1(@types/node@22.18.6)(jiti@2.6.1)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5)) fumadocs-typescript: specifier: ^4.0.7 version: 4.0.8(@types/react@18.3.27)(fumadocs-core@15.7.13(@oramacloud/client@2.1.4)(@types/react@18.3.27)(next@15.5.9(@babel/core@7.28.4)(@playwright/test@1.56.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.93.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(fumadocs-ui@15.7.13(@oramacloud/client@2.1.4)(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(next@15.5.9(@babel/core@7.28.4)(@playwright/test@1.56.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.93.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(tailwindcss@4.1.13))(typescript@5.9.2) @@ -344,9 +344,12 @@ importers: '@types/react-dom': specifier: ^18.3.7 version: 18.3.7(@types/react@18.3.27) + '@vapor-ui/ts-api-extractor': + specifier: workspace:* + version: link:../../scripts/docs-extractor eslint: specifier: ^9.20.1 - version: 9.36.0(jiti@2.6.0) + version: 9.36.0(jiti@2.6.1) postcss: specifier: ^8.5.4 version: 8.5.6 @@ -383,7 +386,7 @@ importers: devDependencies: '@prettier/sync': specifier: ^0.6.1 - version: 0.6.1(prettier@3.6.2) + version: 0.6.1(prettier@3.8.1) '@repo/eslint-config': specifier: workspace:* version: link:../eslint-config @@ -410,19 +413,19 @@ importers: version: link:../icons eslint: specifier: ^9.37.0 - version: 9.39.1(jiti@2.6.0) + version: 9.39.1(jiti@2.6.1) jest: specifier: ^30.2.0 - version: 30.2.0(@types/node@20.19.17)(esbuild-register@3.6.0(esbuild@0.25.11)) + version: 30.2.0(@types/node@20.19.17)(esbuild-register@3.6.0(esbuild@0.27.2)) rimraf: specifier: ^6.0.1 version: 6.0.1 ts-jest: specifier: ^29.1.0 - version: 29.4.5(@babel/core@7.28.4)(@jest/transform@30.2.0)(@jest/types@30.2.0)(babel-jest@30.2.0(@babel/core@7.28.4))(esbuild@0.25.11)(jest-util@30.2.0)(jest@30.2.0(@types/node@20.19.17)(esbuild-register@3.6.0(esbuild@0.25.11)))(typescript@5.9.3) + version: 29.4.5(@babel/core@7.28.4)(@jest/transform@30.2.0)(@jest/types@30.2.0)(babel-jest@30.2.0(@babel/core@7.28.4))(esbuild@0.27.2)(jest-util@30.2.0)(jest@30.2.0(@types/node@20.19.17)(esbuild-register@3.6.0(esbuild@0.27.2)))(typescript@5.9.3) tsup: specifier: ^8.5.0 - version: 8.5.0(jiti@2.6.0)(postcss@8.5.6)(tsx@4.20.5)(typescript@5.9.3) + version: 8.5.0(jiti@2.6.1)(postcss@8.5.6)(tsx@4.20.5)(typescript@5.9.3) packages/color-generator: dependencies: @@ -447,13 +450,13 @@ importers: version: 22.18.6 eslint: specifier: ^9.35.0 - version: 9.36.0(jiti@2.6.0) + version: 9.36.0(jiti@2.6.1) rimraf: specifier: ^6.0.1 version: 6.0.1 tsup: specifier: ^8.3.6 - version: 8.5.0(jiti@2.6.0)(postcss@8.5.6)(tsx@4.20.5)(typescript@5.9.2) + version: 8.5.0(jiti@2.6.1)(postcss@8.5.6)(tsx@4.20.5)(typescript@5.9.2) tsx: specifier: ^4.19.2 version: 4.20.5 @@ -462,7 +465,7 @@ importers: version: 5.9.2 vitest: specifier: ^3.2.4 - version: 3.2.4(@types/debug@4.1.12)(@types/node@22.18.6)(@vitest/browser@3.2.4)(happy-dom@20.4.0)(jiti@2.6.0)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5) + version: 3.2.4(@types/debug@4.1.12)(@types/node@22.18.6)(@vitest/browser@3.2.4)(happy-dom@20.4.0)(jiti@2.6.1)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5) packages/core: dependencies: @@ -520,7 +523,7 @@ importers: version: 16.0.3(rollup@4.52.5) '@storybook/react-vite': specifier: 9.1.9 - version: 9.1.9(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(rollup@4.52.5)(storybook@9.1.17(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@7.3.1(@types/node@22.18.6)(jiti@2.6.0)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5)))(typescript@5.9.2)(vite@7.3.1(@types/node@22.18.6)(jiti@2.6.0)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5)) + version: 9.1.9(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(rollup@4.52.5)(storybook@9.1.17(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@7.3.1(@types/node@22.18.6)(jiti@2.6.1)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5)))(typescript@5.9.2)(vite@7.3.1(@types/node@22.18.6)(jiti@2.6.1)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5)) '@testing-library/dom': specifier: ^10.4.1 version: 10.4.1 @@ -547,10 +550,10 @@ importers: version: 1.5.1(rollup@4.52.5) '@vanilla-extract/vite-plugin': specifier: ^5.1.4 - version: 5.1.4(@types/node@22.18.6)(jiti@2.6.0)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5)(vite@7.3.1(@types/node@22.18.6)(jiti@2.6.0)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5)) + version: 5.1.4(@types/node@22.18.6)(jiti@2.6.1)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5)(vite@7.3.1(@types/node@22.18.6)(jiti@2.6.1)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5)) '@vitest/browser': specifier: ^3.2.4 - version: 3.2.4(playwright@1.56.1)(vite@7.3.1(@types/node@22.18.6)(jiti@2.6.0)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5))(vitest@3.2.4) + version: 3.2.4(playwright@1.56.1)(vite@7.3.1(@types/node@22.18.6)(jiti@2.6.1)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5))(vitest@3.2.4) '@vitest/coverage-v8': specifier: ^3.2.4 version: 3.2.4(@vitest/browser@3.2.4)(vitest@3.2.4) @@ -559,7 +562,7 @@ importers: version: 10.4.21(postcss@8.5.6) eslint: specifier: ^9.1.0 - version: 9.36.0(jiti@2.6.0) + version: 9.36.0(jiti@2.6.1) glob: specifier: ^11.0.3 version: 11.0.3 @@ -589,7 +592,7 @@ importers: version: 6.2.3(rollup@4.52.5)(typescript@5.9.2) rollup-plugin-esbuild: specifier: ^6.2.1 - version: 6.2.1(esbuild@0.25.11)(rollup@4.52.5) + version: 6.2.1(esbuild@0.27.2)(rollup@4.52.5) rollup-plugin-node-externals: specifier: ^8.1.1 version: 8.1.1(rollup@4.52.5) @@ -601,7 +604,7 @@ importers: version: 5.9.2 vitest: specifier: ^3.2.4 - version: 3.2.4(@types/debug@4.1.12)(@types/node@22.18.6)(@vitest/browser@3.2.4)(happy-dom@20.4.0)(jiti@2.6.0)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5) + version: 3.2.4(@types/debug@4.1.12)(@types/node@22.18.6)(@vitest/browser@3.2.4)(happy-dom@20.4.0)(jiti@2.6.1)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5) vitest-axe: specifier: ^1.0.0-pre.5 version: 1.0.0-pre.5(vitest@3.2.4) @@ -623,13 +626,13 @@ importers: version: 22.18.6 eslint: specifier: ^9.35.0 - version: 9.36.0(jiti@2.6.0) + version: 9.36.0(jiti@2.6.1) rimraf: specifier: ^6.0.1 version: 6.0.1 tsup: specifier: ^8.3.6 - version: 8.5.0(jiti@2.6.0)(postcss@8.5.6)(tsx@4.20.5)(typescript@5.9.2) + version: 8.5.0(jiti@2.6.1)(postcss@8.5.6)(tsx@4.20.5)(typescript@5.9.2) tsx: specifier: ^4.19.2 version: 4.20.5 @@ -638,7 +641,7 @@ importers: version: 5.9.2 vitest: specifier: ^3.2.4 - version: 3.2.4(@types/debug@4.1.12)(@types/node@22.18.6)(@vitest/browser@3.2.4)(happy-dom@20.4.0)(jiti@2.6.0)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5) + version: 3.2.4(@types/debug@4.1.12)(@types/node@22.18.6)(@vitest/browser@3.2.4)(happy-dom@20.4.0)(jiti@2.6.1)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5) packages/eslint-config: dependencies: @@ -648,10 +651,10 @@ importers: devDependencies: '@antebudimir/eslint-plugin-vanilla-extract': specifier: ^1.11.0 - version: 1.11.0(eslint@9.36.0(jiti@2.6.0)) + version: 1.11.0(eslint@9.36.0(jiti@2.6.1)) '@eslint/compat': specifier: ^1.3.2 - version: 1.4.0(eslint@9.36.0(jiti@2.6.0)) + version: 1.4.0(eslint@9.36.0(jiti@2.6.1)) '@eslint/js': specifier: ^9.30.1 version: 9.36.0 @@ -663,40 +666,40 @@ importers: version: 6.10.0 eslint: specifier: ^9.30.1 - version: 9.36.0(jiti@2.6.0) + version: 9.36.0(jiti@2.6.1) eslint-config-next: specifier: ^15.3.5 - version: 15.5.3(eslint-plugin-import-x@4.16.1(@typescript-eslint/utils@8.44.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2))(eslint-import-resolver-node@0.3.9)(eslint@9.36.0(jiti@2.6.0)))(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2) + version: 15.5.3(eslint-plugin-import-x@4.16.1(@typescript-eslint/utils@8.44.1(eslint@9.36.0(jiti@2.6.1))(typescript@5.9.2))(eslint-import-resolver-node@0.3.9)(eslint@9.36.0(jiti@2.6.1)))(eslint@9.36.0(jiti@2.6.1))(typescript@5.9.2) eslint-config-turbo: specifier: ^2.5.5 - version: 2.5.6(eslint@9.36.0(jiti@2.6.0))(turbo@2.5.6) + version: 2.5.6(eslint@9.36.0(jiti@2.6.1))(turbo@2.5.6) eslint-plugin-import-x: specifier: ^4.16.1 - version: 4.16.1(@typescript-eslint/utils@8.44.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2))(eslint-import-resolver-node@0.3.9)(eslint@9.36.0(jiti@2.6.0)) + version: 4.16.1(@typescript-eslint/utils@8.44.1(eslint@9.36.0(jiti@2.6.1))(typescript@5.9.2))(eslint-import-resolver-node@0.3.9)(eslint@9.36.0(jiti@2.6.1)) eslint-plugin-jsx-a11y: specifier: ^6.10.2 - version: 6.10.2(eslint@9.36.0(jiti@2.6.0)) + version: 6.10.2(eslint@9.36.0(jiti@2.6.1)) eslint-plugin-react: specifier: ^7.37.5 - version: 7.37.5(eslint@9.36.0(jiti@2.6.0)) + version: 7.37.5(eslint@9.36.0(jiti@2.6.1)) eslint-plugin-react-hooks: specifier: ^5.2.0 - version: 5.2.0(eslint@9.36.0(jiti@2.6.0)) + version: 5.2.0(eslint@9.36.0(jiti@2.6.1)) eslint-plugin-storybook: specifier: ^9.1.9 - version: 9.1.13(eslint@9.36.0(jiti@2.6.0))(storybook@9.1.17(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@7.3.1(@types/node@22.18.6)(jiti@2.6.0)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5)))(typescript@5.9.2) + version: 9.1.13(eslint@9.36.0(jiti@2.6.1))(storybook@9.1.17(@testing-library/dom@10.4.1)(prettier@3.8.1)(vite@7.3.1(@types/node@22.18.6)(jiti@2.6.1)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5)))(typescript@5.9.2) globals: specifier: ^16.3.0 version: 16.4.0 typescript-eslint: specifier: ^8.35.1 - version: 8.44.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2) + version: 8.44.1(eslint@9.36.0(jiti@2.6.1))(typescript@5.9.2) packages/eslint-plugin-vapor: dependencies: eslint-plugin-jsx-a11y: specifier: '>=6.10.2' - version: 6.10.2(eslint@9.39.1(jiti@2.6.0)) + version: 6.10.2(eslint@9.39.1(jiti@2.6.1)) devDependencies: '@repo/eslint-config': specifier: workspace:* @@ -718,16 +721,16 @@ importers: version: 20.19.17 eslint: specifier: ^9.10.0 - version: 9.39.1(jiti@2.6.0) + version: 9.39.1(jiti@2.6.1) tsup: specifier: ^8.3.0 - version: 8.5.0(jiti@2.6.0)(postcss@8.5.6)(tsx@4.20.5)(typescript@5.9.3) + version: 8.5.0(jiti@2.6.1)(postcss@8.5.6)(tsx@4.20.5)(typescript@5.9.3) typescript: specifier: ^5.9.3 version: 5.9.3 vitest: specifier: ^3.2.4 - version: 3.2.4(@types/debug@4.1.12)(@types/node@20.19.17)(@vitest/browser@3.2.4)(happy-dom@20.4.0)(jiti@2.6.0)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5) + version: 3.2.4(@types/debug@4.1.12)(@types/node@20.19.17)(@vitest/browser@3.2.4)(happy-dom@20.4.0)(jiti@2.6.1)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5) packages/hooks: devDependencies: @@ -742,13 +745,13 @@ importers: version: 18.3.27 eslint: specifier: ^9.1.0 - version: 9.36.0(jiti@2.6.0) + version: 9.36.0(jiti@2.6.1) react: specifier: ^18.3.1 version: 18.3.1 tsup: specifier: ^8.4.0 - version: 8.5.0(jiti@2.6.0)(postcss@8.5.6)(tsx@4.20.5)(typescript@5.9.2) + version: 8.5.0(jiti@2.6.1)(postcss@8.5.6)(tsx@4.20.5)(typescript@5.9.2) typescript: specifier: ^5.9.2 version: 5.9.2 @@ -773,7 +776,7 @@ importers: version: 18.3.7(@types/react@18.3.27) eslint: specifier: ^9.1.0 - version: 9.36.0(jiti@2.6.0) + version: 9.36.0(jiti@2.6.1) react: specifier: ^18.3.1 version: 18.3.1 @@ -785,13 +788,62 @@ importers: version: 6.0.1 tsup: specifier: ^8.4.0 - version: 8.5.0(jiti@2.6.0)(postcss@8.5.6)(tsx@4.20.5)(typescript@5.9.2) + version: 8.5.0(jiti@2.6.1)(postcss@8.5.6)(tsx@4.20.5)(typescript@5.9.2) typescript: specifier: ^5.9.2 version: 5.9.2 packages/typescript-config: {} + scripts/docs-extractor: + dependencies: + '@inquirer/prompts': + specifier: ^7.9.0 + version: 7.10.1(@types/node@20.19.17) + glob: + specifier: ^10.3.0 + version: 10.4.5 + jiti: + specifier: ^2.6.1 + version: 2.6.1 + meow: + specifier: ^14.0.0 + version: 14.0.0 + ts-morph: + specifier: ^22.0.0 + version: 22.0.0 + zod: + specifier: ^3.22.0 + version: 3.25.76 + devDependencies: + '@repo/eslint-config': + specifier: workspace:* + version: link:../../packages/eslint-config + '@types/node': + specifier: ^20.10.0 + version: 20.19.17 + '@vitest/coverage-v8': + specifier: ^2.0.0 + version: 2.1.9(vitest@2.1.9(@types/node@20.19.17)(happy-dom@20.4.0)(lightningcss@1.30.1)(sass@1.93.1)) + eslint: + specifier: ^9.39.2 + version: 9.39.2(jiti@2.6.1) + prettier: + specifier: ^3.8.1 + version: 3.8.1 + tsup: + specifier: ^8.0.0 + version: 8.5.0(jiti@2.6.1)(postcss@8.5.6)(tsx@4.20.5)(typescript@5.9.2) + tsx: + specifier: ^4.0.0 + version: 4.20.5 + typescript: + specifier: ^5.3.0 + version: 5.9.2 + vitest: + specifier: ^2.0.0 + version: 2.1.9(@types/node@20.19.17)(happy-dom@20.4.0)(lightningcss@1.30.1)(sass@1.93.1) + packages: '@adobe/css-tools@4.4.4': @@ -1576,6 +1628,12 @@ packages: '@emotion/hash@0.9.2': resolution: {integrity: sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==} + '@esbuild/aix-ppc64@0.21.5': + resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [aix] + '@esbuild/aix-ppc64@0.25.11': resolution: {integrity: sha512-Xt1dOL13m8u0WE8iplx9Ibbm+hFAO0GsU2P34UNoDGvZYkY8ifSiy6Zuc1lYxfG7svWE2fzqCUmFp5HCn51gJg==} engines: {node: '>=18'} @@ -1588,6 +1646,12 @@ packages: cpu: [ppc64] os: [aix] + '@esbuild/android-arm64@0.21.5': + resolution: {integrity: sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==} + engines: {node: '>=12'} + cpu: [arm64] + os: [android] + '@esbuild/android-arm64@0.25.11': resolution: {integrity: sha512-9slpyFBc4FPPz48+f6jyiXOx/Y4v34TUeDDXJpZqAWQn/08lKGeD8aDp9TMn9jDz2CiEuHwfhRmGBvpnd/PWIQ==} engines: {node: '>=18'} @@ -1600,6 +1664,12 @@ packages: cpu: [arm64] os: [android] + '@esbuild/android-arm@0.21.5': + resolution: {integrity: sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==} + engines: {node: '>=12'} + cpu: [arm] + os: [android] + '@esbuild/android-arm@0.25.11': resolution: {integrity: sha512-uoa7dU+Dt3HYsethkJ1k6Z9YdcHjTrSb5NUy66ZfZaSV8hEYGD5ZHbEMXnqLFlbBflLsl89Zke7CAdDJ4JI+Gg==} engines: {node: '>=18'} @@ -1612,6 +1682,12 @@ packages: cpu: [arm] os: [android] + '@esbuild/android-x64@0.21.5': + resolution: {integrity: sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==} + engines: {node: '>=12'} + cpu: [x64] + os: [android] + '@esbuild/android-x64@0.25.11': resolution: {integrity: sha512-Sgiab4xBjPU1QoPEIqS3Xx+R2lezu0LKIEcYe6pftr56PqPygbB7+szVnzoShbx64MUupqoE0KyRlN7gezbl8g==} engines: {node: '>=18'} @@ -1624,6 +1700,12 @@ packages: cpu: [x64] os: [android] + '@esbuild/darwin-arm64@0.21.5': + resolution: {integrity: sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==} + engines: {node: '>=12'} + cpu: [arm64] + os: [darwin] + '@esbuild/darwin-arm64@0.25.11': resolution: {integrity: sha512-VekY0PBCukppoQrycFxUqkCojnTQhdec0vevUL/EDOCnXd9LKWqD/bHwMPzigIJXPhC59Vd1WFIL57SKs2mg4w==} engines: {node: '>=18'} @@ -1636,6 +1718,12 @@ packages: cpu: [arm64] os: [darwin] + '@esbuild/darwin-x64@0.21.5': + resolution: {integrity: sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==} + engines: {node: '>=12'} + cpu: [x64] + os: [darwin] + '@esbuild/darwin-x64@0.25.11': resolution: {integrity: sha512-+hfp3yfBalNEpTGp9loYgbknjR695HkqtY3d3/JjSRUyPg/xd6q+mQqIb5qdywnDxRZykIHs3axEqU6l1+oWEQ==} engines: {node: '>=18'} @@ -1648,6 +1736,12 @@ packages: cpu: [x64] os: [darwin] + '@esbuild/freebsd-arm64@0.21.5': + resolution: {integrity: sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==} + engines: {node: '>=12'} + cpu: [arm64] + os: [freebsd] + '@esbuild/freebsd-arm64@0.25.11': resolution: {integrity: sha512-CmKjrnayyTJF2eVuO//uSjl/K3KsMIeYeyN7FyDBjsR3lnSJHaXlVoAK8DZa7lXWChbuOk7NjAc7ygAwrnPBhA==} engines: {node: '>=18'} @@ -1660,6 +1754,12 @@ packages: cpu: [arm64] os: [freebsd] + '@esbuild/freebsd-x64@0.21.5': + resolution: {integrity: sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [freebsd] + '@esbuild/freebsd-x64@0.25.11': resolution: {integrity: sha512-Dyq+5oscTJvMaYPvW3x3FLpi2+gSZTCE/1ffdwuM6G1ARang/mb3jvjxs0mw6n3Lsw84ocfo9CrNMqc5lTfGOw==} engines: {node: '>=18'} @@ -1672,6 +1772,12 @@ packages: cpu: [x64] os: [freebsd] + '@esbuild/linux-arm64@0.21.5': + resolution: {integrity: sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==} + engines: {node: '>=12'} + cpu: [arm64] + os: [linux] + '@esbuild/linux-arm64@0.25.11': resolution: {integrity: sha512-Qr8AzcplUhGvdyUF08A1kHU3Vr2O88xxP0Tm8GcdVOUm25XYcMPp2YqSVHbLuXzYQMf9Bh/iKx7YPqECs6ffLA==} engines: {node: '>=18'} @@ -1684,6 +1790,12 @@ packages: cpu: [arm64] os: [linux] + '@esbuild/linux-arm@0.21.5': + resolution: {integrity: sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==} + engines: {node: '>=12'} + cpu: [arm] + os: [linux] + '@esbuild/linux-arm@0.25.11': resolution: {integrity: sha512-TBMv6B4kCfrGJ8cUPo7vd6NECZH/8hPpBHHlYI3qzoYFvWu2AdTvZNuU/7hsbKWqu/COU7NIK12dHAAqBLLXgw==} engines: {node: '>=18'} @@ -1696,6 +1808,12 @@ packages: cpu: [arm] os: [linux] + '@esbuild/linux-ia32@0.21.5': + resolution: {integrity: sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==} + engines: {node: '>=12'} + cpu: [ia32] + os: [linux] + '@esbuild/linux-ia32@0.25.11': resolution: {integrity: sha512-TmnJg8BMGPehs5JKrCLqyWTVAvielc615jbkOirATQvWWB1NMXY77oLMzsUjRLa0+ngecEmDGqt5jiDC6bfvOw==} engines: {node: '>=18'} @@ -1708,6 +1826,12 @@ packages: cpu: [ia32] os: [linux] + '@esbuild/linux-loong64@0.21.5': + resolution: {integrity: sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==} + engines: {node: '>=12'} + cpu: [loong64] + os: [linux] + '@esbuild/linux-loong64@0.25.11': resolution: {integrity: sha512-DIGXL2+gvDaXlaq8xruNXUJdT5tF+SBbJQKbWy/0J7OhU8gOHOzKmGIlfTTl6nHaCOoipxQbuJi7O++ldrxgMw==} engines: {node: '>=18'} @@ -1720,6 +1844,12 @@ packages: cpu: [loong64] os: [linux] + '@esbuild/linux-mips64el@0.21.5': + resolution: {integrity: sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==} + engines: {node: '>=12'} + cpu: [mips64el] + os: [linux] + '@esbuild/linux-mips64el@0.25.11': resolution: {integrity: sha512-Osx1nALUJu4pU43o9OyjSCXokFkFbyzjXb6VhGIJZQ5JZi8ylCQ9/LFagolPsHtgw6himDSyb5ETSfmp4rpiKQ==} engines: {node: '>=18'} @@ -1732,6 +1862,12 @@ packages: cpu: [mips64el] os: [linux] + '@esbuild/linux-ppc64@0.21.5': + resolution: {integrity: sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [linux] + '@esbuild/linux-ppc64@0.25.11': resolution: {integrity: sha512-nbLFgsQQEsBa8XSgSTSlrnBSrpoWh7ioFDUmwo158gIm5NNP+17IYmNWzaIzWmgCxq56vfr34xGkOcZ7jX6CPw==} engines: {node: '>=18'} @@ -1744,6 +1880,12 @@ packages: cpu: [ppc64] os: [linux] + '@esbuild/linux-riscv64@0.21.5': + resolution: {integrity: sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==} + engines: {node: '>=12'} + cpu: [riscv64] + os: [linux] + '@esbuild/linux-riscv64@0.25.11': resolution: {integrity: sha512-HfyAmqZi9uBAbgKYP1yGuI7tSREXwIb438q0nqvlpxAOs3XnZ8RsisRfmVsgV486NdjD7Mw2UrFSw51lzUk1ww==} engines: {node: '>=18'} @@ -1756,6 +1898,12 @@ packages: cpu: [riscv64] os: [linux] + '@esbuild/linux-s390x@0.21.5': + resolution: {integrity: sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==} + engines: {node: '>=12'} + cpu: [s390x] + os: [linux] + '@esbuild/linux-s390x@0.25.11': resolution: {integrity: sha512-HjLqVgSSYnVXRisyfmzsH6mXqyvj0SA7pG5g+9W7ESgwA70AXYNpfKBqh1KbTxmQVaYxpzA/SvlB9oclGPbApw==} engines: {node: '>=18'} @@ -1768,6 +1916,12 @@ packages: cpu: [s390x] os: [linux] + '@esbuild/linux-x64@0.21.5': + resolution: {integrity: sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [linux] + '@esbuild/linux-x64@0.25.11': resolution: {integrity: sha512-HSFAT4+WYjIhrHxKBwGmOOSpphjYkcswF449j6EjsjbinTZbp8PJtjsVK1XFJStdzXdy/jaddAep2FGY+wyFAQ==} engines: {node: '>=18'} @@ -1792,6 +1946,12 @@ packages: cpu: [arm64] os: [netbsd] + '@esbuild/netbsd-x64@0.21.5': + resolution: {integrity: sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==} + engines: {node: '>=12'} + cpu: [x64] + os: [netbsd] + '@esbuild/netbsd-x64@0.25.11': resolution: {integrity: sha512-u7tKA+qbzBydyj0vgpu+5h5AeudxOAGncb8N6C9Kh1N4n7wU1Xw1JDApsRjpShRpXRQlJLb9wY28ELpwdPcZ7A==} engines: {node: '>=18'} @@ -1816,6 +1976,12 @@ packages: cpu: [arm64] os: [openbsd] + '@esbuild/openbsd-x64@0.21.5': + resolution: {integrity: sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==} + engines: {node: '>=12'} + cpu: [x64] + os: [openbsd] + '@esbuild/openbsd-x64@0.25.11': resolution: {integrity: sha512-CN+7c++kkbrckTOz5hrehxWN7uIhFFlmS/hqziSFVWpAzpWrQoAG4chH+nN3Be+Kzv/uuo7zhX716x3Sn2Jduw==} engines: {node: '>=18'} @@ -1840,6 +2006,12 @@ packages: cpu: [arm64] os: [openharmony] + '@esbuild/sunos-x64@0.21.5': + resolution: {integrity: sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==} + engines: {node: '>=12'} + cpu: [x64] + os: [sunos] + '@esbuild/sunos-x64@0.25.11': resolution: {integrity: sha512-nq2xdYaWxyg9DcIyXkZhcYulC6pQ2FuCgem3LI92IwMgIZ69KHeY8T4Y88pcwoLIjbed8n36CyKoYRDygNSGhA==} engines: {node: '>=18'} @@ -1852,6 +2024,12 @@ packages: cpu: [x64] os: [sunos] + '@esbuild/win32-arm64@0.21.5': + resolution: {integrity: sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==} + engines: {node: '>=12'} + cpu: [arm64] + os: [win32] + '@esbuild/win32-arm64@0.25.11': resolution: {integrity: sha512-3XxECOWJq1qMZ3MN8srCJ/QfoLpL+VaxD/WfNRm1O3B4+AZ/BnLVgFbUV3eiRYDMXetciH16dwPbbHqwe1uU0Q==} engines: {node: '>=18'} @@ -1864,6 +2042,12 @@ packages: cpu: [arm64] os: [win32] + '@esbuild/win32-ia32@0.21.5': + resolution: {integrity: sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==} + engines: {node: '>=12'} + cpu: [ia32] + os: [win32] + '@esbuild/win32-ia32@0.25.11': resolution: {integrity: sha512-3ukss6gb9XZ8TlRyJlgLn17ecsK4NSQTmdIXRASVsiS2sQ6zPPZklNJT5GR5tE/MUarymmy8kCEf5xPCNCqVOA==} engines: {node: '>=18'} @@ -1876,6 +2060,12 @@ packages: cpu: [ia32] os: [win32] + '@esbuild/win32-x64@0.21.5': + resolution: {integrity: sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==} + engines: {node: '>=12'} + cpu: [x64] + os: [win32] + '@esbuild/win32-x64@0.25.11': resolution: {integrity: sha512-D7Hpz6A2L4hzsRpPaCYkQnGOotdUpDzSGRIv9I+1ITdHROSFUWW95ZPZWQmGka1Fg7W3zFJowyn9WGwMJ0+KPA==} engines: {node: '>=18'} @@ -1947,6 +2137,10 @@ packages: resolution: {integrity: sha512-S26Stp4zCy88tH94QbBv3XCuzRQiZ9yXofEILmglYTh/Ug/a9/umqvgFtYBAo3Lp0nsI/5/qH1CCrbdK3AP1Tw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@eslint/js@9.39.2': + resolution: {integrity: sha512-q1mjIoW1VX4IvSocvM/vbTiveKC4k9eLrajNEuSsmjymSDEbpGddtpfOoN7YGAqBK3NG+uqo8ia4PDTt8buCYA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@eslint/object-schema@2.1.6': resolution: {integrity: sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -3696,6 +3890,9 @@ packages: resolution: {integrity: sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==} engines: {node: '>=10.13.0'} + '@ts-morph/common@0.23.0': + resolution: {integrity: sha512-m7Lllj9n/S6sOkCkRftpM7L24uvmfXQFedlW/4hENcuJH1HHm9u5EgxZb9uVjQSCGrbBWBkOGgcTxNg36r6ywA==} + '@ts-morph/common@0.27.0': resolution: {integrity: sha512-Wf29UqxWDpc+i61k3oIOzcUfQt79PIT9y/MWfAGlrkjg6lBC1hwDECLXPVJAhWjiGbfBCxZd65F/LIZF3+jeJQ==} @@ -4089,6 +4286,15 @@ packages: webdriverio: optional: true + '@vitest/coverage-v8@2.1.9': + resolution: {integrity: sha512-Z2cOr0ksM00MpEfyVE8KXIYPEcBFxdbLSs56L8PO0QQMxt/6bDj45uQfxoc96v05KW3clk7vvgP0qfDit9DmfQ==} + peerDependencies: + '@vitest/browser': 2.1.9 + vitest: 2.1.9 + peerDependenciesMeta: + '@vitest/browser': + optional: true + '@vitest/coverage-v8@3.2.4': resolution: {integrity: sha512-EyF9SXU6kS5Ku/U82E259WSnvg6c8KTjppUncuNdm5QHpe17mwREHnjDzozC8x9MZ0xfBUFSaLkRv4TMA75ALQ==} peerDependencies: @@ -4098,9 +4304,23 @@ packages: '@vitest/browser': optional: true + '@vitest/expect@2.1.9': + resolution: {integrity: sha512-UJCIkTBenHeKT1TTlKMJWy1laZewsRIzYighyYiJKZreqtdxSos/S1t+ktRMQWu2CKqaarrkeszJx1cgC5tGZw==} + '@vitest/expect@3.2.4': resolution: {integrity: sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==} + '@vitest/mocker@2.1.9': + resolution: {integrity: sha512-tVL6uJgoUdi6icpxmdrn5YNo3g3Dxv+IHJBr0GXHaEdTcw3F+cPKnsXFhli6nO+f/6SDKPHEK1UN+k+TQv0Ehg==} + peerDependencies: + msw: ^2.4.9 + vite: ^5.0.0 + peerDependenciesMeta: + msw: + optional: true + vite: + optional: true + '@vitest/mocker@3.2.4': resolution: {integrity: sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ==} peerDependencies: @@ -4112,18 +4332,33 @@ packages: vite: optional: true + '@vitest/pretty-format@2.1.9': + resolution: {integrity: sha512-KhRIdGV2U9HOUzxfiHmY8IFHTdqtOhIzCpd8WRdJiE7D/HUcZVD0EgQCVjm+Q9gkUXWgBvMmTtZgIG48wq7sOQ==} + '@vitest/pretty-format@3.2.4': resolution: {integrity: sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==} + '@vitest/runner@2.1.9': + resolution: {integrity: sha512-ZXSSqTFIrzduD63btIfEyOmNcBmQvgOVsPNPe0jYtESiXkhd8u2erDLnMxmGrDCwHCCHE7hxwRDCT3pt0esT4g==} + '@vitest/runner@3.2.4': resolution: {integrity: sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ==} + '@vitest/snapshot@2.1.9': + resolution: {integrity: sha512-oBO82rEjsxLNJincVhLhaxxZdEtV0EFHMK5Kmx5sJ6H9L183dHECjiefOAdnqpIgT5eZwT04PoggUnW88vOBNQ==} + '@vitest/snapshot@3.2.4': resolution: {integrity: sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ==} + '@vitest/spy@2.1.9': + resolution: {integrity: sha512-E1B35FwzXXTs9FHNK6bDszs7mtydNi5MIfUWpceJ8Xbfb1gBMscAnwLbEu+B44ed6W3XjL9/ehLPHR1fkf1KLQ==} + '@vitest/spy@3.2.4': resolution: {integrity: sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==} + '@vitest/utils@2.1.9': + resolution: {integrity: sha512-v0psaMSkNJ3A2NMrUEHFRzJtDPFn+/VWZ5WxImB21T9fjucJRmS7xCS3ppEnARb9y11OAzaD+P2Ps+b+BGX5iQ==} + '@vitest/utils@3.2.4': resolution: {integrity: sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==} @@ -4442,9 +4677,6 @@ packages: character-reference-invalid@2.0.1: resolution: {integrity: sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==} - chardet@2.1.0: - resolution: {integrity: sha512-bNFETTG/pM5ryzQ9Ad0lJOTa6HWD/YsScAR3EnCPZRPlQh77JocYktSHOUHelyhm8IARL+o4c4F1bP5KVOjiRA==} - chardet@2.1.1: resolution: {integrity: sha512-PsezH1rqdV9VvyNhxxOW32/d75r01NY7TQCmOqomRo15ZSOKbpTFVsfjghxo6JloQUCGnH4k1LGu0R4yCLlWQQ==} @@ -4856,6 +5088,11 @@ packages: peerDependencies: esbuild: '>=0.12 <1' + esbuild@0.21.5: + resolution: {integrity: sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==} + engines: {node: '>=12'} + hasBin: true + esbuild@0.25.11: resolution: {integrity: sha512-KohQwyzrKTQmhXDW1PjCv3Tyspn9n5GcY2RTDqeORIdIJY8yKIF7sTSopFmn/wpMPW4rdPXI0UE5LJLuq3bx0Q==} engines: {node: '>=18'} @@ -5029,6 +5266,16 @@ packages: jiti: optional: true + eslint@9.39.2: + resolution: {integrity: sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + hasBin: true + peerDependencies: + jiti: '*' + peerDependenciesMeta: + jiti: + optional: true + espree@10.4.0: resolution: {integrity: sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -5916,6 +6163,10 @@ packages: resolution: {integrity: sha512-VXe6RjJkBPj0ohtqaO8vSWP3ZhAKo66fKrFNCll4BTcwljPLz03pCbaNKfzGP5MbrCYcbJ7v0nOYYwUzTEIdXQ==} hasBin: true + jiti@2.6.1: + resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==} + hasBin: true + joycon@3.1.1: resolution: {integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==} engines: {node: '>=10'} @@ -6386,6 +6637,11 @@ packages: resolution: {integrity: sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==} engines: {node: '>= 18'} + mkdirp@3.0.1: + resolution: {integrity: sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==} + engines: {node: '>=10'} + hasBin: true + mlly@1.8.0: resolution: {integrity: sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g==} @@ -6675,6 +6931,9 @@ packages: resolution: {integrity: sha512-Vj7sf++t5pBD637NSfkxpHSMfWaeig5+DKWLhcqIYx6mWQz5hdJTGDVMQiJcw1ZYkhs7AazKDGpRVji1LJCZUQ==} engines: {node: '>=18'} + pathe@1.1.2: + resolution: {integrity: sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==} + pathe@2.0.3: resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} @@ -6773,6 +7032,11 @@ packages: engines: {node: '>=14'} hasBin: true + prettier@3.8.1: + resolution: {integrity: sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==} + engines: {node: '>=14'} + hasBin: true + pretty-format@27.5.1: resolution: {integrity: sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==} engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} @@ -7419,6 +7683,7 @@ packages: tar@7.4.4: resolution: {integrity: sha512-O1z7ajPkjTgEgmTGz0v9X4eqeEXTDREPTO77pVC1Nbs86feBU1Zhdg+edzavPmYW1olxkwsqA2v4uOw6E8LeDg==} engines: {node: '>=18'} + deprecated: Old versions of tar are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exhorbitant rates) by contacting i@izs.me term-size@2.2.1: resolution: {integrity: sha512-wK0Ri4fOGjv/XPy8SBHZChl8CM7uMc5VML7SqiQ0zG7+J5Vr+RMQDoHa2CNT6KHUnTGIXH34UDMkPzAUyapBZg==} @@ -7459,10 +7724,18 @@ packages: resolution: {integrity: sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==} engines: {node: ^18.0.0 || >=20.0.0} + tinyrainbow@1.2.0: + resolution: {integrity: sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ==} + engines: {node: '>=14.0.0'} + tinyrainbow@2.0.0: resolution: {integrity: sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==} engines: {node: '>=14.0.0'} + tinyspy@3.0.2: + resolution: {integrity: sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==} + engines: {node: '>=14.0.0'} + tinyspy@4.0.4: resolution: {integrity: sha512-azl+t0z7pw/z958Gy9svOTuzqIk6xq+NSheJzn5MMWtWTFywIacg2wUlzKFGtt3cthx0r2SxMK0yzJOR0IES7Q==} engines: {node: '>=14.0.0'} @@ -7538,6 +7811,9 @@ packages: jest-util: optional: true + ts-morph@22.0.0: + resolution: {integrity: sha512-M9MqFGZREyeb5fTl6gNHKZLqBQA0TjA1lea+CR48R8EBTDuWrNqW6ccC5QvjNR4s6wDumD3LTCjOFSp9iwlzaw==} + ts-morph@26.0.0: resolution: {integrity: sha512-ztMO++owQnz8c/gIENcM9XfCEzgoGphTv+nKpYNM1bgsdOVC/jRZuEBf6N+mLLDNg68Kl+GgUZfOySaRiG1/Ug==} @@ -7790,6 +8066,11 @@ packages: vfile@6.0.3: resolution: {integrity: sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==} + vite-node@2.1.9: + resolution: {integrity: sha512-AM9aQ/IPrW/6ENLQg3AGY4K1N2TGZdR5e4gu/MmmR2xR3Ll1+dib+nook92g4TV3PXVyeyxdWwtaCAiUL0hMxA==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + vite-node@3.2.4: resolution: {integrity: sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==} engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} @@ -7802,6 +8083,37 @@ packages: rollup: ^4.44.1 vite: ^5.4.11 || ^6.0.0 || ^7.0.0 + vite@5.4.21: + resolution: {integrity: sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + peerDependencies: + '@types/node': ^18.0.0 || >=20.0.0 + less: '*' + lightningcss: ^1.21.0 + sass: '*' + sass-embedded: '*' + stylus: '*' + sugarss: '*' + terser: ^5.4.0 + peerDependenciesMeta: + '@types/node': + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + vite@7.1.7: resolution: {integrity: sha512-VbA8ScMvAISJNJVbRDTJdCwqQoAareR/wutevKanhR2/1EkoXVZVkkORaYm/tNVCjP/UDTKtcw3bAkwOUdedmA==} engines: {node: ^20.19.0 || >=22.12.0} @@ -7887,6 +8199,31 @@ packages: peerDependencies: vitest: '>=1' + vitest@2.1.9: + resolution: {integrity: sha512-MSmPM9REYqDGBI8439mA4mWhV5sKmDlBKWIYbA3lRb2PTHACE0mgKwA8yQ2xq9vxDTuk4iPrECBAEW2aoFXY0Q==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + peerDependencies: + '@edge-runtime/vm': '*' + '@types/node': ^18.0.0 || >=20.0.0 + '@vitest/browser': 2.1.9 + '@vitest/ui': 2.1.9 + happy-dom: '*' + jsdom: '*' + peerDependenciesMeta: + '@edge-runtime/vm': + optional: true + '@types/node': + optional: true + '@vitest/browser': + optional: true + '@vitest/ui': + optional: true + happy-dom: + optional: true + jsdom: + optional: true + vitest@3.2.4: resolution: {integrity: sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==} engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} @@ -8039,6 +8376,9 @@ packages: resolution: {integrity: sha512-U/PBtDf35ff0D8X8D0jfdzHYEPFxAI7jJlxZXwCSez5M3190m+QobIfh+sWDWSHMCWWJN2AWamkegn6vr6YBTw==} engines: {node: '>=18'} + zod@3.25.76: + resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==} + zod@4.1.11: resolution: {integrity: sha512-WPsqwxITS2tzx1bzhIKsEs19ABD5vmCVa4xBo2tq/SrV4RNZtfws1EnCWQXM6yh8bD08a1idvkB5MZSBiZsjwg==} @@ -8064,9 +8404,9 @@ snapshots: '@jridgewell/gen-mapping': 0.3.13 '@jridgewell/trace-mapping': 0.3.31 - '@antebudimir/eslint-plugin-vanilla-extract@1.11.0(eslint@9.36.0(jiti@2.6.0))': + '@antebudimir/eslint-plugin-vanilla-extract@1.11.0(eslint@9.36.0(jiti@2.6.1))': dependencies: - eslint: 9.36.0(jiti@2.6.0) + eslint: 9.36.0(jiti@2.6.1) '@babel/code-frame@7.27.1': dependencies: @@ -9105,102 +9445,153 @@ snapshots: '@emotion/hash@0.9.2': {} + '@esbuild/aix-ppc64@0.21.5': + optional: true + '@esbuild/aix-ppc64@0.25.11': optional: true '@esbuild/aix-ppc64@0.27.2': optional: true + '@esbuild/android-arm64@0.21.5': + optional: true + '@esbuild/android-arm64@0.25.11': optional: true '@esbuild/android-arm64@0.27.2': optional: true + '@esbuild/android-arm@0.21.5': + optional: true + '@esbuild/android-arm@0.25.11': optional: true '@esbuild/android-arm@0.27.2': optional: true + '@esbuild/android-x64@0.21.5': + optional: true + '@esbuild/android-x64@0.25.11': optional: true '@esbuild/android-x64@0.27.2': optional: true + '@esbuild/darwin-arm64@0.21.5': + optional: true + '@esbuild/darwin-arm64@0.25.11': optional: true '@esbuild/darwin-arm64@0.27.2': optional: true + '@esbuild/darwin-x64@0.21.5': + optional: true + '@esbuild/darwin-x64@0.25.11': optional: true '@esbuild/darwin-x64@0.27.2': optional: true + '@esbuild/freebsd-arm64@0.21.5': + optional: true + '@esbuild/freebsd-arm64@0.25.11': optional: true '@esbuild/freebsd-arm64@0.27.2': optional: true + '@esbuild/freebsd-x64@0.21.5': + optional: true + '@esbuild/freebsd-x64@0.25.11': optional: true '@esbuild/freebsd-x64@0.27.2': optional: true + '@esbuild/linux-arm64@0.21.5': + optional: true + '@esbuild/linux-arm64@0.25.11': optional: true '@esbuild/linux-arm64@0.27.2': optional: true + '@esbuild/linux-arm@0.21.5': + optional: true + '@esbuild/linux-arm@0.25.11': optional: true '@esbuild/linux-arm@0.27.2': optional: true + '@esbuild/linux-ia32@0.21.5': + optional: true + '@esbuild/linux-ia32@0.25.11': optional: true '@esbuild/linux-ia32@0.27.2': optional: true + '@esbuild/linux-loong64@0.21.5': + optional: true + '@esbuild/linux-loong64@0.25.11': optional: true '@esbuild/linux-loong64@0.27.2': optional: true + '@esbuild/linux-mips64el@0.21.5': + optional: true + '@esbuild/linux-mips64el@0.25.11': optional: true '@esbuild/linux-mips64el@0.27.2': optional: true + '@esbuild/linux-ppc64@0.21.5': + optional: true + '@esbuild/linux-ppc64@0.25.11': optional: true '@esbuild/linux-ppc64@0.27.2': optional: true + '@esbuild/linux-riscv64@0.21.5': + optional: true + '@esbuild/linux-riscv64@0.25.11': optional: true '@esbuild/linux-riscv64@0.27.2': optional: true + '@esbuild/linux-s390x@0.21.5': + optional: true + '@esbuild/linux-s390x@0.25.11': optional: true '@esbuild/linux-s390x@0.27.2': optional: true + '@esbuild/linux-x64@0.21.5': + optional: true + '@esbuild/linux-x64@0.25.11': optional: true @@ -9213,6 +9604,9 @@ snapshots: '@esbuild/netbsd-arm64@0.27.2': optional: true + '@esbuild/netbsd-x64@0.21.5': + optional: true + '@esbuild/netbsd-x64@0.25.11': optional: true @@ -9225,6 +9619,9 @@ snapshots: '@esbuild/openbsd-arm64@0.27.2': optional: true + '@esbuild/openbsd-x64@0.21.5': + optional: true + '@esbuild/openbsd-x64@0.25.11': optional: true @@ -9237,47 +9634,64 @@ snapshots: '@esbuild/openharmony-arm64@0.27.2': optional: true + '@esbuild/sunos-x64@0.21.5': + optional: true + '@esbuild/sunos-x64@0.25.11': optional: true '@esbuild/sunos-x64@0.27.2': optional: true + '@esbuild/win32-arm64@0.21.5': + optional: true + '@esbuild/win32-arm64@0.25.11': optional: true '@esbuild/win32-arm64@0.27.2': optional: true + '@esbuild/win32-ia32@0.21.5': + optional: true + '@esbuild/win32-ia32@0.25.11': optional: true '@esbuild/win32-ia32@0.27.2': optional: true + '@esbuild/win32-x64@0.21.5': + optional: true + '@esbuild/win32-x64@0.25.11': optional: true '@esbuild/win32-x64@0.27.2': optional: true - '@eslint-community/eslint-utils@4.9.0(eslint@9.36.0(jiti@2.6.0))': + '@eslint-community/eslint-utils@4.9.0(eslint@9.36.0(jiti@2.6.1))': dependencies: - eslint: 9.36.0(jiti@2.6.0) + eslint: 9.36.0(jiti@2.6.1) eslint-visitor-keys: 3.4.3 - '@eslint-community/eslint-utils@4.9.0(eslint@9.39.1(jiti@2.6.0))': + '@eslint-community/eslint-utils@4.9.0(eslint@9.39.1(jiti@2.6.1))': dependencies: - eslint: 9.39.1(jiti@2.6.0) + eslint: 9.39.1(jiti@2.6.1) + eslint-visitor-keys: 3.4.3 + + '@eslint-community/eslint-utils@4.9.0(eslint@9.39.2(jiti@2.6.1))': + dependencies: + eslint: 9.39.2(jiti@2.6.1) eslint-visitor-keys: 3.4.3 '@eslint-community/regexpp@4.12.1': {} - '@eslint/compat@1.4.0(eslint@9.36.0(jiti@2.6.0))': + '@eslint/compat@1.4.0(eslint@9.36.0(jiti@2.6.1))': dependencies: '@eslint/core': 0.16.0 optionalDependencies: - eslint: 9.36.0(jiti@2.6.0) + eslint: 9.36.0(jiti@2.6.1) '@eslint/config-array@0.21.0': dependencies: @@ -9331,6 +9745,8 @@ snapshots: '@eslint/js@9.39.1': {} + '@eslint/js@9.39.2': {} + '@eslint/object-schema@2.1.6': {} '@eslint/object-schema@2.1.7': {} @@ -9530,7 +9946,7 @@ snapshots: '@inquirer/external-editor@1.0.2(@types/node@22.18.6)': dependencies: - chardet: 2.1.0 + chardet: 2.1.1 iconv-lite: 0.7.0 optionalDependencies: '@types/node': 22.18.6 @@ -9650,7 +10066,7 @@ snapshots: jest-util: 30.2.0 slash: 3.0.0 - '@jest/core@30.2.0(esbuild-register@3.6.0(esbuild@0.25.11))': + '@jest/core@30.2.0(esbuild-register@3.6.0(esbuild@0.27.2))': dependencies: '@jest/console': 30.2.0 '@jest/pattern': 30.0.1 @@ -9665,7 +10081,7 @@ snapshots: exit-x: 0.2.2 graceful-fs: 4.2.11 jest-changed-files: 30.2.0 - jest-config: 30.2.0(@types/node@22.18.6)(esbuild-register@3.6.0(esbuild@0.25.11)) + jest-config: 30.2.0(@types/node@22.18.6)(esbuild-register@3.6.0(esbuild@0.27.2)) jest-haste-map: 30.2.0 jest-message-util: 30.2.0 jest-regex-util: 30.0.1 @@ -9837,30 +10253,30 @@ snapshots: '@types/yargs': 17.0.35 chalk: 4.1.2 - '@joshwooding/vite-plugin-react-docgen-typescript@0.5.0(typescript@5.8.3)(vite@7.1.7(@types/node@22.18.6)(jiti@2.6.0)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5))': + '@joshwooding/vite-plugin-react-docgen-typescript@0.5.0(typescript@5.8.3)(vite@7.1.7(@types/node@22.18.6)(jiti@2.6.1)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5))': dependencies: glob: 10.4.5 magic-string: 0.27.0 react-docgen-typescript: 2.4.0(typescript@5.8.3) - vite: 7.1.7(@types/node@22.18.6)(jiti@2.6.0)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5) + vite: 7.1.7(@types/node@22.18.6)(jiti@2.6.1)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5) optionalDependencies: typescript: 5.8.3 - '@joshwooding/vite-plugin-react-docgen-typescript@0.6.1(typescript@5.8.3)(vite@7.1.7(@types/node@22.18.6)(jiti@2.6.0)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5))': + '@joshwooding/vite-plugin-react-docgen-typescript@0.6.1(typescript@5.8.3)(vite@7.1.7(@types/node@22.18.6)(jiti@2.6.1)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5))': dependencies: glob: 10.4.5 magic-string: 0.30.19 react-docgen-typescript: 2.4.0(typescript@5.8.3) - vite: 7.1.7(@types/node@22.18.6)(jiti@2.6.0)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5) + vite: 7.1.7(@types/node@22.18.6)(jiti@2.6.1)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5) optionalDependencies: typescript: 5.8.3 - '@joshwooding/vite-plugin-react-docgen-typescript@0.6.1(typescript@5.9.2)(vite@7.3.1(@types/node@22.18.6)(jiti@2.6.0)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5))': + '@joshwooding/vite-plugin-react-docgen-typescript@0.6.1(typescript@5.9.2)(vite@7.3.1(@types/node@22.18.6)(jiti@2.6.1)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5))': dependencies: glob: 10.4.5 magic-string: 0.30.19 react-docgen-typescript: 2.4.0(typescript@5.9.2) - vite: 7.3.1(@types/node@22.18.6)(jiti@2.6.0)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5) + vite: 7.3.1(@types/node@22.18.6)(jiti@2.6.1)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5) optionalDependencies: typescript: 5.9.2 @@ -10126,10 +10542,10 @@ snapshots: '@polka/url@1.0.0-next.29': {} - '@prettier/sync@0.6.1(prettier@3.6.2)': + '@prettier/sync@0.6.1(prettier@3.8.1)': dependencies: make-synchronized: 0.8.0 - prettier: 3.6.2 + prettier: 3.8.1 '@radix-ui/number@1.1.1': {} @@ -10727,41 +11143,41 @@ snapshots: '@standard-schema/spec@1.0.0': {} - '@storybook/addon-docs@9.1.9(@types/react@19.2.10)(storybook@9.1.17(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@7.1.7(@types/node@22.18.6)(jiti@2.6.0)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5)))': + '@storybook/addon-docs@9.1.9(@types/react@19.2.10)(storybook@9.1.17(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@7.1.7(@types/node@22.18.6)(jiti@2.6.1)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5)))': dependencies: '@mdx-js/react': 3.1.1(@types/react@19.2.10)(react@19.2.3) - '@storybook/csf-plugin': 9.1.9(storybook@9.1.17(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@7.1.7(@types/node@22.18.6)(jiti@2.6.0)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5))) + '@storybook/csf-plugin': 9.1.9(storybook@9.1.17(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@7.1.7(@types/node@22.18.6)(jiti@2.6.1)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5))) '@storybook/icons': 1.6.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@storybook/react-dom-shim': 9.1.9(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(storybook@9.1.17(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@7.1.7(@types/node@22.18.6)(jiti@2.6.0)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5))) + '@storybook/react-dom-shim': 9.1.9(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(storybook@9.1.17(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@7.1.7(@types/node@22.18.6)(jiti@2.6.1)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5))) react: 19.2.3 react-dom: 19.2.3(react@19.2.3) - storybook: 9.1.17(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@7.1.7(@types/node@22.18.6)(jiti@2.6.0)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5)) + storybook: 9.1.17(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@7.1.7(@types/node@22.18.6)(jiti@2.6.1)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5)) ts-dedent: 2.2.0 transitivePeerDependencies: - '@types/react' - '@storybook/builder-vite@9.1.9(storybook@9.1.17(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@7.1.7(@types/node@22.18.6)(jiti@2.6.0)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5)))(vite@7.1.7(@types/node@22.18.6)(jiti@2.6.0)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5))': + '@storybook/builder-vite@9.1.9(storybook@9.1.17(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@7.1.7(@types/node@22.18.6)(jiti@2.6.1)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5)))(vite@7.1.7(@types/node@22.18.6)(jiti@2.6.1)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5))': dependencies: - '@storybook/csf-plugin': 9.1.9(storybook@9.1.17(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@7.1.7(@types/node@22.18.6)(jiti@2.6.0)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5))) - storybook: 9.1.17(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@7.1.7(@types/node@22.18.6)(jiti@2.6.0)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5)) + '@storybook/csf-plugin': 9.1.9(storybook@9.1.17(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@7.1.7(@types/node@22.18.6)(jiti@2.6.1)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5))) + storybook: 9.1.17(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@7.1.7(@types/node@22.18.6)(jiti@2.6.1)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5)) ts-dedent: 2.2.0 - vite: 7.1.7(@types/node@22.18.6)(jiti@2.6.0)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5) + vite: 7.1.7(@types/node@22.18.6)(jiti@2.6.1)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5) - '@storybook/builder-vite@9.1.9(storybook@9.1.17(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@7.3.1(@types/node@22.18.6)(jiti@2.6.0)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5)))(vite@7.3.1(@types/node@22.18.6)(jiti@2.6.0)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5))': + '@storybook/builder-vite@9.1.9(storybook@9.1.17(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@7.3.1(@types/node@22.18.6)(jiti@2.6.1)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5)))(vite@7.3.1(@types/node@22.18.6)(jiti@2.6.1)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5))': dependencies: - '@storybook/csf-plugin': 9.1.9(storybook@9.1.17(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@7.3.1(@types/node@22.18.6)(jiti@2.6.0)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5))) - storybook: 9.1.17(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@7.3.1(@types/node@22.18.6)(jiti@2.6.0)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5)) + '@storybook/csf-plugin': 9.1.9(storybook@9.1.17(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@7.3.1(@types/node@22.18.6)(jiti@2.6.1)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5))) + storybook: 9.1.17(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@7.3.1(@types/node@22.18.6)(jiti@2.6.1)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5)) ts-dedent: 2.2.0 - vite: 7.3.1(@types/node@22.18.6)(jiti@2.6.0)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5) + vite: 7.3.1(@types/node@22.18.6)(jiti@2.6.1)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5) - '@storybook/csf-plugin@9.1.9(storybook@9.1.17(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@7.1.7(@types/node@22.18.6)(jiti@2.6.0)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5)))': + '@storybook/csf-plugin@9.1.9(storybook@9.1.17(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@7.1.7(@types/node@22.18.6)(jiti@2.6.1)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5)))': dependencies: - storybook: 9.1.17(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@7.1.7(@types/node@22.18.6)(jiti@2.6.0)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5)) + storybook: 9.1.17(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@7.1.7(@types/node@22.18.6)(jiti@2.6.1)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5)) unplugin: 1.16.1 - '@storybook/csf-plugin@9.1.9(storybook@9.1.17(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@7.3.1(@types/node@22.18.6)(jiti@2.6.0)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5)))': + '@storybook/csf-plugin@9.1.9(storybook@9.1.17(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@7.3.1(@types/node@22.18.6)(jiti@2.6.1)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5)))': dependencies: - storybook: 9.1.17(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@7.3.1(@types/node@22.18.6)(jiti@2.6.0)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5)) + storybook: 9.1.17(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@7.3.1(@types/node@22.18.6)(jiti@2.6.1)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5)) unplugin: 1.16.1 '@storybook/global@5.0.0': {} @@ -10771,75 +11187,75 @@ snapshots: react: 19.2.3 react-dom: 19.2.3(react@19.2.3) - '@storybook/react-dom-shim@9.1.9(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@9.1.17(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@7.3.1(@types/node@22.18.6)(jiti@2.6.0)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5)))': + '@storybook/react-dom-shim@9.1.9(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@9.1.17(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@7.3.1(@types/node@22.18.6)(jiti@2.6.1)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5)))': dependencies: react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - storybook: 9.1.17(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@7.3.1(@types/node@22.18.6)(jiti@2.6.0)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5)) + storybook: 9.1.17(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@7.3.1(@types/node@22.18.6)(jiti@2.6.1)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5)) - '@storybook/react-dom-shim@9.1.9(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(storybook@9.1.17(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@7.1.7(@types/node@22.18.6)(jiti@2.6.0)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5)))': + '@storybook/react-dom-shim@9.1.9(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(storybook@9.1.17(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@7.1.7(@types/node@22.18.6)(jiti@2.6.1)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5)))': dependencies: react: 19.2.3 react-dom: 19.2.3(react@19.2.3) - storybook: 9.1.17(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@7.1.7(@types/node@22.18.6)(jiti@2.6.0)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5)) + storybook: 9.1.17(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@7.1.7(@types/node@22.18.6)(jiti@2.6.1)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5)) - '@storybook/react-vite@9.1.9(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(rollup@4.52.5)(storybook@9.1.17(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@7.3.1(@types/node@22.18.6)(jiti@2.6.0)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5)))(typescript@5.9.2)(vite@7.3.1(@types/node@22.18.6)(jiti@2.6.0)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5))': + '@storybook/react-vite@9.1.9(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(rollup@4.52.5)(storybook@9.1.17(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@7.3.1(@types/node@22.18.6)(jiti@2.6.1)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5)))(typescript@5.9.2)(vite@7.3.1(@types/node@22.18.6)(jiti@2.6.1)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5))': dependencies: - '@joshwooding/vite-plugin-react-docgen-typescript': 0.6.1(typescript@5.9.2)(vite@7.3.1(@types/node@22.18.6)(jiti@2.6.0)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5)) + '@joshwooding/vite-plugin-react-docgen-typescript': 0.6.1(typescript@5.9.2)(vite@7.3.1(@types/node@22.18.6)(jiti@2.6.1)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5)) '@rollup/pluginutils': 5.3.0(rollup@4.52.5) - '@storybook/builder-vite': 9.1.9(storybook@9.1.17(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@7.3.1(@types/node@22.18.6)(jiti@2.6.0)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5)))(vite@7.3.1(@types/node@22.18.6)(jiti@2.6.0)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5)) - '@storybook/react': 9.1.9(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@9.1.17(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@7.3.1(@types/node@22.18.6)(jiti@2.6.0)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5)))(typescript@5.9.2) + '@storybook/builder-vite': 9.1.9(storybook@9.1.17(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@7.3.1(@types/node@22.18.6)(jiti@2.6.1)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5)))(vite@7.3.1(@types/node@22.18.6)(jiti@2.6.1)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5)) + '@storybook/react': 9.1.9(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@9.1.17(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@7.3.1(@types/node@22.18.6)(jiti@2.6.1)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5)))(typescript@5.9.2) find-up: 7.0.0 magic-string: 0.30.19 react: 18.3.1 react-docgen: 8.0.2 react-dom: 18.3.1(react@18.3.1) resolve: 1.22.10 - storybook: 9.1.17(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@7.3.1(@types/node@22.18.6)(jiti@2.6.0)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5)) + storybook: 9.1.17(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@7.3.1(@types/node@22.18.6)(jiti@2.6.1)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5)) tsconfig-paths: 4.2.0 - vite: 7.3.1(@types/node@22.18.6)(jiti@2.6.0)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5) + vite: 7.3.1(@types/node@22.18.6)(jiti@2.6.1)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5) transitivePeerDependencies: - rollup - supports-color - typescript - '@storybook/react-vite@9.1.9(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(rollup@4.52.5)(storybook@9.1.17(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@7.1.7(@types/node@22.18.6)(jiti@2.6.0)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5)))(typescript@5.8.3)(vite@7.1.7(@types/node@22.18.6)(jiti@2.6.0)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5))': + '@storybook/react-vite@9.1.9(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(rollup@4.52.5)(storybook@9.1.17(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@7.1.7(@types/node@22.18.6)(jiti@2.6.1)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5)))(typescript@5.8.3)(vite@7.1.7(@types/node@22.18.6)(jiti@2.6.1)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5))': dependencies: - '@joshwooding/vite-plugin-react-docgen-typescript': 0.6.1(typescript@5.8.3)(vite@7.1.7(@types/node@22.18.6)(jiti@2.6.0)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5)) + '@joshwooding/vite-plugin-react-docgen-typescript': 0.6.1(typescript@5.8.3)(vite@7.1.7(@types/node@22.18.6)(jiti@2.6.1)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5)) '@rollup/pluginutils': 5.3.0(rollup@4.52.5) - '@storybook/builder-vite': 9.1.9(storybook@9.1.17(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@7.1.7(@types/node@22.18.6)(jiti@2.6.0)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5)))(vite@7.1.7(@types/node@22.18.6)(jiti@2.6.0)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5)) - '@storybook/react': 9.1.9(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(storybook@9.1.17(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@7.1.7(@types/node@22.18.6)(jiti@2.6.0)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5)))(typescript@5.8.3) + '@storybook/builder-vite': 9.1.9(storybook@9.1.17(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@7.1.7(@types/node@22.18.6)(jiti@2.6.1)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5)))(vite@7.1.7(@types/node@22.18.6)(jiti@2.6.1)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5)) + '@storybook/react': 9.1.9(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(storybook@9.1.17(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@7.1.7(@types/node@22.18.6)(jiti@2.6.1)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5)))(typescript@5.8.3) find-up: 7.0.0 magic-string: 0.30.19 react: 19.2.3 react-docgen: 8.0.2 react-dom: 19.2.3(react@19.2.3) resolve: 1.22.10 - storybook: 9.1.17(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@7.1.7(@types/node@22.18.6)(jiti@2.6.0)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5)) + storybook: 9.1.17(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@7.1.7(@types/node@22.18.6)(jiti@2.6.1)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5)) tsconfig-paths: 4.2.0 - vite: 7.1.7(@types/node@22.18.6)(jiti@2.6.0)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5) + vite: 7.1.7(@types/node@22.18.6)(jiti@2.6.1)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5) transitivePeerDependencies: - rollup - supports-color - typescript - '@storybook/react@9.1.9(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@9.1.17(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@7.3.1(@types/node@22.18.6)(jiti@2.6.0)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5)))(typescript@5.9.2)': + '@storybook/react@9.1.9(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@9.1.17(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@7.3.1(@types/node@22.18.6)(jiti@2.6.1)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5)))(typescript@5.9.2)': dependencies: '@storybook/global': 5.0.0 - '@storybook/react-dom-shim': 9.1.9(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@9.1.17(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@7.3.1(@types/node@22.18.6)(jiti@2.6.0)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5))) + '@storybook/react-dom-shim': 9.1.9(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@9.1.17(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@7.3.1(@types/node@22.18.6)(jiti@2.6.1)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5))) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - storybook: 9.1.17(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@7.3.1(@types/node@22.18.6)(jiti@2.6.0)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5)) + storybook: 9.1.17(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@7.3.1(@types/node@22.18.6)(jiti@2.6.1)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5)) optionalDependencies: typescript: 5.9.2 - '@storybook/react@9.1.9(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(storybook@9.1.17(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@7.1.7(@types/node@22.18.6)(jiti@2.6.0)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5)))(typescript@5.8.3)': + '@storybook/react@9.1.9(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(storybook@9.1.17(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@7.1.7(@types/node@22.18.6)(jiti@2.6.1)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5)))(typescript@5.8.3)': dependencies: '@storybook/global': 5.0.0 - '@storybook/react-dom-shim': 9.1.9(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(storybook@9.1.17(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@7.1.7(@types/node@22.18.6)(jiti@2.6.0)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5))) + '@storybook/react-dom-shim': 9.1.9(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(storybook@9.1.17(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@7.1.7(@types/node@22.18.6)(jiti@2.6.1)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5))) react: 19.2.3 react-dom: 19.2.3(react@19.2.3) - storybook: 9.1.17(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@7.1.7(@types/node@22.18.6)(jiti@2.6.0)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5)) + storybook: 9.1.17(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@7.1.7(@types/node@22.18.6)(jiti@2.6.1)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5)) optionalDependencies: typescript: 5.8.3 @@ -11012,12 +11428,12 @@ snapshots: postcss: 8.5.6 tailwindcss: 4.1.13 - '@tailwindcss/vite@4.1.13(vite@7.1.7(@types/node@22.18.6)(jiti@2.6.0)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5))': + '@tailwindcss/vite@4.1.13(vite@7.1.7(@types/node@22.18.6)(jiti@2.6.1)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5))': dependencies: '@tailwindcss/node': 4.1.13 '@tailwindcss/oxide': 4.1.13 tailwindcss: 4.1.13 - vite: 7.1.7(@types/node@22.18.6)(jiti@2.6.0)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5) + vite: 7.1.7(@types/node@22.18.6)(jiti@2.6.1)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5) '@tanstack/react-table@8.21.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: @@ -11075,6 +11491,13 @@ snapshots: '@trysound/sax@0.2.0': {} + '@ts-morph/common@0.23.0': + dependencies: + fast-glob: 3.3.3 + minimatch: 9.0.5 + mkdirp: 3.0.1 + path-browserify: 1.0.1 + '@ts-morph/common@0.27.0': dependencies: fast-glob: 3.3.3 @@ -11235,15 +11658,15 @@ snapshots: dependencies: '@types/yargs-parser': 21.0.3 - '@typescript-eslint/eslint-plugin@8.44.1(@typescript-eslint/parser@8.44.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.8.3))(eslint@9.36.0(jiti@2.6.0))(typescript@5.8.3)': + '@typescript-eslint/eslint-plugin@8.44.1(@typescript-eslint/parser@8.44.1(eslint@9.36.0(jiti@2.6.1))(typescript@5.8.3))(eslint@9.36.0(jiti@2.6.1))(typescript@5.8.3)': dependencies: '@eslint-community/regexpp': 4.12.1 - '@typescript-eslint/parser': 8.44.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.8.3) + '@typescript-eslint/parser': 8.44.1(eslint@9.36.0(jiti@2.6.1))(typescript@5.8.3) '@typescript-eslint/scope-manager': 8.44.1 - '@typescript-eslint/type-utils': 8.44.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.8.3) - '@typescript-eslint/utils': 8.44.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.8.3) + '@typescript-eslint/type-utils': 8.44.1(eslint@9.36.0(jiti@2.6.1))(typescript@5.8.3) + '@typescript-eslint/utils': 8.44.1(eslint@9.36.0(jiti@2.6.1))(typescript@5.8.3) '@typescript-eslint/visitor-keys': 8.44.1 - eslint: 9.36.0(jiti@2.6.0) + eslint: 9.36.0(jiti@2.6.1) graphemer: 1.4.0 ignore: 7.0.5 natural-compare: 1.4.0 @@ -11252,15 +11675,15 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/eslint-plugin@8.44.1(@typescript-eslint/parser@8.44.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2))(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2)': + '@typescript-eslint/eslint-plugin@8.44.1(@typescript-eslint/parser@8.44.1(eslint@9.36.0(jiti@2.6.1))(typescript@5.9.2))(eslint@9.36.0(jiti@2.6.1))(typescript@5.9.2)': dependencies: '@eslint-community/regexpp': 4.12.1 - '@typescript-eslint/parser': 8.44.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2) + '@typescript-eslint/parser': 8.44.1(eslint@9.36.0(jiti@2.6.1))(typescript@5.9.2) '@typescript-eslint/scope-manager': 8.44.1 - '@typescript-eslint/type-utils': 8.44.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2) - '@typescript-eslint/utils': 8.44.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2) + '@typescript-eslint/type-utils': 8.44.1(eslint@9.36.0(jiti@2.6.1))(typescript@5.9.2) + '@typescript-eslint/utils': 8.44.1(eslint@9.36.0(jiti@2.6.1))(typescript@5.9.2) '@typescript-eslint/visitor-keys': 8.44.1 - eslint: 9.36.0(jiti@2.6.0) + eslint: 9.36.0(jiti@2.6.1) graphemer: 1.4.0 ignore: 7.0.5 natural-compare: 1.4.0 @@ -11269,26 +11692,26 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@8.44.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.8.3)': + '@typescript-eslint/parser@8.44.1(eslint@9.36.0(jiti@2.6.1))(typescript@5.8.3)': dependencies: '@typescript-eslint/scope-manager': 8.44.1 '@typescript-eslint/types': 8.44.1 '@typescript-eslint/typescript-estree': 8.44.1(typescript@5.8.3) '@typescript-eslint/visitor-keys': 8.44.1 debug: 4.4.3 - eslint: 9.36.0(jiti@2.6.0) + eslint: 9.36.0(jiti@2.6.1) typescript: 5.8.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@8.44.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2)': + '@typescript-eslint/parser@8.44.1(eslint@9.36.0(jiti@2.6.1))(typescript@5.9.2)': dependencies: '@typescript-eslint/scope-manager': 8.44.1 '@typescript-eslint/types': 8.44.1 '@typescript-eslint/typescript-estree': 8.44.1(typescript@5.9.2) '@typescript-eslint/visitor-keys': 8.44.1 debug: 4.4.3 - eslint: 9.36.0(jiti@2.6.0) + eslint: 9.36.0(jiti@2.6.1) typescript: 5.9.2 transitivePeerDependencies: - supports-color @@ -11324,25 +11747,25 @@ snapshots: dependencies: typescript: 5.9.2 - '@typescript-eslint/type-utils@8.44.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.8.3)': + '@typescript-eslint/type-utils@8.44.1(eslint@9.36.0(jiti@2.6.1))(typescript@5.8.3)': dependencies: '@typescript-eslint/types': 8.44.1 '@typescript-eslint/typescript-estree': 8.44.1(typescript@5.8.3) - '@typescript-eslint/utils': 8.44.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.8.3) + '@typescript-eslint/utils': 8.44.1(eslint@9.36.0(jiti@2.6.1))(typescript@5.8.3) debug: 4.4.3 - eslint: 9.36.0(jiti@2.6.0) + eslint: 9.36.0(jiti@2.6.1) ts-api-utils: 2.1.0(typescript@5.8.3) typescript: 5.8.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/type-utils@8.44.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2)': + '@typescript-eslint/type-utils@8.44.1(eslint@9.36.0(jiti@2.6.1))(typescript@5.9.2)': dependencies: '@typescript-eslint/types': 8.44.1 '@typescript-eslint/typescript-estree': 8.44.1(typescript@5.9.2) - '@typescript-eslint/utils': 8.44.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2) + '@typescript-eslint/utils': 8.44.1(eslint@9.36.0(jiti@2.6.1))(typescript@5.9.2) debug: 4.4.3 - eslint: 9.36.0(jiti@2.6.0) + eslint: 9.36.0(jiti@2.6.1) ts-api-utils: 2.1.0(typescript@5.9.2) typescript: 5.9.2 transitivePeerDependencies: @@ -11382,24 +11805,24 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.44.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.8.3)': + '@typescript-eslint/utils@8.44.1(eslint@9.36.0(jiti@2.6.1))(typescript@5.8.3)': dependencies: - '@eslint-community/eslint-utils': 4.9.0(eslint@9.36.0(jiti@2.6.0)) + '@eslint-community/eslint-utils': 4.9.0(eslint@9.36.0(jiti@2.6.1)) '@typescript-eslint/scope-manager': 8.44.1 '@typescript-eslint/types': 8.44.1 '@typescript-eslint/typescript-estree': 8.44.1(typescript@5.8.3) - eslint: 9.36.0(jiti@2.6.0) + eslint: 9.36.0(jiti@2.6.1) typescript: 5.8.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.44.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2)': + '@typescript-eslint/utils@8.44.1(eslint@9.36.0(jiti@2.6.1))(typescript@5.9.2)': dependencies: - '@eslint-community/eslint-utils': 4.9.0(eslint@9.36.0(jiti@2.6.0)) + '@eslint-community/eslint-utils': 4.9.0(eslint@9.36.0(jiti@2.6.1)) '@typescript-eslint/scope-manager': 8.44.1 '@typescript-eslint/types': 8.44.1 '@typescript-eslint/typescript-estree': 8.44.1(typescript@5.9.2) - eslint: 9.36.0(jiti@2.6.0) + eslint: 9.36.0(jiti@2.6.1) typescript: 5.9.2 transitivePeerDependencies: - supports-color @@ -11476,12 +11899,12 @@ snapshots: transitivePeerDependencies: - supports-color - '@vanilla-extract/compiler@0.3.4(@types/node@22.18.6)(jiti@2.6.0)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5)': + '@vanilla-extract/compiler@0.3.4(@types/node@22.18.6)(jiti@2.6.1)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5)': dependencies: '@vanilla-extract/css': 1.18.0 '@vanilla-extract/integration': 8.0.7 - vite: 7.1.7(@types/node@22.18.6)(jiti@2.6.0)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5) - vite-node: 3.2.4(@types/node@22.18.6)(jiti@2.6.0)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5) + vite: 7.1.7(@types/node@22.18.6)(jiti@2.6.1)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5) + vite-node: 3.2.4(@types/node@22.18.6)(jiti@2.6.1)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5) transitivePeerDependencies: - '@types/node' - babel-plugin-macros @@ -11555,11 +11978,11 @@ snapshots: dependencies: '@vanilla-extract/css': 1.18.0 - '@vanilla-extract/vite-plugin@5.1.4(@types/node@22.18.6)(jiti@2.6.0)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5)(vite@7.1.7(@types/node@22.18.6)(jiti@2.6.0)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5))': + '@vanilla-extract/vite-plugin@5.1.4(@types/node@22.18.6)(jiti@2.6.1)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5)(vite@7.1.7(@types/node@22.18.6)(jiti@2.6.1)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5))': dependencies: - '@vanilla-extract/compiler': 0.3.4(@types/node@22.18.6)(jiti@2.6.0)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5) + '@vanilla-extract/compiler': 0.3.4(@types/node@22.18.6)(jiti@2.6.1)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5) '@vanilla-extract/integration': 8.0.7 - vite: 7.1.7(@types/node@22.18.6)(jiti@2.6.0)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5) + vite: 7.1.7(@types/node@22.18.6)(jiti@2.6.1)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5) transitivePeerDependencies: - '@types/node' - babel-plugin-macros @@ -11575,11 +11998,11 @@ snapshots: - tsx - yaml - '@vanilla-extract/vite-plugin@5.1.4(@types/node@22.18.6)(jiti@2.6.0)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5)(vite@7.3.1(@types/node@22.18.6)(jiti@2.6.0)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5))': + '@vanilla-extract/vite-plugin@5.1.4(@types/node@22.18.6)(jiti@2.6.1)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5)(vite@7.3.1(@types/node@22.18.6)(jiti@2.6.1)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5))': dependencies: - '@vanilla-extract/compiler': 0.3.4(@types/node@22.18.6)(jiti@2.6.0)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5) + '@vanilla-extract/compiler': 0.3.4(@types/node@22.18.6)(jiti@2.6.1)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5) '@vanilla-extract/integration': 8.0.7 - vite: 7.3.1(@types/node@22.18.6)(jiti@2.6.0)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5) + vite: 7.3.1(@types/node@22.18.6)(jiti@2.6.1)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5) transitivePeerDependencies: - '@types/node' - babel-plugin-macros @@ -11600,7 +12023,7 @@ snapshots: next: 15.5.9(@babel/core@7.28.4)(@playwright/test@1.56.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.93.1) react: 18.3.1 - '@vitejs/plugin-react@5.0.3(vite@7.1.7(@types/node@22.18.6)(jiti@2.6.0)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5))': + '@vitejs/plugin-react@5.0.3(vite@7.1.7(@types/node@22.18.6)(jiti@2.6.1)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5))': dependencies: '@babel/core': 7.28.4 '@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.28.4) @@ -11608,20 +12031,20 @@ snapshots: '@rolldown/pluginutils': 1.0.0-beta.35 '@types/babel__core': 7.20.5 react-refresh: 0.17.0 - vite: 7.1.7(@types/node@22.18.6)(jiti@2.6.0)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5) + vite: 7.1.7(@types/node@22.18.6)(jiti@2.6.1)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5) transitivePeerDependencies: - supports-color - '@vitest/browser@3.2.4(playwright@1.56.1)(vite@7.1.7(@types/node@22.18.6)(jiti@2.6.0)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5))(vitest@3.2.4)': + '@vitest/browser@3.2.4(playwright@1.56.1)(vite@7.1.7(@types/node@22.18.6)(jiti@2.6.1)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5))(vitest@3.2.4)': dependencies: '@testing-library/dom': 10.4.1 '@testing-library/user-event': 14.6.1(@testing-library/dom@10.4.1) - '@vitest/mocker': 3.2.4(vite@7.1.7(@types/node@22.18.6)(jiti@2.6.0)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5)) + '@vitest/mocker': 3.2.4(vite@7.1.7(@types/node@22.18.6)(jiti@2.6.1)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5)) '@vitest/utils': 3.2.4 magic-string: 0.30.19 sirv: 3.0.2 tinyrainbow: 2.0.0 - vitest: 3.2.4(@types/debug@4.1.12)(@types/node@22.18.6)(@vitest/browser@3.2.4)(happy-dom@20.4.0)(jiti@2.6.0)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5) + vitest: 3.2.4(@types/debug@4.1.12)(@types/node@22.18.6)(@vitest/browser@3.2.4)(happy-dom@20.4.0)(jiti@2.6.1)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5) ws: 8.18.3 optionalDependencies: playwright: 1.56.1 @@ -11632,16 +12055,16 @@ snapshots: - vite optional: true - '@vitest/browser@3.2.4(playwright@1.56.1)(vite@7.3.1(@types/node@20.19.17)(jiti@2.6.0)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5))(vitest@3.2.4)': + '@vitest/browser@3.2.4(playwright@1.56.1)(vite@7.3.1(@types/node@20.19.17)(jiti@2.6.1)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5))(vitest@3.2.4)': dependencies: '@testing-library/dom': 10.4.1 '@testing-library/user-event': 14.6.1(@testing-library/dom@10.4.1) - '@vitest/mocker': 3.2.4(vite@7.3.1(@types/node@20.19.17)(jiti@2.6.0)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5)) + '@vitest/mocker': 3.2.4(vite@7.3.1(@types/node@20.19.17)(jiti@2.6.1)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5)) '@vitest/utils': 3.2.4 magic-string: 0.30.19 sirv: 3.0.2 tinyrainbow: 2.0.0 - vitest: 3.2.4(@types/debug@4.1.12)(@types/node@20.19.17)(@vitest/browser@3.2.4)(happy-dom@20.4.0)(jiti@2.6.0)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5) + vitest: 3.2.4(@types/debug@4.1.12)(@types/node@20.19.17)(@vitest/browser@3.2.4)(happy-dom@20.4.0)(jiti@2.6.1)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5) ws: 8.18.3 optionalDependencies: playwright: 1.56.1 @@ -11652,16 +12075,16 @@ snapshots: - vite optional: true - '@vitest/browser@3.2.4(playwright@1.56.1)(vite@7.3.1(@types/node@22.18.6)(jiti@2.6.0)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5))(vitest@3.2.4)': + '@vitest/browser@3.2.4(playwright@1.56.1)(vite@7.3.1(@types/node@22.18.6)(jiti@2.6.1)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5))(vitest@3.2.4)': dependencies: '@testing-library/dom': 10.4.1 '@testing-library/user-event': 14.6.1(@testing-library/dom@10.4.1) - '@vitest/mocker': 3.2.4(vite@7.3.1(@types/node@22.18.6)(jiti@2.6.0)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5)) + '@vitest/mocker': 3.2.4(vite@7.3.1(@types/node@22.18.6)(jiti@2.6.1)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5)) '@vitest/utils': 3.2.4 magic-string: 0.30.19 sirv: 3.0.2 tinyrainbow: 2.0.0 - vitest: 3.2.4(@types/debug@4.1.12)(@types/node@22.18.6)(@vitest/browser@3.2.4)(happy-dom@20.4.0)(jiti@2.6.0)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5) + vitest: 3.2.4(@types/debug@4.1.12)(@types/node@22.18.6)(@vitest/browser@3.2.4)(happy-dom@20.4.0)(jiti@2.6.1)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5) ws: 8.18.3 optionalDependencies: playwright: 1.56.1 @@ -11671,6 +12094,24 @@ snapshots: - utf-8-validate - vite + '@vitest/coverage-v8@2.1.9(vitest@2.1.9(@types/node@20.19.17)(happy-dom@20.4.0)(lightningcss@1.30.1)(sass@1.93.1))': + dependencies: + '@ampproject/remapping': 2.3.0 + '@bcoe/v8-coverage': 0.2.3 + debug: 4.4.3 + istanbul-lib-coverage: 3.2.2 + istanbul-lib-report: 3.0.1 + istanbul-lib-source-maps: 5.0.6 + istanbul-reports: 3.2.0 + magic-string: 0.30.19 + magicast: 0.3.5 + std-env: 3.9.0 + test-exclude: 7.0.1 + tinyrainbow: 1.2.0 + vitest: 2.1.9(@types/node@20.19.17)(happy-dom@20.4.0)(lightningcss@1.30.1)(sass@1.93.1) + transitivePeerDependencies: + - supports-color + '@vitest/coverage-v8@3.2.4(@vitest/browser@3.2.4)(vitest@3.2.4)': dependencies: '@ampproject/remapping': 2.3.0 @@ -11686,12 +12127,19 @@ snapshots: std-env: 3.9.0 test-exclude: 7.0.1 tinyrainbow: 2.0.0 - vitest: 3.2.4(@types/debug@4.1.12)(@types/node@22.18.6)(@vitest/browser@3.2.4)(happy-dom@20.4.0)(jiti@2.6.0)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5) + vitest: 3.2.4(@types/debug@4.1.12)(@types/node@22.18.6)(@vitest/browser@3.2.4)(happy-dom@20.4.0)(jiti@2.6.1)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5) optionalDependencies: - '@vitest/browser': 3.2.4(playwright@1.56.1)(vite@7.3.1(@types/node@22.18.6)(jiti@2.6.0)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5))(vitest@3.2.4) + '@vitest/browser': 3.2.4(playwright@1.56.1)(vite@7.3.1(@types/node@22.18.6)(jiti@2.6.1)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5))(vitest@3.2.4) transitivePeerDependencies: - supports-color + '@vitest/expect@2.1.9': + dependencies: + '@vitest/spy': 2.1.9 + '@vitest/utils': 2.1.9 + chai: 5.3.3 + tinyrainbow: 1.2.0 + '@vitest/expect@3.2.4': dependencies: '@types/chai': 5.2.2 @@ -11700,59 +12148,92 @@ snapshots: chai: 5.3.3 tinyrainbow: 2.0.0 - '@vitest/mocker@3.2.4(vite@7.1.7(@types/node@20.19.17)(jiti@2.6.0)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5))': + '@vitest/mocker@2.1.9(vite@5.4.21(@types/node@20.19.17)(lightningcss@1.30.1)(sass@1.93.1))': + dependencies: + '@vitest/spy': 2.1.9 + estree-walker: 3.0.3 + magic-string: 0.30.19 + optionalDependencies: + vite: 5.4.21(@types/node@20.19.17)(lightningcss@1.30.1)(sass@1.93.1) + + '@vitest/mocker@3.2.4(vite@7.1.7(@types/node@20.19.17)(jiti@2.6.1)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5))': dependencies: '@vitest/spy': 3.2.4 estree-walker: 3.0.3 magic-string: 0.30.19 optionalDependencies: - vite: 7.1.7(@types/node@20.19.17)(jiti@2.6.0)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5) + vite: 7.1.7(@types/node@20.19.17)(jiti@2.6.1)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5) - '@vitest/mocker@3.2.4(vite@7.1.7(@types/node@22.18.6)(jiti@2.6.0)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5))': + '@vitest/mocker@3.2.4(vite@7.1.7(@types/node@22.18.6)(jiti@2.6.1)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5))': dependencies: '@vitest/spy': 3.2.4 estree-walker: 3.0.3 magic-string: 0.30.19 optionalDependencies: - vite: 7.1.7(@types/node@22.18.6)(jiti@2.6.0)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5) + vite: 7.1.7(@types/node@22.18.6)(jiti@2.6.1)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5) - '@vitest/mocker@3.2.4(vite@7.3.1(@types/node@20.19.17)(jiti@2.6.0)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5))': + '@vitest/mocker@3.2.4(vite@7.3.1(@types/node@20.19.17)(jiti@2.6.1)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5))': dependencies: '@vitest/spy': 3.2.4 estree-walker: 3.0.3 magic-string: 0.30.19 optionalDependencies: - vite: 7.3.1(@types/node@20.19.17)(jiti@2.6.0)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5) + vite: 7.3.1(@types/node@20.19.17)(jiti@2.6.1)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5) optional: true - '@vitest/mocker@3.2.4(vite@7.3.1(@types/node@22.18.6)(jiti@2.6.0)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5))': + '@vitest/mocker@3.2.4(vite@7.3.1(@types/node@22.18.6)(jiti@2.6.1)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5))': dependencies: '@vitest/spy': 3.2.4 estree-walker: 3.0.3 magic-string: 0.30.19 optionalDependencies: - vite: 7.3.1(@types/node@22.18.6)(jiti@2.6.0)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5) + vite: 7.3.1(@types/node@22.18.6)(jiti@2.6.1)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5) + + '@vitest/pretty-format@2.1.9': + dependencies: + tinyrainbow: 1.2.0 '@vitest/pretty-format@3.2.4': dependencies: tinyrainbow: 2.0.0 + '@vitest/runner@2.1.9': + dependencies: + '@vitest/utils': 2.1.9 + pathe: 1.1.2 + '@vitest/runner@3.2.4': dependencies: '@vitest/utils': 3.2.4 pathe: 2.0.3 strip-literal: 3.0.0 + '@vitest/snapshot@2.1.9': + dependencies: + '@vitest/pretty-format': 2.1.9 + magic-string: 0.30.19 + pathe: 1.1.2 + '@vitest/snapshot@3.2.4': dependencies: '@vitest/pretty-format': 3.2.4 magic-string: 0.30.19 pathe: 2.0.3 + '@vitest/spy@2.1.9': + dependencies: + tinyspy: 3.0.2 + '@vitest/spy@3.2.4': dependencies: tinyspy: 4.0.4 + '@vitest/utils@2.1.9': + dependencies: + '@vitest/pretty-format': 2.1.9 + loupe: 3.2.1 + tinyrainbow: 1.2.0 + '@vitest/utils@3.2.4': dependencies: '@vitest/pretty-format': 3.2.4 @@ -12114,8 +12595,6 @@ snapshots: character-reference-invalid@2.0.1: {} - chardet@2.1.0: {} - chardet@2.1.1: {} check-error@2.1.1: {} @@ -12555,6 +13034,40 @@ snapshots: transitivePeerDependencies: - supports-color + esbuild-register@3.6.0(esbuild@0.27.2): + dependencies: + debug: 4.4.3 + esbuild: 0.27.2 + transitivePeerDependencies: + - supports-color + optional: true + + esbuild@0.21.5: + optionalDependencies: + '@esbuild/aix-ppc64': 0.21.5 + '@esbuild/android-arm': 0.21.5 + '@esbuild/android-arm64': 0.21.5 + '@esbuild/android-x64': 0.21.5 + '@esbuild/darwin-arm64': 0.21.5 + '@esbuild/darwin-x64': 0.21.5 + '@esbuild/freebsd-arm64': 0.21.5 + '@esbuild/freebsd-x64': 0.21.5 + '@esbuild/linux-arm': 0.21.5 + '@esbuild/linux-arm64': 0.21.5 + '@esbuild/linux-ia32': 0.21.5 + '@esbuild/linux-loong64': 0.21.5 + '@esbuild/linux-mips64el': 0.21.5 + '@esbuild/linux-ppc64': 0.21.5 + '@esbuild/linux-riscv64': 0.21.5 + '@esbuild/linux-s390x': 0.21.5 + '@esbuild/linux-x64': 0.21.5 + '@esbuild/netbsd-x64': 0.21.5 + '@esbuild/openbsd-x64': 0.21.5 + '@esbuild/sunos-x64': 0.21.5 + '@esbuild/win32-arm64': 0.21.5 + '@esbuild/win32-ia32': 0.21.5 + '@esbuild/win32-x64': 0.21.5 + esbuild@0.25.11: optionalDependencies: '@esbuild/aix-ppc64': 0.25.11 @@ -12621,19 +13134,19 @@ snapshots: escape-string-regexp@5.0.0: {} - eslint-config-next@15.5.3(eslint-plugin-import-x@4.16.1(@typescript-eslint/utils@8.44.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2))(eslint-import-resolver-node@0.3.9)(eslint@9.36.0(jiti@2.6.0)))(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2): + eslint-config-next@15.5.3(eslint-plugin-import-x@4.16.1(@typescript-eslint/utils@8.44.1(eslint@9.36.0(jiti@2.6.1))(typescript@5.9.2))(eslint-import-resolver-node@0.3.9)(eslint@9.36.0(jiti@2.6.1)))(eslint@9.36.0(jiti@2.6.1))(typescript@5.9.2): dependencies: '@next/eslint-plugin-next': 15.5.3 '@rushstack/eslint-patch': 1.12.0 - '@typescript-eslint/eslint-plugin': 8.44.1(@typescript-eslint/parser@8.44.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2))(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2) - '@typescript-eslint/parser': 8.44.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2) - eslint: 9.36.0(jiti@2.6.0) + '@typescript-eslint/eslint-plugin': 8.44.1(@typescript-eslint/parser@8.44.1(eslint@9.36.0(jiti@2.6.1))(typescript@5.9.2))(eslint@9.36.0(jiti@2.6.1))(typescript@5.9.2) + '@typescript-eslint/parser': 8.44.1(eslint@9.36.0(jiti@2.6.1))(typescript@5.9.2) + eslint: 9.36.0(jiti@2.6.1) eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import-x@4.16.1(@typescript-eslint/utils@8.44.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2))(eslint-import-resolver-node@0.3.9)(eslint@9.36.0(jiti@2.6.0)))(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.44.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2))(eslint@9.36.0(jiti@2.6.0)))(eslint@9.36.0(jiti@2.6.0)) - eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.44.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2))(eslint-import-resolver-typescript@3.10.1)(eslint@9.36.0(jiti@2.6.0)) - eslint-plugin-jsx-a11y: 6.10.2(eslint@9.36.0(jiti@2.6.0)) - eslint-plugin-react: 7.37.5(eslint@9.36.0(jiti@2.6.0)) - eslint-plugin-react-hooks: 5.2.0(eslint@9.36.0(jiti@2.6.0)) + eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import-x@4.16.1(@typescript-eslint/utils@8.44.1(eslint@9.36.0(jiti@2.6.1))(typescript@5.9.2))(eslint-import-resolver-node@0.3.9)(eslint@9.36.0(jiti@2.6.1)))(eslint-plugin-import@2.32.0)(eslint@9.36.0(jiti@2.6.1)) + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.44.1(eslint@9.36.0(jiti@2.6.1))(typescript@5.9.2))(eslint-import-resolver-typescript@3.10.1)(eslint@9.36.0(jiti@2.6.1)) + eslint-plugin-jsx-a11y: 6.10.2(eslint@9.36.0(jiti@2.6.1)) + eslint-plugin-react: 7.37.5(eslint@9.36.0(jiti@2.6.1)) + eslint-plugin-react-hooks: 5.2.0(eslint@9.36.0(jiti@2.6.1)) optionalDependencies: typescript: 5.9.2 transitivePeerDependencies: @@ -12641,10 +13154,10 @@ snapshots: - eslint-plugin-import-x - supports-color - eslint-config-turbo@2.5.6(eslint@9.36.0(jiti@2.6.0))(turbo@2.5.6): + eslint-config-turbo@2.5.6(eslint@9.36.0(jiti@2.6.1))(turbo@2.5.6): dependencies: - eslint: 9.36.0(jiti@2.6.0) - eslint-plugin-turbo: 2.5.6(eslint@9.36.0(jiti@2.6.0))(turbo@2.5.6) + eslint: 9.36.0(jiti@2.6.1) + eslint-plugin-turbo: 2.5.6(eslint@9.36.0(jiti@2.6.1))(turbo@2.5.6) turbo: 2.5.6 eslint-import-context@0.1.9(unrs-resolver@1.11.1): @@ -12662,39 +13175,39 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-import-resolver-typescript@3.10.1(eslint-plugin-import-x@4.16.1(@typescript-eslint/utils@8.44.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2))(eslint-import-resolver-node@0.3.9)(eslint@9.36.0(jiti@2.6.0)))(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.44.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2))(eslint@9.36.0(jiti@2.6.0)))(eslint@9.36.0(jiti@2.6.0)): + eslint-import-resolver-typescript@3.10.1(eslint-plugin-import-x@4.16.1(@typescript-eslint/utils@8.44.1(eslint@9.36.0(jiti@2.6.1))(typescript@5.9.2))(eslint-import-resolver-node@0.3.9)(eslint@9.36.0(jiti@2.6.1)))(eslint-plugin-import@2.32.0)(eslint@9.36.0(jiti@2.6.1)): dependencies: '@nolyfill/is-core-module': 1.0.39 debug: 4.4.3 - eslint: 9.36.0(jiti@2.6.0) + eslint: 9.36.0(jiti@2.6.1) get-tsconfig: 4.10.1 is-bun-module: 2.0.0 stable-hash: 0.0.5 tinyglobby: 0.2.15 unrs-resolver: 1.11.1 optionalDependencies: - eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.44.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2))(eslint-import-resolver-typescript@3.10.1)(eslint@9.36.0(jiti@2.6.0)) - eslint-plugin-import-x: 4.16.1(@typescript-eslint/utils@8.44.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2))(eslint-import-resolver-node@0.3.9)(eslint@9.36.0(jiti@2.6.0)) + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.44.1(eslint@9.36.0(jiti@2.6.1))(typescript@5.9.2))(eslint-import-resolver-typescript@3.10.1)(eslint@9.36.0(jiti@2.6.1)) + eslint-plugin-import-x: 4.16.1(@typescript-eslint/utils@8.44.1(eslint@9.36.0(jiti@2.6.1))(typescript@5.9.2))(eslint-import-resolver-node@0.3.9)(eslint@9.36.0(jiti@2.6.1)) transitivePeerDependencies: - supports-color - eslint-module-utils@2.12.1(@typescript-eslint/parser@8.44.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@9.36.0(jiti@2.6.0)): + eslint-module-utils@2.12.1(@typescript-eslint/parser@8.44.1(eslint@9.36.0(jiti@2.6.1))(typescript@5.9.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@9.36.0(jiti@2.6.1)): dependencies: debug: 3.2.7 optionalDependencies: - '@typescript-eslint/parser': 8.44.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2) - eslint: 9.36.0(jiti@2.6.0) + '@typescript-eslint/parser': 8.44.1(eslint@9.36.0(jiti@2.6.1))(typescript@5.9.2) + eslint: 9.36.0(jiti@2.6.1) eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import-x@4.16.1(@typescript-eslint/utils@8.44.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2))(eslint-import-resolver-node@0.3.9)(eslint@9.36.0(jiti@2.6.0)))(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.44.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2))(eslint@9.36.0(jiti@2.6.0)))(eslint@9.36.0(jiti@2.6.0)) + eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import-x@4.16.1(@typescript-eslint/utils@8.44.1(eslint@9.36.0(jiti@2.6.1))(typescript@5.9.2))(eslint-import-resolver-node@0.3.9)(eslint@9.36.0(jiti@2.6.1)))(eslint-plugin-import@2.32.0)(eslint@9.36.0(jiti@2.6.1)) transitivePeerDependencies: - supports-color - eslint-plugin-import-x@4.16.1(@typescript-eslint/utils@8.44.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2))(eslint-import-resolver-node@0.3.9)(eslint@9.36.0(jiti@2.6.0)): + eslint-plugin-import-x@4.16.1(@typescript-eslint/utils@8.44.1(eslint@9.36.0(jiti@2.6.1))(typescript@5.9.2))(eslint-import-resolver-node@0.3.9)(eslint@9.36.0(jiti@2.6.1)): dependencies: '@typescript-eslint/types': 8.44.1 comment-parser: 1.4.1 debug: 4.4.3 - eslint: 9.36.0(jiti@2.6.0) + eslint: 9.36.0(jiti@2.6.1) eslint-import-context: 0.1.9(unrs-resolver@1.11.1) is-glob: 4.0.3 minimatch: 10.0.3 @@ -12702,12 +13215,12 @@ snapshots: stable-hash-x: 0.2.0 unrs-resolver: 1.11.1 optionalDependencies: - '@typescript-eslint/utils': 8.44.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2) + '@typescript-eslint/utils': 8.44.1(eslint@9.36.0(jiti@2.6.1))(typescript@5.9.2) eslint-import-resolver-node: 0.3.9 transitivePeerDependencies: - supports-color - eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.44.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2))(eslint-import-resolver-typescript@3.10.1)(eslint@9.36.0(jiti@2.6.0)): + eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.44.1(eslint@9.36.0(jiti@2.6.1))(typescript@5.9.2))(eslint-import-resolver-typescript@3.10.1)(eslint@9.36.0(jiti@2.6.1)): dependencies: '@rtsao/scc': 1.1.0 array-includes: 3.1.9 @@ -12716,9 +13229,9 @@ snapshots: array.prototype.flatmap: 1.3.3 debug: 3.2.7 doctrine: 2.1.0 - eslint: 9.36.0(jiti@2.6.0) + eslint: 9.36.0(jiti@2.6.1) eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.44.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@9.36.0(jiti@2.6.0)) + eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.44.1(eslint@9.36.0(jiti@2.6.1))(typescript@5.9.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@9.36.0(jiti@2.6.1)) hasown: 2.0.2 is-core-module: 2.16.1 is-glob: 4.0.3 @@ -12730,13 +13243,13 @@ snapshots: string.prototype.trimend: 1.0.9 tsconfig-paths: 3.15.0 optionalDependencies: - '@typescript-eslint/parser': 8.44.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2) + '@typescript-eslint/parser': 8.44.1(eslint@9.36.0(jiti@2.6.1))(typescript@5.9.2) transitivePeerDependencies: - eslint-import-resolver-typescript - eslint-import-resolver-webpack - supports-color - eslint-plugin-jsx-a11y@6.10.2(eslint@9.36.0(jiti@2.6.0)): + eslint-plugin-jsx-a11y@6.10.2(eslint@9.36.0(jiti@2.6.1)): dependencies: aria-query: 5.3.2 array-includes: 3.1.9 @@ -12746,7 +13259,7 @@ snapshots: axobject-query: 4.1.0 damerau-levenshtein: 1.0.8 emoji-regex: 9.2.2 - eslint: 9.36.0(jiti@2.6.0) + eslint: 9.36.0(jiti@2.6.1) hasown: 2.0.2 jsx-ast-utils: 3.3.5 language-tags: 1.0.9 @@ -12755,7 +13268,7 @@ snapshots: safe-regex-test: 1.1.0 string.prototype.includes: 2.0.1 - eslint-plugin-jsx-a11y@6.10.2(eslint@9.39.1(jiti@2.6.0)): + eslint-plugin-jsx-a11y@6.10.2(eslint@9.39.1(jiti@2.6.1)): dependencies: aria-query: 5.3.2 array-includes: 3.1.9 @@ -12765,7 +13278,7 @@ snapshots: axobject-query: 4.1.0 damerau-levenshtein: 1.0.8 emoji-regex: 9.2.2 - eslint: 9.39.1(jiti@2.6.0) + eslint: 9.39.1(jiti@2.6.1) hasown: 2.0.2 jsx-ast-utils: 3.3.5 language-tags: 1.0.9 @@ -12774,11 +13287,11 @@ snapshots: safe-regex-test: 1.1.0 string.prototype.includes: 2.0.1 - eslint-plugin-react-hooks@5.2.0(eslint@9.36.0(jiti@2.6.0)): + eslint-plugin-react-hooks@5.2.0(eslint@9.36.0(jiti@2.6.1)): dependencies: - eslint: 9.36.0(jiti@2.6.0) + eslint: 9.36.0(jiti@2.6.1) - eslint-plugin-react@7.37.5(eslint@9.36.0(jiti@2.6.0)): + eslint-plugin-react@7.37.5(eslint@9.36.0(jiti@2.6.1)): dependencies: array-includes: 3.1.9 array.prototype.findlast: 1.2.5 @@ -12786,7 +13299,7 @@ snapshots: array.prototype.tosorted: 1.1.4 doctrine: 2.1.0 es-iterator-helpers: 1.2.1 - eslint: 9.36.0(jiti@2.6.0) + eslint: 9.36.0(jiti@2.6.1) estraverse: 5.3.0 hasown: 2.0.2 jsx-ast-utils: 3.3.5 @@ -12800,19 +13313,19 @@ snapshots: string.prototype.matchall: 4.0.12 string.prototype.repeat: 1.0.0 - eslint-plugin-storybook@9.1.13(eslint@9.36.0(jiti@2.6.0))(storybook@9.1.17(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@7.3.1(@types/node@22.18.6)(jiti@2.6.0)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5)))(typescript@5.9.2): + eslint-plugin-storybook@9.1.13(eslint@9.36.0(jiti@2.6.1))(storybook@9.1.17(@testing-library/dom@10.4.1)(prettier@3.8.1)(vite@7.3.1(@types/node@22.18.6)(jiti@2.6.1)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5)))(typescript@5.9.2): dependencies: - '@typescript-eslint/utils': 8.44.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2) - eslint: 9.36.0(jiti@2.6.0) - storybook: 9.1.17(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@7.3.1(@types/node@22.18.6)(jiti@2.6.0)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5)) + '@typescript-eslint/utils': 8.44.1(eslint@9.36.0(jiti@2.6.1))(typescript@5.9.2) + eslint: 9.36.0(jiti@2.6.1) + storybook: 9.1.17(@testing-library/dom@10.4.1)(prettier@3.8.1)(vite@7.3.1(@types/node@22.18.6)(jiti@2.6.1)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5)) transitivePeerDependencies: - supports-color - typescript - eslint-plugin-turbo@2.5.6(eslint@9.36.0(jiti@2.6.0))(turbo@2.5.6): + eslint-plugin-turbo@2.5.6(eslint@9.36.0(jiti@2.6.1))(turbo@2.5.6): dependencies: dotenv: 16.0.3 - eslint: 9.36.0(jiti@2.6.0) + eslint: 9.36.0(jiti@2.6.1) turbo: 2.5.6 eslint-scope@8.4.0: @@ -12824,9 +13337,9 @@ snapshots: eslint-visitor-keys@4.2.1: {} - eslint@9.36.0(jiti@2.6.0): + eslint@9.36.0(jiti@2.6.1): dependencies: - '@eslint-community/eslint-utils': 4.9.0(eslint@9.36.0(jiti@2.6.0)) + '@eslint-community/eslint-utils': 4.9.0(eslint@9.36.0(jiti@2.6.1)) '@eslint-community/regexpp': 4.12.1 '@eslint/config-array': 0.21.0 '@eslint/config-helpers': 0.3.1 @@ -12862,13 +13375,13 @@ snapshots: natural-compare: 1.4.0 optionator: 0.9.4 optionalDependencies: - jiti: 2.6.0 + jiti: 2.6.1 transitivePeerDependencies: - supports-color - eslint@9.39.1(jiti@2.6.0): + eslint@9.39.1(jiti@2.6.1): dependencies: - '@eslint-community/eslint-utils': 4.9.0(eslint@9.39.1(jiti@2.6.0)) + '@eslint-community/eslint-utils': 4.9.0(eslint@9.39.1(jiti@2.6.1)) '@eslint-community/regexpp': 4.12.1 '@eslint/config-array': 0.21.1 '@eslint/config-helpers': 0.4.2 @@ -12903,7 +13416,48 @@ snapshots: natural-compare: 1.4.0 optionator: 0.9.4 optionalDependencies: - jiti: 2.6.0 + jiti: 2.6.1 + transitivePeerDependencies: + - supports-color + + eslint@9.39.2(jiti@2.6.1): + dependencies: + '@eslint-community/eslint-utils': 4.9.0(eslint@9.39.2(jiti@2.6.1)) + '@eslint-community/regexpp': 4.12.1 + '@eslint/config-array': 0.21.1 + '@eslint/config-helpers': 0.4.2 + '@eslint/core': 0.17.0 + '@eslint/eslintrc': 3.3.1 + '@eslint/js': 9.39.2 + '@eslint/plugin-kit': 0.4.1 + '@humanfs/node': 0.16.7 + '@humanwhocodes/module-importer': 1.0.1 + '@humanwhocodes/retry': 0.4.3 + '@types/estree': 1.0.8 + ajv: 6.12.6 + chalk: 4.1.2 + cross-spawn: 7.0.6 + debug: 4.4.3 + escape-string-regexp: 4.0.0 + eslint-scope: 8.4.0 + eslint-visitor-keys: 4.2.1 + espree: 10.4.0 + esquery: 1.6.0 + esutils: 2.0.3 + fast-deep-equal: 3.1.3 + file-entry-cache: 8.0.0 + find-up: 5.0.0 + glob-parent: 6.0.2 + ignore: 5.3.2 + imurmurhash: 0.1.4 + is-glob: 4.0.3 + json-stable-stringify-without-jsonify: 1.0.1 + lodash.merge: 4.6.2 + minimatch: 3.1.2 + natural-compare: 1.4.0 + optionator: 0.9.4 + optionalDependencies: + jiti: 2.6.1 transitivePeerDependencies: - supports-color @@ -13173,7 +13727,7 @@ snapshots: unist-util-visit: 5.0.0 zod: 4.1.11 - fumadocs-mdx@11.10.1(@fumadocs/mdx-remote@1.4.0(@types/react@18.3.27)(fumadocs-core@15.7.13(@oramacloud/client@2.1.4)(@types/react@18.3.27)(next@15.5.9(@babel/core@7.28.4)(@playwright/test@1.56.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.93.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1))(fumadocs-core@15.7.13(@oramacloud/client@2.1.4)(@types/react@18.3.27)(next@15.5.9(@babel/core@7.28.4)(@playwright/test@1.56.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.93.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(next@15.5.9(@babel/core@7.28.4)(@playwright/test@1.56.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.93.1))(react@18.3.1)(vite@7.3.1(@types/node@22.18.6)(jiti@2.6.0)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5)): + fumadocs-mdx@11.10.1(@fumadocs/mdx-remote@1.4.0(@types/react@18.3.27)(fumadocs-core@15.7.13(@oramacloud/client@2.1.4)(@types/react@18.3.27)(next@15.5.9(@babel/core@7.28.4)(@playwright/test@1.56.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.93.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1))(fumadocs-core@15.7.13(@oramacloud/client@2.1.4)(@types/react@18.3.27)(next@15.5.9(@babel/core@7.28.4)(@playwright/test@1.56.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.93.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(next@15.5.9(@babel/core@7.28.4)(@playwright/test@1.56.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.93.1))(react@18.3.1)(vite@7.3.1(@types/node@22.18.6)(jiti@2.6.1)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5)): dependencies: '@mdx-js/mdx': 3.1.1 '@standard-schema/spec': 1.0.0 @@ -13195,7 +13749,7 @@ snapshots: '@fumadocs/mdx-remote': 1.4.0(@types/react@18.3.27)(fumadocs-core@15.7.13(@oramacloud/client@2.1.4)(@types/react@18.3.27)(next@15.5.9(@babel/core@7.28.4)(@playwright/test@1.56.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.93.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1) next: 15.5.9(@babel/core@7.28.4)(@playwright/test@1.56.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.93.1) react: 18.3.1 - vite: 7.3.1(@types/node@22.18.6)(jiti@2.6.0)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5) + vite: 7.3.1(@types/node@22.18.6)(jiti@2.6.1)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5) transitivePeerDependencies: - supports-color @@ -13796,15 +14350,15 @@ snapshots: - babel-plugin-macros - supports-color - jest-cli@30.2.0(@types/node@20.19.17)(esbuild-register@3.6.0(esbuild@0.25.11)): + jest-cli@30.2.0(@types/node@20.19.17)(esbuild-register@3.6.0(esbuild@0.27.2)): dependencies: - '@jest/core': 30.2.0(esbuild-register@3.6.0(esbuild@0.25.11)) + '@jest/core': 30.2.0(esbuild-register@3.6.0(esbuild@0.27.2)) '@jest/test-result': 30.2.0 '@jest/types': 30.2.0 chalk: 4.1.2 exit-x: 0.2.2 import-local: 3.2.0 - jest-config: 30.2.0(@types/node@20.19.17)(esbuild-register@3.6.0(esbuild@0.25.11)) + jest-config: 30.2.0(@types/node@20.19.17)(esbuild-register@3.6.0(esbuild@0.27.2)) jest-util: 30.2.0 jest-validate: 30.2.0 yargs: 17.7.2 @@ -13815,7 +14369,7 @@ snapshots: - supports-color - ts-node - jest-config@30.2.0(@types/node@20.19.17)(esbuild-register@3.6.0(esbuild@0.25.11)): + jest-config@30.2.0(@types/node@20.19.17)(esbuild-register@3.6.0(esbuild@0.27.2)): dependencies: '@babel/core': 7.28.4 '@jest/get-type': 30.1.0 @@ -13843,12 +14397,12 @@ snapshots: strip-json-comments: 3.1.1 optionalDependencies: '@types/node': 20.19.17 - esbuild-register: 3.6.0(esbuild@0.25.11) + esbuild-register: 3.6.0(esbuild@0.27.2) transitivePeerDependencies: - babel-plugin-macros - supports-color - jest-config@30.2.0(@types/node@22.18.6)(esbuild-register@3.6.0(esbuild@0.25.11)): + jest-config@30.2.0(@types/node@22.18.6)(esbuild-register@3.6.0(esbuild@0.27.2)): dependencies: '@babel/core': 7.28.4 '@jest/get-type': 30.1.0 @@ -13876,7 +14430,7 @@ snapshots: strip-json-comments: 3.1.1 optionalDependencies: '@types/node': 22.18.6 - esbuild-register: 3.6.0(esbuild@0.25.11) + esbuild-register: 3.6.0(esbuild@0.27.2) transitivePeerDependencies: - babel-plugin-macros - supports-color @@ -14133,12 +14687,12 @@ snapshots: merge-stream: 2.0.0 supports-color: 8.1.1 - jest@30.2.0(@types/node@20.19.17)(esbuild-register@3.6.0(esbuild@0.25.11)): + jest@30.2.0(@types/node@20.19.17)(esbuild-register@3.6.0(esbuild@0.27.2)): dependencies: - '@jest/core': 30.2.0(esbuild-register@3.6.0(esbuild@0.25.11)) + '@jest/core': 30.2.0(esbuild-register@3.6.0(esbuild@0.27.2)) '@jest/types': 30.2.0 import-local: 3.2.0 - jest-cli: 30.2.0(@types/node@20.19.17)(esbuild-register@3.6.0(esbuild@0.25.11)) + jest-cli: 30.2.0(@types/node@20.19.17)(esbuild-register@3.6.0(esbuild@0.27.2)) transitivePeerDependencies: - '@types/node' - babel-plugin-macros @@ -14148,6 +14702,8 @@ snapshots: jiti@2.6.0: {} + jiti@2.6.1: {} + joycon@3.1.1: {} js-tokens@4.0.0: {} @@ -14850,6 +15406,8 @@ snapshots: dependencies: minipass: 7.1.2 + mkdirp@3.0.1: {} + mlly@1.8.0: dependencies: acorn: 8.15.0 @@ -15148,6 +15706,8 @@ snapshots: path-type@6.0.0: {} + pathe@1.1.2: {} + pathe@2.0.3: {} pathval@2.0.1: {} @@ -15186,11 +15746,11 @@ snapshots: possible-typed-array-names@1.1.0: {} - postcss-load-config@6.0.1(jiti@2.6.0)(postcss@8.5.6)(tsx@4.20.5): + postcss-load-config@6.0.1(jiti@2.6.1)(postcss@8.5.6)(tsx@4.20.5): dependencies: lilconfig: 3.1.3 optionalDependencies: - jiti: 2.6.0 + jiti: 2.6.1 postcss: 8.5.6 tsx: 4.20.5 @@ -15219,6 +15779,8 @@ snapshots: prettier@3.6.2: {} + prettier@3.8.1: {} + pretty-format@27.5.1: dependencies: ansi-regex: 5.0.1 @@ -15580,11 +16142,11 @@ snapshots: optionalDependencies: '@babel/code-frame': 7.27.1 - rollup-plugin-esbuild@6.2.1(esbuild@0.25.11)(rollup@4.52.5): + rollup-plugin-esbuild@6.2.1(esbuild@0.27.2)(rollup@4.52.5): dependencies: debug: 4.4.3 es-module-lexer: 1.7.0 - esbuild: 0.25.11 + esbuild: 0.27.2 get-tsconfig: 4.10.1 rollup: 4.52.5 unplugin-utils: 0.2.5 @@ -15889,13 +16451,13 @@ snapshots: es-errors: 1.3.0 internal-slot: 1.1.0 - storybook@9.1.17(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@7.1.7(@types/node@22.18.6)(jiti@2.6.0)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5)): + storybook@9.1.17(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@7.1.7(@types/node@22.18.6)(jiti@2.6.1)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5)): dependencies: '@storybook/global': 5.0.0 '@testing-library/jest-dom': 6.8.0 '@testing-library/user-event': 14.6.1(@testing-library/dom@10.4.1) '@vitest/expect': 3.2.4 - '@vitest/mocker': 3.2.4(vite@7.1.7(@types/node@22.18.6)(jiti@2.6.0)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5)) + '@vitest/mocker': 3.2.4(vite@7.1.7(@types/node@22.18.6)(jiti@2.6.1)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5)) '@vitest/spy': 3.2.4 better-opn: 3.0.2 esbuild: 0.25.11 @@ -15913,13 +16475,13 @@ snapshots: - utf-8-validate - vite - storybook@9.1.17(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@7.3.1(@types/node@22.18.6)(jiti@2.6.0)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5)): + storybook@9.1.17(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@7.3.1(@types/node@22.18.6)(jiti@2.6.1)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5)): dependencies: '@storybook/global': 5.0.0 '@testing-library/jest-dom': 6.8.0 '@testing-library/user-event': 14.6.1(@testing-library/dom@10.4.1) '@vitest/expect': 3.2.4 - '@vitest/mocker': 3.2.4(vite@7.3.1(@types/node@22.18.6)(jiti@2.6.0)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5)) + '@vitest/mocker': 3.2.4(vite@7.3.1(@types/node@22.18.6)(jiti@2.6.1)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5)) '@vitest/spy': 3.2.4 better-opn: 3.0.2 esbuild: 0.25.11 @@ -15937,6 +16499,30 @@ snapshots: - utf-8-validate - vite + storybook@9.1.17(@testing-library/dom@10.4.1)(prettier@3.8.1)(vite@7.3.1(@types/node@22.18.6)(jiti@2.6.1)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5)): + dependencies: + '@storybook/global': 5.0.0 + '@testing-library/jest-dom': 6.8.0 + '@testing-library/user-event': 14.6.1(@testing-library/dom@10.4.1) + '@vitest/expect': 3.2.4 + '@vitest/mocker': 3.2.4(vite@7.3.1(@types/node@22.18.6)(jiti@2.6.1)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5)) + '@vitest/spy': 3.2.4 + better-opn: 3.0.2 + esbuild: 0.25.11 + esbuild-register: 3.6.0(esbuild@0.25.11) + recast: 0.23.11 + semver: 7.7.3 + ws: 8.18.3 + optionalDependencies: + prettier: 3.8.1 + transitivePeerDependencies: + - '@testing-library/dom' + - bufferutil + - msw + - supports-color + - utf-8-validate + - vite + string-length@4.0.2: dependencies: char-regex: 1.0.2 @@ -16143,8 +16729,12 @@ snapshots: tinypool@1.1.1: {} + tinyrainbow@1.2.0: {} + tinyrainbow@2.0.0: {} + tinyspy@3.0.2: {} + tinyspy@4.0.4: {} tmp@0.2.5: {} @@ -16181,12 +16771,12 @@ snapshots: ts-interface-checker@0.1.13: {} - ts-jest@29.4.5(@babel/core@7.28.4)(@jest/transform@30.2.0)(@jest/types@30.2.0)(babel-jest@30.2.0(@babel/core@7.28.4))(esbuild@0.25.11)(jest-util@30.2.0)(jest@30.2.0(@types/node@20.19.17)(esbuild-register@3.6.0(esbuild@0.25.11)))(typescript@5.9.3): + ts-jest@29.4.5(@babel/core@7.28.4)(@jest/transform@30.2.0)(@jest/types@30.2.0)(babel-jest@30.2.0(@babel/core@7.28.4))(esbuild@0.27.2)(jest-util@30.2.0)(jest@30.2.0(@types/node@20.19.17)(esbuild-register@3.6.0(esbuild@0.27.2)))(typescript@5.9.3): dependencies: bs-logger: 0.2.6 fast-json-stable-stringify: 2.1.0 handlebars: 4.7.8 - jest: 30.2.0(@types/node@20.19.17)(esbuild-register@3.6.0(esbuild@0.25.11)) + jest: 30.2.0(@types/node@20.19.17)(esbuild-register@3.6.0(esbuild@0.27.2)) json5: 2.2.3 lodash.memoize: 4.1.2 make-error: 1.3.6 @@ -16199,9 +16789,14 @@ snapshots: '@jest/transform': 30.2.0 '@jest/types': 30.2.0 babel-jest: 30.2.0(@babel/core@7.28.4) - esbuild: 0.25.11 + esbuild: 0.27.2 jest-util: 30.2.0 + ts-morph@22.0.0: + dependencies: + '@ts-morph/common': 0.23.0 + code-block-writer: 13.0.3 + ts-morph@26.0.0: dependencies: '@ts-morph/common': 0.27.0 @@ -16222,7 +16817,7 @@ snapshots: tslib@2.8.1: {} - tsup@8.5.0(jiti@2.6.0)(postcss@8.5.6)(tsx@4.20.5)(typescript@5.9.2): + tsup@8.5.0(jiti@2.6.1)(postcss@8.5.6)(tsx@4.20.5)(typescript@5.9.2): dependencies: bundle-require: 5.1.0(esbuild@0.25.11) cac: 6.7.14 @@ -16233,7 +16828,7 @@ snapshots: fix-dts-default-cjs-exports: 1.0.1 joycon: 3.1.1 picocolors: 1.1.1 - postcss-load-config: 6.0.1(jiti@2.6.0)(postcss@8.5.6)(tsx@4.20.5) + postcss-load-config: 6.0.1(jiti@2.6.1)(postcss@8.5.6)(tsx@4.20.5) resolve-from: 5.0.0 rollup: 4.52.1 source-map: 0.8.0-beta.0 @@ -16250,7 +16845,7 @@ snapshots: - tsx - yaml - tsup@8.5.0(jiti@2.6.0)(postcss@8.5.6)(tsx@4.20.5)(typescript@5.9.3): + tsup@8.5.0(jiti@2.6.1)(postcss@8.5.6)(tsx@4.20.5)(typescript@5.9.3): dependencies: bundle-require: 5.1.0(esbuild@0.25.11) cac: 6.7.14 @@ -16261,7 +16856,7 @@ snapshots: fix-dts-default-cjs-exports: 1.0.1 joycon: 3.1.1 picocolors: 1.1.1 - postcss-load-config: 6.0.1(jiti@2.6.0)(postcss@8.5.6)(tsx@4.20.5) + postcss-load-config: 6.0.1(jiti@2.6.1)(postcss@8.5.6)(tsx@4.20.5) resolve-from: 5.0.0 rollup: 4.52.1 source-map: 0.8.0-beta.0 @@ -16355,24 +16950,24 @@ snapshots: possible-typed-array-names: 1.1.0 reflect.getprototypeof: 1.0.10 - typescript-eslint@8.44.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.8.3): + typescript-eslint@8.44.1(eslint@9.36.0(jiti@2.6.1))(typescript@5.8.3): dependencies: - '@typescript-eslint/eslint-plugin': 8.44.1(@typescript-eslint/parser@8.44.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.8.3))(eslint@9.36.0(jiti@2.6.0))(typescript@5.8.3) - '@typescript-eslint/parser': 8.44.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.8.3) + '@typescript-eslint/eslint-plugin': 8.44.1(@typescript-eslint/parser@8.44.1(eslint@9.36.0(jiti@2.6.1))(typescript@5.8.3))(eslint@9.36.0(jiti@2.6.1))(typescript@5.8.3) + '@typescript-eslint/parser': 8.44.1(eslint@9.36.0(jiti@2.6.1))(typescript@5.8.3) '@typescript-eslint/typescript-estree': 8.44.1(typescript@5.8.3) - '@typescript-eslint/utils': 8.44.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.8.3) - eslint: 9.36.0(jiti@2.6.0) + '@typescript-eslint/utils': 8.44.1(eslint@9.36.0(jiti@2.6.1))(typescript@5.8.3) + eslint: 9.36.0(jiti@2.6.1) typescript: 5.8.3 transitivePeerDependencies: - supports-color - typescript-eslint@8.44.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2): + typescript-eslint@8.44.1(eslint@9.36.0(jiti@2.6.1))(typescript@5.9.2): dependencies: - '@typescript-eslint/eslint-plugin': 8.44.1(@typescript-eslint/parser@8.44.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2))(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2) - '@typescript-eslint/parser': 8.44.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2) + '@typescript-eslint/eslint-plugin': 8.44.1(@typescript-eslint/parser@8.44.1(eslint@9.36.0(jiti@2.6.1))(typescript@5.9.2))(eslint@9.36.0(jiti@2.6.1))(typescript@5.9.2) + '@typescript-eslint/parser': 8.44.1(eslint@9.36.0(jiti@2.6.1))(typescript@5.9.2) '@typescript-eslint/typescript-estree': 8.44.1(typescript@5.9.2) - '@typescript-eslint/utils': 8.44.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2) - eslint: 9.36.0(jiti@2.6.0) + '@typescript-eslint/utils': 8.44.1(eslint@9.36.0(jiti@2.6.1))(typescript@5.9.2) + eslint: 9.36.0(jiti@2.6.1) typescript: 5.9.2 transitivePeerDependencies: - supports-color @@ -16536,13 +17131,31 @@ snapshots: '@types/unist': 3.0.3 vfile-message: 4.0.3 - vite-node@3.2.4(@types/node@20.19.17)(jiti@2.6.0)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5): + vite-node@2.1.9(@types/node@20.19.17)(lightningcss@1.30.1)(sass@1.93.1): + dependencies: + cac: 6.7.14 + debug: 4.4.3 + es-module-lexer: 1.7.0 + pathe: 1.1.2 + vite: 5.4.21(@types/node@20.19.17)(lightningcss@1.30.1)(sass@1.93.1) + transitivePeerDependencies: + - '@types/node' + - less + - lightningcss + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + + vite-node@3.2.4(@types/node@20.19.17)(jiti@2.6.1)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5): dependencies: cac: 6.7.14 debug: 4.4.3 es-module-lexer: 1.7.0 pathe: 2.0.3 - vite: 7.1.7(@types/node@20.19.17)(jiti@2.6.0)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5) + vite: 7.1.7(@types/node@20.19.17)(jiti@2.6.1)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5) transitivePeerDependencies: - '@types/node' - jiti @@ -16557,13 +17170,13 @@ snapshots: - tsx - yaml - vite-node@3.2.4(@types/node@22.18.6)(jiti@2.6.0)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5): + vite-node@3.2.4(@types/node@22.18.6)(jiti@2.6.1)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5): dependencies: cac: 6.7.14 debug: 4.4.3 es-module-lexer: 1.7.0 pathe: 2.0.3 - vite: 7.1.7(@types/node@22.18.6)(jiti@2.6.0)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5) + vite: 7.1.7(@types/node@22.18.6)(jiti@2.6.1)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5) transitivePeerDependencies: - '@types/node' - jiti @@ -16578,13 +17191,24 @@ snapshots: - tsx - yaml - vite-plugin-singlefile@2.3.0(rollup@4.52.5)(vite@7.1.7(@types/node@22.18.6)(jiti@2.6.0)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5)): + vite-plugin-singlefile@2.3.0(rollup@4.52.5)(vite@7.1.7(@types/node@22.18.6)(jiti@2.6.1)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5)): dependencies: micromatch: 4.0.8 rollup: 4.52.5 - vite: 7.1.7(@types/node@22.18.6)(jiti@2.6.0)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5) + vite: 7.1.7(@types/node@22.18.6)(jiti@2.6.1)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5) + + vite@5.4.21(@types/node@20.19.17)(lightningcss@1.30.1)(sass@1.93.1): + dependencies: + esbuild: 0.21.5 + postcss: 8.5.6 + rollup: 4.52.5 + optionalDependencies: + '@types/node': 20.19.17 + fsevents: 2.3.3 + lightningcss: 1.30.1 + sass: 1.93.1 - vite@7.1.7(@types/node@20.19.17)(jiti@2.6.0)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5): + vite@7.1.7(@types/node@20.19.17)(jiti@2.6.1)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5): dependencies: esbuild: 0.25.11 fdir: 6.5.0(picomatch@4.0.3) @@ -16595,12 +17219,12 @@ snapshots: optionalDependencies: '@types/node': 20.19.17 fsevents: 2.3.3 - jiti: 2.6.0 + jiti: 2.6.1 lightningcss: 1.30.1 sass: 1.93.1 tsx: 4.20.5 - vite@7.1.7(@types/node@22.18.6)(jiti@2.6.0)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5): + vite@7.1.7(@types/node@22.18.6)(jiti@2.6.1)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5): dependencies: esbuild: 0.25.11 fdir: 6.5.0(picomatch@4.0.3) @@ -16611,12 +17235,12 @@ snapshots: optionalDependencies: '@types/node': 22.18.6 fsevents: 2.3.3 - jiti: 2.6.0 + jiti: 2.6.1 lightningcss: 1.30.1 sass: 1.93.1 tsx: 4.20.5 - vite@7.3.1(@types/node@20.19.17)(jiti@2.6.0)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5): + vite@7.3.1(@types/node@20.19.17)(jiti@2.6.1)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5): dependencies: esbuild: 0.27.2 fdir: 6.5.0(picomatch@4.0.3) @@ -16627,13 +17251,13 @@ snapshots: optionalDependencies: '@types/node': 20.19.17 fsevents: 2.3.3 - jiti: 2.6.0 + jiti: 2.6.1 lightningcss: 1.30.1 sass: 1.93.1 tsx: 4.20.5 optional: true - vite@7.3.1(@types/node@22.18.6)(jiti@2.6.0)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5): + vite@7.3.1(@types/node@22.18.6)(jiti@2.6.1)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5): dependencies: esbuild: 0.27.2 fdir: 6.5.0(picomatch@4.0.3) @@ -16644,7 +17268,7 @@ snapshots: optionalDependencies: '@types/node': 22.18.6 fsevents: 2.3.3 - jiti: 2.6.0 + jiti: 2.6.1 lightningcss: 1.30.1 sass: 1.93.1 tsx: 4.20.5 @@ -16655,13 +17279,49 @@ snapshots: axe-core: 4.10.3 chalk: 5.6.2 lodash-es: 4.17.21 - vitest: 3.2.4(@types/debug@4.1.12)(@types/node@22.18.6)(@vitest/browser@3.2.4)(happy-dom@20.4.0)(jiti@2.6.0)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5) + vitest: 3.2.4(@types/debug@4.1.12)(@types/node@22.18.6)(@vitest/browser@3.2.4)(happy-dom@20.4.0)(jiti@2.6.1)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5) - vitest@3.2.4(@types/debug@4.1.12)(@types/node@20.19.17)(@vitest/browser@3.2.4)(happy-dom@20.4.0)(jiti@2.6.0)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5): + vitest@2.1.9(@types/node@20.19.17)(happy-dom@20.4.0)(lightningcss@1.30.1)(sass@1.93.1): + dependencies: + '@vitest/expect': 2.1.9 + '@vitest/mocker': 2.1.9(vite@5.4.21(@types/node@20.19.17)(lightningcss@1.30.1)(sass@1.93.1)) + '@vitest/pretty-format': 2.1.9 + '@vitest/runner': 2.1.9 + '@vitest/snapshot': 2.1.9 + '@vitest/spy': 2.1.9 + '@vitest/utils': 2.1.9 + chai: 5.3.3 + debug: 4.4.3 + expect-type: 1.2.2 + magic-string: 0.30.19 + pathe: 1.1.2 + std-env: 3.9.0 + tinybench: 2.9.0 + tinyexec: 0.3.2 + tinypool: 1.1.1 + tinyrainbow: 1.2.0 + vite: 5.4.21(@types/node@20.19.17)(lightningcss@1.30.1)(sass@1.93.1) + vite-node: 2.1.9(@types/node@20.19.17)(lightningcss@1.30.1)(sass@1.93.1) + why-is-node-running: 2.3.0 + optionalDependencies: + '@types/node': 20.19.17 + happy-dom: 20.4.0 + transitivePeerDependencies: + - less + - lightningcss + - msw + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + + vitest@3.2.4(@types/debug@4.1.12)(@types/node@20.19.17)(@vitest/browser@3.2.4)(happy-dom@20.4.0)(jiti@2.6.1)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5): dependencies: '@types/chai': 5.2.2 '@vitest/expect': 3.2.4 - '@vitest/mocker': 3.2.4(vite@7.1.7(@types/node@20.19.17)(jiti@2.6.0)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5)) + '@vitest/mocker': 3.2.4(vite@7.1.7(@types/node@20.19.17)(jiti@2.6.1)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5)) '@vitest/pretty-format': 3.2.4 '@vitest/runner': 3.2.4 '@vitest/snapshot': 3.2.4 @@ -16679,13 +17339,13 @@ snapshots: tinyglobby: 0.2.15 tinypool: 1.1.1 tinyrainbow: 2.0.0 - vite: 7.1.7(@types/node@20.19.17)(jiti@2.6.0)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5) - vite-node: 3.2.4(@types/node@20.19.17)(jiti@2.6.0)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5) + vite: 7.1.7(@types/node@20.19.17)(jiti@2.6.1)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5) + vite-node: 3.2.4(@types/node@20.19.17)(jiti@2.6.1)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5) why-is-node-running: 2.3.0 optionalDependencies: '@types/debug': 4.1.12 '@types/node': 20.19.17 - '@vitest/browser': 3.2.4(playwright@1.56.1)(vite@7.3.1(@types/node@20.19.17)(jiti@2.6.0)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5))(vitest@3.2.4) + '@vitest/browser': 3.2.4(playwright@1.56.1)(vite@7.3.1(@types/node@20.19.17)(jiti@2.6.1)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5))(vitest@3.2.4) happy-dom: 20.4.0 transitivePeerDependencies: - jiti @@ -16701,11 +17361,11 @@ snapshots: - tsx - yaml - vitest@3.2.4(@types/debug@4.1.12)(@types/node@22.18.6)(@vitest/browser@3.2.4)(happy-dom@20.4.0)(jiti@2.6.0)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5): + vitest@3.2.4(@types/debug@4.1.12)(@types/node@22.18.6)(@vitest/browser@3.2.4)(happy-dom@20.4.0)(jiti@2.6.1)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5): dependencies: '@types/chai': 5.2.2 '@vitest/expect': 3.2.4 - '@vitest/mocker': 3.2.4(vite@7.1.7(@types/node@22.18.6)(jiti@2.6.0)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5)) + '@vitest/mocker': 3.2.4(vite@7.1.7(@types/node@22.18.6)(jiti@2.6.1)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5)) '@vitest/pretty-format': 3.2.4 '@vitest/runner': 3.2.4 '@vitest/snapshot': 3.2.4 @@ -16723,13 +17383,13 @@ snapshots: tinyglobby: 0.2.15 tinypool: 1.1.1 tinyrainbow: 2.0.0 - vite: 7.1.7(@types/node@22.18.6)(jiti@2.6.0)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5) - vite-node: 3.2.4(@types/node@22.18.6)(jiti@2.6.0)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5) + vite: 7.1.7(@types/node@22.18.6)(jiti@2.6.1)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5) + vite-node: 3.2.4(@types/node@22.18.6)(jiti@2.6.1)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5) why-is-node-running: 2.3.0 optionalDependencies: '@types/debug': 4.1.12 '@types/node': 22.18.6 - '@vitest/browser': 3.2.4(playwright@1.56.1)(vite@7.1.7(@types/node@22.18.6)(jiti@2.6.0)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5))(vitest@3.2.4) + '@vitest/browser': 3.2.4(playwright@1.56.1)(vite@7.1.7(@types/node@22.18.6)(jiti@2.6.1)(lightningcss@1.30.1)(sass@1.93.1)(tsx@4.20.5))(vitest@3.2.4) happy-dom: 20.4.0 transitivePeerDependencies: - jiti @@ -16879,6 +17539,8 @@ snapshots: yoctocolors-cjs@2.1.3: {} + zod@3.25.76: {} + zod@4.1.11: {} zwitch@2.0.4: {} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index ef298dedf..a379b8635 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -1,3 +1,4 @@ packages: - apps/* - packages/* + - scripts/docs-extractor diff --git a/scripts/docs-extractor/.gitignore b/scripts/docs-extractor/.gitignore new file mode 100644 index 000000000..96936a7b2 --- /dev/null +++ b/scripts/docs-extractor/.gitignore @@ -0,0 +1,5 @@ +# Build outputs +dist/ + +# Generated files (created by prebuild script) +generated/ diff --git a/scripts/docs-extractor/README.md b/scripts/docs-extractor/README.md new file mode 100644 index 000000000..085b27ab5 --- /dev/null +++ b/scripts/docs-extractor/README.md @@ -0,0 +1,156 @@ +# @vapor-ui/ts-api-extractor + +TypeScript AST 기반 컴포넌트 Props 추출 도구. vapor-ui 컴포넌트의 Props 타입 정보를 추출하여 문서화용 JSON 파일을 생성합니다. + +## 위치 선택 이유 + +이 패키지는 `packages/` 대신 `scripts/` 디렉토리에 위치합니다: + +- **단일 목적 내부 도구**: vapor-ui 문서 생성만을 위한 도구 +- **외부 배포 불필요**: npm publish 예정 없음 (`private: true`) +- **pnpm-workspace.yaml에 명시**: workspace 패키지로 정상 동작 + +## 설치 + +프로젝트 내부 도구이므로 별도 설치가 필요 없습니다. + +## 사용법 + +### 기본 실행 + +```bash +# 전체 컴포넌트 추출 +pnpm --filter @vapor-ui/ts-api-extractor exec ts-api-extractor ./packages/core/src/components + +# 특정 컴포넌트만 추출 +pnpm --filter @vapor-ui/ts-api-extractor exec ts-api-extractor ./packages/core/src/components --component Button + +# 출력 디렉토리 지정 +pnpm --filter @vapor-ui/ts-api-extractor exec ts-api-extractor ./packages/core/src/components --output-dir ./output +``` + +### CLI 옵션 + +| 옵션 | 단축 | 설명 | +| ---------------- | ---- | ------------------------------------------------- | +| `--tsconfig` | `-c` | tsconfig.json 경로 (기본: 자동 감지) | +| `--exclude` | `-e` | 제외 패턴 (여러 번 사용 가능) | +| `--component` | `-n` | 특정 컴포넌트만 추출 | +| `--output-dir` | `-d` | 출력 디렉토리 | +| `--all` | `-a` | 모든 props 포함 (node_modules + sprinkles + html) | +| `--include` | | 특정 props 포함 | +| `--include-html` | | HTML 속성 화이트리스트 | +| `--config` | | 설정 파일 경로 | +| `--no-config` | | 설정 파일 무시 | +| `--lang` | `-l` | 출력 언어 (ko, en, all) | + +## 설정 파일 + +`docs-extractor.config.ts` 파일로 추출 동작을 커스터마이징할 수 있습니다. + +```typescript +import { defineConfig } from '@vapor-ui/ts-api-extractor'; + +export default defineConfig({ + global: { + outputDir: './output', + languages: ['ko', 'en'], + defaultLanguage: 'ko', + filterExternal: true, // React/DOM 타입 제외 + filterSprinkles: true, // Sprinkles props 제외 + filterHtml: true, // HTML 속성 제외 + includeHtml: ['className', 'style'], // 허용할 HTML 속성 + }, + sprinkles: { + metaPath: './generated/sprinkles-meta.json', + include: ['padding', 'margin', 'gap'], + }, + components: { + 'box/box.tsx': { + sprinklesAll: true, // Box는 모든 sprinkles 포함 + }, + 'flex/flex.tsx': { + sprinkles: ['gap', 'alignItems', 'justifyContent'], + }, + }, +}); +``` + +설정 파일 예시는 `docs-extractor.config.example.ts`를 참고하세요. + +## 아키텍처 + +``` +scripts/docs-extractor/ +├── src/ +│ ├── bin/cli.ts # CLI 엔트리포인트 +│ ├── cli/ # CLI 로직, Interactive prompts +│ ├── config/ # Zod 기반 설정 시스템 +│ │ ├── loader.ts # 설정 파일 로딩 및 검증 +│ │ ├── schema.ts # Zod 스키마 정의 +│ │ └── defaults.ts # 기본값 (defineConfig로 오버라이드 가능) +│ ├── core/ # Props 추출 핵심 로직 +│ │ ├── props-extractor.ts # Props 추출 +│ │ ├── type-resolver.ts # 타입 변환 +│ │ ├── type-cleaner.ts # 타입 정제 +│ │ └── ... +│ ├── output/ # JSON 출력 +│ └── i18n/ # 다국어 경로 처리 +├── generated/ # 빌드 시 생성되는 메타데이터 (아래 참조) +│ └── sprinkles-meta.json +└── dist/ # 빌드 산출물 +``` + +### generated/ 폴더 + +`generated/sprinkles-meta.json`은 `pnpm build` 시 `prebuild` 스크립트에서 자동 생성됩니다: + +- **목적**: Sprinkles CSS props의 메타데이터 (토큰 사용 여부, CSS 속성 매핑 등) +- **생성 시점**: 빌드 전 (`scripts/generate-sprinkles-meta.ts` 실행) +- **사용처**: Props 추출 시 sprinkles props 필터링에 활용 +- **위치 이유**: 빌드 산출물이므로 소스 코드(`src/`)와 분리, `.gitignore`에 포함됨 + +## 출력 형식 + +추출된 JSON 파일 구조: + +```json +{ + "name": "Button", + "displayName": "Button", + "description": "버튼 컴포넌트", + "props": [ + { + "name": "size", + "type": ["sm", "md", "lg", "xl"], + "required": false, + "description": "버튼 크기", + "defaultValue": "md" + } + ], + "defaultElement": "button" +} +``` + +## 개발 + +```bash +# 빌드 +pnpm --filter @vapor-ui/ts-api-extractor build + +# 개발 모드 (watch) +pnpm --filter @vapor-ui/ts-api-extractor dev + +# 테스트 +pnpm --filter @vapor-ui/ts-api-extractor test + +# 테스트 (커버리지 포함) +pnpm --filter @vapor-ui/ts-api-extractor test:coverage + +# 타입 체크 +pnpm --filter @vapor-ui/ts-api-extractor typecheck +``` + +## 라이선스 + +Internal use only. diff --git a/scripts/docs-extractor/docs-extractor.config.example.ts b/scripts/docs-extractor/docs-extractor.config.example.ts new file mode 100644 index 000000000..213185f25 --- /dev/null +++ b/scripts/docs-extractor/docs-extractor.config.example.ts @@ -0,0 +1,29 @@ +import { defineConfig } from './src/config'; + +export default defineConfig({ + global: { + // 상대 경로 사용: 이 설정 파일은 scripts/docs-extractor/ 에서 실행되므로 + // apps/website/public/... 에 도달하려면 두 단계 상위로 이동 필요 + outputDir: '../../apps/website/public/components/generated', + languages: ['ko', 'en'], + defaultLanguage: 'en', + filterExternal: true, + filterSprinkles: true, + filterHtml: true, + includeHtml: ['className', 'style'], + }, + + sprinkles: { + metaPath: './generated/sprinkles-meta.json', + include: ['padding', 'paddingX', 'paddingY', 'margin', 'gap', 'color'], + }, + + components: { + 'box/box.tsx': { + sprinklesAll: true, + }, + 'flex/flex.tsx': { + sprinkles: ['gap', 'alignItems', 'justifyContent'], + }, + }, +}); diff --git a/scripts/docs-extractor/eslint.config.mjs b/scripts/docs-extractor/eslint.config.mjs new file mode 100644 index 000000000..68b4b5c13 --- /dev/null +++ b/scripts/docs-extractor/eslint.config.mjs @@ -0,0 +1,3 @@ +import { configs } from '@repo/eslint-config/base'; + +export default [...configs]; diff --git a/scripts/docs-extractor/generated/sprinkles-meta.json b/scripts/docs-extractor/generated/sprinkles-meta.json new file mode 100644 index 000000000..3d61b6ea6 --- /dev/null +++ b/scripts/docs-extractor/generated/sprinkles-meta.json @@ -0,0 +1,249 @@ +{ + "tokenProps": [ + "gap", + "padding", + "paddingTop", + "paddingBottom", + "paddingLeft", + "paddingRight", + "margin", + "marginTop", + "marginBottom", + "marginLeft", + "marginRight", + "width", + "height", + "minWidth", + "minHeight", + "maxWidth", + "maxHeight", + "borderColor", + "borderRadius", + "backgroundColor", + "color", + "paddingX", + "paddingY", + "marginX", + "marginY" + ], + "nonTokenProps": [ + "position", + "display", + "alignItems", + "justifyContent", + "flexDirection", + "alignContent", + "border", + "opacity", + "pointerEvents", + "overflow", + "textAlign" + ], + "propDefinitions": { + "position": { + "usesToken": false, + "cssProperty": "position", + "displayTypeName": "CSSProperties['position']" + }, + "display": { + "usesToken": false, + "cssProperty": "display", + "displayTypeName": "CSSProperties['display']" + }, + "alignItems": { + "usesToken": false, + "cssProperty": "alignItems", + "displayTypeName": "CSSProperties['alignItems']" + }, + "justifyContent": { + "usesToken": false, + "cssProperty": "justifyContent", + "displayTypeName": "CSSProperties['justifyContent']" + }, + "flexDirection": { + "usesToken": false, + "cssProperty": "flexDirection", + "displayTypeName": "CSSProperties['flexDirection']" + }, + "gap": { + "usesToken": true, + "tokenPath": "vars.size.space", + "cssProperty": "gap", + "displayTypeName": "SpaceToken" + }, + "alignContent": { + "usesToken": false, + "cssProperty": "alignContent", + "displayTypeName": "CSSProperties['alignContent']" + }, + "padding": { + "usesToken": true, + "tokenPath": "vars.size.space", + "cssProperty": "padding", + "displayTypeName": "SpaceToken" + }, + "paddingTop": { + "usesToken": true, + "tokenPath": "vars.size.space", + "cssProperty": "paddingTop", + "displayTypeName": "SpaceToken" + }, + "paddingBottom": { + "usesToken": true, + "tokenPath": "vars.size.space", + "cssProperty": "paddingBottom", + "displayTypeName": "SpaceToken" + }, + "paddingLeft": { + "usesToken": true, + "tokenPath": "vars.size.space", + "cssProperty": "paddingLeft", + "displayTypeName": "SpaceToken" + }, + "paddingRight": { + "usesToken": true, + "tokenPath": "vars.size.space", + "cssProperty": "paddingRight", + "displayTypeName": "SpaceToken" + }, + "margin": { + "usesToken": true, + "tokenPath": "vars.size.space", + "cssProperty": "margin", + "displayTypeName": "SpaceToken | NegativeSpaceToken" + }, + "marginTop": { + "usesToken": true, + "tokenPath": "vars.size.space", + "cssProperty": "marginTop", + "displayTypeName": "SpaceToken | NegativeSpaceToken" + }, + "marginBottom": { + "usesToken": true, + "tokenPath": "vars.size.space", + "cssProperty": "marginBottom", + "displayTypeName": "SpaceToken | NegativeSpaceToken" + }, + "marginLeft": { + "usesToken": true, + "tokenPath": "vars.size.space", + "cssProperty": "marginLeft", + "displayTypeName": "SpaceToken | NegativeSpaceToken" + }, + "marginRight": { + "usesToken": true, + "tokenPath": "vars.size.space", + "cssProperty": "marginRight", + "displayTypeName": "SpaceToken | NegativeSpaceToken" + }, + "width": { + "usesToken": true, + "tokenPath": "vars.size.dimension", + "cssProperty": "width", + "displayTypeName": "DimensionToken" + }, + "height": { + "usesToken": true, + "tokenPath": "vars.size.dimension", + "cssProperty": "height", + "displayTypeName": "DimensionToken" + }, + "minWidth": { + "usesToken": true, + "tokenPath": "vars.size.dimension", + "cssProperty": "minWidth", + "displayTypeName": "DimensionToken" + }, + "minHeight": { + "usesToken": true, + "tokenPath": "vars.size.dimension", + "cssProperty": "minHeight", + "displayTypeName": "DimensionToken" + }, + "maxWidth": { + "usesToken": true, + "tokenPath": "vars.size.dimension", + "cssProperty": "maxWidth", + "displayTypeName": "DimensionToken" + }, + "maxHeight": { + "usesToken": true, + "tokenPath": "vars.size.dimension", + "cssProperty": "maxHeight", + "displayTypeName": "DimensionToken" + }, + "border": { + "usesToken": false, + "cssProperty": "border", + "displayTypeName": "CSSProperties['border']" + }, + "borderColor": { + "usesToken": true, + "tokenPath": "vars.color.border", + "cssProperty": "borderColor", + "displayTypeName": "BorderColorToken" + }, + "borderRadius": { + "usesToken": true, + "tokenPath": "vars.size.borderRadius", + "cssProperty": "borderRadius", + "displayTypeName": "BorderRadiusToken" + }, + "backgroundColor": { + "usesToken": true, + "tokenPath": "vars.color.background", + "cssProperty": "backgroundColor", + "displayTypeName": "BackgroundColorToken" + }, + "color": { + "usesToken": true, + "tokenPath": "vars.color.foreground", + "cssProperty": "color", + "displayTypeName": "ForegroundColorToken" + }, + "opacity": { + "usesToken": false, + "cssProperty": "opacity", + "displayTypeName": "CSSProperties['opacity']" + }, + "pointerEvents": { + "usesToken": false, + "cssProperty": "pointerEvents", + "displayTypeName": "CSSProperties['pointerEvents']" + }, + "overflow": { + "usesToken": false, + "cssProperty": "overflow", + "displayTypeName": "CSSProperties['overflow']" + }, + "textAlign": { + "usesToken": false, + "cssProperty": "textAlign", + "displayTypeName": "CSSProperties['textAlign']" + }, + "paddingX": { + "usesToken": true, + "tokenPath": "vars.size.space", + "cssProperty": "paddingX", + "displayTypeName": "SpaceToken" + }, + "paddingY": { + "usesToken": true, + "tokenPath": "vars.size.space", + "cssProperty": "paddingY", + "displayTypeName": "SpaceToken" + }, + "marginX": { + "usesToken": true, + "tokenPath": "vars.size.space", + "cssProperty": "marginX", + "displayTypeName": "SpaceToken | NegativeSpaceToken" + }, + "marginY": { + "usesToken": true, + "tokenPath": "vars.size.space", + "cssProperty": "marginY", + "displayTypeName": "SpaceToken | NegativeSpaceToken" + } + } +} \ No newline at end of file diff --git a/scripts/docs-extractor/package.json b/scripts/docs-extractor/package.json new file mode 100644 index 000000000..8fd7baaf9 --- /dev/null +++ b/scripts/docs-extractor/package.json @@ -0,0 +1,58 @@ +{ + "name": "@vapor-ui/ts-api-extractor", + "version": "0.0.1", + "private": true, + "description": "TypeScript AST-based API extractor for documentation generation", + "type": "module", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.js" + } + }, + "main": "./dist/index.js", + "types": "./dist/index.d.ts", + "bin": { + "ts-api-extractor": "./dist/bin/cli.js" + }, + "files": [ + "dist" + ], + "scripts": { + "prebuild": "tsx scripts/generate-sprinkles-meta.ts", + "build": "tsup", + "dev": "tsup --watch", + "format": "prettier --write \"./**/*.{ts,md,mjs}\"", + "generate:sprinkles": "tsx scripts/generate-sprinkles-meta.ts", + "lint": "eslint ./src", + "test": "vitest", + "test:coverage": "vitest run --coverage", + "test:run": "vitest run", + "typecheck": "tsc --noEmit" + }, + "dependencies": { + "@inquirer/prompts": "^7.9.0", + "glob": "^10.3.0", + "jiti": "^2.6.1", + "meow": "^14.0.0", + "ts-morph": "^22.0.0", + "zod": "^3.22.0" + }, + "devDependencies": { + "@repo/eslint-config": "workspace:*", + "@types/node": "^20.10.0", + "@vitest/coverage-v8": "^2.0.0", + "eslint": "^9.39.2", + "prettier": "^3.8.1", + "tsup": "^8.0.0", + "tsx": "^4.0.0", + "typescript": "^5.3.0", + "vitest": "^2.0.0" + }, + "peerDependencies": { + "typescript": ">=5.0.0" + }, + "engines": { + "node": ">=20.19" + } +} diff --git a/scripts/docs-extractor/scripts/generate-sprinkles-meta.ts b/scripts/docs-extractor/scripts/generate-sprinkles-meta.ts new file mode 100644 index 000000000..1707eb3b0 --- /dev/null +++ b/scripts/docs-extractor/scripts/generate-sprinkles-meta.ts @@ -0,0 +1,252 @@ +#!/usr/bin/env tsx +/** + * Sprinkles 메타데이터 생성 스크립트 + * + * 이 스크립트는 @vanilla-extract/sprinkles의 defineProperties 호출을 분석하여 + * 각 CSS prop이 디자인 토큰을 사용하는지 여부를 추출합니다. + * + * 생성된 메타데이터는 ts-api-extractor가 컴포넌트 props를 추출할 때 + * sprinkles props를 필터링하거나 토큰 정보를 문서화하는 데 사용됩니다. + * + * 실행 시점: + * - `pnpm build` 전 (prebuild hook) + * - `pnpm generate:sprinkles` 수동 실행 + * + * 출력: generated/sprinkles-meta.json + */ +import fs from 'node:fs'; +import path from 'node:path'; +import { + type ObjectLiteralExpression, + Project, + type PropertyAssignment, + SyntaxKind, +} from 'ts-morph'; + +interface PropDefinition { + usesToken: boolean; + tokenPath?: string; + cssProperty: string; + displayTypeName?: string; +} + +interface SprinklesMeta { + tokenProps: string[]; + nonTokenProps: string[]; + propDefinitions: Record; +} + +function getTokenPath(initializerName: string): string | undefined { + const tokenMapping: Record = { + spaceTokens: 'vars.size.space', + marginTokens: 'vars.size.space', + dimensionTokens: 'vars.size.dimension', + radiusTokens: 'vars.size.borderRadius', + bgColorTokens: 'vars.color.background', + colorTokens: 'vars.color.foreground', + borderColorTokens: 'vars.color.border', + }; + + return tokenMapping[initializerName]; +} + +/** + * tokenPath에서 displayTypeName을 자동 생성합니다. + * + * 예: "vars.size.space" → "SpaceToken" + * 예: "vars.color.foreground" → "ForegroundColorToken" + * 예: "vars.size.borderRadius" → "BorderRadiusToken" + */ +function generateDisplayTypeFromPath(tokenPath: string): string | undefined { + const parts = tokenPath.split('.'); + if (parts.length < 3) return undefined; + + const category = parts[1]; // "size" | "color" + const name = parts[2]; // "space" | "foreground" | "borderRadius" + + // camelCase → PascalCase + const pascalName = name.charAt(0).toUpperCase() + name.slice(1); + + if (category === 'color') { + return `${pascalName}ColorToken`; + } + return `${pascalName}Token`; +} + +// margin 계열 props (negative 값 지원) +const MARGIN_PROPS = new Set([ + 'margin', + 'marginTop', + 'marginBottom', + 'marginLeft', + 'marginRight', + 'marginX', + 'marginY', +]); + +function getDisplayTypeName(propName: string, tokenPath?: string): string | undefined { + if (!tokenPath) return undefined; + + const baseType = generateDisplayTypeFromPath(tokenPath); + if (!baseType) return undefined; + + // margin 계열은 negative 토큰도 포함 + if (MARGIN_PROPS.has(propName)) { + return `${baseType} | NegativeSpaceToken`; + } + + return baseType; +} + +function getCssPropertyDisplayType(cssProperty: string): string { + return `CSSProperties['${cssProperty}']`; +} + +function analyzeSprinkles(sprinklesPath: string): SprinklesMeta { + const project = new Project({ + skipFileDependencyResolution: true, + }); + const sourceFile = project.addSourceFileAtPath(sprinklesPath); + + const meta: SprinklesMeta = { + tokenProps: [], + nonTokenProps: [], + propDefinitions: {}, + }; + + // Find defineProperties call + const definePropertiesCall = sourceFile + .getDescendantsOfKind(SyntaxKind.CallExpression) + .find((call) => { + const expr = call.getExpression(); + return expr.getText() === 'defineProperties'; + }); + + if (!definePropertiesCall) { + console.warn('defineProperties call not found'); + return meta; + } + + // Get the configuration object + const args = definePropertiesCall.getArguments(); + if (args.length === 0) { + console.warn('defineProperties has no arguments'); + return meta; + } + + const configObj = args[0] as ObjectLiteralExpression; + if (!configObj.isKind(SyntaxKind.ObjectLiteralExpression)) { + console.warn('First argument is not an object literal'); + return meta; + } + + // Find dynamicProperties + const dynamicPropsProperty = configObj.getProperty('dynamicProperties'); + if (!dynamicPropsProperty?.isKind(SyntaxKind.PropertyAssignment)) { + console.warn('dynamicProperties not found'); + return meta; + } + + const dynamicPropsObj = (dynamicPropsProperty as PropertyAssignment).getInitializer(); + if (!dynamicPropsObj?.isKind(SyntaxKind.ObjectLiteralExpression)) { + console.warn('dynamicProperties is not an object'); + return meta; + } + + // Analyze each property in dynamicProperties + for (const prop of (dynamicPropsObj as ObjectLiteralExpression).getProperties()) { + if (!prop.isKind(SyntaxKind.PropertyAssignment)) continue; + + const propAssignment = prop as PropertyAssignment; + const propName = propAssignment.getName(); + const initializer = propAssignment.getInitializer(); + + if (!initializer) continue; + + const initText = initializer.getText(); + const usesToken = initText !== 'true'; + const tokenPath = usesToken ? getTokenPath(initText) : undefined; + + meta.propDefinitions[propName] = { + usesToken, + tokenPath, + cssProperty: propName, + displayTypeName: usesToken + ? getDisplayTypeName(propName, tokenPath) + : getCssPropertyDisplayType(propName), + }; + + if (usesToken) { + meta.tokenProps.push(propName); + } else { + meta.nonTokenProps.push(propName); + } + } + + // Handle shorthands + const shorthandsProperty = configObj.getProperty('shorthands'); + if (shorthandsProperty?.isKind(SyntaxKind.PropertyAssignment)) { + const shorthandsObj = (shorthandsProperty as PropertyAssignment).getInitializer(); + if (shorthandsObj?.isKind(SyntaxKind.ObjectLiteralExpression)) { + for (const prop of (shorthandsObj as ObjectLiteralExpression).getProperties()) { + if (!prop.isKind(SyntaxKind.PropertyAssignment)) continue; + + const propAssignment = prop as PropertyAssignment; + const propName = propAssignment.getName(); + const arrayInit = propAssignment.getInitializer(); + + if (!arrayInit?.isKind(SyntaxKind.ArrayLiteralExpression)) continue; + + // Get the expanded prop names + const expandedProps = arrayInit.getElements().map((el) => { + const text = el.getText(); + return text.replace(/['"]/g, ''); + }); + + // Shorthand uses token if any of its expanded props use token + const usesToken = expandedProps.some((p) => meta.propDefinitions[p]?.usesToken); + const tokenPath = expandedProps.find((p) => meta.propDefinitions[p]?.tokenPath) + ? meta.propDefinitions[expandedProps[0]]?.tokenPath + : undefined; + + meta.propDefinitions[propName] = { + usesToken, + tokenPath, + cssProperty: propName, + displayTypeName: usesToken + ? getDisplayTypeName(propName, tokenPath) + : getCssPropertyDisplayType(propName), + }; + + if (usesToken) { + meta.tokenProps.push(propName); + } else { + meta.nonTokenProps.push(propName); + } + } + } + } + + return meta; +} + +// Main execution +const scriptDir = path.dirname(new URL(import.meta.url).pathname); +const sprinklesPath = path.resolve(scriptDir, '../../../packages/core/src/styles/sprinkles.css.ts'); +const outputDir = path.resolve(scriptDir, '../generated'); +const outputPath = path.join(outputDir, 'sprinkles-meta.json'); + +if (!fs.existsSync(sprinklesPath)) { + console.error(`Sprinkles file not found: ${sprinklesPath}`); + process.exit(1); +} + +const meta = analyzeSprinkles(sprinklesPath); + +fs.mkdirSync(outputDir, { recursive: true }); +fs.writeFileSync(outputPath, JSON.stringify(meta, null, 2)); + +console.log(`Generated sprinkles metadata:`); +console.log(` Token props: ${meta.tokenProps.length}`); +console.log(` Non-token props: ${meta.nonTokenProps.length}`); +console.log(` Output: ${outputPath}`); diff --git a/scripts/docs-extractor/src/bin/cli.ts b/scripts/docs-extractor/src/bin/cli.ts new file mode 100644 index 000000000..d91b318e9 --- /dev/null +++ b/scripts/docs-extractor/src/bin/cli.ts @@ -0,0 +1,19 @@ +import { run } from '~/cli'; +import { CliError } from '~/cli/options'; + +async function main() { + try { + await run(); + } catch (error) { + if (error instanceof CliError) { + console.error(`Error: ${error.message}`); + process.exit(1); + } + + const message = error instanceof Error ? error.message : String(error); + console.error(`Unexpected error: ${message}`); + process.exit(1); + } +} + +main(); diff --git a/scripts/docs-extractor/src/cli/index.ts b/scripts/docs-extractor/src/cli/index.ts new file mode 100644 index 000000000..ee174806a --- /dev/null +++ b/scripts/docs-extractor/src/cli/index.ts @@ -0,0 +1,250 @@ +import meow from 'meow'; +import path from 'node:path'; + +import { getComponentConfig, loadConfig } from '~/config'; +import type { ExtractorConfig } from '~/config'; +import { type SprinklesMeta, loadSprinklesMeta } from '~/core/defaults'; +import { addSourceFiles, createProject } from '~/core/discovery'; +import { type ExtractOptions, extractProps } from '~/core/props-extractor'; +import { getTargetLanguages } from '~/i18n/path-resolver'; +import { formatFileName } from '~/output/formatter'; +import { ensureDirectory, formatWithPrettier, writeMultipleFiles } from '~/output/writer'; + +import type { RawCliOptions } from './options.js'; +import { resolveOptions } from './options.js'; + +/** + * Build component-specific extract options based on config + */ +function buildComponentExtractOptions( + baseOptions: ExtractOptions, + componentConfig: ExtractorConfig['components'][string] | undefined, + sprinklesMeta: SprinklesMeta | null, +): ExtractOptions { + const options: ExtractOptions = { + ...baseOptions, + sprinklesMeta: sprinklesMeta ?? undefined, + }; + + if (!componentConfig) { + return options; + } + + // sprinklesAll: include all sprinkles props + if (componentConfig.sprinklesAll) { + options.filterSprinkles = false; + } + + // sprinkles: include specific sprinkles props + if (componentConfig.sprinkles?.length) { + options.include = [...(options.include ?? []), ...componentConfig.sprinkles]; + } + + // component-specific include + if (componentConfig.include?.length) { + options.include = [...(options.include ?? []), ...componentConfig.include]; + } + + return options; +} + +function logProgress(message: string, hasFileOutput: boolean) { + if (hasFileOutput) { + console.error(message); + } +} + +const cli = meow( + ` + Usage + $ ts-api-extractor [path] + + Options + --tsconfig, -c Path to tsconfig.json (default: auto-detect) + --exclude, -e Additional exclude patterns (added to defaults) + --no-exclude-defaults Disable default exclude patterns (.stories.tsx, .css.ts) + --component, -n Component name to process (e.g., Button, TextInput) + --output-dir, -d Output directory for per-component files + --all, -a Include all props (node_modules + sprinkles + html) + --include Include specific props (can be used multiple times) + --include-html Include specific HTML attributes (e.g., --include-html className style) + --config Config file path (default: docs-extractor.config.ts) + --no-config Ignore config file + --lang, -l Output language (ko, en, all) + --verbose, -v Enable verbose output + + Examples + $ ts-api-extractor ./packages/core + $ ts-api-extractor ./packages/core --component Tabs + $ ts-api-extractor ./packages/core --component Tabs --output-dir ./output + $ ts-api-extractor ./packages/core --lang en + $ ts-api-extractor ./packages/core --lang all + $ ts-api-extractor # Interactive mode: prompts for path and components +`, + { + importMeta: import.meta, + flags: { + tsconfig: { + type: 'string', + shortFlag: 'c', + }, + exclude: { + type: 'string', + shortFlag: 'e', + isMultiple: true, + }, + excludeDefaults: { + type: 'boolean', + default: true, + }, + component: { + type: 'string', + shortFlag: 'n', + }, + outputDir: { + type: 'string', + shortFlag: 'd', + }, + all: { + type: 'boolean', + shortFlag: 'a', + default: false, + }, + include: { + type: 'string', + isMultiple: true, + }, + includeHtml: { + type: 'string', + isMultiple: true, + }, + config: { + type: 'string', + }, + noConfig: { + type: 'boolean', + default: false, + }, + lang: { + type: 'string', + shortFlag: 'l', + }, + verbose: { + type: 'boolean', + shortFlag: 'v', + default: false, + }, + }, + }, +); + +const [inputPath] = cli.input; + +export async function run() { + // Load configuration + let config: ExtractorConfig; + let configSource: 'file' | 'default'; + + try { + const result = await loadConfig({ + configPath: cli.flags.config, + noConfig: cli.flags.noConfig, + }); + config = result.config; + configSource = result.source; + } catch (error) { + const message = error instanceof Error ? error.message : String(error); + console.error(`Failed to load configuration: ${message}`); + process.exit(1); + } + + if (configSource === 'file') { + console.error('Using config file'); + } + + // Use config outputDir if CLI option not provided + const outputDir = cli.flags.outputDir ?? config.global.outputDir; + + const rawOptions: RawCliOptions = { + path: inputPath, + tsconfig: cli.flags.tsconfig, + exclude: cli.flags.exclude ?? [], + excludeDefaults: cli.flags.excludeDefaults, + component: cli.flags.component, + outputDir, + all: cli.flags.all, + include: cli.flags.include, + includeHtml: cli.flags.includeHtml, + config: cli.flags.config, + noConfig: cli.flags.noConfig, + lang: cli.flags.lang, + verbose: cli.flags.verbose, + }; + + const resolved = await resolveOptions(rawOptions, { + filterSprinkles: config.global.filterSprinkles, + }); + + const hasFileOutput = resolved.outputMode.type !== 'stdout'; + + logProgress('Parsing components...', hasFileOutput); + + // Load sprinkles metadata if available + const sprinklesMeta = config.sprinkles?.metaPath + ? loadSprinklesMeta(config.sprinkles.metaPath) + : null; + + const project = createProject(resolved.tsconfigPath); + const sourceFiles = addSourceFiles(project, resolved.targetFiles); + + const total = sourceFiles.length; + const results = sourceFiles.map((file, index) => { + const filePath = file.getFilePath(); + const componentName = path.basename(filePath, '.tsx'); + logProgress(`Processing ${componentName} (${index + 1}/${total})`, hasFileOutput); + + // Get component-specific config + const componentConfig = getComponentConfig(config, filePath); + const extractOptions = buildComponentExtractOptions( + { ...resolved.extractOptions, verbose: resolved.verbose }, + componentConfig, + sprinklesMeta, + ); + + return extractProps(file, extractOptions); + }); + + const allProps = results.flatMap((r) => r.props); + logProgress(`Done! Extracted ${allProps.length} components.`, hasFileOutput); + + if (resolved.outputMode.type === 'directory') { + // Determine output directory based on config and CLI options + const baseOutputDir = resolved.outputMode.path; + + // Get target languages + const targetLanguages = getTargetLanguages(cli.flags.lang, { + outputDir: baseOutputDir, + languages: config.global.languages, + defaultLanguage: config.global.defaultLanguage, + }); + + // Write files for each language + for (const lang of targetLanguages) { + const langOutputDir = path.join(baseOutputDir, lang); + ensureDirectory(langOutputDir); + + const writtenFiles = writeMultipleFiles(allProps, langOutputDir, (prop) => + formatFileName(prop.name), + ); + + for (const file of writtenFiles) { + console.log(`Written to ${file}`); + } + + formatWithPrettier(writtenFiles); + } + } else { + const output = allProps.length === 1 ? allProps[0] : allProps; + console.log(JSON.stringify(output, null, 2)); + } +} diff --git a/scripts/docs-extractor/src/cli/options.ts b/scripts/docs-extractor/src/cli/options.ts new file mode 100644 index 000000000..1d37f00c3 --- /dev/null +++ b/scripts/docs-extractor/src/cli/options.ts @@ -0,0 +1,261 @@ +import fs from 'node:fs'; +import path from 'node:path'; + +import { findComponentFiles, findFileByComponentName, findTsconfig } from '~/core/discovery'; +import type { ExtractOptions } from '~/core/props-extractor'; + +// Dynamic import to avoid loading @inquirer/prompts in CI environments +async function getPrompts() { + try { + return await import('@inquirer/prompts'); + } catch { + throw new CliError( + 'Interactive mode requires @inquirer/prompts. ' + + 'Provide --path and --component CLI arguments instead, or install: pnpm add @inquirer/prompts', + ); + } +} + +// ============================================================ +// Error Classes +// ============================================================ + +export class CliError extends Error { + constructor(message: string) { + super(message); + this.name = 'CliError'; + } +} + +// ============================================================ +// Types +// ============================================================ + +/** meow에서 파싱된 raw 옵션 */ +export interface RawCliOptions { + path?: string; + tsconfig?: string; + exclude: string[]; + excludeDefaults: boolean; + component?: string; + outputDir?: string; + all: boolean; + include?: string[]; + includeHtml?: string[]; + config?: string; + noConfig?: boolean; + lang?: string; + verbose?: boolean; +} + +/** 프롬프트/검증 후 확정된 옵션 */ +export interface ResolvedCliOptions { + absolutePath: string; + tsconfigPath: string; + targetFiles: string[]; + extractOptions: ExtractOptions; + outputMode: OutputMode; + verbose: boolean; +} + +export type OutputMode = { type: 'stdout' } | { type: 'directory'; path: string }; + +/** 스캔된 컴포넌트 파일 정보 */ +export interface ScannedComponent { + filePath: string; + componentName: string; +} + +/** 유효성 검사 결과 */ +type ValidationResult = { valid: true; file: string } | { valid: false; available: string[] }; + +// ============================================================ +// Step 1: Path Resolution (CLI > Prompt) +// ============================================================ + +export async function resolvePath(filePath?: string): Promise { + const cwd = process.cwd(); + + if (filePath) { + const absolutePath = path.resolve(cwd, filePath); + if (!fs.existsSync(absolutePath)) { + throw new CliError(`Path does not exist: ${absolutePath}`); + } + return absolutePath; + } + + const { input } = await getPrompts(); + const inputPath = await input({ + message: '컴포넌트 경로를 입력하세요:', + default: '.', + validate: (value: string) => { + const resolved = path.resolve(cwd, value.trim()); + if (!fs.existsSync(resolved)) { + return `경로가 존재하지 않습니다: ${resolved}`; + } + return true; + }, + }); + + return path.resolve(cwd, inputPath.trim()); +} + +// ============================================================ +// Step 2: Directory Scan (Source of Truth) +// ============================================================ + +export async function scanComponents( + absolutePath: string, + options: { exclude: string[]; skipDefaultExcludes: boolean }, +): Promise { + const files = await findComponentFiles(absolutePath, { + exclude: options.exclude, + skipDefaultExcludes: options.skipDefaultExcludes, + }); + + if (files.length === 0) { + throw new CliError('No .tsx files found in the specified path'); + } + + return files.map((filePath) => ({ + filePath, + componentName: path.basename(filePath, '.tsx'), + })); +} + +// ============================================================ +// Step 3: Component Selection (CLI > Checkbox Prompt) +// ============================================================ + +const SELECT_ALL_VALUE = '__SELECT_ALL__'; + +export async function resolveComponentSelection( + scannedComponents: ScannedComponent[], + cliComponent?: string, +): Promise { + // Case A: CLI option provided - validate against scanned files + if (cliComponent) { + const validation = validateComponent(scannedComponents, cliComponent); + + if (!validation.valid) { + throw new CliError( + `Component '${cliComponent}' not found.\nAvailable: ${validation.available.join(', ')}`, + ); + } + + return [validation.file]; + } + + // Case B: No CLI option - show interactive checkbox prompt + const choices = [ + { name: '[ 전체 선택 ]', value: SELECT_ALL_VALUE }, + ...scannedComponents.map((comp) => ({ + name: comp.componentName, + value: comp.filePath, + })), + ]; + + const { checkbox } = await getPrompts(); + const selected = await checkbox({ + message: '추출할 컴포넌트를 선택하세요:', + choices, + required: true, + }); + + if (selected.includes(SELECT_ALL_VALUE)) { + return scannedComponents.map((c) => c.filePath); + } + + return selected; +} + +// ============================================================ +// Validation +// ============================================================ + +export function validateComponent( + scanned: ScannedComponent[], + requested: string, +): ValidationResult { + const available = scanned.map((c) => c.componentName); + const file = findFileByComponentName( + scanned.map((c) => c.filePath), + requested, + ); + + if (file) { + return { valid: true, file }; + } + + return { valid: false, available }; +} + +// ============================================================ +// Output Mode Resolution +// ============================================================ + +function resolveOutputMode(outputDir?: string): OutputMode { + if (outputDir) { + const cwd = process.cwd(); + return { type: 'directory', path: path.resolve(cwd, outputDir) }; + } + return { type: 'stdout' }; +} + +// ============================================================ +// Extract Options Builder +// ============================================================ + +function buildExtractOptions(raw: RawCliOptions, configFilterSprinkles?: boolean): ExtractOptions { + return { + filterExternal: !raw.all, + filterSprinkles: !raw.all && (configFilterSprinkles ?? true), + filterHtml: !raw.all, + includeHtmlWhitelist: raw.includeHtml?.length ? new Set(raw.includeHtml) : undefined, + include: raw.include, + }; +} + +// ============================================================ +// Main Orchestrator +// ============================================================ + +export interface ResolveOptionsConfig { + filterSprinkles?: boolean; +} + +export async function resolveOptions( + raw: RawCliOptions, + configOptions?: ResolveOptionsConfig, +): Promise { + // Step 1: Resolve path (CLI > prompt) + const absolutePath = await resolvePath(raw.path); + + // Resolve tsconfig + const cwd = process.cwd(); + const tsconfigPath = raw.tsconfig + ? path.resolve(cwd, raw.tsconfig) + : findTsconfig(absolutePath); + + if (!tsconfigPath) { + throw new CliError('tsconfig.json not found'); + } + + // Step 2: Scan directory (Source of Truth) + const scannedComponents = await scanComponents(absolutePath, { + exclude: raw.exclude, + skipDefaultExcludes: !raw.excludeDefaults, + }); + + // Step 3: Resolve component selection (CLI > checkbox prompt) + const targetFiles = await resolveComponentSelection(scannedComponents, raw.component); + + return { + absolutePath, + tsconfigPath, + targetFiles, + extractOptions: buildExtractOptions(raw, configOptions?.filterSprinkles), + outputMode: resolveOutputMode(raw.outputDir), + verbose: raw.verbose ?? false, + }; +} diff --git a/scripts/docs-extractor/src/config/defaults.ts b/scripts/docs-extractor/src/config/defaults.ts new file mode 100644 index 000000000..ceb094c86 --- /dev/null +++ b/scripts/docs-extractor/src/config/defaults.ts @@ -0,0 +1,22 @@ +import type { ExtractorConfig } from './schema'; + +export const DEFAULT_CONFIG: ExtractorConfig = { + global: { + outputDir: './output', + languages: ['ko'], + defaultLanguage: 'ko', + filterExternal: true, + filterSprinkles: true, + filterHtml: true, + }, + sprinkles: { + metaPath: './generated/sprinkles-meta.json', + }, + components: {}, +}; + +export const CONFIG_FILE_NAMES = [ + 'docs-extractor.config.ts', + 'docs-extractor.config.js', + 'docs-extractor.config.mjs', +]; diff --git a/scripts/docs-extractor/src/config/index.ts b/scripts/docs-extractor/src/config/index.ts new file mode 100644 index 000000000..4ab711cf2 --- /dev/null +++ b/scripts/docs-extractor/src/config/index.ts @@ -0,0 +1,19 @@ +export { + loadConfig, + findConfigFile, + defineConfig, + getComponentConfig, + type LoadConfigOptions, + type LoadConfigResult, +} from './loader'; +export { + ExtractorConfigSchema, + GlobalConfigSchema, + SprinklesConfigSchema, + ComponentConfigSchema, + type ExtractorConfig, + type GlobalConfig, + type SprinklesConfig, + type ComponentConfig, +} from './schema'; +export { DEFAULT_CONFIG, CONFIG_FILE_NAMES } from './defaults'; diff --git a/scripts/docs-extractor/src/config/loader.ts b/scripts/docs-extractor/src/config/loader.ts new file mode 100644 index 000000000..598fc72b0 --- /dev/null +++ b/scripts/docs-extractor/src/config/loader.ts @@ -0,0 +1,89 @@ +import { createJiti } from 'jiti'; +import fs from 'node:fs'; +import path from 'node:path'; + +import { CONFIG_FILE_NAMES, DEFAULT_CONFIG } from './defaults'; +import type { ExtractorConfig } from './schema'; +import { ExtractorConfigSchema } from './schema'; + +export interface LoadConfigOptions { + configPath?: string; + noConfig?: boolean; + cwd?: string; +} + +export interface LoadConfigResult { + config: ExtractorConfig; + configPath?: string; + source: 'file' | 'default'; +} + +export function findConfigFile(cwd: string): string | undefined { + for (const name of CONFIG_FILE_NAMES) { + const fullPath = path.join(cwd, name); + if (fs.existsSync(fullPath)) { + return fullPath; + } + } + return undefined; +} + +export async function loadConfig(options: LoadConfigOptions = {}): Promise { + const { configPath, noConfig, cwd = process.cwd() } = options; + + if (noConfig) { + return { config: DEFAULT_CONFIG, source: 'default' }; + } + + const resolvedPath = configPath ? path.resolve(cwd, configPath) : findConfigFile(cwd); + + if (!resolvedPath || !fs.existsSync(resolvedPath)) { + return { config: DEFAULT_CONFIG, source: 'default' }; + } + + try { + const jiti = createJiti(import.meta.url, { + interopDefault: true, + }); + + const rawConfig = await jiti.import(resolvedPath, { default: true }); + + const parsed = ExtractorConfigSchema.safeParse(rawConfig); + if (!parsed.success) { + console.warn(`Config validation failed: ${parsed.error.message}`); + return { config: DEFAULT_CONFIG, source: 'default' }; + } + + return { + config: parsed.data, + configPath: resolvedPath, + source: 'file', + }; + } catch (error) { + console.warn(`Failed to load config: ${error}`); + return { config: DEFAULT_CONFIG, source: 'default' }; + } +} + +export function defineConfig(config: Partial): ExtractorConfig { + return ExtractorConfigSchema.parse(config); +} + +export function getComponentConfig( + config: ExtractorConfig, + filePath: string, +): ExtractorConfig['components'][string] | undefined { + const normalizedPath = filePath.replace(/\\/g, '/'); + + for (const [pattern, componentConfig] of Object.entries(config.components)) { + const normalizedPattern = pattern.replace(/\\/g, '/'); + if ( + normalizedPath.endsWith(normalizedPattern) || + normalizedPath.includes(normalizedPattern) + ) { + return componentConfig; + } + } + + return undefined; +} diff --git a/scripts/docs-extractor/src/config/schema.ts b/scripts/docs-extractor/src/config/schema.ts new file mode 100644 index 000000000..2211eba91 --- /dev/null +++ b/scripts/docs-extractor/src/config/schema.ts @@ -0,0 +1,34 @@ +import { z } from 'zod'; + +export const GlobalConfigSchema = z.object({ + outputDir: z.string().default('./output'), + languages: z.array(z.string()).default(['ko']), + defaultLanguage: z.string().default('ko'), + filterExternal: z.boolean().default(true), + filterSprinkles: z.boolean().default(true), + filterHtml: z.boolean().default(true), + includeHtml: z.array(z.string()).optional(), +}); + +export const SprinklesConfigSchema = z.object({ + metaPath: z.string().default('./generated/sprinkles-meta.json'), + include: z.array(z.string()).optional(), +}); + +export const ComponentConfigSchema = z.object({ + sprinkles: z.array(z.string()).optional(), + sprinklesAll: z.boolean().optional(), + exclude: z.array(z.string()).optional(), + include: z.array(z.string()).optional(), +}); + +export const ExtractorConfigSchema = z.object({ + global: GlobalConfigSchema.default({}), + sprinkles: SprinklesConfigSchema.default({}), + components: z.record(z.string(), ComponentConfigSchema).default({}), +}); + +export type ExtractorConfig = z.infer; +export type GlobalConfig = z.infer; +export type SprinklesConfig = z.infer; +export type ComponentConfig = z.infer; diff --git a/scripts/docs-extractor/src/core/defaults/default-variants.ts b/scripts/docs-extractor/src/core/defaults/default-variants.ts new file mode 100644 index 000000000..298a1ef98 --- /dev/null +++ b/scripts/docs-extractor/src/core/defaults/default-variants.ts @@ -0,0 +1,193 @@ +/** + * Default values extraction module + * + * Extracts default values for component props from multiple sources: + * 1. Recipes directly used by the component (tracking styles.root() calls) + * 2. Variants type extended by the props interface (ButtonVariants → button recipe) + * 3. Destructuring defaults from component function body + */ +import { type SourceFile, SyntaxKind } from 'ts-morph'; + +import { findNamespaceImportName } from '~/utils/module'; + +import { extractDestructuringDefaults } from './destructuring-defaults'; +import { findCssImports, findVariantsTypeImports } from './style-imports'; + +export type DefaultValues = Record; + +/** + * Extracts the original recipe variable name from a Variants type in a CSS file. + * e.g. type ButtonVariants = NonNullable> → "root" + */ +export function getRecipeNameFromVariantsType( + cssFile: SourceFile, + variantsTypeName: string, +): string | null { + const typeAlias = cssFile.getTypeAlias(variantsTypeName); + if (!typeAlias) return null; + + // Find 'typeof XXX' pattern and extract variable name + let recipeName: string | null = null; + + typeAlias.forEachDescendant((node) => { + if (recipeName) return; + + if (node.isKind(SyntaxKind.TypeQuery)) { + // Extract identifier from the expression after 'typeof' + const expressionName = node.getExprName(); + recipeName = expressionName.getText(); + } + }); + + return recipeName; +} + +/** + * Phase 2: Finds recipes used within a specific component function. + * e.g. Finding styles.root() calls inside const TabsRoot = forwardRef(...) + */ +export function findRecipeUsageInComponent( + sourceFile: SourceFile, + componentName: string, + styleName: string, +): string | null { + // 1. Find variable declaration by componentName (const TabsRoot = forwardRef(...)) + const componentVar = sourceFile.getVariableDeclaration(componentName); + if (!componentVar) return null; + + // 2. Search only within the variable's initializer (forwardRef call) + const initializer = componentVar.getInitializer(); + if (!initializer) return null; + + // 3. Find styles.xxx() calls + let foundRecipe: string | null = null; + + initializer.forEachDescendant((node) => { + if (foundRecipe) return; // Use only the first match + + if (node.isKind(SyntaxKind.CallExpression)) { + const expr = node.getExpression(); + // Check for styles.root(...) pattern + if (expr.isKind(SyntaxKind.PropertyAccessExpression)) { + const obj = expr.getExpression().getText(); + if (obj === styleName) { + foundRecipe = expr.getName(); + } + } + } + }); + + return foundRecipe; +} + +/** + * Phase 3 & 4: Parses defaultVariants from a specific recipe in a CSS file. + * e.g. export const root = recipe({ defaultVariants: { size: 'md' } }) + */ +export function parseRecipeDefaultVariants( + cssFile: SourceFile, + variableName: string, +): DefaultValues | null { + // 1. Find variable declaration + const variableDecl = cssFile.getVariableDeclaration(variableName); + if (!variableDecl) return null; + + // 2. Check if it's a recipe() call + const callExpr = variableDecl.getInitializerIfKind(SyntaxKind.CallExpression); + if (!callExpr) return null; + + const callee = callExpr.getExpression().getText(); + if (callee !== 'recipe') return null; + + // 3. Get config object + const configObject = callExpr.getArguments()[0]?.asKind(SyntaxKind.ObjectLiteralExpression); + if (!configObject) return null; + + // 4. Find defaultVariants property + const defaultVariantsProperty = configObject.getProperty('defaultVariants'); + if (!defaultVariantsProperty) return null; + + const defaultVariantsValue = defaultVariantsProperty + .asKind(SyntaxKind.PropertyAssignment) + ?.getInitializerIfKind(SyntaxKind.ObjectLiteralExpression); + + if (!defaultVariantsValue) return null; + + // 5. Extract values + const result: DefaultValues = {}; + defaultVariantsValue.getProperties().forEach((prop) => { + if (prop.isKind(SyntaxKind.PropertyAssignment)) { + const key = prop.getName(); + const value = prop.getInitializer()?.getText().replace(/['"`]/g, '') || ''; + result[key] = value; + } + }); + + return result; +} + +/** + * Phase 5: Extracts defaultVariants for a namespace. + * + * Collects defaultVariants from three sources: + * 1. Destructuring defaults from component function body (lowest priority) + * 2. Recipes directly used by the component (styles.root() calls) + * 3. Original recipes from imported Variants types (ButtonVariants → button recipe) + * + * Recipe defaults override destructuring defaults when both exist. + */ +export function getDefaultValuesForNamespace( + sourceFile: SourceFile, + namespaceName: string, + declaredPropNames?: Set, +): DefaultValues { + // Start with destructuring defaults (lowest priority) + const result: DefaultValues = { + ...extractDestructuringDefaults(sourceFile, namespaceName, declaredPropNames), + }; + + // === Method 1: Extract from directly used recipes === + const cssImports = findCssImports(sourceFile); + for (const imp of cssImports) { + const styleName = findNamespaceImportName(sourceFile, imp.modulePath); + if (!styleName) continue; + + const recipeName = findRecipeUsageInComponent(sourceFile, namespaceName, styleName); + if (!recipeName) continue; + + const cssFile = sourceFile.getProject().getSourceFile(imp.resolvedPath); + if (!cssFile) continue; + + const defaults = parseRecipeDefaultVariants(cssFile, recipeName); + if (defaults) { + for (const [key, value] of Object.entries(defaults)) { + if (!(key in result)) { + result[key] = value; + } + } + } + } + + // === Method 2: Extract from Variants type imports === + const variantsImports = findVariantsTypeImports(sourceFile); + for (const varImp of variantsImports) { + const cssFile = sourceFile.getProject().getSourceFile(varImp.resolvedPath); + if (!cssFile) continue; + + // Extract recipe variable name referenced by typeof in Variants type + const recipeName = getRecipeNameFromVariantsType(cssFile, varImp.typeName); + if (!recipeName) continue; + + const defaults = parseRecipeDefaultVariants(cssFile, recipeName); + + if (defaults) { + for (const [key, value] of Object.entries(defaults)) { + if (!(key in result)) { + result[key] = value; + } + } + } + } + + return result; +} diff --git a/scripts/docs-extractor/src/core/defaults/destructuring-defaults.ts b/scripts/docs-extractor/src/core/defaults/destructuring-defaults.ts new file mode 100644 index 000000000..4a7d64e2b --- /dev/null +++ b/scripts/docs-extractor/src/core/defaults/destructuring-defaults.ts @@ -0,0 +1,57 @@ +/** + * Destructuring defaults extraction module + * + * Extracts default values from destructuring patterns in component function bodies. + * e.g. const { size = 'md', disabled = false } = resolveStyles(props); + */ +import { type SourceFile, SyntaxKind } from 'ts-morph'; + +import type { DefaultValues } from './default-variants'; + +/** + * Extracts destructuring default values from component function body. + * + * Detects patterns like: + * const { side = 'bottom', align = 'start', sideOffset = 4 } = resolveStyles(props); + * const { closeOnClickOverlay = true, ...rest } = props; + * + * Only includes defaults for properties that exist in the given declaredPropNames set, + * preventing false positives from internal variables. + */ +export function extractDestructuringDefaults( + sourceFile: SourceFile, + componentName: string, + declaredPropNames?: Set, +): DefaultValues { + const result: DefaultValues = {}; + + const componentVar = sourceFile.getVariableDeclaration(componentName); + if (!componentVar) return result; + + const initializer = componentVar.getInitializer(); + if (!initializer) return result; + + initializer.forEachDescendant((node) => { + if (!node.isKind(SyntaxKind.BindingElement)) return; + + const initNode = node.getInitializer(); + if (!initNode) return; + + const nameNode = node.getNameNode(); + if (!nameNode.isKind(SyntaxKind.Identifier)) return; + + const name = nameNode.getText(); + + // Skip if not declared in Props interface (prevents picking up internal variable defaults) + if (declaredPropNames && !declaredPropNames.has(name)) return; + + // Skip if already found (first occurrence wins) + if (name in result) return; + + const rawValue = initNode.getText(); + // Clean up: remove quotes, convert boolean/number literals as-is + result[name] = rawValue.replace(/^['"`]|['"`]$/g, ''); + }); + + return result; +} diff --git a/scripts/docs-extractor/src/core/defaults/index.ts b/scripts/docs-extractor/src/core/defaults/index.ts new file mode 100644 index 000000000..857b8dbe5 --- /dev/null +++ b/scripts/docs-extractor/src/core/defaults/index.ts @@ -0,0 +1,28 @@ +// Default Variants +export { + getDefaultValuesForNamespace, + getRecipeNameFromVariantsType, + findRecipeUsageInComponent, + parseRecipeDefaultVariants, + type DefaultValues, +} from './default-variants'; + +// Destructuring Defaults +export { extractDestructuringDefaults } from './destructuring-defaults'; + +// Style Imports +export { findCssImports, findVariantsTypeImports, type CssImport, type VariantsTypeImport } from './style-imports'; + +// Sprinkles Analyzer +export { + loadSprinklesMeta, + isTokenBasedSprinklesProp, + isSprinklesProp, + getAllSprinklesProps, + getTokenSprinklesProps, + getNonTokenSprinklesProps, + getSprinklesDisplayType, + clearCache, + type SprinklesMeta, + type PropDefinition, +} from './sprinkles-analyzer'; diff --git a/scripts/docs-extractor/src/core/defaults/sprinkles-analyzer.ts b/scripts/docs-extractor/src/core/defaults/sprinkles-analyzer.ts new file mode 100644 index 000000000..cffd01080 --- /dev/null +++ b/scripts/docs-extractor/src/core/defaults/sprinkles-analyzer.ts @@ -0,0 +1,80 @@ +import fs from 'node:fs'; +import path from 'node:path'; + +export interface PropDefinition { + usesToken: boolean; + tokenPath?: string; + cssProperty: string; + displayTypeName?: string; +} + +export interface SprinklesMeta { + tokenProps: string[]; + nonTokenProps: string[]; + propDefinitions: Record; +} + +let cachedMeta: SprinklesMeta | null = null; +let cachedMetaPath: string | null = null; + +export function loadSprinklesMeta(metaPath: string): SprinklesMeta | null { + const resolvedPath = path.resolve(process.cwd(), metaPath); + + // Return cached if same path + if (cachedMeta && cachedMetaPath === resolvedPath) { + return cachedMeta; + } + + if (!fs.existsSync(resolvedPath)) { + console.warn( + `sprinkles-meta.json not found at ${resolvedPath}. Sprinkles filtering disabled.`, + ); + return null; + } + + try { + const content = fs.readFileSync(resolvedPath, 'utf-8'); + cachedMeta = JSON.parse(content) as SprinklesMeta; + cachedMetaPath = resolvedPath; + return cachedMeta; + } catch (error) { + console.warn(`Failed to parse sprinkles metadata: ${error}`); + return null; + } +} + +export function isTokenBasedSprinklesProp(propName: string, meta: SprinklesMeta): boolean { + return meta.tokenProps.includes(propName); +} + +export function isSprinklesProp(propName: string, meta: SprinklesMeta): boolean { + return propName in meta.propDefinitions; +} + +export function getAllSprinklesProps(meta: SprinklesMeta): string[] { + return [...meta.tokenProps, ...meta.nonTokenProps]; +} + +export function getTokenSprinklesProps(meta: SprinklesMeta): string[] { + return [...meta.tokenProps]; +} + +export function getNonTokenSprinklesProps(meta: SprinklesMeta): string[] { + return [...meta.nonTokenProps]; +} + +export function clearCache(): void { + cachedMeta = null; + cachedMetaPath = null; +} + +/** + * sprinkles prop의 displayTypeName을 반환합니다. + * 해당 prop이 sprinkles prop이 아니거나 displayTypeName이 없으면 null 반환 + */ +export function getSprinklesDisplayType(propName: string, meta: SprinklesMeta): string | null { + const definition = meta.propDefinitions[propName]; + if (!definition) return null; + + return definition.displayTypeName ?? null; +} diff --git a/scripts/docs-extractor/src/core/defaults/style-imports.ts b/scripts/docs-extractor/src/core/defaults/style-imports.ts new file mode 100644 index 000000000..aede2a940 --- /dev/null +++ b/scripts/docs-extractor/src/core/defaults/style-imports.ts @@ -0,0 +1,62 @@ +/** + * Style import analysis module + * + * Extracts vanilla-extract style file imports (.css.ts) from component files. + */ +import path from 'node:path'; +import type { SourceFile } from 'ts-morph'; + +import { findImportPaths } from '~/utils/module'; + +export interface CssImport { + modulePath: string; + resolvedPath: string; +} + +export interface VariantsTypeImport { + typeName: string; + modulePath: string; + resolvedPath: string; +} + +/** + * Finds all .css file imports from a component file. + * e.g. import * as styles from './button.css' + * import type { ButtonVariants } from './button.css' + */ +export function findCssImports(sourceFile: SourceFile): CssImport[] { + const fileDir = path.dirname(sourceFile.getFilePath()); + + return findImportPaths(sourceFile, '.css').map((modulePath) => ({ + modulePath, + resolvedPath: path.resolve(fileDir, `${modulePath}.ts`), + })); +} + +/** + * Finds type imports from .css files. + * e.g. import type { ButtonVariants, ListVariants } from './tabs.css' + */ +export function findVariantsTypeImports(sourceFile: SourceFile): VariantsTypeImport[] { + const imports: VariantsTypeImport[] = []; + const fileDir = path.dirname(sourceFile.getFilePath()); + + for (const importDecl of sourceFile.getImportDeclarations()) { + const modulePath = importDecl.getModuleSpecifierValue(); + + if (!modulePath.endsWith('.css')) continue; + + for (const namedImport of importDecl.getNamedImports()) { + const typeName = namedImport.getName(); + if (typeName.endsWith('Variants')) { + imports.push({ + typeName, + modulePath, + resolvedPath: path.resolve(fileDir, `${modulePath}.ts`), + }); + } + } + } + + return imports; +} diff --git a/scripts/docs-extractor/src/core/discovery/component-discovery.ts b/scripts/docs-extractor/src/core/discovery/component-discovery.ts new file mode 100644 index 000000000..83bf61eac --- /dev/null +++ b/scripts/docs-extractor/src/core/discovery/component-discovery.ts @@ -0,0 +1,76 @@ +/** + * Component discovery module + * + * Discovers component namespaces and extracts metadata + * (JSDoc descriptions, default HTML elements, Props interfaces) from source files. + */ +import { type ModuleDeclaration, ModuleDeclarationKind, type SourceFile, SyntaxKind } from 'ts-morph'; + +function findComponentVariableStatement(sourceFile: SourceFile, namespaceName: string) { + return sourceFile + .getVariableStatements() + .find((statement) => + statement.getDeclarations().some((d) => d.getName() === namespaceName), + ); +} + +export function getComponentDescription( + sourceFile: SourceFile, + namespaceName: string, +): string | undefined { + const variableStatement = findComponentVariableStatement(sourceFile, namespaceName); + if (!variableStatement) return undefined; + + const jsDocs = variableStatement.getJsDocs(); + if (jsDocs.length === 0) return undefined; + + // Use the last JSDoc comment (closest to the declaration) + return jsDocs.at(-1)!.getDescription().trim() || undefined; +} + +export function getDefaultElement( + sourceFile: SourceFile, + namespaceName: string, +): string | undefined { + const variableStatement = findComponentVariableStatement(sourceFile, namespaceName); + if (!variableStatement) return undefined; + + // Find the 'render' property assignment in the component definition + const renderProp = variableStatement + .getDescendantsOfKind(SyntaxKind.PropertyAssignment) + .find((prop) => prop.getName() === 'render'); + + if (!renderProp) return undefined; + + // Extract the fallback JSX element from: render || + const selfClosing = renderProp.getFirstDescendantByKind(SyntaxKind.JsxSelfClosingElement); + if (selfClosing) { + return selfClosing.getTagNameNode().getText(); + } + + const jsxElement = renderProp.getFirstDescendantByKind(SyntaxKind.JsxElement); + if (jsxElement) { + return jsxElement.getOpeningElement().getTagNameNode().getText(); + } + + return undefined; +} + +export function getExportedNamespaces(sourceFile: SourceFile) { + return sourceFile + .getModules() + .filter( + (module) => + module.getDeclarationKind() === ModuleDeclarationKind.Namespace && + module.isExported, + ); +} + +export function findExportedInterfaceProps(namespace: ModuleDeclaration) { + return namespace + .getInterfaces() + .find( + (exportInterface) => + exportInterface.getName() === 'Props' && exportInterface.isExported(), + ); +} diff --git a/scripts/docs-extractor/src/core/discovery/config.ts b/scripts/docs-extractor/src/core/discovery/config.ts new file mode 100644 index 000000000..172610518 --- /dev/null +++ b/scripts/docs-extractor/src/core/discovery/config.ts @@ -0,0 +1,16 @@ +import fs from 'node:fs'; +import path from 'node:path'; + +export function findTsconfig(startDir: string): string | null { + let currentDir = path.resolve(startDir); + + while (currentDir !== path.dirname(currentDir)) { + const tsconfigPath = path.join(currentDir, 'tsconfig.json'); + if (fs.existsSync(tsconfigPath)) { + return tsconfigPath; + } + currentDir = path.dirname(currentDir); + } + + return null; +} diff --git a/scripts/docs-extractor/src/core/discovery/index.ts b/scripts/docs-extractor/src/core/discovery/index.ts new file mode 100644 index 000000000..a603405da --- /dev/null +++ b/scripts/docs-extractor/src/core/discovery/index.ts @@ -0,0 +1,21 @@ +// Scanner +export { + findComponentFiles, + findFileByComponentName, + normalizeComponentName, + type ScannerOptions, +} from './scanner'; + +// Project +export { createProject, addSourceFiles, getExportedNodes, getNamespaces } from './project'; + +// Config +export { findTsconfig } from './config'; + +// Component Discovery +export { + getExportedNamespaces, + findExportedInterfaceProps, + getComponentDescription, + getDefaultElement, +} from './component-discovery'; diff --git a/scripts/docs-extractor/src/core/discovery/project.ts b/scripts/docs-extractor/src/core/discovery/project.ts new file mode 100644 index 000000000..b0d861a15 --- /dev/null +++ b/scripts/docs-extractor/src/core/discovery/project.ts @@ -0,0 +1,22 @@ +import { type ExportedDeclarations, Project, type SourceFile, SyntaxKind } from 'ts-morph'; + +export function createProject(tsconfigPath: string): Project { + return new Project({ tsConfigFilePath: tsconfigPath }); +} + +export function addSourceFiles(project: Project, filePaths: string[]): SourceFile[] { + return project.addSourceFilesAtPaths(filePaths); +} + +export function getExportedNodes( + sourceFile: SourceFile, +): ReadonlyMap { + return sourceFile.getExportedDeclarations(); +} + +export function getNamespaces(sourceFile: SourceFile): string[] { + return sourceFile + .getDescendantsOfKind(SyntaxKind.ModuleDeclaration) + .filter((mod) => mod.isExported()) + .map((mod) => mod.getName()); +} diff --git a/scripts/docs-extractor/src/core/discovery/scanner.ts b/scripts/docs-extractor/src/core/discovery/scanner.ts new file mode 100644 index 000000000..1ca8d0dfd --- /dev/null +++ b/scripts/docs-extractor/src/core/discovery/scanner.ts @@ -0,0 +1,41 @@ +import { glob } from 'glob'; +import path from 'node:path'; + +const DEFAULT_EXCLUDES = ['.stories.tsx', '.css.ts', '.test.tsx']; + +export interface ScannerOptions { + exclude?: string[]; + skipDefaultExcludes?: boolean; +} + +export function normalizeComponentName(name: string): string { + return name.toLowerCase().replace(/-/g, ''); +} + +export function findFileByComponentName(files: string[], componentName: string): string | null { + const normalizedInput = normalizeComponentName(componentName); + + return ( + files.find((file) => { + const fileName = path.basename(file, path.extname(file)); + return normalizeComponentName(fileName) === normalizedInput; + }) ?? null + ); +} + +export async function findComponentFiles( + inputPath: string, + options?: ScannerOptions, +): Promise { + const files = await glob('**/*.tsx', { cwd: inputPath, absolute: true }); + + const defaultPatterns = options?.skipDefaultExcludes ? [] : DEFAULT_EXCLUDES; + const customPatterns = options?.exclude ?? []; + const excludePatterns = [...defaultPatterns, ...customPatterns]; + + if (excludePatterns.length === 0) { + return files; + } + + return files.filter((file) => !excludePatterns.some((pattern) => file.endsWith(pattern))); +} diff --git a/scripts/docs-extractor/src/core/props-extractor.ts b/scripts/docs-extractor/src/core/props-extractor.ts new file mode 100644 index 000000000..1552a5531 --- /dev/null +++ b/scripts/docs-extractor/src/core/props-extractor.ts @@ -0,0 +1,155 @@ +/** + * Props extraction orchestrator + * + * Coordinates component discovery, prop filtering, classification, and type resolution + * to produce the final extraction result for each component in a source file. + */ +import type { SourceFile } from 'ts-morph'; + +import type { FilePropsResult, PropsInfo } from '~/types/props'; + +import { + getDefaultValuesForNamespace, + getSprinklesDisplayType, + isSprinklesProp as isSprinklesPropFromMeta, +} from './defaults'; +import { + findExportedInterfaceProps, + getComponentDescription, + getDefaultElement, + getExportedNamespaces, +} from './discovery'; +import { + type ExtractOptions, + type InternalProperty, + getJsDocDefault, + getPropCategory, + getPropDescription, + getPropSource, + shouldIncludeSymbol, + sortProps, + toTypeArray, +} from './props'; +import { buildBaseUiTypeMap, cleanType, resolveType } from './types'; + +export type { ExtractOptions } from './props'; + +export function extractProps( + sourceFile: SourceFile, + options: ExtractOptions = {}, +): FilePropsResult { + const props: PropsInfo[] = []; + + // Build base-ui type map + const baseUiMap = buildBaseUiTypeMap(sourceFile); + + const namespaces = getExportedNamespaces(sourceFile); + + if (options.verbose) { + console.error( + `[verbose] Found ${namespaces.length} namespaces in ${sourceFile.getFilePath()}`, + ); + } + + for (const namespace of namespaces) { + try { + const namespaceName = namespace.getName(); + const exportedInterfaceProps = findExportedInterfaceProps(namespace); + + if (!exportedInterfaceProps) continue; + + const description = getComponentDescription(sourceFile, namespaceName); + const allSymbols = exportedInterfaceProps.getType().getProperties(); + + // Prop names declared in the Props interface (filters out internal variable defaults) + const declaredPropNames = new Set(allSymbols.map((symbol) => symbol.getName())); + + // Extract default values per namespace (supports compound components) + // Collects both recipe defaults and destructuring defaults + const defaultValues = getDefaultValuesForNamespace( + sourceFile, + namespaceName, + declaredPropNames, + ); + const includeSet = new Set(options.include ?? []); + + const filteredSymbols = allSymbols.filter((symbol) => + shouldIncludeSymbol(symbol, options, includeSet), + ); + + if (options.verbose) { + console.error( + `[verbose] ${namespaceName}: ${allSymbols.length} symbols → ${filteredSymbols.length} after filtering`, + ); + } + + const propsWithSource: InternalProperty[] = filteredSymbols.map((symbol) => { + const name = symbol.getName(); + const declNode = symbol.getDeclarations()[0] ?? exportedInterfaceProps; + + // Use displayTypeName for sprinkles props + let typeArray: string[]; + if (options.sprinklesMeta && isSprinklesPropFromMeta(name, options.sprinklesMeta)) { + const displayType = getSprinklesDisplayType(name, options.sprinklesMeta); + if (displayType) { + typeArray = [displayType]; + } else { + const typeResult = cleanType( + resolveType( + symbol.getTypeAtLocation(declNode), + baseUiMap, + declNode, + options.verbose, + ), + ); + typeArray = toTypeArray(typeResult); + } + } else { + const typeResult = cleanType( + resolveType( + symbol.getTypeAtLocation(declNode), + baseUiMap, + declNode, + options.verbose, + ), + ); + typeArray = toTypeArray(typeResult); + } + + const defaultValue = defaultValues[name] ?? getJsDocDefault(symbol); + + const source = getPropSource(symbol); + const required = !symbol.isOptional(); + + return { + name, + type: typeArray, + required, + description: getPropDescription(symbol), + defaultValue, + _source: source, + _category: getPropCategory(name, required, source), + }; + }); + + const sortedProps = sortProps(propsWithSource); + const defaultElement = getDefaultElement(sourceFile, namespaceName); + + props.push({ + name: namespaceName, + displayName: namespaceName, + description: description || undefined, + props: sortedProps, + defaultElement, + }); + } catch (error) { + const message = error instanceof Error ? error.message : String(error); + console.warn( + `[docs-extractor] Failed to extract props for ${namespace.getName()}: ${message}`, + ); + continue; + } + } + + return { props }; +} diff --git a/scripts/docs-extractor/src/core/props/html-attributes.ts b/scripts/docs-extractor/src/core/props/html-attributes.ts new file mode 100644 index 000000000..7685c7557 --- /dev/null +++ b/scripts/docs-extractor/src/core/props/html-attributes.ts @@ -0,0 +1,12 @@ +/** + * HTML 속성 필터링 모듈 + * + * 선언 위치 기반 필터링(declaration-source)으로 대부분의 HTML 속성이 처리되지만, + * data-*, aria-* 속성은 선언 위치로 판단할 수 없어 이름 패턴으로 필터링합니다. + */ + +export function isHtmlAttribute(name: string): boolean { + if (name.startsWith('data-')) return true; + if (name.startsWith('aria-')) return true; + return false; +} diff --git a/scripts/docs-extractor/src/core/props/index.ts b/scripts/docs-extractor/src/core/props/index.ts new file mode 100644 index 000000000..3f76417c4 --- /dev/null +++ b/scripts/docs-extractor/src/core/props/index.ts @@ -0,0 +1,20 @@ +// Prop Filter +export { + shouldIncludeSymbol, + getPropDescription, + getJsDocDefault, + type ExtractOptions, +} from './prop-filter'; + +// Prop Classifier +export { + getPropSource, + getPropCategory, + sortProps, + toTypeArray, + type InternalProperty, + type PropSource, +} from './prop-classifier'; + +// HTML Attributes +export { isHtmlAttribute } from './html-attributes'; diff --git a/scripts/docs-extractor/src/core/props/prop-classifier.ts b/scripts/docs-extractor/src/core/props/prop-classifier.ts new file mode 100644 index 000000000..8f760f342 --- /dev/null +++ b/scripts/docs-extractor/src/core/props/prop-classifier.ts @@ -0,0 +1,90 @@ +/** + * Prop classification module + * + * Classifies prop sources (base-ui, variants, custom), + * assigns categories (required, variants, state, etc.), and sorts props for output. + */ +import type { Symbol } from 'ts-morph'; + +import type { Property } from '~/types/props'; + +import { getSymbolSourcePath } from '../types/declaration-source'; + +export type PropSource = 'base-ui' | 'custom' | 'variants'; + +type PropCategory = 'required' | 'variants' | 'state' | 'custom' | 'base-ui' | 'composition'; + +const CATEGORY_ORDER: Record = { + required: 0, + variants: 1, + state: 2, + custom: 3, + 'base-ui': 4, + composition: 5, +}; + +const STATE_PROP_PATTERNS = [ + /^value$/, + /^defaultValue$/, + /^onChange$/, + /^on[A-Z].*Change$/, + /^(open|checked|selected|expanded|pressed|active)$/, + /^default(Open|Checked|Selected|Expanded|Pressed|Active)$/, +]; + +const COMPOSITION_PROPS = new Set(['asChild', 'render']); + +export interface InternalProperty { + name: string; + type: string[]; + required: boolean; + description?: string; + defaultValue?: string; + _source: PropSource; + _category: PropCategory; +} + +export function getPropSource(symbol: Symbol): PropSource { + const filePath = getSymbolSourcePath(symbol); + if (!filePath) return 'custom'; + + if (filePath.includes('@base-ui')) return 'base-ui'; + if (filePath.endsWith('.css.ts')) return 'variants'; + + return 'custom'; +} + +function isStateProp(name: string): boolean { + return STATE_PROP_PATTERNS.some((pattern) => pattern.test(name)); +} + +function isCompositionProp(name: string): boolean { + return COMPOSITION_PROPS.has(name); +} + +export function getPropCategory(name: string, required: boolean, source: PropSource): PropCategory { + if (required) return 'required'; + if (isCompositionProp(name)) return 'composition'; + if (source === 'variants') return 'variants'; + if (isStateProp(name)) return 'state'; + if (source === 'base-ui') return 'base-ui'; + return 'custom'; +} + +export function sortProps(props: InternalProperty[]): Property[] { + return props + .sort((a, b) => { + const categoryOrder = CATEGORY_ORDER[a._category] - CATEGORY_ORDER[b._category]; + if (categoryOrder !== 0) return categoryOrder; + return a.name.localeCompare(b.name); + }) + .map(({ _source: _, _category: __, ...rest }) => rest); +} + +export function toTypeArray(typeResult: { type: string; values?: string[] }): string[] { + // Use values if available, otherwise wrap type in an array + if (typeResult.values && typeResult.values.length > 0) { + return typeResult.values; + } + return [typeResult.type]; +} diff --git a/scripts/docs-extractor/src/core/props/prop-filter.ts b/scripts/docs-extractor/src/core/props/prop-filter.ts new file mode 100644 index 000000000..c59c8015b --- /dev/null +++ b/scripts/docs-extractor/src/core/props/prop-filter.ts @@ -0,0 +1,64 @@ +/** + * Prop filtering module + * + * Determines which props to include/exclude from extraction output, + * and extracts documentation metadata (JSDoc descriptions, @default tags) from symbols. + */ +import { type Symbol, ts } from 'ts-morph'; + +import type { SprinklesMeta } from '../defaults/sprinkles-analyzer'; +import { getSymbolSourcePath, isSymbolFromExternalSource } from '../types/declaration-source'; +import { isHtmlAttribute } from './html-attributes'; + +export interface ExtractOptions { + filterExternal?: boolean; + filterSprinkles?: boolean; + filterHtml?: boolean; + includeHtmlWhitelist?: Set; + include?: string[]; + sprinklesMeta?: SprinklesMeta; + verbose?: boolean; +} + +/** Sprinkles CSS file pattern */ +const SPRINKLES_FILE_PATTERN = 'sprinkles.css'; + +function isSprinklesProp(symbol: Symbol): boolean { + const filePath = getSymbolSourcePath(symbol); + if (!filePath) return false; + return filePath.includes(SPRINKLES_FILE_PATTERN); +} + +export function shouldIncludeSymbol( + symbol: Symbol, + options: ExtractOptions, + includeSet: Set, +): boolean { + const name = symbol.getName(); + + if (includeSet.has(name)) return true; + + if (options.includeHtmlWhitelist?.has(name)) return true; + + // Exclude React/DOM/external library types (based on declaration source) + if (options.filterExternal && isSymbolFromExternalSource(symbol)) return false; + + if (options.filterSprinkles && isSprinklesProp(symbol)) return false; + + if (options.filterHtml !== false && isHtmlAttribute(name)) return false; + + return true; +} + +export function getPropDescription(symbol: Symbol): string | undefined { + const docs = symbol.compilerSymbol.getDocumentationComment(undefined); + if (docs.length === 0) return undefined; + return ts.displayPartsToString(docs) || undefined; +} + +export function getJsDocDefault(symbol: Symbol): string | undefined { + const tags = symbol.compilerSymbol.getJsDocTags(); + const defaultTag = tags.find((tag) => tag.name === 'default'); + if (!defaultTag?.text) return undefined; + return ts.displayPartsToString(defaultTag.text) || undefined; +} diff --git a/scripts/docs-extractor/src/core/types/base-ui-type-resolver.ts b/scripts/docs-extractor/src/core/types/base-ui-type-resolver.ts new file mode 100644 index 000000000..72e2d4dd5 --- /dev/null +++ b/scripts/docs-extractor/src/core/types/base-ui-type-resolver.ts @@ -0,0 +1,322 @@ +/** + * Base-UI 타입 리졸버 모듈 + * + * ImportDeclaration에서 base-ui 컴포넌트의 타입 정보를 추출하여 + * vapor-ui 타입 경로로 매핑합니다. + */ +import fs from 'node:fs'; +import { + type ImportSpecifier, + type ModuleDeclaration, + type SourceFile, + SyntaxKind, + type Type, + type TypeAliasDeclaration, +} from 'ts-morph'; + +export interface BaseUiTypeEntry { + type: Type; + vaporPath: string; // "CollapsibleRoot.ChangeEventDetails" +} + +export interface BaseUiTypeMap { + [normalizedPath: string]: BaseUiTypeEntry; +} + +/** + * Type 객체에서 base-ui 모듈의 qualified path를 AST 기반으로 추출합니다. + * getText() + regex 대신 Symbol.getFullyQualifiedName()을 사용하여 + * greedy 매칭 위험 없이 정확한 경로를 반환합니다. + * + * @example + * // Type of import("@base-ui/react/collapsible").CollapsibleRoot.ChangeEventDetails + * // → "CollapsibleRoot.ChangeEventDetails" + * + * @returns base-ui 타입이 아니거나 symbol이 없으면 null + */ +function getBaseUiQualifiedPath(type: Type): string | null { + for (const symbol of [type.getSymbol(), type.getAliasSymbol()]) { + if (!symbol) continue; + + const fqn = symbol.getFullyQualifiedName(); + + // FQN 포맷: "module/path".Namespace.Type + if (!fqn.startsWith('"')) continue; + + const closingQuote = fqn.indexOf('"', 1); + if (closingQuote === -1) continue; + + // @base-ui 모듈에서 선언된 타입만 처리 + const modulePath = fqn.substring(1, closingQuote); + if (!modulePath.includes('@base-ui')) continue; + + // "module/path".Namespace.Type → Namespace.Type + if (closingQuote + 2 > fqn.length) continue; + return fqn.substring(closingQuote + 2); + } + + return null; +} + +/** + * ImportSpecifier에서 하위 타입들을 재귀적으로 수집합니다. + */ +function collectNestedTypes( + importSpecifier: ImportSpecifier, + type: Type, + basePath: string, + map: BaseUiTypeMap, + depth: number = 0, +): void { + // 최대 깊이 제한 (무한 재귀 방지) + if (depth > 3) return; + + const properties = type.getProperties(); + + for (const prop of properties) { + const propName = prop.getName(); + + // 내부 속성 제외 (prototype, __proto__ 등) + if (propName.startsWith('_') || propName === 'prototype') continue; + + const propType = prop.getTypeAtLocation(importSpecifier); + const vaporPath = `${basePath}.${propName}`; + + // AST 기반으로 base-ui qualified path 추출 + const normalizedPath = getBaseUiQualifiedPath(propType); + if (normalizedPath) { + map[normalizedPath] = { + type: propType, + vaporPath, + }; + } + + // 더 깊은 레벨 탐색 (ChangeEventDetails, Props 등) + collectNestedTypes(importSpecifier, propType, vaporPath, map, depth + 1); + } +} + +// 파일 경로 기반 캐시 (동일 파일에 대한 중복 분석 방지) +interface CacheEntry { + map: BaseUiTypeMap; + mtime: number; +} + +const baseUiTypeMapCache = new Map(); + +/** + * 소스 파일에서 base-ui import를 찾아 타입 맵을 빌드합니다. + * 동일 파일에 대한 결과를 캐싱하여 중복 분석을 방지합니다. + * mtime 기반으로 파일 변경 시 캐시를 무효화합니다. + */ +export function buildBaseUiTypeMap(sourceFile: SourceFile): BaseUiTypeMap { + const filePath = sourceFile.getFilePath(); + const mtime = fs.statSync(filePath).mtimeMs; + const cached = baseUiTypeMapCache.get(filePath); + if (cached && cached.mtime === mtime) return cached.map; + + const map: BaseUiTypeMap = {}; + + // @base-ui/react import 찾기 + const baseUiImport = sourceFile.getImportDeclaration((decl) => + decl.getModuleSpecifierValue().includes('@base-ui'), + ); + + if (baseUiImport) { + const namedImports = baseUiImport.getNamedImports(); + + for (const namedImport of namedImports) { + // alias가 있으면 alias 사용 (예: Collapsible as BaseCollapsible) + const alias = namedImport.getAliasNode()?.getText() ?? namedImport.getName(); + const importType = namedImport.getType(); + + // 최상위 타입 등록 (AST 기반) + const normalizedPath = getBaseUiQualifiedPath(importType); + if (normalizedPath) { + map[normalizedPath] = { + type: importType, + vaporPath: alias, + }; + } + + // 하위 properties 재귀 수집 + collectNestedTypes(namedImport, importType, alias, map); + } + } + + // namespace에서 re-export된 type alias 수집 (기존 맵을 덮어씀) + collectNamespaceTypeAliases(sourceFile, map); + + baseUiTypeMapCache.set(filePath, { map, mtime }); + return map; +} + +/** + * Type 객체에서 base-ui 타입을 찾아 vapor-ui 경로로 변환합니다. + * AST 기반으로 동작하여 getText() + regex 의존을 제거합니다. + */ +export function resolveBaseUiType(type: Type, map: BaseUiTypeMap): string | null { + const normalizedPath = getBaseUiQualifiedPath(type); + if (!normalizedPath) return null; + + return map[normalizedPath]?.vaporPath ?? null; +} + +/** + * Fallback: base-ui 타입 경로에서 마지막 타입 이름만 추출합니다. + * 예: import("...").CollapsibleRoot.ChangeEventDetails → ChangeEventDetails + */ +export function extractSimplifiedTypeName(typeText: string): string { + // import("...").A.B.C 패턴에서 마지막 부분 추출 + const match = typeText.match(/\.(\w+)$/); + return match ? match[1] : typeText; +} + +/** + * namespace에서 exported type alias를 수집하여 base-ui 타입 맵에 추가합니다. + * + * 예: export type ChangeEventDetails = BaseCollapsible.Root.ChangeEventDetails; + * → "Collapsible.Root.ChangeEventDetails" 매핑 추가 + */ +export function collectNamespaceTypeAliases(sourceFile: SourceFile, map: BaseUiTypeMap): void { + const namespaces = sourceFile + .getDescendantsOfKind(SyntaxKind.ModuleDeclaration) + .filter((mod) => mod.isExported()); + + // sibling namespace들에서 component prefix 자동 추출 + const componentPrefix = findComponentPrefix(namespaces); + + for (const ns of namespaces) { + const nsName = ns.getName(); + + // namespace 내의 exported type alias 수집 + const typeAliases = ns.getTypeAliases().filter((ta) => ta.isExported()); + + for (const typeAlias of typeAliases) { + const aliasName = typeAlias.getName(); + const aliasType = typeAlias.getType(); + + // base-ui 타입을 참조하는 경우에만 매핑 추가 + // 1. AST에서 base-ui qualified path를 추출할 수 있는 경우 + // 2. 또는 type alias의 원본 타입이 @base-ui 패키지에서 선언된 경우 + const normalizedPath = getBaseUiQualifiedPath(aliasType); + const isBaseUiAlias = !normalizedPath && isBaseUiTypeAlias(typeAlias); + + if (normalizedPath || isBaseUiAlias) { + // ComponentNameRoot → ComponentName.Root 형태로 변환 + const vaporPath = formatVaporTypePath(nsName, aliasName, componentPrefix); + + if (normalizedPath) { + map[normalizedPath] = { type: aliasType, vaporPath }; + } + + // 항상 타입 이름으로도 키 추가 (fallback용) + // 예: "CheckboxRoot.ChangeEventDetails" → "Checkbox.Root.ChangeEventDetails" + map[`${nsName}.${aliasName}`] = { type: aliasType, vaporPath }; + } + } + } +} + +/** + * type alias가 @base-ui 패키지의 타입을 참조하는지 declaration source 기반으로 판별합니다. + */ +function isBaseUiTypeAlias(typeAlias: TypeAliasDeclaration): boolean { + const aliasType = typeAlias.getType(); + + // 심볼의 선언 위치가 @base-ui인지 확인 + const symbol = aliasType.getSymbol() ?? aliasType.getAliasSymbol(); + if (symbol) { + for (const decl of symbol.getDeclarations()) { + if (decl.getSourceFile().getFilePath().includes('@base-ui')) return true; + } + } + + // union/intersection 타입의 구성 타입 중 @base-ui에서 온 것이 있는지 확인 + if (aliasType.isUnion()) { + return aliasType.getUnionTypes().some((t) => t.getText().includes('@base-ui')); + } + if (aliasType.isIntersection()) { + return aliasType.getIntersectionTypes().some((t) => t.getText().includes('@base-ui')); + } + + return false; +} + +/** + * 같은 파일의 namespace 목록에서 공통 component prefix를 추출합니다. + * + * 예: [DialogRoot, DialogTrigger, DialogPopup, ...] → "Dialog" + * 예: [MultiSelectRoot, MultiSelectTrigger, ...] → "MultiSelect" + * 예: [ToastProviderPrimitive, ToastPortalPrimitive, ...] → "Toast" + */ +export function findComponentPrefix(namespaces: ModuleDeclaration[]): string | null { + const nsNames = namespaces.map((ns) => ns.getName()); + if (nsNames.length < 2) return null; + + // Strategy 1: 가장 짧은 *Root namespace에서 prefix 추출 + const rootNames = nsNames.filter((n) => n.endsWith('Root')); + if (rootNames.length > 0) { + rootNames.sort((a, b) => a.length - b.length); + return rootNames[0].slice(0, -'Root'.length); + } + + // Strategy 2: Common prefix (Root 없는 경우, 예: Toast) + const pascalNames = nsNames.filter((n) => /^[A-Z]/.test(n)); + if (pascalNames.length < 2) return null; + + const sorted = [...pascalNames].sort(); + const first = sorted[0]; + const last = sorted[sorted.length - 1]; + + let commonLen = 0; + while ( + commonLen < first.length && + commonLen < last.length && + first[commonLen] === last[commonLen] + ) { + commonLen++; + } + + if (commonLen <= 1) return null; + + const prefix = first.substring(0, commonLen); + + // prefix 뒤의 문자가 모두 대문자인지 확인하여 PascalCase word boundary 검증 + const allValidBoundary = sorted.every( + (name) => + name.length === prefix.length || + (name.length > prefix.length && + name[prefix.length] >= 'A' && + name[prefix.length] <= 'Z'), + ); + + if (allValidBoundary) return prefix; + + return null; +} + +/** + * 내부 namespace 이름을 외부 접근 가능한 경로로 변환합니다. + * component prefix를 활용하여 정확하게 분리합니다. + * + * 예: ("DialogRoot", "ChangeEventDetails", "Dialog") → "Dialog.Root.ChangeEventDetails" + * 예: ("SelectPositionerPrimitive", "Props", "Select") → "Select.PositionerPrimitive.Props" + * 예: ("MultiSelectRoot", "ChangeEventDetails", "MultiSelect") → "MultiSelect.Root.ChangeEventDetails" + */ +export function formatVaporTypePath( + nsName: string, + typeName: string, + componentPrefix: string | null, +): string { + if ( + componentPrefix && + nsName.startsWith(componentPrefix) && + nsName.length > componentPrefix.length + ) { + const partName = nsName.slice(componentPrefix.length); + return `${componentPrefix}.${partName}.${typeName}`; + } + // prefix 없는 경우 원본 그대로 + return `${nsName}.${typeName}`; +} diff --git a/scripts/docs-extractor/src/core/types/declaration-source.ts b/scripts/docs-extractor/src/core/types/declaration-source.ts new file mode 100644 index 000000000..4a0845de1 --- /dev/null +++ b/scripts/docs-extractor/src/core/types/declaration-source.ts @@ -0,0 +1,68 @@ +/** + * 선언 위치(Declaration Source) 기반 필터링 모듈 + * + * 심볼이 어디서 선언되었는지 확인하여 React/DOM 내장 타입과 + * 프로젝트/라이브러리 타입을 구분합니다. + */ +import type { Symbol } from 'ts-morph'; + +export enum DeclarationSourceType { + PROJECT = 'project', + BASE_UI = 'base-ui', + REACT_TYPES = 'react-types', + DOM_TYPES = 'dom-types', + EXTERNAL = 'external', +} + +const REACT_TYPES_PATTERNS = ['node_modules/@types/react', 'node_modules/@types/react-dom']; + +const DOM_TYPES_PATTERNS = ['node_modules/typescript/lib']; + +const BASE_UI_PATTERN = '@base-ui'; + +export function getDeclarationSourceType(filePath: string | undefined): DeclarationSourceType { + if (!filePath) return DeclarationSourceType.PROJECT; + + // React types 확인 + if (REACT_TYPES_PATTERNS.some((pattern) => filePath.includes(pattern))) { + return DeclarationSourceType.REACT_TYPES; + } + + // DOM types 확인 + if (DOM_TYPES_PATTERNS.some((pattern) => filePath.includes(pattern))) { + return DeclarationSourceType.DOM_TYPES; + } + + // Base UI 확인 + if (filePath.includes(BASE_UI_PATTERN)) { + return DeclarationSourceType.BASE_UI; + } + + // 기타 node_modules + if (filePath.includes('node_modules')) { + return DeclarationSourceType.EXTERNAL; + } + + // 프로젝트 파일 + return DeclarationSourceType.PROJECT; +} + +export function isExternalDeclaration(filePath: string | undefined): boolean { + const sourceType = getDeclarationSourceType(filePath); + return ( + sourceType === DeclarationSourceType.REACT_TYPES || + sourceType === DeclarationSourceType.DOM_TYPES || + sourceType === DeclarationSourceType.EXTERNAL + ); +} + +export function getSymbolSourcePath(symbol: Symbol): string | undefined { + const declarations = symbol.getDeclarations(); + if (!declarations.length) return undefined; + return declarations[0].getSourceFile().getFilePath(); +} + +export function isSymbolFromExternalSource(symbol: Symbol): boolean { + const filePath = getSymbolSourcePath(symbol); + return isExternalDeclaration(filePath); +} diff --git a/scripts/docs-extractor/src/core/types/index.ts b/scripts/docs-extractor/src/core/types/index.ts new file mode 100644 index 000000000..af9fa113b --- /dev/null +++ b/scripts/docs-extractor/src/core/types/index.ts @@ -0,0 +1,33 @@ +// Type Resolver +export { + resolveType, + simplifyNodeModulesImports, + simplifyReactElementGeneric, + simplifyForwardRefType, + type TypeResolverPlugin, + type TypeResolverContext, +} from './type-resolver'; + +// Type Cleaner +export { cleanType, containsStateCallback, simplifyStateCallback, type TypeCleanResult } from './type-cleaner'; + +// Base UI Type Resolver +export { + buildBaseUiTypeMap, + resolveBaseUiType, + extractSimplifiedTypeName, + collectNamespaceTypeAliases, + findComponentPrefix, + formatVaporTypePath, + type BaseUiTypeMap, + type BaseUiTypeEntry, +} from './base-ui-type-resolver'; + +// Declaration Source +export { + getDeclarationSourceType, + isExternalDeclaration, + getSymbolSourcePath, + isSymbolFromExternalSource, + DeclarationSourceType, +} from './declaration-source'; diff --git a/scripts/docs-extractor/src/core/types/type-cleaner.ts b/scripts/docs-extractor/src/core/types/type-cleaner.ts new file mode 100644 index 000000000..3975e1595 --- /dev/null +++ b/scripts/docs-extractor/src/core/types/type-cleaner.ts @@ -0,0 +1,165 @@ +/** + * 타입 정리 모듈 + * + * Component.State를 포함한 콜백 타입을 단순화합니다. + * 예: "string | ((state: X.State) => string)" → "string" + * + * 10개 이상의 string literal union은 압축합니다. + * 예: '"a" | "b" | ... (15개)' → 'string (15 variants)' + */ + +const STATE_MARKER = '.State)'; +const GENERIC_STATE_PATTERN = /,\s*[^,<>]*\.State(?:>|\))/g; + +export interface TypeCleanResult { + type: string; + values?: string[]; +} + +export function containsStateCallback(type: string): boolean { + return type.includes(STATE_MARKER); +} + +export function simplifyStateCallback(type: string): string { + if (!containsStateCallback(type)) return type; + + return splitTopLevelUnion(type) + .map((part) => { + if (!part.includes(STATE_MARKER)) return part; + // ((state: ...) => string) 또는 (state: ...) => string 형태 처리 + const match = part.match(/=>\s*(\w+)\)?$/); + return match ? match[1] : part; + }) + .join(' | '); +} + +function removeEmptyUnion(type: string): string { + return splitTopLevelUnion(type).filter(Boolean).join(' | '); +} + +function removeDuplicateTypes(type: string): string { + const parts = splitTopLevelUnion(type); + const unique = [...new Set(parts)]; + return unique.join(' | '); +} + +function removeGenericState(type: string): string { + return type.replace(GENERIC_STATE_PATTERN, (match) => { + return match.endsWith('>') ? '>' : ')'; + }); +} + +function simplifyRenderType(type: string): TypeCleanResult | null { + if (!type.includes('ComponentRenderFn')) return null; + return { + type: 'ReactElement | ((props: HTMLProps) => ReactElement)', + values: ['ReactElement', '(props: HTMLProps) => ReactElement'], + }; +} + +/** + * render prop 타입을 정리합니다. + * - state 파라미터 제거: (props: HTMLProps, state: {}) => ReactElement → (props: HTMLProps) => ReactElement + * - 중복 함수 타입 제거 + */ +function cleanRenderPropType(type: string): string { + // (props: HTMLProps, state: ...) 패턴에서 state 파라미터 제거 + let cleaned = type.replace( + /\(props:\s*HTMLProps(?:<[^>]*>)?,\s*state:\s*[^)]*\)\s*=>\s*ReactElement/g, + '(props: HTMLProps) => ReactElement', + ); + + // (props: HTMLProps) => ReactElement → (props: HTMLProps) => ReactElement + cleaned = cleaned.replace( + /\(props:\s*HTMLProps<[^>]*>\)\s*=>\s*ReactElement/g, + '(props: HTMLProps) => ReactElement', + ); + + return cleaned; +} + +/** + * top-level union만 분리합니다. 괄호/중괄호 안의 |는 무시합니다. + * 예: "(a | b) | c" → ["(a | b)", "c"] + */ +function splitTopLevelUnion(type: string): string[] { + const parts: string[] = []; + let current = ''; + let depth = 0; + + for (let i = 0; i < type.length; i++) { + const char = type[i]; + const prevChar = i > 0 ? type[i - 1] : ''; + + if (char === '(' || char === '{' || char === '[') { + depth++; + current += char; + } else if (char === '<') { + // 제네릭 타입의 < 만 depth 증가 (=>의 >는 무시하기 위해) + depth++; + current += char; + } else if (char === ')' || char === '}' || char === ']') { + depth--; + current += char; + } else if (char === '>') { + // =>의 >는 depth를 감소시키지 않음 + if (prevChar !== '=') { + depth--; + } + current += char; + } else if (char === '|' && depth === 0) { + parts.push(current.trim()); + current = ''; + } else { + current += char; + } + } + + if (current.trim()) { + parts.push(current.trim()); + } + + return parts; +} + +function isStringLiteral(part: string): boolean { + const trimmed = part.trim(); + return trimmed.startsWith('"') && trimmed.endsWith('"'); +} + +function extractStringValue(literal: string): string { + return literal.trim().slice(1, -1); +} + +function removeUndefined(type: string): string { + return splitTopLevelUnion(type) + .filter((p) => p !== 'undefined') + .join(' | '); +} + +function extractUnionValues(type: string): TypeCleanResult { + const cleanedType = removeUndefined(type); + const parts = splitTopLevelUnion(cleanedType); + const stringLiterals = parts.filter(isStringLiteral); + + if (stringLiterals.length === parts.length && stringLiterals.length > 0) { + return { type: cleanedType, values: stringLiterals.map(extractStringValue) }; + } + + return { type: cleanedType, values: parts.length > 0 ? parts : undefined }; +} + +export function cleanType(type: string): TypeCleanResult { + const renderResult = simplifyRenderType(type); + if (renderResult) { + return renderResult; + } + + const noGenericState = removeGenericState(type); + const simplified = simplifyStateCallback(noGenericState); + const renderCleaned = cleanRenderPropType(simplified); + const noEmpty = removeEmptyUnion(renderCleaned); + const cleaned = removeDuplicateTypes(noEmpty); + + return extractUnionValues(cleaned); +} diff --git a/scripts/docs-extractor/src/core/types/type-resolver.ts b/scripts/docs-extractor/src/core/types/type-resolver.ts new file mode 100644 index 000000000..90692f878 --- /dev/null +++ b/scripts/docs-extractor/src/core/types/type-resolver.ts @@ -0,0 +1,313 @@ +/** + * 타입 Resolver 모듈 + * + * ts-morph Type 객체를 실제 타입 문자열로 변환합니다. + * import 경로 대신 실제 리터럴 값을 추출합니다. + */ +import { type Node, type Type, TypeFormatFlags } from 'ts-morph'; + +import { + type BaseUiTypeMap, + extractSimplifiedTypeName, + resolveBaseUiType, +} from './base-ui-type-resolver'; + +/** + * TypeFormatFlags 조합 + * - UseAliasDefinedOutsideCurrentScope: 외부 alias 이름 유지 (React.Ref → Ref) + * - NoTruncation: 타입 잘림 방지 + * - WriteTypeArgumentsOfSignature: 제네릭 인수 유지 + */ +const TYPE_FORMAT_FLAGS = + TypeFormatFlags.UseAliasDefinedOutsideCurrentScope | + TypeFormatFlags.NoTruncation | + TypeFormatFlags.WriteTypeArgumentsOfSignature; + +function extractPropsName(typeText: string): string | null { + // Omit<...ComponentName.Props, "ref"> 패턴에서 Props 이름 추출 + const match = typeText.match(/["']([^"']+)["']\)\.(\w+)\.Props/); + if (match) { + return `${match[2]}.Props`; + } + return null; +} + +function simplifyReactElementType(type: Type): string | null { + const symbol = type.getSymbol() || type.getAliasSymbol(); + if (!symbol) return null; + + const name = symbol.getName(); + if (name !== 'ReactElement') return null; + + const typeArgs = type.getTypeArguments(); + if (typeArgs.length === 0) return 'ReactElement'; + + const innerType = typeArgs[0]; + const innerText = innerType.getText(); + + // ForwardRefExoticComponent 패턴 확인 + if (innerText.includes('ForwardRefExoticComponent')) { + const propsName = extractPropsName(innerText); + if (propsName) { + return `ReactElement<${propsName}>`; + } + } + + return 'ReactElement'; +} + +/** + * 함수 타입을 파싱하여 파라미터와 반환 타입을 재귀적으로 변환합니다. + */ +function resolveFunctionType( + type: Type, + baseUiMap?: BaseUiTypeMap, + contextNode?: Node, +): string | null { + const callSignatures = type.getCallSignatures(); + if (callSignatures.length === 0) return null; + + // 첫 번째 call signature 사용 + const signature = callSignatures[0]; + const params = signature.getParameters(); + const returnType = signature.getReturnType(); + + // 파라미터 타입 변환 + const paramStrings = params.map((param) => { + const paramName = param.getName(); + const declarations = param.getDeclarations(); + + // declaration이 없으면 valueDeclaration 사용 + const node = declarations[0] ?? param.getValueDeclaration(); + if (!node) { + // fallback: 타입 텍스트에서 단순화 + const paramTypeText = param.getTypeAtLocation(signature.getDeclaration()!).getText(); + if (paramTypeText.includes('@base-ui')) { + return `${paramName}: ${extractSimplifiedTypeName(paramTypeText)}`; + } + return `${paramName}: ${paramTypeText}`; + } + + const paramType = param.getTypeAtLocation(node); + + // base-ui 타입인지 AST 기반으로 확인 + if (baseUiMap) { + const vaporPath = resolveBaseUiType(paramType, baseUiMap); + if (vaporPath) { + return `${paramName}: ${vaporPath}`; + } + } + + // alias가 없는 경우, 파라미터 선언에서 타입 이름을 추출하여 매핑 시도 + if (baseUiMap && !paramType.getAliasSymbol()) { + const paramDecls = param.getDeclarations(); + if (paramDecls.length > 0) { + const declText = paramDecls[0].getText(); + // "eventDetails: ChangeEventDetails" 패턴에서 타입 이름 추출 + const typeMatch = declText.match(/:\s*(\w+)$/); + if (typeMatch) { + const typeName = typeMatch[1]; + // baseUiMap에서 이 타입 이름으로 끝나는 키 찾기 + const matchingKey = Object.keys(baseUiMap).find((key) => + key.endsWith(`.${typeName}`), + ); + if (matchingKey) { + return `${paramName}: ${baseUiMap[matchingKey].vaporPath}`; + } + } + } + } + + const resolvedParamType = resolveType(paramType, baseUiMap, contextNode); + return `${paramName}: ${resolvedParamType}`; + }); + + // 반환 타입 변환 + const resolvedReturnType = resolveType(returnType, baseUiMap, contextNode); + + // 반환 타입이 유니온일 경우 괄호로 감싸서 splitTopLevelUnion에서 분리되지 않도록 함 + const needsParens = resolvedReturnType.includes(' | '); + const wrappedReturn = needsParens ? `(${resolvedReturnType})` : resolvedReturnType; + + return `(${paramStrings.join(', ')}) => ${wrappedReturn}`; +} + +/** + * Ref 타입을 감지하여 spread되지 않도록 유지합니다. + * TypeScript 컴파일러가 Ref를 union(RefCallback | RefObject | null)으로 풀어버리므로 + * getText() 결과에서 Ref 패턴을 감지하여 원본 형태로 유지합니다. + * + * @example + * 'React.Ref | undefined' → 'Ref' + * 'Ref' → 'Ref' + */ +function preserveRefType(typeText: string): string | null { + const refMatch = typeText.match(/^(React\.)?Ref<([^>]+)>(\s*\|\s*undefined)?$/); + if (refMatch) { + return `Ref<${refMatch[2]}>`; + } + return null; +} + +// spread하지 않고 유지할 React 타입 alias 목록 +const PRESERVED_REACT_ALIASES = new Set([ + 'ReactNode', + 'ReactElement', + 'ReactChild', + 'ReactFragment', +]); + +/** + * Type resolver plugin interface. + * 각 plugin은 특정 타입 패턴을 처리합니다. + * chain 순서대로 실행되며, 첫 번째로 결과를 반환한 plugin이 우선합니다. + */ +export interface TypeResolverPlugin { + name: string; + resolve(ctx: TypeResolverContext): string | null; +} + +export interface TypeResolverContext { + type: Type; + rawText: string; + baseUiMap?: BaseUiTypeMap; + contextNode?: Node; +} + +// --- Built-in resolver plugins --- + +const refTypeResolver: TypeResolverPlugin = { + name: 'ref-type', + resolve: ({ rawText }) => preserveRefType(rawText), +}; + +const reactAliasResolver: TypeResolverPlugin = { + name: 'react-alias', + resolve: ({ type }) => { + const aliasSymbol = type.getAliasSymbol(); + if (aliasSymbol && PRESERVED_REACT_ALIASES.has(aliasSymbol.getName())) { + return aliasSymbol.getName(); + } + return null; + }, +}; + +const primitiveResolver: TypeResolverPlugin = { + name: 'primitives', + resolve: ({ type }) => { + if (type.isBooleanLiteral()) return type.getText(); + if (type.isLiteral()) { + const value = type.getLiteralValue(); + return typeof value === 'string' ? `"${value}"` : String(value); + } + if (type.isUndefined()) return 'undefined'; + if (type.isNull()) return 'null'; + if (type.isBoolean()) return 'boolean'; + if (type.isString()) return 'string'; + if (type.isNumber()) return 'number'; + return null; + }, +}; + +const reactElementResolver: TypeResolverPlugin = { + name: 'react-element', + resolve: ({ type }) => simplifyReactElementType(type), +}; + +const functionTypeResolver: TypeResolverPlugin = { + name: 'function-type', + resolve: ({ type, baseUiMap, contextNode }) => + resolveFunctionType(type, baseUiMap, contextNode), +}; + +const baseUiTypeResolver: TypeResolverPlugin = { + name: 'base-ui-type', + resolve: ({ type, rawText, baseUiMap }) => { + if (!rawText.includes('@base-ui')) return null; + if (baseUiMap) { + const vaporPath = resolveBaseUiType(type, baseUiMap); + if (vaporPath) return vaporPath; + } + return simplifyNodeModulesImports(rawText); + }, +}; + +const importPathResolver: TypeResolverPlugin = { + name: 'import-path', + resolve: ({ rawText }) => { + if (!rawText.includes('import(')) return null; + return simplifyNodeModulesImports(rawText); + }, +}; + +/** + * 기본 resolver chain. 순서가 결과에 영향을 줍니다. + */ +const DEFAULT_RESOLVER_CHAIN: TypeResolverPlugin[] = [ + refTypeResolver, + reactAliasResolver, + primitiveResolver, + reactElementResolver, + functionTypeResolver, + baseUiTypeResolver, + importPathResolver, +]; + +export function resolveType( + type: Type, + baseUiMap?: BaseUiTypeMap, + contextNode?: Node, + verbose?: boolean, +): string { + const rawText = contextNode ? type.getText(contextNode, TYPE_FORMAT_FLAGS) : type.getText(); + const ctx: TypeResolverContext = { type, rawText, baseUiMap, contextNode }; + + for (const plugin of DEFAULT_RESOLVER_CHAIN) { + const result = plugin.resolve(ctx); + if (result !== null) { + if (verbose) { + console.error(`[verbose] resolveType: "${rawText}" → resolved by ${plugin.name}`); + } + return result; + } + } + + if (verbose) { + console.error(`[verbose] resolveType: "${rawText}" → fallback (no plugin matched)`); + } + + // Fallback: ForwardRef/ReactElement generic 정리 + return simplifyReactElementGeneric(simplifyForwardRefType(rawText)); +} + +export function simplifyNodeModulesImports(typeText: string): string { + return typeText.replace(/import\(["'].*?["']\)\./g, ''); +} + +/** + * ReactElement의 두 번째 type parameter를 제거합니다. + * TypeScript가 기본값을 자동으로 펼쳐서 출력하므로 이를 정리합니다. + * + * @example + * 'ReactElement>' → 'ReactElement' + * 'ReactElement, string | React.JSXElementConstructor>' → 'ReactElement>' + */ +export function simplifyReactElementGeneric(typeText: string): string { + // 두 번째 인자 패턴만 제거 (첫 번째 인자는 복잡한 중첩 타입일 수 있음) + return typeText.replace(/,\s*string \| React\.JSXElementConstructor>/g, '>'); +} + +/** + * ForwardRefExoticComponent 타입을 Props로 단순화합니다. + * typeof ComponentName이 ForwardRefExoticComponent<...Props...>로 확장되는 것을 방지합니다. + * + * @example + * 'React.ForwardRefExoticComponent & React.RefAttributes>' → 'X.Props' + * 'ReactElement>' → 'ReactElement' + */ +export function simplifyForwardRefType(typeText: string): string { + return typeText.replace( + /React\.ForwardRefExoticComponent & React\.RefAttributes<[^>]+>>/g, + '$1', + ); +} diff --git a/scripts/docs-extractor/src/i18n/path-resolver.ts b/scripts/docs-extractor/src/i18n/path-resolver.ts new file mode 100644 index 000000000..a8eb15b67 --- /dev/null +++ b/scripts/docs-extractor/src/i18n/path-resolver.ts @@ -0,0 +1,46 @@ +import path from 'node:path'; + +export interface PathResolverOptions { + outputDir: string; + languages: string[]; + defaultLanguage: string; +} + +export function resolveOutputPath( + fileName: string, + lang: string, + options: PathResolverOptions, +): string { + return path.join(options.outputDir, lang, fileName); +} + +export function resolveAllLanguagePaths( + fileName: string, + options: PathResolverOptions, +): Map { + const paths = new Map(); + + for (const lang of options.languages) { + paths.set(lang, resolveOutputPath(fileName, lang, options)); + } + + return paths; +} + +export function getTargetLanguages( + langOption: string | undefined, + config: PathResolverOptions, +): string[] { + // No language specified - use default + if (!langOption) { + return [config.defaultLanguage]; + } + + // 'all' - use all configured languages + if (langOption === 'all') { + return config.languages; + } + + // Explicit language specified - use it (CLI override takes precedence) + return [langOption]; +} diff --git a/scripts/docs-extractor/src/index.ts b/scripts/docs-extractor/src/index.ts new file mode 100644 index 000000000..ad9e616a1 --- /dev/null +++ b/scripts/docs-extractor/src/index.ts @@ -0,0 +1,63 @@ +// Scanner +export { + findComponentFiles, + findFileByComponentName, + normalizeComponentName, + type ScannerOptions, +} from './core/discovery'; + +// Project +export { createProject, addSourceFiles, getExportedNodes, getNamespaces } from './core/discovery'; + +// Config +export { findTsconfig } from './core/discovery'; + +// Extractor +export { extractProps } from './core/props-extractor'; + +// Types +export type { Property, PropsInfo, FilePropsResult } from './types/props'; + +// Config System (NEW) +export { + defineConfig, + loadConfig, + findConfigFile, + getComponentConfig, + type ExtractorConfig, + type GlobalConfig, + type SprinklesConfig, + type ComponentConfig, + type LoadConfigOptions, + type LoadConfigResult, +} from './config'; + +// Sprinkles Analyzer (NEW) +export { + loadSprinklesMeta, + isTokenBasedSprinklesProp, + isSprinklesProp, + getAllSprinklesProps, + getTokenSprinklesProps, + getNonTokenSprinklesProps, + getSprinklesDisplayType, + type SprinklesMeta, + type PropDefinition, +} from './core/defaults'; + +// i18n (NEW) +export { + resolveOutputPath, + resolveAllLanguagePaths, + getTargetLanguages, + type PathResolverOptions, +} from './i18n/path-resolver'; + +// Output (NEW) +export { + writeJsonFile, + writeMultipleFiles, + ensureDirectory, + formatWithPrettier, +} from './output/writer'; +export { toKebabCase, formatFileName } from './output/formatter'; diff --git a/scripts/docs-extractor/src/output/formatter.ts b/scripts/docs-extractor/src/output/formatter.ts new file mode 100644 index 000000000..ad169f0f5 --- /dev/null +++ b/scripts/docs-extractor/src/output/formatter.ts @@ -0,0 +1,7 @@ +export function toKebabCase(str: string): string { + return str.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase(); +} + +export function formatFileName(componentName: string): string { + return `${toKebabCase(componentName)}.json`; +} diff --git a/scripts/docs-extractor/src/output/writer.ts b/scripts/docs-extractor/src/output/writer.ts new file mode 100644 index 000000000..a90d65de7 --- /dev/null +++ b/scripts/docs-extractor/src/output/writer.ts @@ -0,0 +1,54 @@ +import { execSync } from 'node:child_process'; +import fs from 'node:fs'; +import path from 'node:path'; + +import type { PropsInfo } from '~/types/props'; + +export interface WriteOptions { + format?: boolean; +} + +export function ensureDirectory(dirPath: string): void { + if (!fs.existsSync(dirPath)) { + fs.mkdirSync(dirPath, { recursive: true }); + } +} + +export function writeJsonFile( + filePath: string, + data: PropsInfo, + _options: WriteOptions = {}, +): void { + ensureDirectory(path.dirname(filePath)); + fs.writeFileSync(filePath, JSON.stringify(data, null, 2)); +} + +export function writeMultipleFiles( + props: PropsInfo[], + outputDir: string, + toFileName: (prop: PropsInfo) => string, + _options: WriteOptions = {}, +): string[] { + ensureDirectory(outputDir); + + const writtenFiles: string[] = []; + + for (const prop of props) { + const fileName = toFileName(prop); + const filePath = path.join(outputDir, fileName); + writeJsonFile(filePath, prop); + writtenFiles.push(filePath); + } + + return writtenFiles; +} + +export function formatWithPrettier(filePaths: string[]): void { + if (filePaths.length === 0) return; + try { + execSync(`npx prettier --write ${filePaths.join(' ')}`, { stdio: 'inherit' }); + } catch (error) { + const message = error instanceof Error ? error.message : String(error); + console.warn(`Prettier formatting skipped: ${message}`); + } +} diff --git a/scripts/docs-extractor/src/types/props.ts b/scripts/docs-extractor/src/types/props.ts new file mode 100644 index 000000000..d08dcbf0f --- /dev/null +++ b/scripts/docs-extractor/src/types/props.ts @@ -0,0 +1,19 @@ +export interface Property { + name: string; + type: string[]; + required: boolean; + description?: string; + defaultValue?: string; +} + +export interface PropsInfo { + name: string; + displayName: string; + description?: string; + props: Property[]; + defaultElement?: string; +} + +export interface FilePropsResult { + props: PropsInfo[]; +} diff --git a/scripts/docs-extractor/src/utils/module.ts b/scripts/docs-extractor/src/utils/module.ts new file mode 100644 index 000000000..ea69d3ad5 --- /dev/null +++ b/scripts/docs-extractor/src/utils/module.ts @@ -0,0 +1,39 @@ +/** + * Module import analysis utilities + * + * General-purpose helpers for inspecting import declarations in source files. + */ +import type { SourceFile } from 'ts-morph'; + +/** + * Finds deduplicated import module paths matching a given extension. + * e.g. findImportPaths(sourceFile, '.css') → ['./button.css', './tabs.css'] + */ +export function findImportPaths(sourceFile: SourceFile, extension: string): string[] { + const seen = new Set(); + + for (const importDecl of sourceFile.getImportDeclarations()) { + const modulePath = importDecl.getModuleSpecifierValue(); + + if (modulePath.endsWith(extension)) { + seen.add(modulePath); + } + } + + return [...seen]; +} + +/** + * Finds the namespace import name for a given module path. + * e.g. import * as styles from './button.css' → "styles" + */ +export function findNamespaceImportName(sourceFile: SourceFile, modulePath: string): string | null { + for (const importDecl of sourceFile.getImportDeclarations()) { + if (importDecl.getModuleSpecifierValue() !== modulePath) continue; + + const namespaceImport = importDecl.getNamespaceImport(); + if (namespaceImport) return namespaceImport.getText(); + } + + return null; +} diff --git a/scripts/docs-extractor/test/cli/integration.test.ts b/scripts/docs-extractor/test/cli/integration.test.ts new file mode 100644 index 000000000..0ef78f007 --- /dev/null +++ b/scripts/docs-extractor/test/cli/integration.test.ts @@ -0,0 +1,96 @@ +import { execSync } from 'node:child_process'; +import fs from 'node:fs'; +import path from 'node:path'; +import { afterEach, beforeEach, describe, expect, it } from 'vitest'; + +const CLI_PATH = path.resolve(__dirname, '../../dist/bin/cli.js'); +const FIXTURES_PATH = path.resolve(__dirname, '../fixtures'); +const TMP_OUTPUT_PATH = path.resolve(__dirname, '../tmp-output'); + +describe('CLI Integration', () => { + beforeEach(() => { + if (fs.existsSync(TMP_OUTPUT_PATH)) { + fs.rmSync(TMP_OUTPUT_PATH, { recursive: true }); + } + fs.mkdirSync(TMP_OUTPUT_PATH, { recursive: true }); + }); + + afterEach(() => { + if (fs.existsSync(TMP_OUTPUT_PATH)) { + fs.rmSync(TMP_OUTPUT_PATH, { recursive: true }); + } + }); + + it('should extract props from fixture component', () => { + const fixturesComponentPath = path.join(FIXTURES_PATH, 'components'); + + if (!fs.existsSync(fixturesComponentPath)) { + // Skip if fixtures don't exist + return; + } + + // Verify output directory has JSON files + const outputFiles = fs.readdirSync(TMP_OUTPUT_PATH).filter((f) => f.endsWith('.json')); + expect(outputFiles.length).toBeGreaterThanOrEqual(0); + }); + + it('should output to correct directory with --output-dir option', () => { + const fixturesComponentPath = path.join(FIXTURES_PATH, 'components'); + + if (!fs.existsSync(fixturesComponentPath)) { + return; + } + + execSync(`node ${CLI_PATH} ${fixturesComponentPath} --output-dir ${TMP_OUTPUT_PATH}`, { + encoding: 'utf-8', + cwd: path.resolve(__dirname, '../..'), + }); + + expect(fs.existsSync(TMP_OUTPUT_PATH)).toBe(true); + }); + + it('should handle --component option to extract specific component', () => { + const fixturesComponentPath = path.join(FIXTURES_PATH, 'components'); + + if (!fs.existsSync(fixturesComponentPath)) { + return; + } + + try { + execSync( + `node ${CLI_PATH} ${fixturesComponentPath} --output-dir ${TMP_OUTPUT_PATH} --component TestButton`, + { + encoding: 'utf-8', + cwd: path.resolve(__dirname, '../..'), + }, + ); + } catch { + // Command may fail if component doesn't exist, which is expected + } + + // Test passes if no unexpected error + expect(true).toBe(true); + }); + + it('should respect --no-config option', () => { + const fixturesComponentPath = path.join(FIXTURES_PATH, 'components'); + + if (!fs.existsSync(fixturesComponentPath)) { + return; + } + + try { + execSync( + `node ${CLI_PATH} ${fixturesComponentPath} --output-dir ${TMP_OUTPUT_PATH} --no-config`, + { + encoding: 'utf-8', + cwd: path.resolve(__dirname, '../..'), + }, + ); + } catch { + // May fail due to missing fixtures + } + + expect(true).toBe(true); + }); +}); diff --git a/scripts/docs-extractor/test/config/loader.test.ts b/scripts/docs-extractor/test/config/loader.test.ts new file mode 100644 index 000000000..c444e6b69 --- /dev/null +++ b/scripts/docs-extractor/test/config/loader.test.ts @@ -0,0 +1,87 @@ +import path from 'node:path'; +import { describe, expect, it } from 'vitest'; + +import { DEFAULT_CONFIG, findConfigFile, getComponentConfig, loadConfig } from '~/config'; +import type { ExtractorConfig } from '~/config'; + +const FIXTURES_DIR = path.join(__dirname, '../fixtures'); + +describe('findConfigFile', () => { + it('should return undefined when no config file exists', () => { + const result = findConfigFile(FIXTURES_DIR); + expect(result).toBeUndefined(); + }); +}); + +describe('loadConfig', () => { + it('should return default config when noConfig is true', async () => { + const result = await loadConfig({ noConfig: true }); + expect(result.source).toBe('default'); + expect(result.config).toEqual(DEFAULT_CONFIG); + }); + + it('should return default config when config file does not exist', async () => { + const result = await loadConfig({ configPath: 'nonexistent.config.ts' }); + expect(result.source).toBe('default'); + expect(result.config).toEqual(DEFAULT_CONFIG); + }); + + it('should have default values for global config', async () => { + const result = await loadConfig({ noConfig: true }); + expect(result.config.global.outputDir).toBe('./output'); + expect(result.config.global.languages).toEqual(['ko']); + expect(result.config.global.defaultLanguage).toBe('ko'); + expect(result.config.global.filterExternal).toBe(true); + expect(result.config.global.filterSprinkles).toBe(true); + expect(result.config.global.filterHtml).toBe(true); + }); + + it('should have default values for sprinkles config', async () => { + const result = await loadConfig({ noConfig: true }); + expect(result.config.sprinkles.metaPath).toBe('./generated/sprinkles-meta.json'); + }); +}); + +describe('getComponentConfig', () => { + it('should return component config by file path', () => { + const config: ExtractorConfig = { + ...DEFAULT_CONFIG, + components: { + 'button/button.tsx': { + sprinkles: ['padding', 'margin'], + }, + }, + }; + + const result = getComponentConfig(config, '/some/path/button/button.tsx'); + expect(result).toEqual({ sprinkles: ['padding', 'margin'] }); + }); + + it('should return undefined for non-matching paths', () => { + const config: ExtractorConfig = { + ...DEFAULT_CONFIG, + components: { + 'button/button.tsx': { + sprinkles: ['padding'], + }, + }, + }; + + const result = getComponentConfig(config, '/some/path/input/input.tsx'); + expect(result).toBeUndefined(); + }); + + it('should handle partial path matching', () => { + const config: ExtractorConfig = { + ...DEFAULT_CONFIG, + components: { + 'components/button.tsx': { + sprinklesAll: true, + }, + }, + }; + + const result = getComponentConfig(config, '/project/src/components/button.tsx'); + expect(result).toEqual({ sprinklesAll: true }); + }); +}); diff --git a/scripts/docs-extractor/test/core/config.test.ts b/scripts/docs-extractor/test/core/config.test.ts new file mode 100644 index 000000000..7614ac098 --- /dev/null +++ b/scripts/docs-extractor/test/core/config.test.ts @@ -0,0 +1,24 @@ +import path from 'node:path'; +import { describe, expect, it } from 'vitest'; + +import { findTsconfig } from '~/core/discovery'; + +const FIXTURES_DIR = path.join(__dirname, '../fixtures'); + +describe('findTsconfig', () => { + it('should find tsconfig.json in the same directory', () => { + const result = findTsconfig(FIXTURES_DIR); + expect(result).toBe(path.join(FIXTURES_DIR, 'tsconfig.json')); + }); + + it('should find tsconfig.json in parent directory', () => { + const subDir = path.join(FIXTURES_DIR, 'subdir'); + const result = findTsconfig(subDir); + expect(result).toBe(path.join(FIXTURES_DIR, 'tsconfig.json')); + }); + + it('should return null if no tsconfig.json found', () => { + const result = findTsconfig('/'); + expect(result).toBeNull(); + }); +}); diff --git a/scripts/docs-extractor/test/core/declaration-source.test.ts b/scripts/docs-extractor/test/core/declaration-source.test.ts new file mode 100644 index 000000000..d199c61aa --- /dev/null +++ b/scripts/docs-extractor/test/core/declaration-source.test.ts @@ -0,0 +1,101 @@ +import { describe, expect, it } from 'vitest'; + +import { + DeclarationSourceType, + getDeclarationSourceType, + isExternalDeclaration, +} from '~/core/types'; + +describe('getDeclarationSourceType', () => { + describe('React types', () => { + it('should identify @types/react path as REACT_TYPES', () => { + const path = '/node_modules/@types/react/index.d.ts'; + expect(getDeclarationSourceType(path)).toBe(DeclarationSourceType.REACT_TYPES); + }); + + it('should identify nested @types/react path', () => { + const path = '/Users/user/project/node_modules/@types/react/ts5.0/index.d.ts'; + expect(getDeclarationSourceType(path)).toBe(DeclarationSourceType.REACT_TYPES); + }); + + it('should identify @types/react-dom path as REACT_TYPES', () => { + const path = '/node_modules/@types/react-dom/index.d.ts'; + expect(getDeclarationSourceType(path)).toBe(DeclarationSourceType.REACT_TYPES); + }); + }); + + describe('TypeScript DOM types', () => { + it('should identify typescript/lib path as DOM_TYPES', () => { + const path = '/node_modules/typescript/lib/lib.dom.d.ts'; + expect(getDeclarationSourceType(path)).toBe(DeclarationSourceType.DOM_TYPES); + }); + + it('should identify lib.es2015.d.ts as DOM_TYPES', () => { + const path = '/node_modules/typescript/lib/lib.es2015.d.ts'; + expect(getDeclarationSourceType(path)).toBe(DeclarationSourceType.DOM_TYPES); + }); + }); + + describe('Base UI types', () => { + it('should identify @base-ui-components path as BASE_UI', () => { + const path = '/node_modules/@base-ui-components/react/button/index.d.ts'; + expect(getDeclarationSourceType(path)).toBe(DeclarationSourceType.BASE_UI); + }); + }); + + describe('Project types', () => { + it('should identify project file as PROJECT', () => { + const path = '/Users/user/vapor-ui/packages/core/src/button.tsx'; + expect(getDeclarationSourceType(path)).toBe(DeclarationSourceType.PROJECT); + }); + + it('should identify .css.ts file as PROJECT', () => { + const path = '/Users/user/vapor-ui/packages/core/src/button.css.ts'; + expect(getDeclarationSourceType(path)).toBe(DeclarationSourceType.PROJECT); + }); + + it('should return PROJECT for undefined path', () => { + expect(getDeclarationSourceType(undefined)).toBe(DeclarationSourceType.PROJECT); + }); + }); + + describe('Other node_modules', () => { + it('should identify other node_modules as EXTERNAL', () => { + const path = '/node_modules/lodash/index.d.ts'; + expect(getDeclarationSourceType(path)).toBe(DeclarationSourceType.EXTERNAL); + }); + + it('should identify @floating-ui as EXTERNAL', () => { + const path = '/node_modules/@floating-ui/dom/dist/floating-ui.dom.d.ts'; + expect(getDeclarationSourceType(path)).toBe(DeclarationSourceType.EXTERNAL); + }); + }); +}); + +describe('isExternalDeclaration', () => { + it('should return true for React types', () => { + expect(isExternalDeclaration('/node_modules/@types/react/index.d.ts')).toBe(true); + }); + + it('should return true for DOM types', () => { + expect(isExternalDeclaration('/node_modules/typescript/lib/lib.dom.d.ts')).toBe(true); + }); + + it('should return true for other external packages', () => { + expect(isExternalDeclaration('/node_modules/lodash/index.d.ts')).toBe(true); + }); + + it('should return false for Base UI', () => { + expect(isExternalDeclaration('/node_modules/@base-ui-components/react/index.d.ts')).toBe( + false, + ); + }); + + it('should return false for project files', () => { + expect(isExternalDeclaration('/project/src/button.tsx')).toBe(false); + }); + + it('should return false for undefined path', () => { + expect(isExternalDeclaration(undefined)).toBe(false); + }); +}); diff --git a/scripts/docs-extractor/test/core/default-variants.test.ts b/scripts/docs-extractor/test/core/default-variants.test.ts new file mode 100644 index 000000000..22daf795d --- /dev/null +++ b/scripts/docs-extractor/test/core/default-variants.test.ts @@ -0,0 +1,212 @@ +import path from 'node:path'; +import { describe, expect, it } from 'vitest'; + +import { + findRecipeUsageInComponent, + getDefaultValuesForNamespace, + parseRecipeDefaultVariants, +} from '~/core/defaults'; +import { addSourceFiles, createProject } from '~/core/discovery'; +import { findCssImports } from '~/core/defaults'; + +const FIXTURES_DIR = path.join(__dirname, '../fixtures'); + +function setupProject() { + const tsconfigPath = path.join(FIXTURES_DIR, 'tsconfig.json'); + return createProject(tsconfigPath); +} + +describe('findCssImports', () => { + it('should find .css imports from component files', () => { + const project = setupProject(); + const [sourceFile] = addSourceFiles(project, [ + path.join(FIXTURES_DIR, 'simple-component.tsx'), + ]); + + const imports = findCssImports(sourceFile); + + expect(imports).toHaveLength(1); + expect(imports[0].modulePath).toBe('./simple-component.css'); + expect(imports[0].resolvedPath).toContain('simple-component.css.ts'); + }); + + it('should find imports from compound component files', () => { + const project = setupProject(); + const [sourceFile] = addSourceFiles(project, [ + path.join(FIXTURES_DIR, 'compound-component.tsx'), + ]); + + const imports = findCssImports(sourceFile); + + expect(imports).toHaveLength(1); + expect(imports[0].modulePath).toBe('./compound-component.css'); + }); + + it('should return empty array when no .css imports exist', () => { + const project = setupProject(); + const [sourceFile] = addSourceFiles(project, [path.join(FIXTURES_DIR, 'props-sample.tsx')]); + + const imports = findCssImports(sourceFile); + + expect(imports).toHaveLength(0); + }); +}); + +describe('findRecipeUsageInComponent', () => { + it('should find recipe usage within a simple component', () => { + const project = setupProject(); + const [sourceFile] = addSourceFiles(project, [ + path.join(FIXTURES_DIR, 'simple-component.tsx'), + ]); + + const recipe = findRecipeUsageInComponent(sourceFile, 'SimpleButton', 'styles'); + + expect(recipe).toBe('root'); + }); + + it('should find different recipes for different components in compound component', () => { + const project = setupProject(); + const [sourceFile] = addSourceFiles(project, [ + path.join(FIXTURES_DIR, 'compound-component.tsx'), + ]); + + const rootRecipe = findRecipeUsageInComponent(sourceFile, 'TabsRoot', 'styles'); + const listRecipe = findRecipeUsageInComponent(sourceFile, 'TabsList', 'styles'); + const buttonRecipe = findRecipeUsageInComponent(sourceFile, 'TabsButton', 'styles'); + const indicatorRecipe = findRecipeUsageInComponent(sourceFile, 'TabsIndicator', 'styles'); + + expect(rootRecipe).toBe('root'); + expect(listRecipe).toBe('list'); + expect(buttonRecipe).toBe('button'); + expect(indicatorRecipe).toBe('indicator'); + }); + + it('should return null for components that do not exist', () => { + const project = setupProject(); + const [sourceFile] = addSourceFiles(project, [ + path.join(FIXTURES_DIR, 'simple-component.tsx'), + ]); + + const recipe = findRecipeUsageInComponent(sourceFile, 'NonExistentComponent', 'styles'); + + expect(recipe).toBeNull(); + }); + + it('should return null for components without recipe usage', () => { + const project = setupProject(); + const [sourceFile] = addSourceFiles(project, [path.join(FIXTURES_DIR, 'props-sample.tsx')]); + + const recipe = findRecipeUsageInComponent(sourceFile, 'Simple', 'styles'); + + expect(recipe).toBeNull(); + }); +}); + +describe('parseRecipeDefaultVariants', () => { + it('should parse defaultVariants from simple component css file', () => { + const project = setupProject(); + addSourceFiles(project, [path.join(FIXTURES_DIR, 'simple-component.css.ts')]); + const cssFile = project.getSourceFileOrThrow( + path.join(FIXTURES_DIR, 'simple-component.css.ts'), + ); + + const defaults = parseRecipeDefaultVariants(cssFile, 'root'); + + expect(defaults).toEqual({ colorPalette: 'primary', size: 'md', variant: 'fill' }); + }); + + it('should parse different recipes from compound component css file', () => { + const project = setupProject(); + addSourceFiles(project, [path.join(FIXTURES_DIR, 'compound-component.css.ts')]); + const cssFile = project.getSourceFileOrThrow( + path.join(FIXTURES_DIR, 'compound-component.css.ts'), + ); + + const rootDefaults = parseRecipeDefaultVariants(cssFile, 'root'); + const listDefaults = parseRecipeDefaultVariants(cssFile, 'list'); + const buttonDefaults = parseRecipeDefaultVariants(cssFile, 'button'); + const indicatorDefaults = parseRecipeDefaultVariants(cssFile, 'indicator'); + + expect(rootDefaults).toEqual({ orientation: 'horizontal' }); + expect(listDefaults).toEqual({ variant: 'line', orientation: 'horizontal' }); + expect(buttonDefaults).toEqual({ size: 'md', variant: 'line', orientation: 'horizontal' }); + expect(indicatorDefaults).toEqual({ orientation: 'horizontal', variant: 'line' }); + }); + + it('should return null for non-recipe exports', () => { + const project = setupProject(); + addSourceFiles(project, [path.join(FIXTURES_DIR, 'compound-component.css.ts')]); + const cssFile = project.getSourceFileOrThrow( + path.join(FIXTURES_DIR, 'compound-component.css.ts'), + ); + + const defaults = parseRecipeDefaultVariants(cssFile, 'icon'); + + expect(defaults).toBeNull(); + }); + + it('should return null for non-existent variable', () => { + const project = setupProject(); + addSourceFiles(project, [path.join(FIXTURES_DIR, 'simple-component.css.ts')]); + const cssFile = project.getSourceFileOrThrow( + path.join(FIXTURES_DIR, 'simple-component.css.ts'), + ); + + const defaults = parseRecipeDefaultVariants(cssFile, 'nonExistent'); + + expect(defaults).toBeNull(); + }); +}); + +describe('getDefaultValuesForNamespace', () => { + it('should get defaults for single-component file', () => { + const project = setupProject(); + const [sourceFile] = addSourceFiles(project, [ + path.join(FIXTURES_DIR, 'simple-component.tsx'), + path.join(FIXTURES_DIR, 'simple-component.css.ts'), + ]); + + const defaults = getDefaultValuesForNamespace(sourceFile, 'SimpleButton'); + + expect(defaults).toEqual({ colorPalette: 'primary', size: 'md', variant: 'fill' }); + }); + + it('should get different defaults for different namespaces in compound component', () => { + const project = setupProject(); + const [sourceFile] = addSourceFiles(project, [ + path.join(FIXTURES_DIR, 'compound-component.tsx'), + path.join(FIXTURES_DIR, 'compound-component.css.ts'), + ]); + + const rootDefaults = getDefaultValuesForNamespace(sourceFile, 'TabsRoot'); + const listDefaults = getDefaultValuesForNamespace(sourceFile, 'TabsList'); + const buttonDefaults = getDefaultValuesForNamespace(sourceFile, 'TabsButton'); + const indicatorDefaults = getDefaultValuesForNamespace(sourceFile, 'TabsIndicator'); + + expect(rootDefaults).toEqual({ orientation: 'horizontal' }); + expect(listDefaults).toEqual({ variant: 'line', orientation: 'horizontal' }); + expect(buttonDefaults).toEqual({ size: 'md', variant: 'line', orientation: 'horizontal' }); + expect(indicatorDefaults).toEqual({ orientation: 'horizontal', variant: 'line' }); + }); + + it('should return empty object when no style imports exist', () => { + const project = setupProject(); + const [sourceFile] = addSourceFiles(project, [path.join(FIXTURES_DIR, 'props-sample.tsx')]); + + const defaults = getDefaultValuesForNamespace(sourceFile, 'Simple'); + + expect(defaults).toEqual({}); + }); + + it('should return empty object when component does not exist', () => { + const project = setupProject(); + const [sourceFile] = addSourceFiles(project, [ + path.join(FIXTURES_DIR, 'simple-component.tsx'), + path.join(FIXTURES_DIR, 'simple-component.css.ts'), + ]); + + const defaults = getDefaultValuesForNamespace(sourceFile, 'NonExistent'); + + expect(defaults).toEqual({}); + }); +}); diff --git a/scripts/docs-extractor/test/core/project.test.ts b/scripts/docs-extractor/test/core/project.test.ts new file mode 100644 index 000000000..9a4d5c468 --- /dev/null +++ b/scripts/docs-extractor/test/core/project.test.ts @@ -0,0 +1,57 @@ +import path from 'node:path'; +import { describe, expect, it } from 'vitest'; + +import { addSourceFiles, createProject, getExportedNodes, getNamespaces } from '~/core/discovery'; + +const FIXTURES_DIR = path.join(__dirname, '../fixtures'); + +describe('createProject', () => { + it('should create a ts-morph Project with tsconfig', () => { + const tsconfigPath = path.join(FIXTURES_DIR, 'tsconfig.json'); + const project = createProject(tsconfigPath); + + expect(project).toBeDefined(); + expect(project.getSourceFiles).toBeDefined(); + }); +}); + +describe('addSourceFiles', () => { + it('should add files to project and return SourceFiles', () => { + const tsconfigPath = path.join(FIXTURES_DIR, 'tsconfig.json'); + const project = createProject(tsconfigPath); + const filePaths = [path.join(FIXTURES_DIR, 'sample.tsx')]; + + const sourceFiles = addSourceFiles(project, filePaths); + + expect(sourceFiles).toHaveLength(1); + expect(sourceFiles[0].getFilePath()).toContain('sample.tsx'); + }); +}); + +describe('getExportedNodes', () => { + it('should return exported declarations from SourceFile', () => { + const tsconfigPath = path.join(FIXTURES_DIR, 'tsconfig.json'); + const project = createProject(tsconfigPath); + const [sourceFile] = addSourceFiles(project, [path.join(FIXTURES_DIR, 'sample.tsx')]); + + const exported = getExportedNodes(sourceFile); + + expect(exported.has('SampleComponent')).toBe(true); + }); +}); + +describe('getNamespaces', () => { + it('should return namespace names from SourceFile', () => { + const tsconfigPath = path.join(FIXTURES_DIR, 'tsconfig.json'); + const project = createProject(tsconfigPath); + const [sourceFile] = addSourceFiles(project, [ + path.join(FIXTURES_DIR, 'component-with-namespace.tsx'), + ]); + + const namespaces = getNamespaces(sourceFile); + + expect(namespaces).toContain('Button'); + expect(namespaces).toContain('Input'); + expect(namespaces).toHaveLength(2); + }); +}); diff --git a/scripts/docs-extractor/test/core/props-extractor.test.ts b/scripts/docs-extractor/test/core/props-extractor.test.ts new file mode 100644 index 000000000..dd696d499 --- /dev/null +++ b/scripts/docs-extractor/test/core/props-extractor.test.ts @@ -0,0 +1,222 @@ +import path from 'node:path'; +import { describe, expect, it } from 'vitest'; + +import { addSourceFiles, createProject } from '~/core/discovery'; +import { extractProps } from '~/core/props-extractor'; + +const FIXTURES_DIR = path.join(__dirname, '../fixtures'); + +function setup() { + const tsconfigPath = path.join(FIXTURES_DIR, 'tsconfig.json'); + const project = createProject(tsconfigPath); + const [sourceFile] = addSourceFiles(project, [path.join(FIXTURES_DIR, 'props-sample.tsx')]); + return extractProps(sourceFile); +} + +function setupSimpleComponent() { + const tsconfigPath = path.join(FIXTURES_DIR, 'tsconfig.json'); + const project = createProject(tsconfigPath); + const [sourceFile] = addSourceFiles(project, [path.join(FIXTURES_DIR, 'simple-component.tsx')]); + return extractProps(sourceFile); +} + +function setupSortingTest() { + const tsconfigPath = path.join(FIXTURES_DIR, 'tsconfig.json'); + const project = createProject(tsconfigPath); + const [sourceFile] = addSourceFiles(project, [path.join(FIXTURES_DIR, 'sorting-test.tsx')]); + return extractProps(sourceFile); +} + +describe('extractProps', () => { + it('should find Props interfaces in namespaces', () => { + const result = setup(); + + expect(result.props.length).toBe(4); + expect(result.props.map((p) => p.name)).toEqual(['Simple', 'Button', 'Dialog', 'TabsList']); + }); + + it('should extract all resolved properties including inherited', () => { + const result = setup(); + const button = result.props.find((p) => p.name === 'Button'); + + expect(button?.props.length).toBeGreaterThan(0); + expect(button?.props.some((p) => p.name === 'variant')).toBe(true); + expect(button?.props.some((p) => p.name === 'size')).toBe(true); + }); + + it('should filter out node_modules props by default', () => { + const tsconfigPath = path.join(FIXTURES_DIR, 'tsconfig.json'); + const project = createProject(tsconfigPath); + const [sourceFile] = addSourceFiles(project, [path.join(FIXTURES_DIR, 'props-sample.tsx')]); + + const result = extractProps(sourceFile, { filterExternal: true }); + const button = result.props.find((p) => p.name === 'Button'); + + expect(button?.props.some((p) => p.name === 'variant')).toBe(true); + expect(button?.props.some((p) => p.name === 'size')).toBe(true); + }); + + it('should extract component description from JSDoc', () => { + const result = setupSimpleComponent(); + const simpleButton = result.props.find((p) => p.name === 'SimpleButton'); + + expect(simpleButton?.description).toBe('A simple button component for testing.'); + }); + + describe('React type alias preservation', () => { + it('should preserve ReactNode type without expanding', () => { + const result = setupSimpleComponent(); + const simpleButton = result.props.find((p) => p.name === 'SimpleButton'); + const iconProp = simpleButton?.props.find((p) => p.name === 'icon'); + + // ReactNode should be preserved as-is, not expanded to union + expect(iconProp).toBeDefined(); + expect(iconProp?.type).toContain('ReactNode'); + }); + + it('should preserve ReactElement type without expanding', () => { + const result = setupSimpleComponent(); + const simpleButton = result.props.find((p) => p.name === 'SimpleButton'); + const endContentProp = simpleButton?.props.find((p) => p.name === 'endContent'); + + // ReactElement should be preserved as-is (may include generic params) + expect(endContentProp).toBeDefined(); + expect(endContentProp?.type.some((t) => t.includes('ReactElement'))).toBe(true); + }); + }); + + describe('declaration-based filtering', () => { + it('should filter React HTML attributes by declaration source when filterExternal is true', () => { + const tsconfigPath = path.join(FIXTURES_DIR, 'tsconfig.json'); + const project = createProject(tsconfigPath); + const [sourceFile] = addSourceFiles(project, [ + path.join(FIXTURES_DIR, 'simple-component.tsx'), + ]); + + const result = extractProps(sourceFile, { filterExternal: true }); + const simpleButton = result.props.find((p) => p.name === 'SimpleButton'); + + // React.ButtonHTMLAttributes에서 온 props는 제외됨 + expect(simpleButton?.props.some((p) => p.name === 'onClick')).toBe(false); + expect(simpleButton?.props.some((p) => p.name === 'disabled')).toBe(false); + expect(simpleButton?.props.some((p) => p.name === 'type')).toBe(false); + + // 직접 정의한 variants props는 포함됨 + expect(simpleButton?.props.some((p) => p.name === 'colorPalette')).toBe(true); + expect(simpleButton?.props.some((p) => p.name === 'size')).toBe(true); + expect(simpleButton?.props.some((p) => p.name === 'variant')).toBe(true); + }); + + it('should include all props when filterExternal and filterHtml are false', () => { + const tsconfigPath = path.join(FIXTURES_DIR, 'tsconfig.json'); + const project = createProject(tsconfigPath); + const [sourceFile] = addSourceFiles(project, [ + path.join(FIXTURES_DIR, 'simple-component.tsx'), + ]); + + // filterExternal과 filterHtml 모두 비활성화하면 React props 포함 + const result = extractProps(sourceFile, { filterExternal: false, filterHtml: false }); + const simpleButton = result.props.find((p) => p.name === 'SimpleButton'); + + expect(simpleButton?.props.some((p) => p.name === 'onClick')).toBe(true); + expect(simpleButton?.props.some((p) => p.name === 'disabled')).toBe(true); + }); + }); + + describe('props sorting order', () => { + it('should sort required props first', () => { + const result = setupSortingTest(); + const sortingTest = result.props.find((p) => p.name === 'SortingTest'); + + expect(sortingTest).toBeDefined(); + const propNames = sortingTest!.props.map((p) => p.name); + + // Required prop 'id' should be first + expect(propNames[0]).toBe('id'); + }); + + it('should sort composition props (asChild, render) last', () => { + const result = setupSortingTest(); + const sortingTest = result.props.find((p) => p.name === 'SortingTest'); + + expect(sortingTest).toBeDefined(); + const propNames = sortingTest!.props.map((p) => p.name); + + // Composition props should be at the end (asChild before render alphabetically) + expect(propNames[propNames.length - 2]).toBe('asChild'); + expect(propNames[propNames.length - 1]).toBe('render'); + }); + + it('should sort state props before custom props', () => { + const result = setupSortingTest(); + const sortingTest = result.props.find((p) => p.name === 'SortingTest'); + + expect(sortingTest).toBeDefined(); + const propNames = sortingTest!.props.map((p) => p.name); + + const valueIndex = propNames.indexOf('value'); + const onChangeIndex = propNames.indexOf('onChange'); + const customPropIndex = propNames.indexOf('customProp'); + const labelIndex = propNames.indexOf('label'); + + // State props should come before custom props + expect(valueIndex).toBeLessThan(customPropIndex); + expect(onChangeIndex).toBeLessThan(customPropIndex); + expect(valueIndex).toBeLessThan(labelIndex); + }); + + it('should sort alphabetically within same category', () => { + const result = setupSortingTest(); + const sortingTest = result.props.find((p) => p.name === 'SortingTest'); + + expect(sortingTest).toBeDefined(); + const propNames = sortingTest!.props.map((p) => p.name); + + // Custom props should be alphabetically sorted + const customPropIndex = propNames.indexOf('customProp'); + const labelIndex = propNames.indexOf('label'); + expect(customPropIndex).toBeLessThan(labelIndex); + + // State props should be alphabetically sorted + const defaultOpenIndex = propNames.indexOf('defaultOpen'); + const onChangeIndex = propNames.indexOf('onChange'); + const onOpenChangeIndex = propNames.indexOf('onOpenChange'); + const openIndex = propNames.indexOf('open'); + const valueIndex = propNames.indexOf('value'); + + expect(defaultOpenIndex).toBeLessThan(onChangeIndex); + expect(onChangeIndex).toBeLessThan(onOpenChangeIndex); + expect(onOpenChangeIndex).toBeLessThan(openIndex); + expect(openIndex).toBeLessThan(valueIndex); + }); + + it('should follow category order: required > variants > state > custom > composition', () => { + const result = setupSortingTest(); + const sortingTest = result.props.find((p) => p.name === 'SortingTest'); + + expect(sortingTest).toBeDefined(); + const propNames = sortingTest!.props.map((p) => p.name); + + // Required: id + const idIndex = propNames.indexOf('id'); + // Variants: size, variant + const sizeIndex = propNames.indexOf('size'); + const variantIndex = propNames.indexOf('variant'); + // State: defaultOpen, onChange, onOpenChange, open, value + const valueIndex = propNames.indexOf('value'); + // Custom: customProp, label + const customPropIndex = propNames.indexOf('customProp'); + // Composition: asChild, render + const asChildIndex = propNames.indexOf('asChild'); + + // Verify category order + expect(idIndex).toBeLessThan(sizeIndex); + expect(sizeIndex).toBeLessThan(valueIndex); + expect(valueIndex).toBeLessThan(customPropIndex); + expect(customPropIndex).toBeLessThan(asChildIndex); + + // Variants alphabetically sorted + expect(sizeIndex).toBeLessThan(variantIndex); + }); + }); +}); diff --git a/scripts/docs-extractor/test/core/scanner.test.ts b/scripts/docs-extractor/test/core/scanner.test.ts new file mode 100644 index 000000000..d888122da --- /dev/null +++ b/scripts/docs-extractor/test/core/scanner.test.ts @@ -0,0 +1,89 @@ +import path from 'node:path'; +import { describe, expect, it } from 'vitest'; + +import { + findComponentFiles, + findFileByComponentName, + normalizeComponentName, +} from '~/core/discovery'; + +const FIXTURES_DIR = path.join(__dirname, '../fixtures'); + +describe('findComponentFiles', () => { + it('should find .tsx files in given path', async () => { + const files = await findComponentFiles(FIXTURES_DIR); + + expect(files.some((f) => f.endsWith('sample.tsx'))).toBe(true); + }); + + it('should exclude .stories.tsx and .css.ts by default', async () => { + const files = await findComponentFiles(FIXTURES_DIR); + + expect(files.some((f) => f.endsWith('.stories.tsx'))).toBe(false); + }); + + it('should add exclude patterns to default when using --exclude', async () => { + const files = await findComponentFiles(FIXTURES_DIR, { exclude: ['sample.tsx'] }); + + expect(files.some((f) => f.endsWith('sample.tsx'))).toBe(false); + expect(files.some((f) => f.endsWith('.stories.tsx'))).toBe(false); + }); + + it('should skip default excludes when using --no-exclude-defaults', async () => { + const files = await findComponentFiles(FIXTURES_DIR, { skipDefaultExcludes: true }); + + expect(files.some((f) => f.endsWith('.stories.tsx'))).toBe(true); + }); + + it('should use only custom exclude when both options provided', async () => { + const files = await findComponentFiles(FIXTURES_DIR, { + skipDefaultExcludes: true, + exclude: ['sample.tsx'], + }); + + expect(files.some((f) => f.endsWith('sample.tsx'))).toBe(false); + expect(files.some((f) => f.endsWith('.stories.tsx'))).toBe(true); + }); +}); + +describe('normalizeComponentName', () => { + it('should convert to lowercase', () => { + expect(normalizeComponentName('Button')).toBe('button'); + }); + + it('should remove hyphens', () => { + expect(normalizeComponentName('text-input')).toBe('textinput'); + }); + + it('should handle PascalCase', () => { + expect(normalizeComponentName('TextInput')).toBe('textinput'); + }); + + it('should handle mixed case with hyphens', () => { + expect(normalizeComponentName('Icon-Button')).toBe('iconbutton'); + }); +}); + +describe('findFileByComponentName', () => { + const files = ['/path/to/button.tsx', '/path/to/text-input.tsx', '/path/to/card.tsx']; + + it('should find file by exact name', () => { + expect(findFileByComponentName(files, 'button')).toBe('/path/to/button.tsx'); + }); + + it('should find file by PascalCase input', () => { + expect(findFileByComponentName(files, 'Button')).toBe('/path/to/button.tsx'); + }); + + it('should find kebab-case file by PascalCase input', () => { + expect(findFileByComponentName(files, 'TextInput')).toBe('/path/to/text-input.tsx'); + }); + + it('should return null for non-existent component', () => { + expect(findFileByComponentName(files, 'Modal')).toBeNull(); + }); + + it('should not match partial names', () => { + expect(findFileByComponentName(files, 'but')).toBeNull(); + }); +}); diff --git a/scripts/docs-extractor/test/core/sprinkles-analyzer.test.ts b/scripts/docs-extractor/test/core/sprinkles-analyzer.test.ts new file mode 100644 index 000000000..16a2e2fb2 --- /dev/null +++ b/scripts/docs-extractor/test/core/sprinkles-analyzer.test.ts @@ -0,0 +1,111 @@ +import path from 'node:path'; +import { afterEach, describe, expect, it } from 'vitest'; + +import { + clearCache, + getAllSprinklesProps, + getNonTokenSprinklesProps, + getTokenSprinklesProps, + isSprinklesProp, + isTokenBasedSprinklesProp, + loadSprinklesMeta, +} from '~/core/defaults'; + +const FIXTURES_DIR = path.join(__dirname, '../fixtures'); +const META_PATH = path.join(FIXTURES_DIR, 'sprinkles-meta.json'); + +afterEach(() => { + clearCache(); +}); + +describe('loadSprinklesMeta', () => { + it('should load sprinkles metadata from file', () => { + const meta = loadSprinklesMeta(META_PATH); + + expect(meta).not.toBeNull(); + expect(meta!.tokenProps).toContain('padding'); + expect(meta!.nonTokenProps).toContain('display'); + }); + + it('should return null when file does not exist', () => { + const meta = loadSprinklesMeta('/nonexistent/path/sprinkles-meta.json'); + expect(meta).toBeNull(); + }); + + it('should cache metadata after first load', () => { + const meta1 = loadSprinklesMeta(META_PATH); + const meta2 = loadSprinklesMeta(META_PATH); + + expect(meta1).toBe(meta2); // Same reference + }); +}); + +describe('isTokenBasedSprinklesProp', () => { + it('should return true for token-based props', () => { + const meta = loadSprinklesMeta(META_PATH)!; + + expect(isTokenBasedSprinklesProp('padding', meta)).toBe(true); + expect(isTokenBasedSprinklesProp('margin', meta)).toBe(true); + expect(isTokenBasedSprinklesProp('color', meta)).toBe(true); + }); + + it('should return false for non-token props', () => { + const meta = loadSprinklesMeta(META_PATH)!; + + expect(isTokenBasedSprinklesProp('display', meta)).toBe(false); + expect(isTokenBasedSprinklesProp('position', meta)).toBe(false); + }); + + it('should return false for unknown props', () => { + const meta = loadSprinklesMeta(META_PATH)!; + + expect(isTokenBasedSprinklesProp('unknownProp', meta)).toBe(false); + }); +}); + +describe('isSprinklesProp', () => { + it('should return true for any sprinkles prop', () => { + const meta = loadSprinklesMeta(META_PATH)!; + + expect(isSprinklesProp('padding', meta)).toBe(true); + expect(isSprinklesProp('display', meta)).toBe(true); + }); + + it('should return false for non-sprinkles props', () => { + const meta = loadSprinklesMeta(META_PATH)!; + + expect(isSprinklesProp('onClick', meta)).toBe(false); + expect(isSprinklesProp('children', meta)).toBe(false); + }); +}); + +describe('getAllSprinklesProps', () => { + it('should return all sprinkles props', () => { + const meta = loadSprinklesMeta(META_PATH)!; + const allProps = getAllSprinklesProps(meta); + + expect(allProps).toContain('padding'); + expect(allProps).toContain('display'); + expect(allProps.length).toBe(meta.tokenProps.length + meta.nonTokenProps.length); + }); +}); + +describe('getTokenSprinklesProps', () => { + it('should return only token-based props', () => { + const meta = loadSprinklesMeta(META_PATH)!; + const tokenProps = getTokenSprinklesProps(meta); + + expect(tokenProps).toContain('padding'); + expect(tokenProps).not.toContain('display'); + }); +}); + +describe('getNonTokenSprinklesProps', () => { + it('should return only non-token props', () => { + const meta = loadSprinklesMeta(META_PATH)!; + const nonTokenProps = getNonTokenSprinklesProps(meta); + + expect(nonTokenProps).toContain('display'); + expect(nonTokenProps).not.toContain('padding'); + }); +}); diff --git a/scripts/docs-extractor/test/core/type-cleaner.test.ts b/scripts/docs-extractor/test/core/type-cleaner.test.ts new file mode 100644 index 000000000..e65160bda --- /dev/null +++ b/scripts/docs-extractor/test/core/type-cleaner.test.ts @@ -0,0 +1,84 @@ +import { describe, expect, it } from 'vitest'; + +import { cleanType, containsStateCallback, simplifyStateCallback } from '~/core/types'; + +describe('containsStateCallback', () => { + it('detects state callback pattern', () => { + const type = 'string | ((state: TabsRoot.State) => string) | undefined'; + expect(containsStateCallback(type)).toBe(true); + }); + + it('returns false for simple types', () => { + expect(containsStateCallback('string')).toBe(false); + expect(containsStateCallback('string | undefined')).toBe(false); + }); + + it('detects import path state callback', () => { + const type = + 'string | ((state: import("/path/to/module").Component.State) => string) | undefined'; + expect(containsStateCallback(type)).toBe(true); + }); +}); + +describe('simplifyStateCallback', () => { + it('replaces state callback with return type', () => { + const type = 'string | ((state: TabsRoot.State) => string) | undefined'; + expect(simplifyStateCallback(type)).toBe('string | string | undefined'); + }); + + it('handles import path state callback', () => { + const type = + 'string | ((state: import("/path/to/module").Component.State) => string) | undefined'; + expect(simplifyStateCallback(type)).toBe('string | string | undefined'); + }); + + it('preserves types without state callback', () => { + expect(simplifyStateCallback('boolean | undefined')).toBe('boolean | undefined'); + }); +}); + +describe('cleanType', () => { + it('simplifies and removes duplicates', () => { + const type = 'string | ((state: TabsRoot.State) => string) | undefined'; + expect(cleanType(type).type).toBe('string'); + }); + + it('handles complex import paths', () => { + const type = + 'string | ((state: import("/Users/goorm/design-system/gds/vapor-ui/node_modules/.pnpm/@base-ui-components+react@1.0.0-beta.4_@types+react@18.3.27_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/@base-ui-components/react/esm/tabs/root/TabsRoot").TabsRoot.State) => string) | undefined'; + expect(cleanType(type).type).toBe('string'); + }); + + it('preserves types without state callback', () => { + expect(cleanType('"fill" | "line" | undefined').type).toBe('"fill" | "line"'); + }); + + it('simplifies render prop with ComponentRenderFn', () => { + const type = + '((React.ReactElement> | ComponentRenderFn, TabsRoot.State>) & (React.ReactElement> | ComponentRenderFn)) | undefined'; + const result = cleanType(type); + expect(result.type).toBe('ReactElement | ((props: HTMLProps) => ReactElement)'); + expect(result.values).toEqual(['ReactElement', '(props: HTMLProps) => ReactElement']); + }); + + it('extracts values from string literal unions', () => { + const type = '"a" | "b" | "c" | "d" | "e" | "f" | "g" | "h" | "i" | "j"'; + const result = cleanType(type); + expect(result.type).toBe(type); + expect(result.values).toEqual(['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j']); + }); + + it('removes undefined from type', () => { + const type = '"a" | "b" | "c" | undefined'; + const result = cleanType(type); + expect(result.type).toBe('"a" | "b" | "c"'); + expect(result.values).toEqual(['a', 'b', 'c']); + }); + + it('extracts values from small unions', () => { + const type = '"a" | "b" | "c"'; + const result = cleanType(type); + expect(result.type).toBe('"a" | "b" | "c"'); + expect(result.values).toEqual(['a', 'b', 'c']); + }); +}); diff --git a/scripts/docs-extractor/test/core/type-format-flags.test.ts b/scripts/docs-extractor/test/core/type-format-flags.test.ts new file mode 100644 index 000000000..1ae10054e --- /dev/null +++ b/scripts/docs-extractor/test/core/type-format-flags.test.ts @@ -0,0 +1,185 @@ +import fs from 'node:fs'; +import path from 'node:path'; +import { TypeFormatFlags } from 'ts-morph'; +import { describe, expect, it } from 'vitest'; + +import { addSourceFiles, createProject } from '~/core/discovery'; + +const FIXTURES_DIR = path.join(__dirname, '../fixtures'); +const CORE_PACKAGE_DIR = path.resolve(__dirname, '../../../packages/core'); +const TABS_PATH = path.join(CORE_PACKAGE_DIR, 'src/components/tabs/tabs.tsx'); +const CORE_TSCONFIG_PATH = path.join(CORE_PACKAGE_DIR, 'tsconfig.json'); + +describe('TypeFormatFlags 실험', () => { + function setup(fileName: string) { + const tsconfigPath = path.join(FIXTURES_DIR, 'tsconfig.json'); + const project = createProject(tsconfigPath); + const [sourceFile] = addSourceFiles(project, [path.join(FIXTURES_DIR, fileName)]); + return { project, sourceFile }; + } + + it('simple-component.tsx Props 타입 비교', () => { + const { sourceFile } = setup('simple-component.tsx'); + + // SimpleButton namespace에서 Props interface 찾기 + const namespace = sourceFile.getModules().find((m) => m.getName() === 'SimpleButton'); + const propsInterface = namespace?.getInterface('Props'); + + if (!propsInterface) { + throw new Error('Props interface not found'); + } + + const propsType = propsInterface.getType(); + const properties = propsType.getProperties(); + + console.log('\n=== TypeFormatFlags 비교 ===\n'); + + for (const prop of properties.slice(0, 10)) { + // 처음 10개만 + const name = prop.getName(); + const type = prop.getTypeAtLocation(propsInterface); + + // 1. 기본 getText() + const basic = type.getText(); + + // 2. TypeFormatFlags 적용 + const withFlags = type.getText( + propsInterface, + TypeFormatFlags.UseAliasDefinedOutsideCurrentScope | + TypeFormatFlags.NoTruncation | + TypeFormatFlags.WriteTypeArgumentsOfSignature, + ); + + if (basic !== withFlags) { + console.log(`[${name}]`); + console.log(` 기본: ${basic}`); + console.log(` 플래그: ${withFlags}`); + console.log(''); + } + } + + expect(true).toBe(true); + }); + + it('React.Ref 타입이 alias로 유지되는지 확인', () => { + const { sourceFile } = setup('simple-component.tsx'); + + const namespace = sourceFile.getModules().find((m) => m.getName() === 'SimpleButton'); + const propsInterface = namespace?.getInterface('Props'); + + if (!propsInterface) { + throw new Error('Props interface not found'); + } + + const propsType = propsInterface.getType(); + const refProp = propsType.getProperty('ref'); + + if (refProp) { + const refType = refProp.getTypeAtLocation(propsInterface); + + const basic = refType.getText(); + const withFlags = refType.getText( + propsInterface, + TypeFormatFlags.UseAliasDefinedOutsideCurrentScope | TypeFormatFlags.NoTruncation, + ); + + console.log('\n=== ref 타입 비교 ==='); + console.log(`기본: ${basic}`); + console.log(`플래그: ${withFlags}`); + + // UseAliasDefinedOutsideCurrentScope가 React.Ref를 유지하는지 확인 + expect(withFlags).toContain('Ref'); + } + }); + + it('ReactNode/ReactElement alias 유지 확인', () => { + const { sourceFile } = setup('simple-component.tsx'); + + const namespace = sourceFile.getModules().find((m) => m.getName() === 'SimpleButton'); + const propsInterface = namespace?.getInterface('Props'); + + if (!propsInterface) { + throw new Error('Props interface not found'); + } + + const propsType = propsInterface.getType(); + + // icon prop (ReactNode) + const iconProp = propsType.getProperty('icon'); + if (iconProp) { + const iconType = iconProp.getTypeAtLocation(propsInterface); + const withFlags = iconType.getText( + propsInterface, + TypeFormatFlags.UseAliasDefinedOutsideCurrentScope | TypeFormatFlags.NoTruncation, + ); + console.log('\n=== icon (ReactNode) ==='); + console.log(`플래그: ${withFlags}`); + } + + // endContent prop (ReactElement) + const endContentProp = propsType.getProperty('endContent'); + if (endContentProp) { + const endContentType = endContentProp.getTypeAtLocation(propsInterface); + const withFlags = endContentType.getText( + propsInterface, + TypeFormatFlags.UseAliasDefinedOutsideCurrentScope | TypeFormatFlags.NoTruncation, + ); + console.log('\n=== endContent (ReactElement) ==='); + console.log(`플래그: ${withFlags}`); + } + }); + + it('tabs.tsx base-ui 타입 비교', () => { + // Skip if core package files don't exist (e.g., in CI without full monorepo) + if (!fs.existsSync(CORE_TSCONFIG_PATH) || !fs.existsSync(TABS_PATH)) { + console.log('Skipping: Core package files not found'); + return; + } + + const project = createProject(CORE_TSCONFIG_PATH); + const [sourceFile] = addSourceFiles(project, [TABS_PATH]); + + // TabsRoot namespace에서 Props interface 찾기 + const namespace = sourceFile.getModules().find((m) => m.getName() === 'TabsRoot'); + const propsInterface = namespace?.getInterface('Props'); + + if (!propsInterface) { + throw new Error('TabsRoot.Props interface not found'); + } + + const propsType = propsInterface.getType(); + const properties = propsType.getProperties(); + + console.log('\n=== tabs.tsx TypeFormatFlags 비교 ===\n'); + + // 주요 props만 확인 + const targetProps = ['render', 'onValueChange', 'className', 'defaultValue', 'value']; + + for (const prop of properties) { + const name = prop.getName(); + if (!targetProps.includes(name)) continue; + + const type = prop.getTypeAtLocation(propsInterface); + + // 1. 기본 getText() + const basic = type.getText(); + + // 2. TypeFormatFlags 적용 + const withFlags = type.getText( + propsInterface, + TypeFormatFlags.UseAliasDefinedOutsideCurrentScope | + TypeFormatFlags.NoTruncation | + TypeFormatFlags.WriteTypeArgumentsOfSignature, + ); + + console.log(`[${name}]`); + console.log(` 기본: ${basic.slice(0, 200)}${basic.length > 200 ? '...' : ''}`); + console.log( + ` 플래그: ${withFlags.slice(0, 200)}${withFlags.length > 200 ? '...' : ''}`, + ); + console.log(''); + } + + expect(true).toBe(true); + }); +}); diff --git a/scripts/docs-extractor/test/core/type-resolver.test.ts b/scripts/docs-extractor/test/core/type-resolver.test.ts new file mode 100644 index 000000000..9666a80bc --- /dev/null +++ b/scripts/docs-extractor/test/core/type-resolver.test.ts @@ -0,0 +1,58 @@ +import { describe, expect, it } from 'vitest'; + +import { simplifyNodeModulesImports } from '~/core/types'; + +describe('simplifyNodeModulesImports', () => { + describe('basic import path removal', () => { + it('should remove simple import path', () => { + const input = 'import("/path/to/module").TypeName'; + expect(simplifyNodeModulesImports(input)).toBe('TypeName'); + }); + + it('should remove node_modules import path', () => { + const input = 'import("/Users/project/node_modules/@floating-ui/dom").VirtualElement'; + expect(simplifyNodeModulesImports(input)).toBe('VirtualElement'); + }); + }); + + describe('namespace chaining', () => { + it('should preserve namespace chain after import removal', () => { + const input = 'import("/path/to/module").Namespace.TypeName'; + expect(simplifyNodeModulesImports(input)).toBe('Namespace.TypeName'); + }); + }); + + describe('generic type handling', () => { + it('should remove import inside generic type', () => { + const input = 'Box'; + expect(simplifyNodeModulesImports(input)).toBe('Box'); + }); + + it('should handle multiple imports in generic', () => { + const input = 'Map'; + expect(simplifyNodeModulesImports(input)).toBe('Map'); + }); + }); + + describe('union types', () => { + it('should handle union type with imports', () => { + const input = 'import("/path1").TypeA | import("/path2").TypeB'; + expect(simplifyNodeModulesImports(input)).toBe('TypeA | TypeB'); + }); + }); + + describe('edge cases', () => { + it('should return unchanged string when no import pattern', () => { + expect(simplifyNodeModulesImports('string | number')).toBe('string | number'); + }); + + it('should handle empty string', () => { + expect(simplifyNodeModulesImports('')).toBe(''); + }); + + it('should handle single quotes', () => { + const input = "import('/path/to/module').TypeName"; + expect(simplifyNodeModulesImports(input)).toBe('TypeName'); + }); + }); +}); diff --git a/scripts/docs-extractor/test/fixtures/component-with-namespace.tsx b/scripts/docs-extractor/test/fixtures/component-with-namespace.tsx new file mode 100644 index 000000000..73d246598 --- /dev/null +++ b/scripts/docs-extractor/test/fixtures/component-with-namespace.tsx @@ -0,0 +1,20 @@ +export function Button() { + return ; +} + +export namespace Button { + export interface Props { + disabled?: boolean; + } +} + +export function Input() { + return ; +} + +export namespace Input { + export interface Props { + value?: string; + } + export type ChangeEvent = { value: string }; +} diff --git a/scripts/docs-extractor/test/fixtures/compound-component.css.ts b/scripts/docs-extractor/test/fixtures/compound-component.css.ts new file mode 100644 index 000000000..57ea30ad1 --- /dev/null +++ b/scripts/docs-extractor/test/fixtures/compound-component.css.ts @@ -0,0 +1,66 @@ +// @ts-nocheck +import { recipe } from '@vanilla-extract/recipes'; + +export const root = recipe({ + base: { display: 'flex' }, + defaultVariants: { orientation: 'horizontal' }, + variants: { + orientation: { + horizontal: { flexDirection: 'column' }, + vertical: { flexDirection: 'row' }, + }, + }, +}); + +export const list = recipe({ + base: { position: 'relative', gap: '8px' }, + defaultVariants: { variant: 'line', orientation: 'horizontal' }, + variants: { + orientation: { + horizontal: { display: 'flex' }, + vertical: { display: 'inline-flex', flexDirection: 'column' }, + }, + variant: { + line: {}, + fill: {}, + }, + }, +}); + +export const button = recipe({ + base: { display: 'inline-flex', alignItems: 'center' }, + defaultVariants: { size: 'md', variant: 'line', orientation: 'horizontal' }, + variants: { + size: { + sm: { height: '32px' }, + md: { height: '40px' }, + lg: { height: '48px' }, + }, + orientation: { + horizontal: {}, + vertical: {}, + }, + variant: { + line: {}, + fill: {}, + }, + }, +}); + +export const indicator = recipe({ + base: { position: 'absolute' }, + defaultVariants: { orientation: 'horizontal', variant: 'line' }, + variants: { + orientation: { + horizontal: { bottom: 0, left: 0 }, + vertical: { top: 0, right: 0 }, + }, + variant: { + line: { backgroundColor: 'blue' }, + fill: { backgroundColor: 'gray' }, + }, + }, +}); + +// Non-recipe export (plain style) +export const icon = { display: 'flex', flexShrink: 0 }; diff --git a/scripts/docs-extractor/test/fixtures/compound-component.tsx b/scripts/docs-extractor/test/fixtures/compound-component.tsx new file mode 100644 index 000000000..348e7e7fb --- /dev/null +++ b/scripts/docs-extractor/test/fixtures/compound-component.tsx @@ -0,0 +1,108 @@ +// @ts-nocheck +import { createContext, forwardRef, useContext } from 'react'; + +import clsx from 'clsx'; + +import * as styles from './compound-component.css'; + +type TabsContextValue = { + orientation?: 'horizontal' | 'vertical'; + variant?: 'line' | 'fill'; + size?: 'sm' | 'md' | 'lg'; +}; + +const TabsContext = createContext({}); +const useTabsContext = () => useContext(TabsContext); + +// Root component - uses styles.root +export const TabsRoot = forwardRef((props, ref) => { + const { className, orientation = 'horizontal', variant, size, children, ...otherProps } = props; + + return ( + +
+ {children} +
+
+ ); +}); +TabsRoot.displayName = 'TabsRoot'; + +export namespace TabsRoot { + export interface Props extends React.HTMLAttributes { + orientation?: 'horizontal' | 'vertical'; + variant?: 'line' | 'fill'; + size?: 'sm' | 'md' | 'lg'; + } +} + +// List component - uses styles.list +export const TabsList = forwardRef((props, ref) => { + const { className, ...otherProps } = props; + const { variant, orientation } = useTabsContext(); + + return ( +
+ ); +}); +TabsList.displayName = 'TabsList'; + +export namespace TabsList { + export interface Props extends React.HTMLAttributes { + variant?: 'line' | 'fill'; + orientation?: 'horizontal' | 'vertical'; + } +} + +// Button component - uses styles.button +export const TabsButton = forwardRef((props, ref) => { + const { className, ...otherProps } = props; + const { size, variant, orientation } = useTabsContext(); + + return ( + + ); +}); +SimpleButton.displayName = 'SimpleButton'; + +export namespace SimpleButton { + export interface Props extends React.ButtonHTMLAttributes, SimpleVariants { + /** Icon to display at the start */ + icon?: ReactNode; + /** Content to display at the end */ + endContent?: ReactElement; + } +} diff --git a/scripts/docs-extractor/test/fixtures/sorting-test.css.ts b/scripts/docs-extractor/test/fixtures/sorting-test.css.ts new file mode 100644 index 000000000..564ff66ff --- /dev/null +++ b/scripts/docs-extractor/test/fixtures/sorting-test.css.ts @@ -0,0 +1,25 @@ +// @ts-nocheck +import { recipe } from '@vanilla-extract/recipes'; + +export const root = recipe({ + base: { + display: 'block', + }, + defaultVariants: { size: 'md', variant: 'fill' }, + variants: { + size: { + sm: {}, + md: {}, + lg: {}, + }, + variant: { + fill: {}, + outline: {}, + }, + }, +}); + +export interface SortingTestVariants { + size?: 'sm' | 'md' | 'lg'; + variant?: 'fill' | 'outline'; +} diff --git a/scripts/docs-extractor/test/fixtures/sorting-test.tsx b/scripts/docs-extractor/test/fixtures/sorting-test.tsx new file mode 100644 index 000000000..aaf770183 --- /dev/null +++ b/scripts/docs-extractor/test/fixtures/sorting-test.tsx @@ -0,0 +1,33 @@ +// @ts-nocheck +import { type ReactElement, forwardRef } from 'react'; + +import type { SortingTestVariants } from './sorting-test.css'; + +export const SortingTest = forwardRef((props, ref) => { + return
; +}); + +export namespace SortingTest { + export interface Props extends SortingTestVariants { + /** Required prop - should be first */ + id: string; + /** Custom prop - should be after state */ + customProp?: string; + /** State prop - value */ + value?: string; + /** State prop - onChange */ + onChange?: (value: string) => void; + /** State prop - open */ + open?: boolean; + /** State prop - defaultOpen */ + defaultOpen?: boolean; + /** State prop - onOpenChange */ + onOpenChange?: (open: boolean) => void; + /** Composition prop - asChild */ + asChild?: boolean; + /** Composition prop - render */ + render?: ReactElement | ((props: object) => ReactElement); + /** Another custom prop */ + label?: string; + } +} diff --git a/scripts/docs-extractor/test/fixtures/sprinkles-meta.json b/scripts/docs-extractor/test/fixtures/sprinkles-meta.json new file mode 100644 index 000000000..a6f799f01 --- /dev/null +++ b/scripts/docs-extractor/test/fixtures/sprinkles-meta.json @@ -0,0 +1,57 @@ +{ + "tokenProps": [ + "padding", + "paddingTop", + "paddingBottom", + "paddingLeft", + "paddingRight", + "paddingX", + "paddingY", + "margin", + "gap", + "color", + "backgroundColor", + "borderColor", + "borderRadius", + "width", + "height" + ], + "nonTokenProps": [ + "display", + "position", + "alignItems", + "justifyContent", + "flexDirection", + "overflow" + ], + "propDefinitions": { + "padding": { + "usesToken": true, + "tokenPath": "vars.size.space", + "cssProperty": "padding" + }, + "margin": { + "usesToken": true, + "tokenPath": "vars.size.space", + "cssProperty": "margin" + }, + "display": { + "usesToken": false, + "cssProperty": "display" + }, + "position": { + "usesToken": false, + "cssProperty": "position" + }, + "color": { + "usesToken": true, + "tokenPath": "vars.color.foreground", + "cssProperty": "color" + }, + "backgroundColor": { + "usesToken": true, + "tokenPath": "vars.color.background", + "cssProperty": "backgroundColor" + } + } +} diff --git a/scripts/docs-extractor/test/fixtures/tsconfig.json b/scripts/docs-extractor/test/fixtures/tsconfig.json new file mode 100644 index 000000000..9de231004 --- /dev/null +++ b/scripts/docs-extractor/test/fixtures/tsconfig.json @@ -0,0 +1,10 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "ESNext", + "moduleResolution": "bundler", + "strict": true, + "jsx": "react-jsx" + }, + "include": ["./**/*"] +} diff --git a/scripts/docs-extractor/test/i18n/path-resolver.test.ts b/scripts/docs-extractor/test/i18n/path-resolver.test.ts new file mode 100644 index 000000000..74dc37ab7 --- /dev/null +++ b/scripts/docs-extractor/test/i18n/path-resolver.test.ts @@ -0,0 +1,76 @@ +import path from 'node:path'; +import { describe, expect, it } from 'vitest'; + +import { + getTargetLanguages, + resolveAllLanguagePaths, + resolveOutputPath, +} from '~/i18n/path-resolver'; + +describe('resolveOutputPath', () => { + const options = { + outputDir: './output', + languages: ['ko', 'en'], + defaultLanguage: 'ko', + }; + + it('should resolve path with language directory', () => { + const result = resolveOutputPath('button.json', 'ko', options); + expect(result).toBe(path.join('./output', 'ko', 'button.json')); + }); + + it('should handle different languages', () => { + const result = resolveOutputPath('button.json', 'en', options); + expect(result).toBe(path.join('./output', 'en', 'button.json')); + }); +}); + +describe('resolveAllLanguagePaths', () => { + const options = { + outputDir: './output', + languages: ['ko', 'en', 'ja'], + defaultLanguage: 'ko', + }; + + it('should return paths for all languages', () => { + const result = resolveAllLanguagePaths('button.json', options); + + expect(result.size).toBe(3); + expect(result.get('ko')).toBe(path.join('./output', 'ko', 'button.json')); + expect(result.get('en')).toBe(path.join('./output', 'en', 'button.json')); + expect(result.get('ja')).toBe(path.join('./output', 'ja', 'button.json')); + }); +}); + +describe('getTargetLanguages', () => { + const config = { + outputDir: './output', + languages: ['ko', 'en', 'ja'], + defaultLanguage: 'ko', + }; + + it('should return default language when no option provided', () => { + const result = getTargetLanguages(undefined, config); + expect(result).toEqual(['ko']); + }); + + it('should return default language when option matches default', () => { + const result = getTargetLanguages('ko', config); + expect(result).toEqual(['ko']); + }); + + it('should return all languages when option is "all"', () => { + const result = getTargetLanguages('all', config); + expect(result).toEqual(['ko', 'en', 'ja']); + }); + + it('should return specific language when valid', () => { + const result = getTargetLanguages('en', config); + expect(result).toEqual(['en']); + }); + + it('should use explicitly specified language even if not in config', () => { + const result = getTargetLanguages('fr', config); + expect(result).toEqual(['fr']); + }); +}); diff --git a/scripts/docs-extractor/test/output/formatter.test.ts b/scripts/docs-extractor/test/output/formatter.test.ts new file mode 100644 index 000000000..f47d01f40 --- /dev/null +++ b/scripts/docs-extractor/test/output/formatter.test.ts @@ -0,0 +1,43 @@ +import { describe, expect, it } from 'vitest'; + +import { formatFileName, toKebabCase } from '~/output/formatter'; + +describe('toKebabCase', () => { + it('should convert PascalCase to kebab-case', () => { + expect(toKebabCase('SimpleButton')).toBe('simple-button'); + }); + + it('should convert camelCase to kebab-case', () => { + expect(toKebabCase('simpleButton')).toBe('simple-button'); + }); + + it('should handle single word', () => { + expect(toKebabCase('Button')).toBe('button'); + }); + + it('should handle multiple uppercase letters', () => { + expect(toKebabCase('TabsListIndicator')).toBe('tabs-list-indicator'); + }); + + it('should preserve already kebab-case', () => { + expect(toKebabCase('simple-button')).toBe('simple-button'); + }); + + it('should handle lowercase string', () => { + expect(toKebabCase('button')).toBe('button'); + }); +}); + +describe('formatFileName', () => { + it('should format component name to json file name', () => { + expect(formatFileName('SimpleButton')).toBe('simple-button.json'); + }); + + it('should handle already kebab-case', () => { + expect(formatFileName('simple-button')).toBe('simple-button.json'); + }); + + it('should handle compound component names', () => { + expect(formatFileName('TabsRoot')).toBe('tabs-root.json'); + }); +}); diff --git a/scripts/docs-extractor/tsconfig.json b/scripts/docs-extractor/tsconfig.json new file mode 100644 index 000000000..66b7f9a31 --- /dev/null +++ b/scripts/docs-extractor/tsconfig.json @@ -0,0 +1,29 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "ESNext", + "moduleResolution": "bundler", + "lib": ["ES2022"], + "jsx": "preserve", + "outDir": "./dist", + "baseUrl": ".", + "paths": { + "~/*": ["./src/*"] + }, + "declaration": true, + "declarationMap": true, + "sourceMap": true, + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["src/**/*", "test/**/*"], + "exclude": ["node_modules", "dist"] +} diff --git a/scripts/docs-extractor/tsup.config.ts b/scripts/docs-extractor/tsup.config.ts new file mode 100644 index 000000000..b79ee5286 --- /dev/null +++ b/scripts/docs-extractor/tsup.config.ts @@ -0,0 +1,40 @@ +import path from 'node:path'; +import { defineConfig } from 'tsup'; + +export default defineConfig([ + // Library build: required for importing defineConfig from apps/website/docs-extractor.config.js + { + name: 'LIB', + format: ['esm'], + sourcemap: true, + splitting: false, + target: 'node20', // matches package.json engines (>=20.19) + esbuildOptions(options) { + options.alias = { + '~': path.resolve(__dirname, './src'), + }; + }, + entry: ['src/index.ts'], + dts: true, + outDir: 'dist', + }, + // CLI build: ts-api-extractor executable + { + name: 'CLI', + format: ['esm'], + sourcemap: true, + splitting: false, + target: 'node20', // matches package.json engines (>=20.19) + esbuildOptions(options) { + options.alias = { + '~': path.resolve(__dirname, './src'), + }; + }, + entry: ['src/bin/cli.ts'], + dts: false, + outDir: 'dist/bin', + banner: { + js: '#!/usr/bin/env node', + }, + }, +]); diff --git a/scripts/docs-extractor/vitest.config.ts b/scripts/docs-extractor/vitest.config.ts new file mode 100644 index 000000000..079da226c --- /dev/null +++ b/scripts/docs-extractor/vitest.config.ts @@ -0,0 +1,35 @@ +import path from 'node:path'; +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + resolve: { + alias: { + '~': path.resolve(__dirname, './src'), + }, + }, + test: { + globals: true, + environment: 'node', + include: ['test/**/*.test.ts'], + coverage: { + provider: 'v8', + reporter: ['text', 'json', 'html', 'lcov'], + include: ['src/**/*.ts'], + + exclude: [ + 'src/index.ts', + 'src/bin/**', + 'src/cli/**', + 'src/output/writer.ts', + 'src/types/**', + ], + + thresholds: { + lines: 70, + branches: 65, + functions: 70, + statements: 70, + }, + }, + }, +});