Merge remote-tracking branch 'origin/dev' into 58-feature-improve-installation-section-to-accommodate-other-frameworks

This commit is contained in:
zernonia 2023-09-19 11:42:05 +08:00
commit 8dcb1e1839
59 changed files with 5890 additions and 757 deletions

53
.github/workflows/test.yaml vendored Normal file
View File

@ -0,0 +1,53 @@
name: Test
on:
push:
branches:
- dev
paths:
- 'packages/**'
pull_request:
branches:
- dev
paths:
- 'packages/**'
jobs:
test:
needs: build
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v2
- name: Setup Node.js environment
uses: actions/setup-node@v2
with:
node-version: 16
- uses: pnpm/action-setup@v2
name: Install pnpm
with:
version: 8
run_install: false
- name: Get pnpm store directory
shell: bash
run: |
echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
- uses: actions/cache@v3
name: Setup pnpm cache
with:
path: ${{ env.STORE_PATH }}
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-pnpm-store-
- name: Install dependencies
run: pnpm i --frozen-lockfile
- name: Test
run: pnpm test

1
.gitignore vendored
View File

@ -7,6 +7,7 @@ yarn-error.log*
pnpm-debug.log* pnpm-debug.log*
lerna-debug.log* lerna-debug.log*
.nuxt
.env .env
node_modules node_modules
.DS_Store .DS_Store

View File

@ -17,7 +17,6 @@ To use utility classes for theming set `tailwind.cssVariables` to `false` in you
```json {8} title="components.json" ```json {8} title="components.json"
{ {
"style": "default", "style": "default",
"rsc": true,
"tailwind": { "tailwind": {
"config": "tailwind.config.js", "config": "tailwind.config.js",
"css": "app/globals.css", "css": "app/globals.css",
@ -43,7 +42,6 @@ To use CSS variables for theming set `tailwind.cssVariables` to `true` in your `
```json {8} title="components.json" ```json {8} title="components.json"
{ {
"style": "default", "style": "default",
"rsc": true,
"tailwind": { "tailwind": {
"config": "tailwind.config.js", "config": "tailwind.config.js",
"css": "app/globals.css", "css": "app/globals.css",

View File

@ -22,7 +22,7 @@
}, },
{ {
"name": "CommandGroup.vue", "name": "CommandGroup.vue",
"content": "<script setup lang=\"ts\">\nimport type { ComboboxGroupProps } from 'radix-vue'\nimport { ComboboxGroup, ComboboxLabel } from 'radix-vue'\nimport { cn } from '@/lib/utils'\n\nconst props = defineProps<ComboboxGroupProps & {\n heading?: string\n}>()\n</script>\n\n<template>\n <ComboboxGroup\n v-bind=\"props\"\n :class=\"cn('overflow-hidden p-1 text-foreground', $attrs.class ?? '')\"\n >\n <ComboboxLabel class=\"px-2 py-1.5 text-xs font-medium text-muted-foreground\">\n {{ heading }}\n </ComboboxLabel>\n <slot />\n </ComboboxGroup>\n</template>\n" "content": "<script setup lang=\"ts\">\nimport type { ComboboxGroupProps } from 'radix-vue'\nimport { ComboboxGroup, ComboboxLabel } from 'radix-vue'\nimport { cn } from '@/lib/utils'\n\nconst props = defineProps<ComboboxGroupProps & {\n heading?: string\n}>()\n</script>\n\n<template>\n <ComboboxGroup\n v-bind=\"props\"\n :class=\"cn('overflow-hidden p-1 text-foreground', $attrs.class ?? '')\"\n >\n <ComboboxLabel v-if=\"heading\" class=\"px-2 py-1.5 text-xs font-medium text-muted-foreground\">\n {{ heading }}\n </ComboboxLabel>\n <slot />\n </ComboboxGroup>\n</template>\n"
}, },
{ {
"name": "CommandInput.vue", "name": "CommandInput.vue",

View File

@ -24,12 +24,6 @@
}, },
"required": ["config", "css", "baseColor", "cssVariables"] "required": ["config", "css", "baseColor", "cssVariables"]
}, },
"rsc": {
"type": "boolean"
},
"tsx": {
"type": "boolean"
},
"aliases": { "aliases": {
"type": "object", "type": "object",
"properties": { "properties": {
@ -43,5 +37,5 @@
"required": ["utils", "components"] "required": ["utils", "components"]
} }
}, },
"required": ["style", "tailwind", "rsc", "aliases"] "required": ["style", "tailwind", "aliases"]
} }

View File

@ -40,6 +40,11 @@
"@commitlint/config-conventional" "@commitlint/config-conventional"
] ]
}, },
"pnpm": {
"patchedDependencies": {
"detype@0.6.3": "patches/detype@0.6.3.patch"
}
},
"simple-git-hooks": { "simple-git-hooks": {
"pre-commit": "pnpm lint-staged", "pre-commit": "pnpm lint-staged",
"commit-msg": "pnpm commitlint --edit ${1}" "commit-msg": "pnpm commitlint --edit ${1}"

View File

@ -1,7 +1,7 @@
{ {
"name": "shadcn-vue", "name": "shadcn-vue",
"type": "module", "type": "module",
"version": "0.1.3", "version": "0.1.6",
"description": "Add components to your apps.", "description": "Add components to your apps.",
"publishConfig": { "publishConfig": {
"access": "public" "access": "public"
@ -52,6 +52,7 @@
"chalk": "5.3.0", "chalk": "5.3.0",
"commander": "^11.0.0", "commander": "^11.0.0",
"cosmiconfig": "^8.3.6", "cosmiconfig": "^8.3.6",
"detype": "^0.6.3",
"diff": "^5.1.0", "diff": "^5.1.0",
"execa": "^8.0.1", "execa": "^8.0.1",
"fs-extra": "^11.1.1", "fs-extra": "^11.1.1",

View File

@ -7,7 +7,7 @@ import { execa } from 'execa'
import ora from 'ora' import ora from 'ora'
import prompts from 'prompts' import prompts from 'prompts'
import * as z from 'zod' import * as z from 'zod'
import { transformImport } from '../utils/transformers/transform-import' import { transform } from '@/src/utils/transformers'
import { getConfig } from '@/src/utils/get-config' import { getConfig } from '@/src/utils/get-config'
import { getPackageManager } from '@/src/utils/get-package-manager' import { getPackageManager } from '@/src/utils/get-package-manager'
import { handleError } from '@/src/utils/handle-error' import { handleError } from '@/src/utils/handle-error'
@ -129,15 +129,11 @@ export const add = new Command()
if (existingComponent.length && !options.overwrite) { if (existingComponent.length && !options.overwrite) {
if (selectedComponents.includes(item.name)) { if (selectedComponents.includes(item.name)) {
logger.warn( logger.warn(
`\nComponent ${ `Component ${item.name} already exists. Use ${chalk.green(
item.name
} already exists. Use ${chalk.green(
'--overwrite', '--overwrite',
)} to overwrite.`, )} to overwrite.`,
) )
spinner.stop() process.exit(1)
process.exitCode = 1
return
} }
continue continue
@ -145,18 +141,26 @@ export const add = new Command()
for (const file of item.files) { for (const file of item.files) {
const componentDir = path.resolve(targetDir, item.name) const componentDir = path.resolve(targetDir, item.name)
const filePath = path.resolve( let filePath = path.resolve(
targetDir, targetDir,
item.name, item.name,
file.name, file.name,
) )
// Run transformers. if (!config.typescript)
const content = transformImport(file.content, config) filePath = filePath.replace(/\.ts$/, '.js')
if (!existsSync(componentDir)) if (!existsSync(componentDir))
await fs.mkdir(componentDir, { recursive: true }) await fs.mkdir(componentDir, { recursive: true })
// Run transformers.
const content = await transform({
filename: file.name,
raw: file.content,
config,
baseColor,
})
await fs.writeFile(filePath, content) await fs.writeFile(filePath, content)
} }

View File

@ -17,6 +17,7 @@ import {
import { logger } from '../utils/logger' import { logger } from '../utils/logger'
import { handleError } from '../utils/handle-error' import { handleError } from '../utils/handle-error'
import { getPackageManager } from '../utils/get-package-manager' import { getPackageManager } from '../utils/get-package-manager'
import { transformByDetype } from '../utils/transformers/transform-sfc'
import { import {
type Config, type Config,
DEFAULT_COMPONENTS, DEFAULT_COMPONENTS,
@ -36,11 +37,6 @@ const PROJECT_DEPENDENCIES = {
'clsx', 'clsx',
'tailwind-merge', 'tailwind-merge',
], ],
vue: [
'tailwindcss',
'postcss',
'autoprefixer',
],
nuxt: [ nuxt: [
'@nuxtjs/tailwindcss', '@nuxtjs/tailwindcss',
], ],
@ -260,7 +256,7 @@ export async function runInit(cwd: string, config: Config) {
// Write cn file. // Write cn file.
await fs.writeFile( await fs.writeFile(
`${config.resolvedPaths.utils}.${extension}`, `${config.resolvedPaths.utils}.${extension}`,
extension === 'ts' ? templates.UTILS : templates.UTILS_JS, extension === 'ts' ? templates.UTILS : await transformByDetype(templates.UTILS, '.ts'),
'utf8', 'utf8',
) )
@ -270,12 +266,11 @@ export async function runInit(cwd: string, config: Config) {
const dependenciesSpinner = ora('Installing dependencies...')?.start() const dependenciesSpinner = ora('Installing dependencies...')?.start()
const packageManager = await getPackageManager(cwd) const packageManager = await getPackageManager(cwd)
// TODO: add support for other icon libraries.
const deps = PROJECT_DEPENDENCIES.base.concat( const deps = PROJECT_DEPENDENCIES.base.concat(
config.framework === 'nuxt' ? PROJECT_DEPENDENCIES.nuxt : PROJECT_DEPENDENCIES.vue, config.framework === 'nuxt' ? PROJECT_DEPENDENCIES.nuxt : [],
).concat( ).concat(
config.style === 'new-york' ? [] : ['lucide-vue-next'], config.style === 'new-york' ? [] : ['lucide-vue-next'],
) ).filter(Boolean)
await execa( await execa(
packageManager, packageManager,

View File

@ -10,7 +10,7 @@ export const DEFAULT_STYLE = 'default'
export const DEFAULT_COMPONENTS = '@/components' export const DEFAULT_COMPONENTS = '@/components'
export const DEFAULT_UTILS = '@/lib/utils' export const DEFAULT_UTILS = '@/lib/utils'
export const DEFAULT_TAILWIND_CSS = 'src/assets/index.css' export const DEFAULT_TAILWIND_CSS = 'src/assets/index.css'
export const DEFAULT_TAILWIND_CSS_NUXT = 'assets/style/tailwind.css' export const DEFAULT_TAILWIND_CSS_NUXT = 'assets/css/tailwind.css'
export const DEFAULT_TAILWIND_CONFIG = 'tailwind.config.js' export const DEFAULT_TAILWIND_CONFIG = 'tailwind.config.js'
export const DEFAULT_TAILWIND_BASE_COLOR = 'slate' export const DEFAULT_TAILWIND_BASE_COLOR = 'slate'
@ -24,14 +24,14 @@ export const rawConfigSchema = z
.object({ .object({
$schema: z.string().optional(), $schema: z.string().optional(),
style: z.string(), style: z.string(),
typescript: z.boolean().default(false), typescript: z.boolean().default(true),
tailwind: z.object({ tailwind: z.object({
config: z.string(), config: z.string(),
css: z.string(), css: z.string(),
baseColor: z.string(), baseColor: z.string(),
cssVariables: z.boolean().default(true), cssVariables: z.boolean().default(true),
}), }),
framework: z.string(), framework: z.string().default('Vite'),
aliases: z.object({ aliases: z.object({
components: z.string(), components: z.string(),
utils: z.string(), utils: z.string(),
@ -64,35 +64,40 @@ export async function getConfig(cwd: string) {
export async function resolveConfigPaths(cwd: string, config: RawConfig) { export async function resolveConfigPaths(cwd: string, config: RawConfig) {
let tsConfig: ConfigLoaderResult | undefined let tsConfig: ConfigLoaderResult | undefined
let tsConfigPath = path.resolve(
cwd,
config.framework === 'nuxt' ? '.nuxt/tsconfig.json' : './tsconfig.json',
)
if (config.typescript) { if (config.typescript) {
const TSCONFIG_PATH = config.framework === 'nuxt' ? '.nuxt/tsconfig.json' : './tsconfig.json'
// Read tsconfig.json. // Read tsconfig.json.
const tsconfigPath = path.resolve(cwd, TSCONFIG_PATH) tsConfig = loadConfig(tsConfigPath)
tsConfig = loadConfig(tsconfigPath)
// In new Vue project, tsconfig has references to tsconfig.app.json, which is causing the path not resolving correctly // In new Vue project, tsconfig has references to tsconfig.app.json, which is causing the path not resolving correctly
// If no paths were found, try to load tsconfig.app.json. // If no paths were found, try to load tsconfig.app.json.
if ('paths' in tsConfig && Object.keys(tsConfig.paths).length === 0) { if ('paths' in tsConfig && Object.keys(tsConfig.paths).length === 0) {
const FALLBACK_TSCONFIG_PATH = path.resolve(cwd, './tsconfig.app.json') tsConfigPath = path.resolve(cwd, './tsconfig.app.json')
if (existsSync(FALLBACK_TSCONFIG_PATH)) if (existsSync(tsConfigPath))
tsConfig = loadConfig(FALLBACK_TSCONFIG_PATH) tsConfig = loadConfig(tsConfigPath)
}
}
else {
tsConfigPath = path.resolve(cwd, './jsconfig.json')
tsConfig = loadConfig(tsConfigPath)
} }
if (tsConfig.resultType === 'failed') { if (tsConfig.resultType === 'failed') {
throw new Error( throw new Error(
`Failed to load tsconfig.json. ${tsConfig.message ?? ''}`.trim(), `Failed to load ${tsConfigPath}. ${tsConfig.message ?? ''}`.trim(),
) )
} }
}
return configSchema.parse({ return configSchema.parse({
...config, ...config,
resolvedPaths: { resolvedPaths: {
tailwindConfig: path.resolve(cwd, config.tailwind.config), tailwindConfig: path.resolve(cwd, config.tailwind.config),
tailwindCss: path.resolve(cwd, config.tailwind.css), tailwindCss: path.resolve(cwd, config.tailwind.css),
utils: tsConfig ? await resolveImport(config.aliases.utils, tsConfig) : config.aliases.utils, utils: resolveImport(config.aliases.utils, tsConfig),
components: tsConfig ? await resolveImport(config.aliases.components, tsConfig) : config.aliases.components, components: resolveImport(config.aliases.components, tsConfig),
}, },
}) })
} }

View File

@ -1,4 +1,5 @@
import path from 'node:path' import path from 'node:path'
import process from 'node:process'
import { HttpsProxyAgent } from 'https-proxy-agent' import { HttpsProxyAgent } from 'https-proxy-agent'
import fetch from 'node-fetch' import fetch from 'node-fetch'
import type * as z from 'zod' import type * as z from 'zod'

View File

@ -15,7 +15,7 @@ export function useEmitAsProps<Name extends string>(
const result: Record<string, any> = {} const result: Record<string, any> = {}
if (!events?.length) { if (!events?.length) {
console.warn( console.warn(
'No emitted event found. Please check component: \${vm?.type.__name}', \`No emitted event found. Please check component: \${vm?.type.__name}\`,
) )
} }
@ -25,31 +25,6 @@ export function useEmitAsProps<Name extends string>(
return result return result
}` }`
export const UTILS_JS = `import { clsx } from "clsx"
import { twMerge } from "tailwind-merge"
export function cn(...inputs) {
return twMerge(clsx(inputs))
}
export function useEmitAsProps(emit) {
const vm = getCurrentInstance()
const events = vm?.type.emits
const result = {}
if (!events?.length) {
console.warn(
'No emitted event found. Please check component: \${vm?.type.__name}',
)
}
events?.forEach((ev) => {
result[toHandlerKey(camelize(ev))] = (...arg) => emit(ev, ...arg)
})
return result
}
`
export const TAILWIND_CONFIG = `/** @type {import('tailwindcss').Config} */ export const TAILWIND_CONFIG = `/** @type {import('tailwindcss').Config} */
module.exports = { module.exports = {
darkMode: ["class"], darkMode: ["class"],

View File

@ -6,6 +6,8 @@ import type * as z from 'zod'
import type { Config } from '@/src/utils/get-config' import type { Config } from '@/src/utils/get-config'
import type { registryBaseColorSchema } from '@/src/utils/registry/schema' import type { registryBaseColorSchema } from '@/src/utils/registry/schema'
import { transformCssVars } from '@/src/utils/transformers/transform-css-vars' import { transformCssVars } from '@/src/utils/transformers/transform-css-vars'
import { transformImport } from '@/src/utils/transformers/transform-import'
import { transformSFC } from '@/src/utils/transformers/transform-sfc'
export interface TransformOpts { export interface TransformOpts {
filename: string filename: string
@ -22,6 +24,7 @@ export type Transformer<Output = SourceFile> = (
const transformers: Transformer[] = [ const transformers: Transformer[] = [
transformCssVars, transformCssVars,
transformImport,
] ]
const project = new Project({ const project = new Project({
@ -36,14 +39,14 @@ async function createTempSourceFile(filename: string) {
export async function transform(opts: TransformOpts) { export async function transform(opts: TransformOpts) {
const tempFile = await createTempSourceFile(opts.filename) const tempFile = await createTempSourceFile(opts.filename)
const sourceFile = project.createSourceFile(tempFile, opts.raw, { const sourceFile = project.createSourceFile(tempFile, opts.raw, {
scriptKind: ScriptKind.TSX, scriptKind: ScriptKind.Unknown,
}) })
for (const transformer of transformers) for (const transformer of transformers)
transformer({ sourceFile, ...opts }) transformer({ sourceFile, ...opts })
// return await transformJsx({ return await transformSFC({
// sourceFile, sourceFile,
// ...opts, ...opts,
// }) })
} }

View File

@ -14,7 +14,12 @@ export const transformCssVars: Transformer = async ({
sourceFile.getDescendantsOfKind(SyntaxKind.StringLiteral).forEach((node) => { sourceFile.getDescendantsOfKind(SyntaxKind.StringLiteral).forEach((node) => {
const value = node.getText() const value = node.getText()
if (value) {
if (value.includes('cn(')) {
const splitted = value.split('\'').map(i => applyColorMapping(i, baseColor.inlineColors))
node.replaceWithText(`${splitted.join('\'')}`)
}
else if (value) {
const valueWithColorMapping = applyColorMapping( const valueWithColorMapping = applyColorMapping(
value.replace(/"/g, ''), value.replace(/"/g, ''),
baseColor.inlineColors, baseColor.inlineColors,

View File

@ -1,49 +1,32 @@
import MagicString from 'magic-string' import type { Transformer } from '@/src/utils/transformers'
import type { z } from 'zod'
import type { Config } from '../get-config'
import type { registryBaseColorSchema } from '../registry/schema'
export interface TransformOpts { export const transformImport: Transformer = async ({ sourceFile, config }) => {
filename: string const importDeclarations = sourceFile.getImportDeclarations()
raw: string
config: Config for (const importDeclaration of importDeclarations) {
baseColor?: z.infer<typeof registryBaseColorSchema> const moduleSpecifier = importDeclaration.getModuleSpecifierValue()
// Replace @/lib/registry/[style] with the components alias.
if (moduleSpecifier.startsWith('@/lib/registry/')) {
importDeclaration.setModuleSpecifier(
moduleSpecifier.replace(
/^@\/lib\/registry\/[^/]+/,
config.aliases.components,
),
)
} }
export function transformImport(content: string, config: Config) { // Replace `import { cn } from "@/lib/utils"`
const s = new MagicString(content) if (moduleSpecifier === '@/lib/utils') {
s.replaceAll(/@\/registry\/[^/]+/g, config.aliases.components) const namedImports = importDeclaration.getNamedImports()
s.replaceAll(/@\/lib\/utils/g, config.aliases.utils) const cnImport = namedImports.find(i => i.getName() === 'cn')
return s.toString() if (cnImport) {
importDeclaration.setModuleSpecifier(
moduleSpecifier.replace(/^@\/lib\/utils/, config.aliases.utils),
)
}
}
} }
// export const transformImport: Transformer = async ({ sourceFile, config }) => { return sourceFile
// const importDeclarations = sourceFile.getImportDeclarations() }
// for (const importDeclaration of importDeclarations) {
// const moduleSpecifier = importDeclaration.getModuleSpecifierValue()
// // Replace @/registry/[style] with the components alias.
// if (moduleSpecifier.startsWith('@/registry/')) {
// importDeclaration.setModuleSpecifier(
// moduleSpecifier.replace(
// /^@\/registry\/[^/]+/,
// config.aliases.components,
// ),
// )
// }
// // Replace `import { cn } from "@/lib/utils"`
// if (moduleSpecifier === '@/lib/utils') {
// const namedImports = importDeclaration.getNamedImports()
// const cnImport = namedImports.find(i => i.getName() === 'cn')
// if (cnImport) {
// importDeclaration.setModuleSpecifier(
// moduleSpecifier.replace(/^@\/lib\/utils/, config.aliases.utils),
// )
// }
// }
// }
// return sourceFile
// }

View File

@ -0,0 +1,20 @@
import { createRequire } from 'node:module'
import type { Transformer } from '@/src/utils/transformers'
// required cause Error: Dynamic require of "@babel/core" is not supported
const require = createRequire(import.meta.url)
const { transform } = require('detype')
export async function transformByDetype(content: string, filename: string) {
return await transform(content, filename, {
removeTsComments: true,
})
}
export const transformSFC: Transformer<string> = async ({ sourceFile, config }) => {
const output = sourceFile?.getFullText()
if (config?.typescript)
return output
return await transformByDetype(output, 'app.vue')
}

View File

@ -63,7 +63,8 @@ test('init config-full', async () => {
expect(mockWriteFile).toHaveBeenNthCalledWith( expect(mockWriteFile).toHaveBeenNthCalledWith(
3, 3,
expect.stringMatching(/src\/lib\/utils.ts$/), expect.stringMatching(/src\/lib\/utils.ts$/),
expect.stringContaining('import { type ClassValue, clsx } from "clsx"'), // eslint-disable-next-line @typescript-eslint/quotes
expect.stringContaining("import { type ClassValue, clsx } from 'clsx'"),
'utf8', 'utf8',
) )
expect(execa).toHaveBeenCalledWith( expect(execa).toHaveBeenCalledWith(
@ -74,7 +75,6 @@ test('init config-full', async () => {
'class-variance-authority', 'class-variance-authority',
'clsx', 'clsx',
'tailwind-merge', 'tailwind-merge',
'@radix-ui/react-icons',
], ],
{ {
cwd: targetDir, cwd: targetDir,
@ -133,7 +133,8 @@ test('init config-partial', async () => {
expect(mockWriteFile).toHaveBeenNthCalledWith( expect(mockWriteFile).toHaveBeenNthCalledWith(
3, 3,
expect.stringMatching(/utils.ts$/), expect.stringMatching(/utils.ts$/),
expect.stringContaining('import { type ClassValue, clsx } from "clsx"'), // eslint-disable-next-line @typescript-eslint/quotes
expect.stringContaining("import { type ClassValue, clsx } from 'clsx'"),
'utf8', 'utf8',
) )
expect(execa).toHaveBeenCalledWith( expect(execa).toHaveBeenCalledWith(
@ -144,7 +145,7 @@ test('init config-partial', async () => {
'class-variance-authority', 'class-variance-authority',
'clsx', 'clsx',
'tailwind-merge', 'tailwind-merge',
'lucide-react', 'lucide-vue-next',
], ],
{ {
cwd: targetDir, cwd: targetDir,

View File

@ -1,12 +1,11 @@
{ {
"style": "default", "style": "new-york",
"tailwind": { "tailwind": {
"config": "tailwind.config.ts", "config": "tailwind.config.ts",
"css": "src/app/globals.css", "css": "src/app/globals.css",
"baseColor": "zinc", "baseColor": "zinc",
"cssVariables": true "cssVariables": true
}, },
"rsc": false,
"aliases": { "aliases": {
"utils": "~/lib/utils", "utils": "~/lib/utils",
"components": "~/components" "components": "~/components"

View File

@ -1,6 +1,5 @@
{ {
"style": "default", "style": "default",
"tsx": false,
"tailwind": { "tailwind": {
"config": "./tailwind.config.js", "config": "./tailwind.config.js",
"css": "./src/assets/css/tailwind.css", "css": "./src/assets/css/tailwind.css",
@ -10,5 +9,6 @@
"aliases": { "aliases": {
"utils": "@/lib/utils", "utils": "@/lib/utils",
"components": "@/components" "components": "@/components"
} },
"typescript": false
} }

View File

@ -0,0 +1,24 @@
# Nuxt dev/build outputs
.output
.data
.nuxt
.nitro
.cache
dist
# Node dependencies
node_modules
# Logs
logs
*.log
# Misc
.DS_Store
.fleet
.idea
# Local env files
.env
.env.*
!.env.example

View File

@ -0,0 +1 @@
shamefully-hoist=true

View File

@ -0,0 +1,75 @@
# Nuxt 3 Minimal Starter
Look at the [Nuxt 3 documentation](https://nuxt.com/docs/getting-started/introduction) to learn more.
## Setup
Make sure to install the dependencies:
```bash
# npm
npm install
# pnpm
pnpm install
# yarn
yarn install
# bun
bun install
```
## Development Server
Start the development server on `http://localhost:3000`:
```bash
# npm
npm run dev
# pnpm
pnpm run dev
# yarn
yarn dev
# bun
bun run dev
```
## Production
Build the application for production:
```bash
# npm
npm run build
# pnpm
pnpm run build
# yarn
yarn build
# bun
bun run build
```
Locally preview production build:
```bash
# npm
npm run preview
# pnpm
pnpm run preview
# yarn
yarn preview
# bun
bun run preview
```
Check out the [deployment documentation](https://nuxt.com/docs/getting-started/deployment) for more information.

28
packages/cli/test/fixtures/nuxt/app.vue vendored Normal file
View File

@ -0,0 +1,28 @@
<template>
<div>
<UiButton :variant="'secondary'" :size="'lg'">
Save
</UiButton>
<UiAlertDialog>
<UiAlertDialogTrigger as-child>
<UiButton variant="outline">
Show Dialog
</UiButton>
</UiAlertDialogTrigger>
<UiAlertDialogContent>
<UiAlertDialogHeader>
<UiAlertDialogTitle>Are you absolutely sure?</UiAlertDialogTitle>
<UiAlertDialogDescription>
This action cannot be undone. This will permanently delete your
account and remove your data from our servers.
</UiAlertDialogDescription>
</UiAlertDialogHeader>
<UiAlertDialogFooter>
<UiAlertDialogCancel>Cancel</UiAlertDialogCancel>
<UiAlertDialogAction>Continue</UiAlertDialogAction>
</UiAlertDialogFooter>
</UiAlertDialogContent>
</UiAlertDialog>
</div>
</template>

View File

@ -0,0 +1,78 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer base {
:root {
--background: 0 0% 100%;
--foreground: 222.2 84% 4.9%;
--muted: 210 40% 96.1%;
--muted-foreground: 215.4 16.3% 46.9%;
--popover: 0 0% 100%;
--popover-foreground: 222.2 84% 4.9%;
--card: 0 0% 100%;
--card-foreground: 222.2 84% 4.9%;
--border: 214.3 31.8% 91.4%;
--input: 214.3 31.8% 91.4%;
--primary: 222.2 47.4% 11.2%;
--primary-foreground: 210 40% 98%;
--secondary: 210 40% 96.1%;
--secondary-foreground: 222.2 47.4% 11.2%;
--accent: 210 40% 96.1%;
--accent-foreground: 222.2 47.4% 11.2%;
--destructive: 0 84.2% 60.2%;
--destructive-foreground: 210 40% 98%;
--ring: 222.2 84% 4.9%;
--radius: 0.5rem;
}
.dark {
--background: 222.2 84% 4.9%;
--foreground: 210 40% 98%;
--muted: 217.2 32.6% 17.5%;
--muted-foreground: 215 20.2% 65.1%;
--popover: 222.2 84% 4.9%;
--popover-foreground: 210 40% 98%;
--card: 222.2 84% 4.9%;
--card-foreground: 210 40% 98%;
--border: 217.2 32.6% 17.5%;
--input: 217.2 32.6% 17.5%;
--primary: 210 40% 98%;
--primary-foreground: 222.2 47.4% 11.2%;
--secondary: 217.2 32.6% 17.5%;
--secondary-foreground: 210 40% 98%;
--accent: 217.2 32.6% 17.5%;
--accent-foreground: 210 40% 98%;
--destructive: 0 62.8% 30.6%;
--destructive-foreground: 210 40% 98%;
--ring: 212.7 26.8% 83.9%;
}
}
@layer base {
* {
@apply border-border;
}
body {
@apply bg-background text-foreground;
}
}

View File

@ -0,0 +1,15 @@
{
"style": "default",
"typescript": true,
"tailwind": {
"config": "tailwind.config.js",
"css": "assets/css/tailwind.css",
"baseColor": "slate",
"cssVariables": true
},
"framework": "nuxt",
"aliases": {
"components": "@/components",
"utils": "@/lib/utils"
}
}

View File

@ -0,0 +1,16 @@
<script setup lang="ts">
import { type AlertDialogEmits, type AlertDialogProps, AlertDialogRoot } from 'radix-vue'
import { useEmitAsProps } from '@/lib/utils'
const props = defineProps<AlertDialogProps>()
const emits = defineEmits<AlertDialogEmits>()
const emitsAsProps = useEmitAsProps(emits)
</script>
<template>
<AlertDialogRoot v-bind="{ ...props, ...emitsAsProps }">
<slot />
</AlertDialogRoot>
</template>

View File

@ -0,0 +1,13 @@
<script setup lang="ts">
import { AlertDialogAction, type AlertDialogActionProps } from 'radix-vue'
import { cn } from '@/lib/utils'
import { buttonVariants } from '@/components/ui/button'
const props = defineProps<AlertDialogActionProps>()
</script>
<template>
<AlertDialogAction v-bind="props" :class="cn(buttonVariants(), $attrs.class ?? '')">
<slot />
</AlertDialogAction>
</template>

View File

@ -0,0 +1,13 @@
<script setup lang="ts">
import { AlertDialogCancel, type AlertDialogCancelProps } from 'radix-vue'
import { cn } from '@/lib/utils'
import { buttonVariants } from '@/components/ui/button'
const props = defineProps<AlertDialogCancelProps>()
</script>
<template>
<AlertDialogCancel v-bind="props" :class="cn(buttonVariants({ variant: 'outline' }), 'mt-2 sm:mt-0', $attrs.class ?? '')">
<slot />
</AlertDialogCancel>
</template>

View File

@ -0,0 +1,35 @@
<script setup lang="ts">
import {
AlertDialogContent,
type AlertDialogContentEmits,
type AlertDialogContentProps,
AlertDialogOverlay,
AlertDialogPortal,
} from 'radix-vue'
import { cn, useEmitAsProps } from '@/lib/utils'
const props = defineProps<AlertDialogContentProps & { class?: string }>()
const emits = defineEmits<AlertDialogContentEmits>()
const emitsAsProps = useEmitAsProps(emits)
</script>
<template>
<AlertDialogPortal>
<AlertDialogOverlay
class="fixed inset-0 z-50 bg-background/80 backdrop-blur-sm data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0"
/>
<AlertDialogContent
v-bind="{ ...props, ...emitsAsProps }"
:class="
cn(
'fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border border-border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg md:w-full',
props.class,
)
"
>
<slot />
</AlertDialogContent>
</AlertDialogPortal>
</template>

View File

@ -0,0 +1,18 @@
<script setup lang="ts">
import {
AlertDialogDescription,
type AlertDialogDescriptionProps,
} from 'radix-vue'
import { cn } from '@/lib/utils'
const props = defineProps<AlertDialogDescriptionProps & { class?: string }>()
</script>
<template>
<AlertDialogDescription
:class="cn('text-muted-foreground text-sm', props.class)"
:as-child="props.asChild"
>
<slot />
</AlertDialogDescription>
</template>

View File

@ -0,0 +1,23 @@
<script setup lang="ts">
import { cn } from '@/lib/utils'
const props = defineProps({
class: {
type: String,
default: '',
},
})
</script>
<template>
<div
:class="
cn(
'flex flex-col space-y-2 sm:space-y-0 mt-3.5 sm:flex-row sm:justify-end sm:space-x-2',
props.class,
)
"
>
<slot />
</div>
</template>

View File

@ -0,0 +1,18 @@
<script setup lang="ts">
import { cn } from '@/lib/utils'
const props = defineProps({
class: {
type: String,
default: '',
},
})
</script>
<template>
<div
:class="cn('flex flex-col space-y-2 text-center sm:text-left', props.class)"
>
<slot />
</div>
</template>

View File

@ -0,0 +1,15 @@
<script setup lang="ts">
import { AlertDialogTitle, type AlertDialogTitleProps } from 'radix-vue'
import { cn } from '@/lib/utils'
const props = defineProps<AlertDialogTitleProps & { class?: string }>()
</script>
<template>
<AlertDialogTitle
:as-child="props.asChild"
:class="cn('text-lg text-foreground font-semibold', props.class)"
>
<slot />
</AlertDialogTitle>
</template>

View File

@ -0,0 +1,11 @@
<script setup lang="ts">
import { AlertDialogTrigger, type AlertDialogTriggerProps } from 'radix-vue'
const props = defineProps<AlertDialogTriggerProps>()
</script>
<template>
<AlertDialogTrigger v-bind="props">
<slot />
</AlertDialogTrigger>
</template>

View File

@ -0,0 +1,9 @@
export { default as AlertDialog } from './AlertDialog.vue'
export { default as AlertDialogTrigger } from './AlertDialogTrigger.vue'
export { default as AlertDialogContent } from './AlertDialogContent.vue'
export { default as AlertDialogHeader } from './AlertDialogHeader.vue'
export { default as AlertDialogTitle } from './AlertDialogTitle.vue'
export { default as AlertDialogDescription } from './AlertDialogDescription.vue'
export { default as AlertDialogFooter } from './AlertDialogFooter.vue'
export { default as AlertDialogAction } from './AlertDialogAction.vue'
export { default as AlertDialogCancel } from './AlertDialogCancel.vue'

View File

@ -0,0 +1,23 @@
<script setup lang="ts">
import { buttonVariants } from '.'
import { cn } from '@/lib/utils'
interface Props {
variant?: NonNullable<Parameters<typeof buttonVariants>[0]>['variant']
size?: NonNullable<Parameters<typeof buttonVariants>[0]>['size']
as?: string
}
withDefaults(defineProps<Props>(), {
as: 'button',
})
</script>
<template>
<component
:is="as"
:class="cn(buttonVariants({ variant, size }), $attrs.class ?? '')"
>
<slot />
</component>
</template>

View File

@ -0,0 +1,32 @@
import { cva } from 'class-variance-authority'
export { default as Button } from './Button.vue'
export const buttonVariants = cva(
'inline-flex items-center justify-center rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50',
{
variants: {
variant: {
default: 'bg-primary text-primary-foreground hover:bg-primary/90',
destructive:
'bg-destructive text-destructive-foreground hover:bg-destructive/90',
outline:
'border border-input bg-background hover:bg-accent hover:text-accent-foreground',
secondary:
'bg-secondary text-secondary-foreground hover:bg-secondary/80',
ghost: 'hover:bg-accent hover:text-accent-foreground',
link: 'text-primary underline-offset-4 hover:underline',
},
size: {
default: 'h-10 px-4 py-2',
sm: 'h-9 rounded-md px-3',
lg: 'h-11 rounded-md px-8',
icon: 'h-10 w-10',
},
},
defaultVariants: {
variant: 'default',
size: 'default',
},
},
)

View File

@ -0,0 +1,26 @@
import { type ClassValue, clsx } from 'clsx'
import { twMerge } from 'tailwind-merge'
import { camelize, getCurrentInstance, toHandlerKey } from 'vue'
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs))
}
export function useEmitAsProps<Name extends string>(
emit: (name: Name, ...args: any[]) => void,
) {
const vm = getCurrentInstance()
const events = vm?.type.emits as Name[]
const result: Record<string, any> = {}
if (!events?.length) {
console.warn(
`No emitted event found. Please check component: ${vm?.type.__name}`,
)
}
events?.forEach((ev) => {
result[toHandlerKey(camelize(ev))] = (...arg: any) => emit(ev, ...arg)
})
return result
}

View File

@ -0,0 +1,12 @@
// https://nuxt.com/docs/api/configuration/nuxt-config
export default defineNuxtConfig({
devtools: { enabled: true },
modules: ['@nuxtjs/tailwindcss'],
components: [
{
path: '~/components/ui',
extensions: ['.vue'],
prefix: 'Ui',
},
],
})

View File

@ -0,0 +1,25 @@
{
"name": "test-cli-nuxt",
"type": "module",
"private": true,
"scripts": {
"build": "nuxt build",
"dev": "nuxt dev",
"generate": "nuxt generate",
"preview": "nuxt preview",
"postinstall": "nuxt prepare"
},
"dependencies": {
"class-variance-authority": "^0.7.0",
"clsx": "^2.0.0",
"lucide-vue-next": "^0.276.0",
"radix-vue": "^0.2.2",
"tailwind-merge": "^1.14.0",
"tailwindcss-animate": "^1.0.7"
},
"devDependencies": {
"@nuxt/devtools": "latest",
"@nuxtjs/tailwindcss": "^6.8.0",
"nuxt": "^3.7.3"
}
}

View File

@ -0,0 +1,70 @@
/** @type {import('tailwindcss').Config} */
module.exports = {
darkMode: ['class'],
theme: {
container: {
center: true,
padding: '2rem',
screens: {
'2xl': '1400px',
},
},
extend: {
colors: {
border: 'hsl(var(--border))',
input: 'hsl(var(--input))',
ring: 'hsl(var(--ring))',
background: 'hsl(var(--background))',
foreground: 'hsl(var(--foreground))',
primary: {
DEFAULT: 'hsl(var(--primary))',
foreground: 'hsl(var(--primary-foreground))',
},
secondary: {
DEFAULT: 'hsl(var(--secondary))',
foreground: 'hsl(var(--secondary-foreground))',
},
destructive: {
DEFAULT: 'hsl(var(--destructive))',
foreground: 'hsl(var(--destructive-foreground))',
},
muted: {
DEFAULT: 'hsl(var(--muted))',
foreground: 'hsl(var(--muted-foreground))',
},
accent: {
DEFAULT: 'hsl(var(--accent))',
foreground: 'hsl(var(--accent-foreground))',
},
popover: {
DEFAULT: 'hsl(var(--popover))',
foreground: 'hsl(var(--popover-foreground))',
},
card: {
DEFAULT: 'hsl(var(--card))',
foreground: 'hsl(var(--card-foreground))',
},
},
borderRadius: {
lg: 'var(--radius)',
md: 'calc(var(--radius) - 2px)',
sm: 'calc(var(--radius) - 4px)',
},
keyframes: {
'accordion-down': {
from: { height: 0 },
to: { height: 'var(--radix-accordion-content-height)' },
},
'accordion-up': {
from: { height: 'var(--radix-accordion-content-height)' },
to: { height: 0 },
},
},
animation: {
'accordion-down': 'accordion-down 0.2s ease-out',
'accordion-up': 'accordion-up 0.2s ease-out',
},
},
},
plugins: [require('tailwindcss-animate')],
}

View File

@ -0,0 +1,4 @@
{
// https://nuxt.com/docs/guide/concepts/typescript
"extends": "./.nuxt/tsconfig.json"
}

View File

@ -1,25 +1,22 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`transform css vars 1`] = ` exports[`transform css vars 1`] = `
"import * as React from \\"react\\" "<script setup lang=\\"ts\\"></script>
export function Foo() { <template>
return <div class=\\"bg-background hover:bg-muted text-primary-foreground sm:focus:text-accent-foreground\\">foo</div> <div class=\\"bg-background hover:bg-muted text-primary-foreground sm:focus:text-accent-foreground\\">foo</div>
}\\" </template>\\""
"
`; `;
exports[`transform css vars 2`] = ` exports[`transform css vars 2`] = `
"import * as React from \\"react\\" "<script setup lang=\\"ts\\"></script>
export function Foo() { <template>
return <div class=\\"bg-white hover:bg-stone-100 text-stone-50 sm:focus:text-stone-900 dark:bg-stone-950 dark:hover:bg-stone-800 dark:text-stone-900 dark:sm:focus:text-stone-50\\">foo</div> <div class=\\"bg-white hover:bg-stone-100 text-stone-50 sm:focus:text-stone-900 dark:bg-stone-950 dark:hover:bg-stone-800 dark:text-stone-900 dark:sm:focus:text-stone-50\\">foo</div>
}\\"\\" </template>\\""
"
`; `;
exports[`transform css vars 3`] = ` exports[`transform css vars 3`] = `
"import * as React from \\"react\\" "<script setup lang=\\"ts\\"></script>
export function Foo() { <template>
return <div class={cn(\\"bg-white hover:bg-stone-100 dark:bg-stone-950 dark:hover:bg-stone-800\\", true && \\"text-stone-50 sm:focus:text-stone-900 dark:text-stone-900 dark:sm:focus:text-stone-50\\")}>foo</div> <div :class=\\"cn( 'bg-white hover:bg-stone-100 dark:bg-stone-950 dark:hover:bg-stone-800', true && 'text-stone-50 sm:focus:text-stone-900 dark:text-stone-900 dark:sm:focus:text-stone-50')\\" >foo</div>
}\\"\\" </template>\\""
"
`; `;

View File

@ -1,8 +1,7 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`transform import 1`] = ` exports[`transform import 1`] = `
"import * as React from \\"react\\" "import { Foo } from \\"bar\\"
import { Foo } from \\"bar\\"
import { Button } from \\"@/components/ui/button\\" import { Button } from \\"@/components/ui/button\\"
import { Label} from \\"ui/label\\" import { Label} from \\"ui/label\\"
import { Box } from \\"@/components/box\\" import { Box } from \\"@/components/box\\"
@ -12,8 +11,7 @@ import { Foo } from \\"bar\\"
`; `;
exports[`transform import 2`] = ` exports[`transform import 2`] = `
"import * as React from \\"react\\" "import { Foo } from \\"bar\\"
import { Foo } from \\"bar\\"
import { Button } from \\"~/src/components/ui/button\\" import { Button } from \\"~/src/components/ui/button\\"
import { Label} from \\"ui/label\\" import { Label} from \\"ui/label\\"
import { Box } from \\"~/src/components/box\\" import { Box } from \\"~/src/components/box\\"
@ -24,8 +22,7 @@ import { Foo } from \\"bar\\"
`; `;
exports[`transform import 3`] = ` exports[`transform import 3`] = `
"import * as React from \\"react\\" "import { Foo } from \\"bar\\"
import { Foo } from \\"bar\\"
import { Button } from \\"~/src/components/ui/button\\" import { Button } from \\"~/src/components/ui/button\\"
import { Label} from \\"ui/label\\" import { Label} from \\"ui/label\\"
import { Box } from \\"~/src/components/box\\" import { Box } from \\"~/src/components/box\\"

View File

@ -1,31 +0,0 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`transform rsc 1`] = `
"import * as React from \\"react\\"
import { Foo } from \\"bar\\"
"
`;
exports[`transform rsc 2`] = `
"\\"use client\\"
import * as React from \\"react\\"
import { Foo } from \\"bar\\"
"
`;
exports[`transform rsc 3`] = `
" import * as React from \\"react\\"
import { Foo } from \\"bar\\"
"
`;
exports[`transform rsc 4`] = `
"\\"use foo\\"
import * as React from \\"react\\"
import { Foo } from \\"bar\\"
\\"use client\\"
"
`;

View File

@ -0,0 +1,14 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`transformSFC > basic 1`] = `
"<script setup>
const array = [1, 2, 3];
</script>
<template>
<div v-bind=\\"{ array }\\">template</div>
</template>
<style scoped></style>
"
`;

View File

@ -2,7 +2,7 @@ import { describe, expect, test } from 'vitest'
import { import {
applyColorMapping, applyColorMapping,
splitclass, splitClassName,
} from '../../src/utils/transformers/transform-css-vars' } from '../../src/utils/transformers/transform-css-vars'
import baseColor from '../fixtures/colors/slate.json' import baseColor from '../fixtures/colors/slate.json'
@ -44,8 +44,8 @@ describe('split class', () => {
input: 'sm:focus:text-accent-foreground/30', input: 'sm:focus:text-accent-foreground/30',
output: ['sm:focus', 'text-accent-foreground', '30'], output: ['sm:focus', 'text-accent-foreground', '30'],
}, },
])('splitclass($input) -> $output', ({ input, output }) => { ])('splitClassName($input) -> $output', ({ input, output }) => {
expect(splitclass(input)).toStrictEqual(output) expect(splitClassName(input)).toStrictEqual(output)
}) })
}) })

View File

@ -12,18 +12,18 @@ test('get raw config', async () => {
await getRawConfig(path.resolve(__dirname, '../fixtures/config-partial')), await getRawConfig(path.resolve(__dirname, '../fixtures/config-partial')),
).toEqual({ ).toEqual({
style: 'default', style: 'default',
framework: 'Vite',
tailwind: { tailwind: {
config: './tailwind.config.ts', config: './tailwind.config.ts',
css: './src/assets/css/tailwind.css', css: './src/assets/css/tailwind.css',
baseColor: 'neutral', baseColor: 'neutral',
cssVariables: false, cssVariables: false,
}, },
rsc: false,
tsx: true,
aliases: { aliases: {
components: '@/components', components: '@/components',
utils: '@/lib/utils', utils: '@/lib/utils',
}, },
typescript: true,
}) })
expect( expect(
@ -50,12 +50,11 @@ test('get config', async () => {
baseColor: 'neutral', baseColor: 'neutral',
cssVariables: false, cssVariables: false,
}, },
rsc: false,
tsx: true,
aliases: { aliases: {
components: '@/components', components: '@/components',
utils: '@/lib/utils', utils: '@/lib/utils',
}, },
framework: 'Vite',
resolvedPaths: { resolvedPaths: {
tailwindConfig: path.resolve( tailwindConfig: path.resolve(
__dirname, __dirname,
@ -78,14 +77,13 @@ test('get config', async () => {
'./lib/utils', './lib/utils',
), ),
}, },
typescript: true,
}) })
expect( expect(
await getConfig(path.resolve(__dirname, '../fixtures/config-full')), await getConfig(path.resolve(__dirname, '../fixtures/config-full')),
).toEqual({ ).toEqual({
style: 'new-york', style: 'new-york',
rsc: false,
tsx: true,
tailwind: { tailwind: {
config: 'tailwind.config.ts', config: 'tailwind.config.ts',
baseColor: 'zinc', baseColor: 'zinc',
@ -96,6 +94,7 @@ test('get config', async () => {
components: '~/components', components: '~/components',
utils: '~/lib/utils', utils: '~/lib/utils',
}, },
framework: 'Vite',
resolvedPaths: { resolvedPaths: {
tailwindConfig: path.resolve( tailwindConfig: path.resolve(
__dirname, __dirname,
@ -118,10 +117,11 @@ test('get config', async () => {
'./src/lib/utils', './src/lib/utils',
), ),
}, },
typescript: true,
}) })
expect( expect(
await getConfig(path.resolve(__dirname, '../fixtures/config-jsx')), await getConfig(path.resolve(__dirname, '../fixtures/config-js')),
).toEqual({ ).toEqual({
style: 'default', style: 'default',
tailwind: { tailwind: {
@ -130,29 +130,29 @@ test('get config', async () => {
baseColor: 'neutral', baseColor: 'neutral',
cssVariables: false, cssVariables: false,
}, },
rsc: false, typescript: false,
tsx: false,
aliases: { aliases: {
components: '@/components', components: '@/components',
utils: '@/lib/utils', utils: '@/lib/utils',
}, },
framework: 'Vite',
resolvedPaths: { resolvedPaths: {
tailwindConfig: path.resolve( tailwindConfig: path.resolve(
__dirname, __dirname,
'../fixtures/config-jsx', '../fixtures/config-js',
'tailwind.config.js', 'tailwind.config.js',
), ),
tailwindCss: path.resolve( tailwindCss: path.resolve(
__dirname, __dirname,
'../fixtures/config-jsx', '../fixtures/config-js',
'./src/assets/css/tailwind.css', './src/assets/css/tailwind.css',
), ),
components: path.resolve( components: path.resolve(
__dirname, __dirname,
'../fixtures/config-jsx', '../fixtures/config-js',
'./components', './components',
), ),
utils: path.resolve(__dirname, '../fixtures/config-jsx', './lib/utils'), utils: path.resolve(__dirname, '../fixtures/config-js', './lib/utils'),
}, },
}) })
}) })

View File

@ -6,36 +6,60 @@ test('resolve tree', async () => {
const index = [ const index = [
{ {
name: 'button', name: 'button',
dependencies: ['@radix-ui/react-slot'], dependencies: ['radix-vue'],
type: 'components:ui', type: 'components:ui',
files: ['button.tsx'], files: [
'button/Button.vue',
'button/index.ts',
],
}, },
{ {
name: 'dialog', name: 'dialog',
dependencies: ['@radix-ui/react-dialog'], dependencies: ['radix-vue'],
registryDependencies: ['button'], registryDependencies: ['button'],
type: 'components:ui', type: 'components:ui',
files: ['dialog.tsx'], files: ['dialog/Dialog.vue',
'dialog/DialogContent.vue',
'dialog/DialogDescription.vue',
'dialog/DialogFooter.vue',
'dialog/DialogHeader.vue',
'dialog/DialogTitle.vue',
'dialog/DialogTrigger.vue',
'dialog/index.ts',
],
}, },
{ {
name: 'input', name: 'input',
registryDependencies: ['button'], registryDependencies: ['button'],
type: 'components:ui', type: 'components:ui',
files: ['input.tsx'], files: [
'input/Input.vue',
'input/index.ts',
],
}, },
{ {
name: 'alert-dialog', name: 'alert-dialog',
dependencies: ['@radix-ui/react-alert-dialog'], dependencies: ['radix-vue'],
registryDependencies: ['button', 'dialog'], registryDependencies: ['button', 'dialog'],
type: 'components:ui', type: 'components:ui',
files: ['alert-dialog.tsx'], files: ['alert-dialog/AlertDialog.vue',
}, 'alert-dialog/AlertDialogAction.vue',
{ 'alert-dialog/AlertDialogCancel.vue',
name: 'example-card', 'alert-dialog/AlertDialogContent.vue',
type: 'components:component', 'alert-dialog/AlertDialogDescription.vue',
files: ['example-card.tsx'], 'alert-dialog/AlertDialogFooter.vue',
registryDependencies: ['button', 'dialog', 'input'], 'alert-dialog/AlertDialogHeader.vue',
'alert-dialog/AlertDialogTitle.vue',
'alert-dialog/AlertDialogTrigger.vue',
'alert-dialog/index.ts',
],
}, },
// {
// name: 'example-card',
// type: 'components:component',
// files: ['example-card.tsx'],
// registryDependencies: ['button', 'dialog', 'input'],
// },
] ]
expect( expect(
@ -52,11 +76,11 @@ test('resolve tree', async () => {
.sort(), .sort(),
).toEqual(['alert-dialog', 'button', 'dialog']) ).toEqual(['alert-dialog', 'button', 'dialog'])
expect( // expect(
(await resolveTree(index, ['example-card'])) // (await resolveTree(index, ['example-card']))
.map(entry => entry.name) // .map(entry => entry.name)
.sort(), // .sort(),
).toEqual(['button', 'dialog', 'example-card', 'input']) // ).toEqual(['button', 'dialog', 'example-card', 'input'])
expect( expect(
(await resolveTree(index, ['foo'])).map(entry => entry.name).sort(), (await resolveTree(index, ['foo'])).map(entry => entry.name).sort(),

View File

@ -6,7 +6,7 @@ import { resolveImport } from '../../src/utils/resolve-import'
test('resolve import', async () => { test('resolve import', async () => {
expect( expect(
await resolveImport('@/foo/bar', { resolveImport('@/foo/bar', {
absoluteBaseUrl: '/Users/shadcn/Projects/foobar', absoluteBaseUrl: '/Users/shadcn/Projects/foobar',
paths: { paths: {
'@/*': ['./src/*'], '@/*': ['./src/*'],
@ -17,7 +17,7 @@ test('resolve import', async () => {
).toEqual('/Users/shadcn/Projects/foobar/src/foo/bar') ).toEqual('/Users/shadcn/Projects/foobar/src/foo/bar')
expect( expect(
await resolveImport('~/components/foo/bar/baz', { resolveImport('~/components/foo/bar/baz', {
absoluteBaseUrl: '/Users/shadcn/Projects/foobar', absoluteBaseUrl: '/Users/shadcn/Projects/foobar',
paths: { paths: {
'@/*': ['./src/*'], '@/*': ['./src/*'],
@ -28,7 +28,7 @@ test('resolve import', async () => {
).toEqual('/Users/shadcn/Projects/foobar/src/components/foo/bar/baz') ).toEqual('/Users/shadcn/Projects/foobar/src/components/foo/bar/baz')
expect( expect(
await resolveImport('components/foo/bar', { resolveImport('components/foo/bar', {
absoluteBaseUrl: '/Users/shadcn/Projects/foobar', absoluteBaseUrl: '/Users/shadcn/Projects/foobar',
paths: { paths: {
'components/*': ['./src/app/components/*'], 'components/*': ['./src/app/components/*'],
@ -39,7 +39,7 @@ test('resolve import', async () => {
).toEqual('/Users/shadcn/Projects/foobar/src/app/components/foo/bar') ).toEqual('/Users/shadcn/Projects/foobar/src/app/components/foo/bar')
expect( expect(
await resolveImport('lib/utils', { resolveImport('lib/utils', {
absoluteBaseUrl: '/Users/shadcn/Projects/foobar', absoluteBaseUrl: '/Users/shadcn/Projects/foobar',
paths: { paths: {
'components/*': ['./src/app/components/*'], 'components/*': ['./src/app/components/*'],
@ -52,30 +52,30 @@ test('resolve import', async () => {
test('resolve import with base url', async () => { test('resolve import with base url', async () => {
const cwd = path.resolve(__dirname, '../fixtures/with-base-url') const cwd = path.resolve(__dirname, '../fixtures/with-base-url')
const config = (await loadConfig(cwd)) as ConfigLoaderSuccessResult const config = (loadConfig(cwd)) as ConfigLoaderSuccessResult
expect(await resolveImport('@/components/ui', config)).toEqual( expect(resolveImport('@/components/ui', config)).toEqual(
path.resolve(cwd, 'components/ui'), path.resolve(cwd, 'components/ui'),
) )
expect(await resolveImport('@/lib/utils', config)).toEqual( expect(resolveImport('@/lib/utils', config)).toEqual(
path.resolve(cwd, 'lib/utils'), path.resolve(cwd, 'lib/utils'),
) )
expect(await resolveImport('foo/bar', config)).toEqual( expect(resolveImport('foo/bar', config)).toEqual(
path.resolve(cwd, 'foo/bar'), path.resolve(cwd, 'foo/bar'),
) )
}) })
test('resolve import without base url', async () => { test('resolve import without base url', async () => {
const cwd = path.resolve(__dirname, '../fixtures/without-base-url') const cwd = path.resolve(__dirname, '../fixtures/without-base-url')
const config = (await loadConfig(cwd)) as ConfigLoaderSuccessResult const config = (loadConfig(cwd)) as ConfigLoaderSuccessResult
expect(await resolveImport('~/components/ui', config)).toEqual( expect(resolveImport('~/components/ui', config)).toEqual(
path.resolve(cwd, 'components/ui'), path.resolve(cwd, 'components/ui'),
) )
expect(await resolveImport('~/lib/utils', config)).toEqual( expect(resolveImport('~/lib/utils', config)).toEqual(
path.resolve(cwd, 'lib/utils'), path.resolve(cwd, 'lib/utils'),
) )
expect(await resolveImport('foo/bar', config)).toEqual( expect(resolveImport('foo/bar', config)).toEqual(
path.resolve(cwd, 'foo/bar'), path.resolve(cwd, 'foo/bar'),
) )
}) })

View File

@ -1,75 +1,69 @@
// import { expect, test } from 'vitest' import { expect, test } from 'vitest'
// import { transform } from '../../src/utils/transformers' import { transform } from '../../src/utils/transformers'
// import stone from '../fixtures/colors/stone.json' import stone from '../fixtures/colors/stone.json'
// test('transform css vars', async () => { test('transform css vars', async () => {
// expect( expect(
// await transform({ await transform({
// filename: 'test.ts', filename: 'app.vue',
// raw: `import * as React from "react" raw: `<script setup lang="ts"></script>
// export function Foo() { <template>
// return <div class="bg-background hover:bg-muted text-primary-foreground sm:focus:text-accent-foreground">foo</div> <div class="bg-background hover:bg-muted text-primary-foreground sm:focus:text-accent-foreground">foo</div>
// }" </template>"`,
// `, config: {
// config: { tailwind: {
// tsx: true, baseColor: 'stone',
// tailwind: { cssVariables: true,
// baseColor: 'stone', },
// cssVariables: true, aliases: {
// }, components: '@/components',
// aliases: { utils: '@/lib/utils',
// components: '@/components', },
// utils: '@/lib/utils', },
// }, baseColor: stone,
// }, }),
// baseColor: stone, ).toMatchSnapshot()
// }),
// ).toMatchSnapshot()
// expect( expect(
// await transform({ await transform({
// filename: 'test.ts', filename: 'app.vue',
// raw: `import * as React from "react" raw: `<script setup lang="ts"></script>
// export function Foo() { <template>
// return <div class="bg-background hover:bg-muted text-primary-foreground sm:focus:text-accent-foreground">foo</div> <div class="bg-background hover:bg-muted text-primary-foreground sm:focus:text-accent-foreground">foo</div>
// }" </template>"`,
// `, config: {
// config: { tailwind: {
// tsx: true, baseColor: 'stone',
// tailwind: { cssVariables: false,
// baseColor: 'stone', },
// cssVariables: false, aliases: {
// }, components: '@/components',
// aliases: { utils: '@/lib/utils',
// components: '@/components', },
// utils: '@/lib/utils', },
// }, baseColor: stone,
// }, }),
// baseColor: stone, ).toMatchSnapshot()
// }),
// ).toMatchSnapshot()
// expect( expect(
// await transform({ await transform({
// filename: 'test.ts', filename: 'app.vue',
// raw: `import * as React from "react" raw: `<script setup lang="ts"></script>
// export function Foo() { <template>
// return <div class={cn("bg-background hover:bg-muted", true && "text-primary-foreground sm:focus:text-accent-foreground")}>foo</div> <div :class="cn('bg-background hover:bg-muted', true && 'text-primary-foreground sm:focus:text-accent-foreground')">foo</div>
// }" </template>"`,
// `, config: {
// config: { tailwind: {
// tsx: true, baseColor: 'stone',
// tailwind: { cssVariables: false,
// baseColor: 'stone', },
// cssVariables: false, aliases: {
// }, components: '@/components',
// aliases: { utils: '@/lib/utils',
// components: '@/components', },
// utils: '@/lib/utils', },
// }, baseColor: stone,
// }, }),
// baseColor: stone, ).toMatchSnapshot()
// }), })
// ).toMatchSnapshot()
// })

View File

@ -1,74 +1,67 @@
// import { expect, test } from 'vitest' import { expect, test } from 'vitest'
import { transform } from '../../src/utils/transformers'
// import { transform } from '../../src/utils/transformers' test('transform import', async () => {
expect(
await transform({
filename: 'app.vue',
raw: `import { Foo } from "bar"
import { Button } from "@/lib/registry/new-york/ui/button"
import { Label} from "ui/label"
import { Box } from "@/lib/registry/new-york/box"
// test('transform import', async () => { import { cn } from "@/lib/utils"
// expect( `,
// await transform({ config: {
// filename: 'test.ts', tailwind: {
// raw: `import * as React from "react" baseColor: 'neutral',
// import { Foo } from "bar" cssVariables: true,
// import { Button } from "@/registry/new-york/ui/button" },
// import { Label} from "ui/label" aliases: {
// import { Box } from "@/registry/new-york/box" components: '@/components',
utils: '@/lib/utils',
},
},
}),
).toMatchSnapshot()
// import { cn } from "@/lib/utils" expect(
// `, await transform({
// config: { filename: 'app.vue',
// tsx: true, raw: `import { Foo } from "bar"
// tailwind: { import { Button } from "@/lib/registry/new-york/ui/button"
// baseColor: 'neutral', import { Label} from "ui/label"
// cssVariables: true, import { Box } from "@/lib/registry/new-york/box"
// },
// aliases: {
// components: '@/components',
// utils: '@/lib/utils',
// },
// },
// }),
// ).toMatchSnapshot()
// expect( import { cn, foo, bar } from "@/lib/utils"
// await transform({ import { bar } from "@/lib/utils/bar"
// filename: 'test.ts', `,
// raw: `import * as React from "react" config: {
// import { Foo } from "bar" aliases: {
// import { Button } from "@/registry/new-york/ui/button" components: '~/src/components',
// import { Label} from "ui/label" utils: '~/lib',
// import { Box } from "@/registry/new-york/box" },
},
}),
).toMatchSnapshot()
// import { cn, foo, bar } from "@/lib/utils" expect(
// import { bar } from "@/lib/utils/bar" await transform({
// `, filename: 'app.vue',
// config: { raw: `import { Foo } from "bar"
// tsx: true, import { Button } from "@/lib/registry/new-york/ui/button"
// aliases: { import { Label} from "ui/label"
// components: '~/src/components', import { Box } from "@/lib/registry/new-york/box"
// utils: '~/lib',
// },
// },
// }),
// ).toMatchSnapshot()
// expect( import { cn } from "@/lib/utils"
// await transform({ import { bar } from "@/lib/utils/bar"
// filename: 'test.ts', `,
// raw: `import * as React from "react" config: {
// import { Foo } from "bar" aliases: {
// import { Button } from "@/registry/new-york/ui/button" components: '~/src/components',
// import { Label} from "ui/label" utils: '~/src/utils',
// import { Box } from "@/registry/new-york/box" },
},
// import { cn } from "@/lib/utils" }),
// import { bar } from "@/lib/utils/bar" ).toMatchSnapshot()
// `, })
// config: {
// tsx: true,
// aliases: {
// components: '~/src/components',
// utils: '~/src/utils',
// },
// },
// }),
// ).toMatchSnapshot()
// })

View File

@ -1,65 +0,0 @@
// import { expect, test } from 'vitest'
// import { transform } from '../../src/utils/transformers'
// test('transform rsc', async () => {
// expect(
// await transform({
// filename: 'test.ts',
// raw: `import * as React from "react"
// import { Foo } from "bar"
// `,
// config: {
// tsx: true,
// rsc: true,
// },
// }),
// ).toMatchSnapshot()
// expect(
// await transform({
// filename: 'test.ts',
// raw: `"use client"
// import * as React from "react"
// import { Foo } from "bar"
// `,
// config: {
// tsx: true,
// rsc: true,
// },
// }),
// ).toMatchSnapshot()
// expect(
// await transform({
// filename: 'test.ts',
// raw: `"use client"
// import * as React from "react"
// import { Foo } from "bar"
// `,
// config: {
// tsx: true,
// rsc: false,
// },
// }),
// ).toMatchSnapshot()
// expect(
// await transform({
// filename: 'test.ts',
// raw: `"use foo"
// import * as React from "react"
// import { Foo } from "bar"
// "use client"
// `,
// config: {
// tsx: true,
// rsc: false,
// },
// }),
// ).toMatchSnapshot()
// })

View File

@ -0,0 +1,25 @@
import { describe, expect, test } from 'vitest'
import { transform } from '../../src/utils/transformers'
describe('transformSFC', () => {
test('basic', async () => {
const result = await transform({
filename: 'app.vue',
raw: `<script lang="ts" setup>
const array: (number | string)[] = [1, 2, 3]
</script>
<template>
<div v-bind="{ array }">
template
</div>
</template>
<style scoped>
</style>
`,
config: {},
})
expect(result).toMatchSnapshot()
})
})

View File

@ -4,6 +4,7 @@
"compilerOptions": { "compilerOptions": {
"isolatedModules": false, "isolatedModules": false,
"baseUrl": ".", "baseUrl": ".",
"module": "ES2020",
"paths": { "paths": {
"@/*": ["./*"] "@/*": ["./*"]
} }

View File

@ -0,0 +1,16 @@
diff --git a/dist/index.js b/dist/index.js
index 8b8e0d078e27474da1cf58ce3fef1d7acefb1cd4..314b23766204dcc6d2873e8ea654dcc3040ba0a5 100644
--- a/dist/index.js
+++ b/dist/index.js
@@ -146,9 +146,9 @@ async function removeTypesFromVueSfcScript(code, fileName, script, templateAst,
(0, import_template_ast_types.traverse)(templateAst, {
enter(node) {
if ((0, import_template_ast_types.isSimpleExpressionNode)(node) && !node.isStatic) {
- expressions.add(node.content);
+ expressions.add(`[${node.content}]`);
} else if ((0, import_template_ast_types.isComponentNode)(node)) {
- expressions.add(node.tag);
+ expressions.add(`[${node.tag}]`);
}
}
});

File diff suppressed because it is too large Load Diff