243 lines
5.7 KiB
TypeScript
243 lines
5.7 KiB
TypeScript
import type { Framework } from '@/src/utils/frameworks'
|
|
import type {
|
|
Config,
|
|
RawConfig,
|
|
} from '@/src/utils/get-config'
|
|
import { FRAMEWORKS } from '@/src/utils/frameworks'
|
|
import {
|
|
getConfig,
|
|
resolveConfigPaths,
|
|
} from '@/src/utils/get-config'
|
|
import { getPackageInfo } from '@/src/utils/get-package-info'
|
|
import fg from 'fast-glob'
|
|
import fs from 'fs-extra'
|
|
import path from 'pathe'
|
|
import { loadConfig } from 'tsconfig-paths'
|
|
import { z } from 'zod'
|
|
|
|
export interface ProjectInfo {
|
|
framework: Framework
|
|
typescript: boolean
|
|
tailwindConfigFile: string | null
|
|
tailwindCssFile: string | null
|
|
aliasPrefix: string | null
|
|
}
|
|
|
|
const PROJECT_SHARED_IGNORE = [
|
|
'**/node_modules/**',
|
|
'.nuxt',
|
|
'public',
|
|
'dist',
|
|
'build',
|
|
]
|
|
|
|
const TS_CONFIG_SCHEMA = z.object({
|
|
compilerOptions: z.object({
|
|
paths: z.record(z.string().or(z.array(z.string()))),
|
|
}),
|
|
})
|
|
|
|
export async function getProjectInfo(cwd: string): Promise<ProjectInfo | null> {
|
|
const [
|
|
configFiles,
|
|
typescript,
|
|
tailwindConfigFile,
|
|
tailwindCssFile,
|
|
aliasPrefix,
|
|
packageJson,
|
|
] = await Promise.all([
|
|
fg.glob('**/{nuxt,vite,astro}.config.*|composer.json', {
|
|
cwd,
|
|
deep: 3,
|
|
ignore: PROJECT_SHARED_IGNORE,
|
|
}),
|
|
isTypeScriptProject(cwd),
|
|
getTailwindConfigFile(cwd),
|
|
getTailwindCssFile(cwd),
|
|
getTsConfigAliasPrefix(cwd),
|
|
getPackageInfo(cwd, false),
|
|
])
|
|
|
|
const type: ProjectInfo = {
|
|
framework: FRAMEWORKS.vite, // TODO: Maybe add a manual installation
|
|
typescript,
|
|
tailwindConfigFile,
|
|
tailwindCssFile,
|
|
aliasPrefix,
|
|
}
|
|
|
|
// Nuxt.
|
|
if (configFiles.find(file => file.startsWith('nuxt.config.'))?.length) {
|
|
type.framework = FRAMEWORKS.nuxt
|
|
return type
|
|
}
|
|
|
|
// Astro.
|
|
if (configFiles.find(file => file.startsWith('astro.config.'))?.length) {
|
|
type.framework = FRAMEWORKS.astro
|
|
return type
|
|
}
|
|
|
|
// Laravel.
|
|
if (configFiles.find(file => file.startsWith('composer.json'))?.length) {
|
|
type.framework = FRAMEWORKS.laravel
|
|
return type
|
|
}
|
|
|
|
// Vite.
|
|
// We'll assume that it got caught by the Remix check above.
|
|
if (configFiles.find(file => file.startsWith('vite.config.'))?.length) {
|
|
type.framework = FRAMEWORKS.vite
|
|
return type
|
|
}
|
|
|
|
return type
|
|
}
|
|
|
|
export async function getTailwindCssFile(cwd: string) {
|
|
const files = await fg.glob(['**/*.css', '**/*.scss'], {
|
|
cwd,
|
|
deep: 5,
|
|
ignore: PROJECT_SHARED_IGNORE,
|
|
})
|
|
|
|
if (!files.length) {
|
|
return null
|
|
}
|
|
|
|
for (const file of files) {
|
|
const contents = await fs.readFile(path.resolve(cwd, file), 'utf8')
|
|
// Assume that if the file contains `@tailwind base` it's the main css file.
|
|
if (contents.includes('@tailwind base')) {
|
|
return file
|
|
}
|
|
}
|
|
|
|
return null
|
|
}
|
|
|
|
export async function getTailwindConfigFile(cwd: string) {
|
|
const files = await fg.glob('tailwind.config.*', {
|
|
cwd,
|
|
deep: 3,
|
|
ignore: PROJECT_SHARED_IGNORE,
|
|
})
|
|
|
|
if (!files.length) {
|
|
return null
|
|
}
|
|
|
|
return files[0]
|
|
}
|
|
|
|
export async function getTsConfigAliasPrefix(cwd: string) {
|
|
const tsConfig = await loadConfig(cwd)
|
|
if (
|
|
tsConfig?.resultType === 'failed'
|
|
|| !Object.entries(tsConfig?.paths).length
|
|
) {
|
|
return null
|
|
}
|
|
|
|
// This assume that the first alias is the prefix.
|
|
for (const [alias, paths] of Object.entries(tsConfig.paths)) {
|
|
if (
|
|
paths.includes('./*')
|
|
|| paths.includes('./src/*')
|
|
|| paths.includes('./app/*')
|
|
|| paths.includes('./resources/js/*') // Laravel.
|
|
) {
|
|
const cleanAlias = alias.replace(/\/\*$/, '') ?? null
|
|
// handle Nuxt
|
|
return cleanAlias === '#build' ? '@' : cleanAlias
|
|
}
|
|
}
|
|
|
|
// Use the first alias as the prefix.
|
|
return Object.keys(tsConfig?.paths)?.[0].replace(/\/\*$/, '') ?? null
|
|
}
|
|
|
|
export async function isTypeScriptProject(cwd: string) {
|
|
const files = await fg.glob('tsconfig.*', {
|
|
cwd,
|
|
deep: 1,
|
|
ignore: PROJECT_SHARED_IGNORE,
|
|
})
|
|
|
|
return files.length > 0
|
|
}
|
|
|
|
export async function getTsConfig(cwd: string) {
|
|
for (const fallback of [
|
|
'tsconfig.json',
|
|
'tsconfig.web.json',
|
|
'tsconfig.app.json',
|
|
]) {
|
|
const filePath = path.resolve(cwd, fallback)
|
|
if (!(await fs.pathExists(filePath))) {
|
|
continue
|
|
}
|
|
|
|
// We can't use fs.readJSON because it doesn't support comments.
|
|
const contents = await fs.readFile(filePath, 'utf8')
|
|
const cleanedContents = contents.replace(/\/\*\s*\*\//g, '')
|
|
const result = TS_CONFIG_SCHEMA.safeParse(JSON.parse(cleanedContents))
|
|
|
|
if (result.error) {
|
|
continue
|
|
}
|
|
|
|
return result.data
|
|
}
|
|
|
|
return null
|
|
}
|
|
|
|
export async function getProjectConfig(
|
|
cwd: string,
|
|
defaultProjectInfo: ProjectInfo | null = null,
|
|
): Promise<Config | null> {
|
|
// Check for existing component config.
|
|
const [existingConfig, projectInfo] = await Promise.all([
|
|
getConfig(cwd),
|
|
!defaultProjectInfo
|
|
? getProjectInfo(cwd)
|
|
: Promise.resolve(defaultProjectInfo),
|
|
])
|
|
|
|
if (existingConfig) {
|
|
return existingConfig
|
|
}
|
|
|
|
if (
|
|
!projectInfo
|
|
|| !projectInfo.tailwindConfigFile
|
|
|| !projectInfo.tailwindCssFile
|
|
) {
|
|
return null
|
|
}
|
|
|
|
const config: RawConfig = {
|
|
$schema: 'https://shadcn-vue.com/schema.json',
|
|
typescript: true,
|
|
style: 'new-york',
|
|
tailwind: {
|
|
config: projectInfo.tailwindConfigFile,
|
|
baseColor: 'zinc',
|
|
css: projectInfo.tailwindCssFile,
|
|
cssVariables: true,
|
|
prefix: '',
|
|
},
|
|
iconLibrary: 'lucide',
|
|
aliases: {
|
|
components: `${projectInfo.aliasPrefix}/components`,
|
|
ui: `${projectInfo.aliasPrefix}/components/ui`,
|
|
// hooks: `${projectInfo.aliasPrefix}/hooks`,
|
|
lib: `${projectInfo.aliasPrefix}/lib`,
|
|
utils: `${projectInfo.aliasPrefix}/lib/utils`,
|
|
},
|
|
}
|
|
|
|
return await resolveConfigPaths(cwd, config)
|
|
}
|