feat: use unjs modules and improve cli from main shadcn-ui source, custom ui dir (#324)
* feat: add devDeps, add nypm for installing deps
* feat: custom ui dir
* refactor: use consola instead of chalk
* test: ui alias
* refactor: import { z } from 'zod' instead of *, replace node:path with pathe
* chore: add components name to `configFile` option
* chore: update `c12` which fix json5 parse issue
and it also supports .config directory
* chore: update `https-proxy-agent`
* fix: await until dependencies are installed then run detypes process
* feat: add tailwind prefix
* test: tw-prefix snapshot
* chore: add prefix option to init
* test: apply prefix
* fix: tw-prefix parse wrongly
* chore: hide prefix temporarily
---------
Co-authored-by: zernonia <zernonia@gmail.com>
This commit is contained in:
parent
c487137ac5
commit
0e84af73de
|
|
@ -4,7 +4,6 @@ description: Powerful table and datagrids built using TanStack Table.
|
|||
primitive: https://tanstack.com/table/v8/docs/guide/introduction
|
||||
---
|
||||
|
||||
|
||||
<ComponentPreview name="DataTableDemo" />
|
||||
|
||||
## Introduction
|
||||
|
|
@ -56,7 +55,6 @@ npm install @tanstack/vue-table
|
|||
|
||||
<ComponentPreview name="DataTableColumnPinningDemo" />
|
||||
|
||||
|
||||
## Prerequisites
|
||||
|
||||
We are going to build a table to show recent payments. Here's what our data looks like:
|
||||
|
|
@ -219,7 +217,6 @@ const table = useVueTable({
|
|||
|
||||
</Callout>
|
||||
|
||||
|
||||
### Render the table
|
||||
|
||||
Finally, we'll render our table in our index component.
|
||||
|
|
@ -270,7 +267,6 @@ Let's format the amount cell to display the dollar amount. We'll also align the
|
|||
|
||||
Update the `header` and `cell` definitions for amount as follows:
|
||||
|
||||
|
||||
```ts:line-numbers title="components/payments/columns.ts" {5-17}
|
||||
import { h } from 'vue'
|
||||
|
||||
|
|
@ -345,7 +341,6 @@ function copy(id: string) {
|
|||
|
||||
Update our columns definition to add a new `actions` column. The `actions` cell returns a `<Dropdown />` component.
|
||||
|
||||
|
||||
```ts:line-numbers showLineNumber{2,6-16}
|
||||
import { ColumnDef } from "@tanstack/vue-table"
|
||||
import DropdownAction from '@/components/DataTableDropDown.vue'
|
||||
|
|
|
|||
|
|
@ -42,6 +42,9 @@
|
|||
},
|
||||
"components": {
|
||||
"type": "string"
|
||||
},
|
||||
"ui": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": ["utils", "components"]
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ export default antfu(
|
|||
'no-tabs': 0,
|
||||
'import/first': 0,
|
||||
'node/prefer-global/process': 0,
|
||||
'style/no-tabs': 0,
|
||||
},
|
||||
},
|
||||
)
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@
|
|||
"dev": "tsup --watch",
|
||||
"build": "tsup",
|
||||
"typecheck": "tsc --noEmit",
|
||||
"clean": "rimraf dist && rimraf components",
|
||||
"clean": "node ./scripts/rimraf.js",
|
||||
"lint": "eslint .",
|
||||
"lint:fix": "eslint --fix .",
|
||||
"start:dev": "COMPONENTS_REGISTRY_URL=http://localhost:3001 node dist/index.js",
|
||||
|
|
@ -45,41 +45,39 @@
|
|||
"test:ui": "vitest --ui"
|
||||
},
|
||||
"dependencies": {
|
||||
"@antfu/ni": "^0.21.8",
|
||||
"@babel/core": "^7.22.17",
|
||||
"@babel/parser": "^7.22.16",
|
||||
"@babel/plugin-transform-typescript": "^7.22.15",
|
||||
"@babel/core": "^7.24.0",
|
||||
"@babel/parser": "^7.24.0",
|
||||
"@vue/compiler-sfc": "^3.4",
|
||||
"chalk": "5.3.0",
|
||||
"commander": "^11.0.0",
|
||||
"cosmiconfig": "^8.3.6",
|
||||
"c12": "^1.9.0",
|
||||
"commander": "^12.0.0",
|
||||
"consola": "^3.2.3",
|
||||
"detype": "npm:detypes@^0.7.9",
|
||||
"diff": "^5.1.0",
|
||||
"execa": "^8.0.1",
|
||||
"fs-extra": "^11.1.1",
|
||||
"https-proxy-agent": "^7.0.2",
|
||||
"diff": "^5.2.0",
|
||||
"fs-extra": "^11.2.0",
|
||||
"https-proxy-agent": "^7.0.4",
|
||||
"lodash.template": "^4.5.0",
|
||||
"magic-string": "^0.30.3",
|
||||
"node-fetch": "^3.3.2",
|
||||
"ora": "^7.0.1",
|
||||
"magic-string": "^0.30.8",
|
||||
"nypm": "^0.3.8",
|
||||
"ofetch": "^1.3.3",
|
||||
"ora": "^8.0.1",
|
||||
"pathe": "^1.1.2",
|
||||
"prompts": "^2.4.2",
|
||||
"radix-vue": "^1.4.8",
|
||||
"recast": "^0.23.4",
|
||||
"rimraf": "^5.0.1",
|
||||
"ts-morph": "^19.0.0",
|
||||
"radix-vue": "^1.4.9",
|
||||
"ts-morph": "^21.0.1",
|
||||
"tsconfig-paths": "^4.2.0",
|
||||
"vite-tsconfig-paths": "^4.2.1",
|
||||
"zod": "^3.22.2"
|
||||
"zod": "^3.22.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/babel__core": "^7.20.1",
|
||||
"@types/diff": "^5.0.3",
|
||||
"@types/fs-extra": "^11.0.1",
|
||||
"@types/lodash.template": "^4.5.1",
|
||||
"@types/prompts": "^2.4.4",
|
||||
"@types/babel__core": "^7.20.5",
|
||||
"@types/diff": "^5.0.9",
|
||||
"@types/fs-extra": "^11.0.4",
|
||||
"@types/lodash.template": "^4.5.3",
|
||||
"@types/node": "^20.11.24",
|
||||
"@types/prompts": "^2.4.9",
|
||||
"@vitest/ui": "^0.34.4",
|
||||
"tsup": "^7.2.0",
|
||||
"type-fest": "^4.3.1",
|
||||
"typescript": "^5.2.2"
|
||||
"tsup": "^8.0.2",
|
||||
"type-fest": "^4.10.3",
|
||||
"typescript": "^5.3.3",
|
||||
"vite-tsconfig-paths": "^4.3.1"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
10
packages/cli/scripts/rimraf.js
Normal file
10
packages/cli/scripts/rimraf.js
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
import fsp from 'node:fs/promises'
|
||||
|
||||
function rmdir(dirs) {
|
||||
dirs.forEach(async (dir) => {
|
||||
await fsp.unlink(dir).catch(() => {})
|
||||
await fsp.rm(dir, { recursive: true, force: true }).catch(() => {})
|
||||
})
|
||||
}
|
||||
|
||||
rmdir(['dist', 'components'])
|
||||
|
|
@ -1,17 +1,16 @@
|
|||
import { existsSync, promises as fs, rmSync } from 'node:fs'
|
||||
import path from 'node:path'
|
||||
import process from 'node:process'
|
||||
import chalk from 'chalk'
|
||||
import path from 'pathe'
|
||||
import { consola } from 'consola'
|
||||
import { colors } from 'consola/utils'
|
||||
import { Command } from 'commander'
|
||||
import { execa } from 'execa'
|
||||
import ora from 'ora'
|
||||
import prompts from 'prompts'
|
||||
import * as z from 'zod'
|
||||
import { z } from 'zod'
|
||||
import { addDependency, addDevDependency } from 'nypm'
|
||||
import { transform } from '@/src/utils/transformers'
|
||||
import { getConfig } from '@/src/utils/get-config'
|
||||
import { getPackageManager } from '@/src/utils/get-package-manager'
|
||||
import { handleError } from '@/src/utils/handle-error'
|
||||
import { logger } from '@/src/utils/logger'
|
||||
import {
|
||||
fetchTree,
|
||||
getItemTargetPath,
|
||||
|
|
@ -52,15 +51,15 @@ export const add = new Command()
|
|||
const cwd = path.resolve(options.cwd)
|
||||
|
||||
if (!existsSync(cwd)) {
|
||||
logger.error(`The path ${cwd} does not exist. Please try again.`)
|
||||
consola.error(`The path ${cwd} does not exist. Please try again.`)
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
const config = await getConfig(cwd)
|
||||
|
||||
if (!config) {
|
||||
logger.warn(
|
||||
`Configuration is missing. Please run ${chalk.green('init')} to create a components.json file.`,
|
||||
)
|
||||
consola.warn(`Configuration is missing. Please run ${colors.green('init')} to create a components.json file.`)
|
||||
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
|
|
@ -88,7 +87,7 @@ export const add = new Command()
|
|||
}
|
||||
|
||||
if (!selectedComponents?.length) {
|
||||
logger.warn('No components selected. Exiting.')
|
||||
consola.warn('No components selected. Exiting.')
|
||||
process.exit(0)
|
||||
}
|
||||
|
||||
|
|
@ -97,7 +96,7 @@ export const add = new Command()
|
|||
const baseColor = await getRegistryBaseColor(config.tailwind.baseColor)
|
||||
|
||||
if (!payload.length) {
|
||||
logger.warn('Selected components not found. Exiting.')
|
||||
consola.warn('Selected components not found. Exiting.')
|
||||
process.exit(0)
|
||||
}
|
||||
|
||||
|
|
@ -114,7 +113,6 @@ export const add = new Command()
|
|||
}
|
||||
|
||||
const spinner = ora('Installing components...').start()
|
||||
const skippedDeps = new Set<string>()
|
||||
for (const item of payload) {
|
||||
spinner.text = `Installing ${item.name}...`
|
||||
const targetDir = getItemTargetPath(
|
||||
|
|
@ -144,8 +142,8 @@ export const add = new Command()
|
|||
})
|
||||
|
||||
if (!overwrite) {
|
||||
logger.info(
|
||||
`Skipped ${item.name}. To overwrite, run with the ${chalk.green(
|
||||
consola.info(
|
||||
`Skipped ${item.name}. To overwrite, run with the ${colors.green(
|
||||
'--overwrite',
|
||||
)} flag.`,
|
||||
)
|
||||
|
|
@ -159,6 +157,20 @@ export const add = new Command()
|
|||
}
|
||||
}
|
||||
|
||||
// Install dependencies.
|
||||
await Promise.allSettled(
|
||||
[
|
||||
item.dependencies?.length && await addDependency(item.dependencies, {
|
||||
cwd,
|
||||
silent: true,
|
||||
}),
|
||||
item.devDependencies?.length && await addDevDependency(item.devDependencies, {
|
||||
cwd,
|
||||
silent: true,
|
||||
}),
|
||||
],
|
||||
)
|
||||
|
||||
const componentDir = path.resolve(targetDir, item.name)
|
||||
if (!existsSync(componentDir))
|
||||
await fs.mkdir(componentDir, { recursive: true })
|
||||
|
|
@ -201,25 +213,6 @@ export const add = new Command()
|
|||
|
||||
await fs.writeFile(filePath, content)
|
||||
}
|
||||
|
||||
// Install dependencies.
|
||||
if (item.dependencies?.length) {
|
||||
item.dependencies.forEach(dep =>
|
||||
skippedDeps.add(dep),
|
||||
)
|
||||
|
||||
const packageManager = await getPackageManager(cwd)
|
||||
await execa(
|
||||
packageManager,
|
||||
[
|
||||
packageManager === 'npm' ? 'install' : 'add',
|
||||
...item.dependencies,
|
||||
],
|
||||
{
|
||||
cwd,
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
spinner.succeed('Done.')
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,14 +1,14 @@
|
|||
import { existsSync, promises as fs } from 'node:fs'
|
||||
import path from 'node:path'
|
||||
import process from 'node:process'
|
||||
import chalk from 'chalk'
|
||||
import path from 'pathe'
|
||||
import { consola } from 'consola'
|
||||
import { colors } from 'consola/utils'
|
||||
import { Command } from 'commander'
|
||||
import { type Change, diffLines } from 'diff'
|
||||
import * as z from 'zod'
|
||||
import { z } from 'zod'
|
||||
import type { Config } from '@/src/utils/get-config'
|
||||
import { getConfig } from '@/src/utils/get-config'
|
||||
import { handleError } from '@/src/utils/handle-error'
|
||||
import { logger } from '@/src/utils/logger'
|
||||
import {
|
||||
fetchTree,
|
||||
getItemTargetPath,
|
||||
|
|
@ -45,14 +45,14 @@ export const diff = new Command()
|
|||
const cwd = path.resolve(options.cwd)
|
||||
|
||||
if (!existsSync(cwd)) {
|
||||
logger.error(`The path ${cwd} does not exist. Please try again.`)
|
||||
consola.error(`The path ${cwd} does not exist. Please try again.`)
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
const config = await getConfig(cwd)
|
||||
if (!config) {
|
||||
logger.warn(
|
||||
`Configuration is missing. Please run ${chalk.green(
|
||||
consola.warn(
|
||||
`Configuration is missing. Please run ${colors.green(
|
||||
'init',
|
||||
)} to create a components.json file.`,
|
||||
)
|
||||
|
|
@ -88,19 +88,20 @@ export const diff = new Command()
|
|||
}
|
||||
|
||||
if (!componentsWithUpdates.length) {
|
||||
logger.info('No updates found.')
|
||||
consola.info('No updates found.')
|
||||
process.exit(0)
|
||||
}
|
||||
|
||||
logger.info('The following components have updates available:')
|
||||
consola.info('The following components have updates available:')
|
||||
for (const component of componentsWithUpdates) {
|
||||
logger.info(`- ${component.name}`)
|
||||
consola.info(`- ${component.name}`)
|
||||
for (const change of component.changes)
|
||||
logger.info(` - ${change.filePath}`)
|
||||
consola.info(` - ${change.filePath}`)
|
||||
}
|
||||
logger.break()
|
||||
logger.info(
|
||||
`Run ${chalk.green('diff <component>')} to see the changes.`,
|
||||
|
||||
consola.log('')
|
||||
consola.info(
|
||||
`Run ${colors.green('diff <component>')} to see the changes.`,
|
||||
)
|
||||
process.exit(0)
|
||||
}
|
||||
|
|
@ -111,8 +112,8 @@ export const diff = new Command()
|
|||
)
|
||||
|
||||
if (!component) {
|
||||
logger.error(
|
||||
`The component ${chalk.green(options.component)} does not exist.`,
|
||||
consola.error(
|
||||
`The component ${colors.green(options.component)} does not exist.`,
|
||||
)
|
||||
process.exit(1)
|
||||
}
|
||||
|
|
@ -120,14 +121,14 @@ export const diff = new Command()
|
|||
const changes = await diffComponent(component, config)
|
||||
|
||||
if (!changes.length) {
|
||||
logger.info(`No updates found for ${options.component}.`)
|
||||
consola.info(`No updates found for ${options.component}.`)
|
||||
process.exit(0)
|
||||
}
|
||||
|
||||
for (const change of changes) {
|
||||
logger.info(`- ${change.filePath}`)
|
||||
consola.info(`- ${change.filePath}`)
|
||||
printDiff(change.patch)
|
||||
logger.info('')
|
||||
consola.log('')
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
|
|
@ -184,10 +185,10 @@ function printDiff(diff: Change[]) {
|
|||
diff.forEach((part) => {
|
||||
if (part) {
|
||||
if (part.added)
|
||||
return process.stdout.write(chalk.green(part.value))
|
||||
return process.stdout.write(colors.green(part.value))
|
||||
|
||||
if (part.removed)
|
||||
return process.stdout.write(chalk.red(part.value))
|
||||
return process.stdout.write(colors.red(part.value))
|
||||
|
||||
return process.stdout.write(part.value)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,22 +1,21 @@
|
|||
import { existsSync, promises as fs } from 'node:fs'
|
||||
import path from 'node:path'
|
||||
import process from 'node:process'
|
||||
import chalk from 'chalk'
|
||||
import path from 'pathe'
|
||||
import { Command } from 'commander'
|
||||
import { execa } from 'execa'
|
||||
import template from 'lodash.template'
|
||||
import ora from 'ora'
|
||||
import prompts from 'prompts'
|
||||
import * as z from 'zod'
|
||||
import { z } from 'zod'
|
||||
import { addDependency, addDevDependency } from 'nypm'
|
||||
import { consola } from 'consola'
|
||||
import { colors } from 'consola/utils'
|
||||
import * as templates from '../utils/templates'
|
||||
import {
|
||||
getRegistryBaseColor,
|
||||
getRegistryBaseColors,
|
||||
getRegistryStyles,
|
||||
} from '../utils/registry'
|
||||
import { logger } from '../utils/logger'
|
||||
import { handleError } from '../utils/handle-error'
|
||||
import { getPackageManager } from '../utils/get-package-manager'
|
||||
import { transformByDetype } from '../utils/transformers/transform-sfc'
|
||||
import {
|
||||
type Config,
|
||||
|
|
@ -29,6 +28,7 @@ import {
|
|||
resolveConfigPaths,
|
||||
} from '../utils/get-config'
|
||||
import { transformCJSToESM } from '../utils/transformers/transform-cjs-to-esm'
|
||||
import { applyPrefixesCss } from '../utils/transformers/transform-tw-prefix'
|
||||
|
||||
const PROJECT_DEPENDENCIES = {
|
||||
base: [
|
||||
|
|
@ -64,7 +64,7 @@ export const init = new Command()
|
|||
|
||||
// Ensure target directory exists.
|
||||
if (!existsSync(cwd)) {
|
||||
logger.error(`The path ${cwd} does not exist. Please try again.`)
|
||||
consola.error(`The path ${cwd} does not exist. Please try again.`)
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
|
|
@ -74,11 +74,11 @@ export const init = new Command()
|
|||
|
||||
await runInit(cwd, config)
|
||||
|
||||
logger.info('')
|
||||
logger.info(
|
||||
`${chalk.green('Success!')} Project initialization completed.`,
|
||||
consola.log('')
|
||||
consola.info(
|
||||
`${colors.green('Success!')} Project initialization completed.`,
|
||||
)
|
||||
logger.info('')
|
||||
consola.log('')
|
||||
}
|
||||
catch (error) {
|
||||
handleError(error)
|
||||
|
|
@ -90,7 +90,7 @@ export async function promptForConfig(
|
|||
defaultConfig: Config | null = null,
|
||||
skip = false,
|
||||
) {
|
||||
const highlight = (text: string) => chalk.cyan(text)
|
||||
const highlight = (text: string) => colors.cyan(text)
|
||||
|
||||
const styles = await getRegistryStyles()
|
||||
const baseColors = await getRegistryBaseColors()
|
||||
|
|
@ -151,6 +151,14 @@ export async function promptForConfig(
|
|||
active: 'yes',
|
||||
inactive: 'no',
|
||||
},
|
||||
// {
|
||||
// type: 'text',
|
||||
// name: 'tailwindPrefix',
|
||||
// message: `Are you using a custom ${highlight(
|
||||
// 'tailwind prefix eg. tw-',
|
||||
// )}? (Leave blank if not)`,
|
||||
// initial: '',
|
||||
// },
|
||||
{
|
||||
type: 'text',
|
||||
name: 'tailwindConfig',
|
||||
|
|
@ -187,6 +195,7 @@ export async function promptForConfig(
|
|||
css: options.tailwindCss,
|
||||
baseColor: options.tailwindBaseColor,
|
||||
cssVariables: options.tailwindCssVariables,
|
||||
// prefix: options.tailwindPrefix,
|
||||
},
|
||||
aliases: {
|
||||
utils: options.utils,
|
||||
|
|
@ -207,7 +216,7 @@ export async function promptForConfig(
|
|||
}
|
||||
|
||||
// Write to file.
|
||||
logger.info('')
|
||||
consola.log('')
|
||||
const spinner = ora('Writing components.json...').start()
|
||||
const targetPath = path.resolve(cwd, 'components.json')
|
||||
await fs.writeFile(targetPath, JSON.stringify(config, null, 2), 'utf8')
|
||||
|
|
@ -247,8 +256,8 @@ export async function runInit(cwd: string, config: Config) {
|
|||
transformCJSToESM(
|
||||
config.resolvedPaths.tailwindConfig,
|
||||
config.tailwind.cssVariables
|
||||
? template(templates.TAILWIND_CONFIG_WITH_VARIABLES)({ extension, framework: config.framework })
|
||||
: template(templates.TAILWIND_CONFIG)({ extension, framework: config.framework }),
|
||||
? template(templates.TAILWIND_CONFIG_WITH_VARIABLES)({ extension, framework: config.framework, prefix: config.tailwind.prefix })
|
||||
: template(templates.TAILWIND_CONFIG)({ extension, framework: config.framework, prefix: config.tailwind.prefix }),
|
||||
),
|
||||
'utf8',
|
||||
)
|
||||
|
|
@ -259,7 +268,9 @@ export async function runInit(cwd: string, config: Config) {
|
|||
await fs.writeFile(
|
||||
config.resolvedPaths.tailwindCss,
|
||||
config.tailwind.cssVariables
|
||||
? baseColor.cssVarsTemplate
|
||||
? config.tailwind.prefix
|
||||
? applyPrefixesCss(baseColor.cssVarsTemplate, config.tailwind.prefix)
|
||||
: baseColor.cssVarsTemplate
|
||||
: baseColor.inlineColorsTemplate,
|
||||
'utf8',
|
||||
)
|
||||
|
|
@ -276,20 +287,29 @@ export async function runInit(cwd: string, config: Config) {
|
|||
|
||||
// Install dependencies.
|
||||
const dependenciesSpinner = ora('Installing dependencies...')?.start()
|
||||
const packageManager = await getPackageManager(cwd)
|
||||
|
||||
const deps = PROJECT_DEPENDENCIES.base.concat(
|
||||
config.framework === 'nuxt' ? PROJECT_DEPENDENCIES.nuxt : [],
|
||||
).concat(
|
||||
config.style === 'new-york' ? ['@radix-icons/vue'] : ['lucide-vue-next'],
|
||||
).filter(Boolean)
|
||||
|
||||
await execa(
|
||||
packageManager,
|
||||
[packageManager === 'npm' ? 'install' : 'add', ...deps],
|
||||
{
|
||||
cwd,
|
||||
},
|
||||
async function addNuxtDevDeps() {
|
||||
if (config.framework === 'nuxt') {
|
||||
await addDevDependency(PROJECT_DEPENDENCIES.nuxt, {
|
||||
cwd,
|
||||
silent: true,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
await Promise.allSettled(
|
||||
[
|
||||
addNuxtDevDeps(),
|
||||
addDependency(deps, {
|
||||
cwd,
|
||||
silent: true,
|
||||
}),
|
||||
],
|
||||
)
|
||||
|
||||
dependenciesSpinner?.succeed()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
import path from 'node:path'
|
||||
import { existsSync } from 'node:fs'
|
||||
import { cosmiconfig } from 'cosmiconfig'
|
||||
import path from 'pathe'
|
||||
import { loadConfig as c12LoadConfig } from 'c12'
|
||||
import type { ConfigLoaderResult } from 'tsconfig-paths'
|
||||
import { loadConfig } from 'tsconfig-paths'
|
||||
import * as z from 'zod'
|
||||
import { z } from 'zod'
|
||||
import { resolveImport } from '@/src/utils/resolve-import'
|
||||
|
||||
export const DEFAULT_STYLE = 'default'
|
||||
|
|
@ -19,12 +19,6 @@ export const TAILWIND_CSS_PATH = {
|
|||
astro: 'src/styles/globals.css',
|
||||
}
|
||||
|
||||
// TODO: Figure out if we want to support all cosmiconfig formats.
|
||||
// A simple components.json file would be nice.
|
||||
const explorer = cosmiconfig('components', {
|
||||
searchPlaces: ['components.json'],
|
||||
})
|
||||
|
||||
export const rawConfigSchema = z
|
||||
.object({
|
||||
$schema: z.string().optional(),
|
||||
|
|
@ -35,11 +29,13 @@ export const rawConfigSchema = z
|
|||
css: z.string(),
|
||||
baseColor: z.string(),
|
||||
cssVariables: z.boolean().default(true),
|
||||
prefix: z.string().optional(),
|
||||
}),
|
||||
framework: z.string().default('Vite'),
|
||||
aliases: z.object({
|
||||
components: z.string(),
|
||||
utils: z.string(),
|
||||
ui: z.string().default('').optional(),
|
||||
}),
|
||||
})
|
||||
.strict()
|
||||
|
|
@ -53,6 +49,7 @@ export const configSchema = rawConfigSchema
|
|||
tailwindCss: z.string(),
|
||||
utils: z.string(),
|
||||
components: z.string(),
|
||||
ui: z.string(),
|
||||
}),
|
||||
})
|
||||
|
||||
|
|
@ -103,15 +100,22 @@ export async function resolveConfigPaths(cwd: string, config: RawConfig) {
|
|||
tailwindCss: path.resolve(cwd, config.tailwind.css),
|
||||
utils: resolveImport(config.aliases.utils, tsConfig),
|
||||
components: resolveImport(config.aliases.components, tsConfig),
|
||||
ui: config.aliases.ui
|
||||
? resolveImport(config.aliases.ui, tsConfig)
|
||||
: resolveImport(config.aliases.components, tsConfig),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export async function getRawConfig(cwd: string): Promise<RawConfig | null> {
|
||||
try {
|
||||
const configResult = await explorer.search(cwd)
|
||||
const configResult = await c12LoadConfig({
|
||||
name: 'components',
|
||||
configFile: 'components',
|
||||
cwd,
|
||||
})
|
||||
|
||||
if (!configResult)
|
||||
if (!configResult.config || Object.keys(configResult.config).length === 0)
|
||||
return null
|
||||
|
||||
return rawConfigSchema.parse(configResult.config)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import path from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import path from 'pathe'
|
||||
import fs from 'fs-extra'
|
||||
import { type PackageJson } from 'type-fest'
|
||||
|
||||
|
|
|
|||
|
|
@ -1,16 +0,0 @@
|
|||
import { detect } from '@antfu/ni'
|
||||
|
||||
export async function getPackageManager(
|
||||
targetDir: string,
|
||||
): Promise<'yarn' | 'pnpm' | 'bun' | 'npm'> {
|
||||
const packageManager = await detect({ programmatic: true, cwd: targetDir })
|
||||
|
||||
if (packageManager === 'yarn@berry')
|
||||
return 'yarn'
|
||||
if (packageManager === 'pnpm@6')
|
||||
return 'pnpm'
|
||||
if (packageManager === 'bun')
|
||||
return 'bun'
|
||||
|
||||
return packageManager ?? 'npm'
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
import { existsSync } from 'node:fs'
|
||||
import path from 'node:path'
|
||||
import path from 'pathe'
|
||||
import fs from 'fs-extra'
|
||||
|
||||
export async function getProjectInfo() {
|
||||
|
|
|
|||
|
|
@ -1,16 +1,16 @@
|
|||
import { logger } from './logger'
|
||||
import { consola } from 'consola'
|
||||
|
||||
export function handleError(error: unknown) {
|
||||
if (typeof error === 'string') {
|
||||
logger.error(error)
|
||||
consola.error(error)
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
if (error instanceof Error) {
|
||||
logger.error(error.message)
|
||||
consola.error(error.message)
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
logger.error('Something went wrong. Please try again.')
|
||||
consola.error('Something went wrong. Please try again.')
|
||||
process.exit(1)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,19 +0,0 @@
|
|||
import chalk from 'chalk'
|
||||
|
||||
export const logger = {
|
||||
error(...args: unknown[]) {
|
||||
console.log(chalk.red(...args))
|
||||
},
|
||||
warn(...args: unknown[]) {
|
||||
console.log(chalk.yellow(...args))
|
||||
},
|
||||
info(...args: unknown[]) {
|
||||
console.log(chalk.cyan(...args))
|
||||
},
|
||||
success(...args: unknown[]) {
|
||||
console.log(chalk.green(...args))
|
||||
},
|
||||
break() {
|
||||
console.log('')
|
||||
},
|
||||
}
|
||||
|
|
@ -1,8 +1,9 @@
|
|||
import path from 'node:path'
|
||||
import process from 'node:process'
|
||||
import path from 'pathe'
|
||||
import { HttpsProxyAgent } from 'https-proxy-agent'
|
||||
import fetch from 'node-fetch'
|
||||
import { ofetch } from 'ofetch'
|
||||
import type * as z from 'zod'
|
||||
import consola from 'consola'
|
||||
import {
|
||||
registryBaseColorSchema,
|
||||
registryIndexSchema,
|
||||
|
|
@ -122,9 +123,12 @@ export function getItemTargetPath(
|
|||
override?: string,
|
||||
) {
|
||||
// Allow overrides for all items but ui.
|
||||
if (override && item.type !== 'components:ui')
|
||||
if (override)
|
||||
return override
|
||||
|
||||
if (item.type === 'components:ui' && config.aliases.ui)
|
||||
return config.resolvedPaths.ui
|
||||
|
||||
const [parent, type] = item.type.split(':')
|
||||
if (!(parent in config.resolvedPaths))
|
||||
return null
|
||||
|
|
@ -139,17 +143,18 @@ async function fetchRegistry(paths: string[]) {
|
|||
try {
|
||||
const results = await Promise.all(
|
||||
paths.map(async (path) => {
|
||||
const response = await fetch(`${baseUrl}/registry/${path}`, {
|
||||
const response = await ofetch(`${baseUrl}/registry/${path}`, {
|
||||
// @ts-expect-error agent type
|
||||
agent,
|
||||
})
|
||||
return await response.json()
|
||||
|
||||
return response
|
||||
}),
|
||||
)
|
||||
return results
|
||||
}
|
||||
catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(error)
|
||||
consola.error(error)
|
||||
throw new Error(`Failed to fetch registry from ${baseUrl}.`)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
import * as z from 'zod'
|
||||
import { z } from 'zod'
|
||||
|
||||
// TODO: Extract this to a shared package.
|
||||
export const registryItemSchema = z.object({
|
||||
name: z.string(),
|
||||
dependencies: z.array(z.string()).optional(),
|
||||
devDependencies: z.array(z.string()).optional(),
|
||||
registryDependencies: z.array(z.string()).optional(),
|
||||
files: z.array(z.string()),
|
||||
type: z.enum(['components:ui', 'components:component', 'components:example']),
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ module.exports = {
|
|||
'./app/**/*.{<%- extension %>,<%- extension %>x,vue}',
|
||||
'./src/**/*.{<%- extension %>,<%- extension %>x,vue}',
|
||||
],
|
||||
prefix: "<%- prefix %>",
|
||||
theme: {
|
||||
container: {
|
||||
center: true,
|
||||
|
|
@ -51,6 +52,7 @@ export const TAILWIND_CONFIG_WITH_VARIABLES = `const animate = require("tailwind
|
|||
module.exports = {
|
||||
darkMode: ["class"],
|
||||
safelist: ["dark"],
|
||||
prefix: "<%- prefix %>",
|
||||
<% if (framework === 'vite') { %>
|
||||
content: [
|
||||
'./pages/**/*.{<%- extension %>,<%- extension %>x,vue}',
|
||||
|
|
|
|||
|
|
@ -1,12 +1,13 @@
|
|||
import { promises as fs } from 'node:fs'
|
||||
import { tmpdir } from 'node:os'
|
||||
import path from 'node:path'
|
||||
import path from 'pathe'
|
||||
import { Project, ScriptKind, type SourceFile } from 'ts-morph'
|
||||
import type * as z from 'zod'
|
||||
import type { Config } from '@/src/utils/get-config'
|
||||
import type { registryBaseColorSchema } from '@/src/utils/registry/schema'
|
||||
import { transformCssVars } from '@/src/utils/transformers/transform-css-vars'
|
||||
import { transformImport } from '@/src/utils/transformers/transform-import'
|
||||
import { transformTwPrefixes } from '@/src/utils/transformers/transform-tw-prefix'
|
||||
import { transformSFC } from '@/src/utils/transformers/transform-sfc'
|
||||
|
||||
export interface TransformOpts {
|
||||
|
|
@ -25,6 +26,7 @@ export type Transformer<Output = SourceFile> = (
|
|||
const transformers: Transformer[] = [
|
||||
transformCssVars,
|
||||
transformImport,
|
||||
// transformTwPrefixes,
|
||||
]
|
||||
|
||||
const project = new Project({
|
||||
|
|
|
|||
|
|
@ -88,29 +88,28 @@ export function applyColorMapping(
|
|||
if (input.includes(' border '))
|
||||
input = input.replace(' border ', ' border border-border ')
|
||||
|
||||
// Build color mappings.
|
||||
const classNames = input.split(' ')
|
||||
const lightMode: string[] = []
|
||||
const darkMode: string[] = []
|
||||
const lightMode = new Set<string>()
|
||||
const darkMode = new Set<string>()
|
||||
for (const className of classNames) {
|
||||
const [variant, value, modifier] = splitClassName(className)
|
||||
const prefix = PREFIXES.find(prefix => value?.startsWith(prefix))
|
||||
if (!prefix) {
|
||||
if (!lightMode.includes(className))
|
||||
lightMode.push(className)
|
||||
if (!lightMode.has(className))
|
||||
lightMode.add(className)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
const needle = value?.replace(prefix, '')
|
||||
if (needle && needle in mapping.light) {
|
||||
lightMode.push(
|
||||
lightMode.add(
|
||||
[variant, `${prefix}${mapping.light[needle]}`]
|
||||
.filter(Boolean)
|
||||
.join(':') + (modifier ? `/${modifier}` : ''),
|
||||
)
|
||||
|
||||
darkMode.push(
|
||||
darkMode.add(
|
||||
['dark', variant, `${prefix}${mapping.dark[needle]}`]
|
||||
.filter(Boolean)
|
||||
.join(':') + (modifier ? `/${modifier}` : ''),
|
||||
|
|
@ -118,9 +117,9 @@ export function applyColorMapping(
|
|||
continue
|
||||
}
|
||||
|
||||
if (!lightMode.includes(className))
|
||||
lightMode.push(className)
|
||||
if (!lightMode.has(className))
|
||||
lightMode.add(className)
|
||||
}
|
||||
const combined = `${lightMode.join(' ').replace(/\'/g, '')} ${darkMode.join(' ').trim()}`.trim()
|
||||
return `${combined}`
|
||||
|
||||
return [...Array.from(lightMode), ...Array.from(darkMode)].join(' ').trim()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,12 +8,19 @@ export const transformImport: Transformer = async ({ sourceFile, config }) => {
|
|||
|
||||
// Replace @/lib/registry/[style] with the components alias.
|
||||
if (moduleSpecifier.startsWith('@/lib/registry/')) {
|
||||
importDeclaration.setModuleSpecifier(
|
||||
moduleSpecifier.replace(
|
||||
/^@\/lib\/registry\/[^/]+/,
|
||||
config.aliases.components,
|
||||
),
|
||||
)
|
||||
if (config.aliases.ui) {
|
||||
importDeclaration.setModuleSpecifier(
|
||||
moduleSpecifier.replace(/^@\/lib\/registry\/[^/]+\/ui/, config.aliases.ui),
|
||||
)
|
||||
}
|
||||
else {
|
||||
importDeclaration.setModuleSpecifier(
|
||||
moduleSpecifier.replace(
|
||||
/^@\/lib\/registry\/[^/]+/,
|
||||
config.aliases.components,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Replace `import { cn } from "@/lib/utils"`
|
||||
|
|
|
|||
80
packages/cli/src/utils/transformers/transform-tw-prefix.ts
Normal file
80
packages/cli/src/utils/transformers/transform-tw-prefix.ts
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
import { SyntaxKind } from 'ts-morph'
|
||||
import { MagicString, parse } from '@vue/compiler-sfc'
|
||||
import type { SFCTemplateBlock } from '@vue/compiler-sfc'
|
||||
import { splitClassName } from './transform-css-vars'
|
||||
import type { Transformer } from '@/src/utils/transformers'
|
||||
|
||||
export const transformTwPrefixes: Transformer = async ({
|
||||
sourceFile,
|
||||
config,
|
||||
}) => {
|
||||
const isVueFile = sourceFile.getFilePath().endsWith('vue')
|
||||
if (!config.tailwind?.prefix)
|
||||
return sourceFile
|
||||
|
||||
let template: SFCTemplateBlock | null = null
|
||||
|
||||
if (isVueFile) {
|
||||
const parsed = parse(sourceFile.getText())
|
||||
template = parsed.descriptor.template
|
||||
|
||||
if (!template)
|
||||
return sourceFile
|
||||
}
|
||||
|
||||
sourceFile.getDescendantsOfKind(SyntaxKind.StringLiteral).forEach((node) => {
|
||||
if (template && template.loc.start.offset >= node.getPos())
|
||||
return sourceFile
|
||||
|
||||
const attrName = sourceFile.getDescendantAtPos(node.getPos() - 2)?.getText()
|
||||
if (isVueFile && attrName !== 'class')
|
||||
return sourceFile
|
||||
|
||||
const value = node.getText()
|
||||
const hasClosingDoubleQuote = value.match(/"/g)?.length === 2
|
||||
if (value.search('\'') === -1 && hasClosingDoubleQuote) {
|
||||
const mapped = applyPrefix(value.replace(/"/g, ''), config.tailwind.prefix)
|
||||
node.replaceWithText(`"${mapped}"`)
|
||||
}
|
||||
else {
|
||||
const s = new MagicString(value)
|
||||
s.replace(/'(.*?)'/g, (substring) => {
|
||||
return `'${applyPrefix(substring.replace(/\'/g, ''), config.tailwind.prefix)}'`
|
||||
})
|
||||
node.replaceWithText(s.toString())
|
||||
}
|
||||
})
|
||||
|
||||
return sourceFile
|
||||
}
|
||||
|
||||
export function applyPrefix(input: string, prefix: string = '') {
|
||||
const classNames = input.split(' ')
|
||||
const prefixed: string[] = []
|
||||
for (const className of classNames) {
|
||||
const [variant, value, modifier] = splitClassName(className)
|
||||
if (variant) {
|
||||
modifier
|
||||
? prefixed.push(`${variant}:${prefix}${value}/${modifier}`)
|
||||
: prefixed.push(`${variant}:${prefix}${value}`)
|
||||
}
|
||||
else {
|
||||
modifier
|
||||
? prefixed.push(`${prefix}${value}/${modifier}`)
|
||||
: prefixed.push(`${prefix}${value}`)
|
||||
}
|
||||
}
|
||||
return prefixed.join(' ')
|
||||
}
|
||||
|
||||
export function applyPrefixesCss(css: string, prefix: string) {
|
||||
const lines = css.split('\n')
|
||||
for (const line of lines) {
|
||||
if (line.includes('@apply')) {
|
||||
const originalTWCls = line.replace('@apply', '').trim()
|
||||
const prefixedTwCls = applyPrefix(originalTWCls, prefix)
|
||||
css = css.replace(originalTWCls, prefixedTwCls)
|
||||
}
|
||||
}
|
||||
return css
|
||||
}
|
||||
|
|
@ -1,14 +1,13 @@
|
|||
import fs from 'node:fs'
|
||||
import path from 'node:path'
|
||||
import { execa } from 'execa'
|
||||
import path from 'pathe'
|
||||
import { addDependency, addDevDependency } from 'nypm'
|
||||
import { afterEach, expect, test, vi } from 'vitest'
|
||||
|
||||
import { runInit } from '../../src/commands/init'
|
||||
import { getConfig } from '../../src/utils/get-config'
|
||||
import * as getPackageManger from '../../src/utils/get-package-manager'
|
||||
import * as registry from '../../src/utils/registry'
|
||||
|
||||
vi.mock('execa')
|
||||
vi.mock('nypm')
|
||||
vi.mock('fs/promises', () => ({
|
||||
writeFile: vi.fn(),
|
||||
mkdir: vi.fn(),
|
||||
|
|
@ -16,7 +15,6 @@ vi.mock('fs/promises', () => ({
|
|||
vi.mock('ora')
|
||||
|
||||
test('init config-full', async () => {
|
||||
vi.spyOn(getPackageManger, 'getPackageManager').mockResolvedValue('pnpm')
|
||||
vi.spyOn(registry, 'getRegistryBaseColor').mockResolvedValue({
|
||||
inlineColors: {},
|
||||
cssVars: {},
|
||||
|
|
@ -67,10 +65,8 @@ test('init config-full', async () => {
|
|||
expect.stringContaining("import { type ClassValue, clsx } from 'clsx'"),
|
||||
'utf8',
|
||||
)
|
||||
expect(execa).toHaveBeenCalledWith(
|
||||
'pnpm',
|
||||
expect(addDependency).toHaveBeenCalledWith(
|
||||
[
|
||||
'add',
|
||||
'tailwindcss-animate',
|
||||
'class-variance-authority',
|
||||
'clsx',
|
||||
|
|
@ -80,6 +76,7 @@ test('init config-full', async () => {
|
|||
],
|
||||
{
|
||||
cwd: targetDir,
|
||||
silent: true,
|
||||
},
|
||||
)
|
||||
|
||||
|
|
@ -88,7 +85,6 @@ test('init config-full', async () => {
|
|||
})
|
||||
|
||||
test('init config-partial', async () => {
|
||||
vi.spyOn(getPackageManger, 'getPackageManager').mockResolvedValue('npm')
|
||||
vi.spyOn(registry, 'getRegistryBaseColor').mockResolvedValue({
|
||||
inlineColors: {},
|
||||
cssVars: {},
|
||||
|
|
@ -139,10 +135,8 @@ test('init config-partial', async () => {
|
|||
expect.stringContaining("import { type ClassValue, clsx } from 'clsx'"),
|
||||
'utf8',
|
||||
)
|
||||
expect(execa).toHaveBeenCalledWith(
|
||||
'npm',
|
||||
expect(addDependency).toHaveBeenCalledWith(
|
||||
[
|
||||
'install',
|
||||
'tailwindcss-animate',
|
||||
'class-variance-authority',
|
||||
'clsx',
|
||||
|
|
@ -152,6 +146,7 @@ test('init config-partial', async () => {
|
|||
],
|
||||
{
|
||||
cwd: targetDir,
|
||||
silent: true,
|
||||
},
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -4,10 +4,12 @@
|
|||
"config": "tailwind.config.ts",
|
||||
"css": "src/app/globals.css",
|
||||
"baseColor": "zinc",
|
||||
"cssVariables": true
|
||||
"cssVariables": true,
|
||||
"prefix": "tw-"
|
||||
},
|
||||
"aliases": {
|
||||
"utils": "~/lib/utils",
|
||||
"components": "~/components"
|
||||
"components": "~/components",
|
||||
"ui": "~/ui"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ export default {
|
|||
'./app/**/*.{<%- extension %>,<%- extension %>x,vue}',
|
||||
'./src/**/*.{<%- extension %>,<%- extension %>x,vue}',
|
||||
],
|
||||
prefix: \\"<%- prefix %>\\",
|
||||
theme: {
|
||||
container: {
|
||||
center: true,
|
||||
|
|
@ -48,6 +49,7 @@ exports[`handle tailwind config template correctly 2`] = `
|
|||
export default {
|
||||
darkMode: [\\"class\\"],
|
||||
safelist: [\\"dark\\"],
|
||||
prefix: \\"<%- prefix %>\\",
|
||||
<% if (framework === 'vite') { %>
|
||||
content: [
|
||||
'./pages/**/*.{<%- extension %>,<%- extension %>x,vue}',
|
||||
|
|
|
|||
|
|
@ -0,0 +1,127 @@
|
|||
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
||||
|
||||
exports[`transform tailwind prefix 1`] = `
|
||||
"const testVariants = cva(
|
||||
\\"tw-bg-background hover:tw-bg-muted tw-text-primary-foreground sm:focus:tw-text-accent-foreground\\",
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
default:
|
||||
\\"tw-bg-primary tw-text-primary-foreground hover:tw-bg-primary/90\\",
|
||||
},
|
||||
size: {
|
||||
default: \\"tw-h-10 tw-px-4 tw-py-2\\",
|
||||
},
|
||||
},
|
||||
}
|
||||
);
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`transform tailwind prefix 2`] = `
|
||||
"<template>
|
||||
<div class=\\"tw-bg-background hover:tw-bg-muted tw-text-primary-foreground sm:focus:tw-text-accent-foreground\\">
|
||||
foo
|
||||
</div>
|
||||
</template>
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`transform tailwind prefix 3`] = `
|
||||
"<template>
|
||||
<div class=\\"tw-bg-white hover:tw-bg-stone-100 tw-text-stone-50 sm:focus:tw-text-stone-900 dark:tw-bg-stone-950 dark:hover:tw-bg-stone-800 dark:tw-text-stone-900 dark:sm:focus:tw-text-stone-50\\">
|
||||
foo
|
||||
</div>
|
||||
</template>
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`transform tailwind prefix 4`] = `
|
||||
"<template>
|
||||
<div id=\\"testing\\" v-bind=\\"props\\" @click=\\"handleSomething\\" :data-test=\\"true\\" :class=\\"cn('tw-bg-white hover:tw-bg-stone-100 dark:tw-bg-stone-950 dark:hover:tw-bg-stone-800', true && 'tw-text-stone-50 sm:focus:tw-text-stone-900 dark:tw-text-stone-900 dark:sm:focus:tw-text-stone-50')\\">
|
||||
foo
|
||||
</div>
|
||||
</template>
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`transform tailwind prefix 5`] = `
|
||||
"@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
@layer base {
|
||||
:root {
|
||||
--background: 0 0% 100%;
|
||||
--foreground: 224 71.4% 4.1%;
|
||||
|
||||
--muted: 220 14.3% 95.9%;
|
||||
--muted-foreground: 220 8.9% 46.1%;
|
||||
|
||||
--popover: 0 0% 100%;
|
||||
--popover-foreground: 224 71.4% 4.1%;
|
||||
|
||||
--card: 0 0% 100%;
|
||||
--card-foreground: 224 71.4% 4.1%;
|
||||
|
||||
--border: 220 13% 91%;
|
||||
--input: 220 13% 91%;
|
||||
|
||||
--primary: 220.9 39.3% 11%;
|
||||
--primary-foreground: 210 20% 98%;
|
||||
|
||||
--secondary: 220 14.3% 95.9%;
|
||||
--secondary-foreground: 220.9 39.3% 11%;
|
||||
|
||||
--accent: 220 14.3% 95.9%;
|
||||
--accent-foreground: 220.9 39.3% 11%;
|
||||
|
||||
--destructive: 0 84.2% 60.2%;
|
||||
--destructive-foreground: 210 20% 98%;
|
||||
|
||||
--ring: 217.9 10.6% 64.9%;
|
||||
|
||||
--radius: 0.5rem;
|
||||
}
|
||||
|
||||
.dark {
|
||||
--background: 224 71.4% 4.1%;
|
||||
--foreground: 210 20% 98%;
|
||||
|
||||
--muted: 215 27.9% 16.9%;
|
||||
--muted-foreground: 217.9 10.6% 64.9%;
|
||||
|
||||
--popover: 224 71.4% 4.1%;
|
||||
--popover-foreground: 210 20% 98%;
|
||||
|
||||
--card: 224 71.4% 4.1%;
|
||||
--card-foreground: 210 20% 98%;
|
||||
|
||||
--border: 215 27.9% 16.9%;
|
||||
--input: 215 27.9% 16.9%;
|
||||
|
||||
--primary: 210 20% 98%;
|
||||
--primary-foreground: 220.9 39.3% 11%;
|
||||
|
||||
--secondary: 215 27.9% 16.9%;
|
||||
--secondary-foreground: 210 20% 98%;
|
||||
|
||||
--accent: 215 27.9% 16.9%;
|
||||
--accent-foreground: 210 20% 98%;
|
||||
|
||||
--destructive: 0 62.8% 30.6%;
|
||||
--destructive-foreground: 0 85.7% 97.3%;
|
||||
|
||||
--ring: 215 27.9% 16.9%;
|
||||
}
|
||||
}
|
||||
|
||||
@layer base {
|
||||
* {
|
||||
@apply tw-border-border;
|
||||
}
|
||||
body {
|
||||
@apply tw-bg-background tw-text-foreground;
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
import { describe, expect, test } from 'vitest'
|
||||
import { describe, expect, it } from 'vitest'
|
||||
|
||||
import {
|
||||
applyColorMapping,
|
||||
|
|
@ -7,7 +7,7 @@ import {
|
|||
import baseColor from '../fixtures/colors/slate.json'
|
||||
|
||||
describe('split class', () => {
|
||||
test.each([
|
||||
it.each([
|
||||
{
|
||||
input: 'bg-popover',
|
||||
output: [null, 'bg-popover', null],
|
||||
|
|
@ -50,7 +50,7 @@ describe('split class', () => {
|
|||
})
|
||||
|
||||
describe('apply color mapping', async () => {
|
||||
test.each([
|
||||
it.each([
|
||||
{
|
||||
input: 'bg-background text-foreground',
|
||||
output: 'bg-white text-slate-950 dark:bg-slate-950 dark:text-slate-50',
|
||||
|
|
@ -64,7 +64,7 @@ describe('apply color mapping', async () => {
|
|||
input:
|
||||
'text-destructive border-destructive/50 dark:border-destructive [&>svg]:text-destructive text-destructive',
|
||||
output:
|
||||
'text-red-500 border-red-500/50 dark:border-red-500 [&>svg]:text-red-500 text-red-500 dark:text-red-900 dark:border-red-900/50 dark:dark:border-red-900 dark:[&>svg]:text-red-900 dark:text-red-900',
|
||||
'text-red-500 border-red-500/50 dark:border-red-500 [&>svg]:text-red-500 dark:text-red-900 dark:border-red-900/50 dark:dark:border-red-900 dark:[&>svg]:text-red-900',
|
||||
},
|
||||
{
|
||||
input:
|
||||
|
|
|
|||
42
packages/cli/test/utils/apply-prefix.test.ts
Normal file
42
packages/cli/test/utils/apply-prefix.test.ts
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
import { describe, expect, it } from 'vitest'
|
||||
|
||||
import { applyPrefix } from '../../src/utils/transformers/transform-tw-prefix'
|
||||
|
||||
describe('apply tailwind prefix', () => {
|
||||
it.each([
|
||||
{
|
||||
input: 'bg-slate-800 text-gray-500',
|
||||
output: 'tw-bg-slate-800 tw-text-gray-500',
|
||||
},
|
||||
{
|
||||
input: 'hover:dark:bg-background dark:text-foreground',
|
||||
output: 'hover:dark:tw-bg-background dark:tw-text-foreground',
|
||||
},
|
||||
{
|
||||
input:
|
||||
'rounded-lg border border-slate-200 bg-white text-slate-950 shadow-sm dark:border-slate-800 dark:bg-slate-950 dark:text-slate-50',
|
||||
output:
|
||||
'tw-rounded-lg tw-border tw-border-slate-200 tw-bg-white tw-text-slate-950 tw-shadow-sm dark:tw-border-slate-800 dark:tw-bg-slate-950 dark:tw-text-slate-50',
|
||||
},
|
||||
{
|
||||
input:
|
||||
'text-red-500 border-red-500/50 dark:border-red-500 [&>svg]:text-red-500 text-red-500 dark:text-red-900 dark:border-red-900/50 dark:dark:border-red-900 dark:[&>svg]:text-red-900 dark:text-red-900',
|
||||
output:
|
||||
'tw-text-red-500 tw-border-red-500/50 dark:tw-border-red-500 [&>svg]:tw-text-red-500 tw-text-red-500 dark:tw-text-red-900 dark:tw-border-red-900/50 dark:dark:tw-border-red-900 dark:[&>svg]:tw-text-red-900 dark:tw-text-red-900',
|
||||
},
|
||||
{
|
||||
input:
|
||||
'flex h-full w-full items-center justify-center rounded-full bg-muted',
|
||||
output:
|
||||
'tw-flex tw-h-full tw-w-full tw-items-center tw-justify-center tw-rounded-full tw-bg-muted',
|
||||
},
|
||||
{
|
||||
input:
|
||||
'absolute right-4 top-4 bg-primary rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-secondary',
|
||||
output:
|
||||
'tw-absolute tw-right-4 tw-top-4 tw-bg-primary tw-rounded-sm tw-opacity-70 tw-ring-offset-background tw-transition-opacity hover:tw-opacity-100 focus:tw-outline-none focus:tw-ring-2 focus:tw-ring-ring focus:tw-ring-offset-2 disabled:tw-pointer-events-none data-[state=open]:tw-bg-secondary',
|
||||
},
|
||||
])(`applyTwPrefix($input) -> $output`, ({ input, output }) => {
|
||||
expect(applyPrefix(input, 'tw-')).toBe(output)
|
||||
})
|
||||
})
|
||||
|
|
@ -1,9 +1,9 @@
|
|||
import path from 'node:path'
|
||||
import { expect, test } from 'vitest'
|
||||
import path from 'pathe'
|
||||
import { expect, it } from 'vitest'
|
||||
|
||||
import { getConfig, getRawConfig } from '../../src/utils/get-config'
|
||||
|
||||
test('get raw config', async () => {
|
||||
it('get raw config', async () => {
|
||||
expect(
|
||||
await getRawConfig(path.resolve(__dirname, '../fixtures/config-none')),
|
||||
).toEqual(null)
|
||||
|
|
@ -31,7 +31,7 @@ test('get raw config', async () => {
|
|||
).rejects.toThrowError()
|
||||
})
|
||||
|
||||
test('get config', async () => {
|
||||
it('get config', async () => {
|
||||
expect(
|
||||
await getConfig(path.resolve(__dirname, '../fixtures/config-none')),
|
||||
).toEqual(null)
|
||||
|
|
@ -71,6 +71,11 @@ test('get config', async () => {
|
|||
'../fixtures/config-partial',
|
||||
'./components',
|
||||
),
|
||||
ui: path.resolve(
|
||||
__dirname,
|
||||
'../fixtures/config-partial',
|
||||
'./components',
|
||||
),
|
||||
utils: path.resolve(
|
||||
__dirname,
|
||||
'../fixtures/config-partial',
|
||||
|
|
@ -89,9 +94,11 @@ test('get config', async () => {
|
|||
baseColor: 'zinc',
|
||||
css: 'src/app/globals.css',
|
||||
cssVariables: true,
|
||||
prefix: 'tw-',
|
||||
},
|
||||
aliases: {
|
||||
components: '~/components',
|
||||
ui: '~/ui',
|
||||
utils: '~/lib/utils',
|
||||
},
|
||||
framework: 'Vite',
|
||||
|
|
@ -111,6 +118,11 @@ test('get config', async () => {
|
|||
'../fixtures/config-full',
|
||||
'./src/components',
|
||||
),
|
||||
ui: path.resolve(
|
||||
__dirname,
|
||||
'../fixtures/config-full',
|
||||
'./src/ui',
|
||||
),
|
||||
utils: path.resolve(
|
||||
__dirname,
|
||||
'../fixtures/config-full',
|
||||
|
|
@ -152,6 +164,11 @@ test('get config', async () => {
|
|||
'../fixtures/config-js',
|
||||
'./components',
|
||||
),
|
||||
ui: path.resolve(
|
||||
__dirname,
|
||||
'../fixtures/config-js',
|
||||
'./components',
|
||||
),
|
||||
utils: path.resolve(__dirname, '../fixtures/config-js', './lib/utils'),
|
||||
},
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,26 +0,0 @@
|
|||
import path from 'node:path'
|
||||
import { expect, test } from 'vitest'
|
||||
|
||||
import { getPackageManager } from '../../src/utils/get-package-manager'
|
||||
|
||||
test('get package manager', async () => {
|
||||
expect(
|
||||
await getPackageManager(path.resolve(__dirname, '../fixtures/project-yarn')),
|
||||
).toBe('yarn')
|
||||
|
||||
expect(
|
||||
await getPackageManager(path.resolve(__dirname, '../fixtures/project-npm')),
|
||||
).toBe('npm')
|
||||
|
||||
expect(
|
||||
await getPackageManager(path.resolve(__dirname, '../fixtures/project-pnpm')),
|
||||
).toBe('pnpm')
|
||||
|
||||
expect(
|
||||
await getPackageManager(path.resolve(__dirname, '../fixtures/project-bun')),
|
||||
).toBe('bun')
|
||||
|
||||
expect(
|
||||
await getPackageManager(path.resolve(__dirname, '../fixtures/next')),
|
||||
).toBe('pnpm')
|
||||
})
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
import path from 'node:path'
|
||||
import path from 'pathe'
|
||||
import { type ConfigLoaderSuccessResult, loadConfig } from 'tsconfig-paths'
|
||||
import { expect, test } from 'vitest'
|
||||
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
import { expect, test } from 'vitest'
|
||||
import { expect, it } from 'vitest'
|
||||
|
||||
import { transform } from '../../src/utils/transformers'
|
||||
import stone from '../fixtures/colors/stone.json'
|
||||
|
||||
test('transform css vars', async () => {
|
||||
it('transform css vars', async () => {
|
||||
expect(
|
||||
await transform({
|
||||
filename: 'app.vue',
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { resolve } from 'node:path'
|
||||
import { resolve } from 'pathe'
|
||||
import { describe, expect, test } from 'vitest'
|
||||
import { transform } from '../../src/utils/transformers'
|
||||
|
||||
|
|
|
|||
115
packages/cli/test/utils/transform-tw-prefix.test.ts
Normal file
115
packages/cli/test/utils/transform-tw-prefix.test.ts
Normal file
|
|
@ -0,0 +1,115 @@
|
|||
import { expect, it } from 'vitest'
|
||||
|
||||
import { transform } from '../../src/utils/transformers'
|
||||
import { applyPrefixesCss } from '../../src/utils/transformers/transform-tw-prefix'
|
||||
import stone from '../fixtures/colors/stone.json'
|
||||
|
||||
it('transform tailwind prefix', async () => {
|
||||
// expect(
|
||||
// await transform({
|
||||
// filename: 'test.ts',
|
||||
// raw: `const testVariants = cva(
|
||||
// 'bg-background hover:bg-muted text-primary-foreground sm:focus:text-accent-foreground',
|
||||
// {
|
||||
// variants: {
|
||||
// variant: {
|
||||
// default: 'bg-primary text-primary-foreground hover:bg-primary/90',
|
||||
// },
|
||||
// size: {
|
||||
// default: 'h-10 px-4 py-2',
|
||||
// },
|
||||
// },
|
||||
// },
|
||||
// )`,
|
||||
// config: {
|
||||
// tailwind: {
|
||||
// baseColor: 'stone',
|
||||
// prefix: 'tw-',
|
||||
// },
|
||||
// aliases: {
|
||||
// components: '@/components',
|
||||
// utils: '@/lib/utils',
|
||||
// },
|
||||
// },
|
||||
// baseColor: 'stone',
|
||||
// }),
|
||||
// ).toMatchSnapshot()
|
||||
|
||||
// expect(
|
||||
// await transform({
|
||||
// filename: 'app.vue',
|
||||
// raw: `<template>
|
||||
// <div class="bg-background hover:bg-muted text-primary-foreground sm:focus:text-accent-foreground">
|
||||
// foo
|
||||
// </div>
|
||||
// </template>
|
||||
// `,
|
||||
// config: {
|
||||
// tailwind: {
|
||||
// baseColor: 'stone',
|
||||
// prefix: 'tw-',
|
||||
// },
|
||||
// aliases: {
|
||||
// components: '@/components',
|
||||
// utils: '@/lib/utils',
|
||||
// },
|
||||
// },
|
||||
// baseColor: 'stone',
|
||||
// }),
|
||||
// ).toMatchSnapshot()
|
||||
|
||||
// expect(
|
||||
// await transform({
|
||||
// filename: 'app.vue',
|
||||
// raw: `<template>
|
||||
// <div class="bg-background hover:bg-muted text-primary-foreground sm:focus:text-accent-foreground">
|
||||
// foo
|
||||
// </div>
|
||||
// </template>
|
||||
// `,
|
||||
// config: {
|
||||
// tailwind: {
|
||||
// baseColor: 'stone',
|
||||
// cssVariables: false,
|
||||
// prefix: 'tw-',
|
||||
// },
|
||||
// aliases: {
|
||||
// components: '@/components',
|
||||
// utils: '@/lib/utils',
|
||||
// },
|
||||
// },
|
||||
// baseColor: stone,
|
||||
// }),
|
||||
// ).toMatchSnapshot()
|
||||
|
||||
// expect(
|
||||
// await transform({
|
||||
// filename: 'app.vue',
|
||||
// raw: `<template>
|
||||
// <div id="testing" v-bind="props" @click="handleSomething" :data-test="true" :class="cn('bg-background hover:bg-muted', true && 'text-primary-foreground sm:focus:text-accent-foreground')">
|
||||
// foo
|
||||
// </div>
|
||||
// </template>
|
||||
// `,
|
||||
// config: {
|
||||
// tailwind: {
|
||||
// baseColor: 'stone',
|
||||
// cssVariables: false,
|
||||
// prefix: 'tw-',
|
||||
// },
|
||||
// aliases: {
|
||||
// components: '@/components',
|
||||
// utils: '@/lib/utils',
|
||||
// },
|
||||
// },
|
||||
// baseColor: stone,
|
||||
// }),
|
||||
// ).toMatchSnapshot()
|
||||
|
||||
// expect(
|
||||
// applyPrefixesCss(
|
||||
// '@tailwind base;\n@tailwind components;\n@tailwind utilities;\n \n@layer base {\n :root {\n --background: 0 0% 100%;\n --foreground: 224 71.4% 4.1%;\n \n --muted: 220 14.3% 95.9%;\n --muted-foreground: 220 8.9% 46.1%;\n \n --popover: 0 0% 100%;\n --popover-foreground: 224 71.4% 4.1%;\n \n --card: 0 0% 100%;\n --card-foreground: 224 71.4% 4.1%;\n \n --border: 220 13% 91%;\n --input: 220 13% 91%;\n \n --primary: 220.9 39.3% 11%;\n --primary-foreground: 210 20% 98%;\n \n --secondary: 220 14.3% 95.9%;\n --secondary-foreground: 220.9 39.3% 11%;\n \n --accent: 220 14.3% 95.9%;\n --accent-foreground: 220.9 39.3% 11%;\n \n --destructive: 0 84.2% 60.2%;\n --destructive-foreground: 210 20% 98%;\n \n --ring: 217.9 10.6% 64.9%;\n \n --radius: 0.5rem;\n }\n \n .dark {\n --background: 224 71.4% 4.1%;\n --foreground: 210 20% 98%;\n \n --muted: 215 27.9% 16.9%;\n --muted-foreground: 217.9 10.6% 64.9%;\n \n --popover: 224 71.4% 4.1%;\n --popover-foreground: 210 20% 98%;\n \n --card: 224 71.4% 4.1%;\n --card-foreground: 210 20% 98%;\n \n --border: 215 27.9% 16.9%;\n --input: 215 27.9% 16.9%;\n \n --primary: 210 20% 98%;\n --primary-foreground: 220.9 39.3% 11%;\n \n --secondary: 215 27.9% 16.9%;\n --secondary-foreground: 210 20% 98%;\n \n --accent: 215 27.9% 16.9%;\n --accent-foreground: 210 20% 98%;\n \n --destructive: 0 62.8% 30.6%;\n --destructive-foreground: 0 85.7% 97.3%;\n \n --ring: 215 27.9% 16.9%;\n }\n}\n \n@layer base {\n * {\n @apply border-border;\n }\n body {\n @apply bg-background text-foreground;\n }\n}',
|
||||
// 'tw-',
|
||||
// ),
|
||||
// ).toMatchSnapshot()
|
||||
})
|
||||
1133
pnpm-lock.yaml
1133
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user