feat(cli): add support for config file (#245)

* feat(cli): add config files and examples in docs

* docs(www): remove cli advanced options

---------

Co-authored-by: shadcn <m@shadcn.com>
This commit is contained in:
Prince Hernandez
2023-05-28 03:21:11 -03:00
committed by GitHub
parent 22f23b7db3
commit dffbe89f7d
5 changed files with 87 additions and 15 deletions

View File

@@ -45,6 +45,7 @@
"dependencies": {
"chalk": "5.2.0",
"commander": "^10.0.0",
"cosmiconfig": "^8.1.3",
"execa": "^7.0.0",
"fs-extra": "^11.1.0",
"https-proxy-agent": "^6.2.0",

View File

@@ -7,6 +7,7 @@ import ora from "ora"
import prompts from "prompts"
import { Component, getAvailableComponents } from "./utils/get-components"
import { Config, getCliConfig } from "./utils/get-config"
import { getPackageInfo } from "./utils/get-package-info"
import { getPackageManager } from "./utils/get-package-manager"
import { getProjectInfo } from "./utils/get-project-info"
@@ -27,6 +28,8 @@ const PROJECT_DEPENDENCIES = [
async function main() {
const packageInfo = await getPackageInfo()
const projectInfo = await getProjectInfo()
const cliConfig = await getCliConfig()
const packageManager = getPackageManager()
const program = new Command()
@@ -144,11 +147,7 @@ async function main() {
selectedComponents = await promptForComponents(availableComponents)
}
const dir = await promptForDestinationDir(
projectInfo?.srcComponentsUiDir
? "./src/components/ui"
: "./components/ui"
)
const dir = await promptForDestinationDir(cliConfig)
if (!selectedComponents?.length) {
logger.warn("No components selected. Nothing to install.")
@@ -171,10 +170,16 @@ async function main() {
// Write the files.
for (const file of component.files) {
// Replace alias with the project's alias.
if (projectInfo?.alias) {
file.content = file.content.replace(/@\//g, projectInfo.alias)
}
// because these are the predefined routes for the utils and components we can
// use them as a replacer for the defined routes on the installed file.
file.content = file.content.replace(
"@/lib/utils",
cliConfig.utilsLocation
)
file.content = file.content.replace(
"@/components/ui/",
cliConfig.componentDirAlias
)
const filePath = path.resolve(dir, file.name)
await fs.writeFile(filePath, file.content)
@@ -210,13 +215,17 @@ async function promptForComponents(components: Component[]) {
return selectedComponents
}
async function promptForDestinationDir(installDir = "./components/ui") {
async function promptForDestinationDir(cliConfig: Config) {
if (!cliConfig.askForDir) {
return cliConfig.componentsDirInstallation
}
const { dir } = await prompts([
{
type: "text",
name: "dir",
message: "Where would you like to install the component(s)?",
initial: installDir,
initial: cliConfig.componentsDirInstallation,
},
])

View File

@@ -0,0 +1,63 @@
import { cosmiconfig } from "cosmiconfig"
import * as z from "zod"
export const COMPONENTS_DIR = "./components/ui/"
export const UTILS_LOCATION = "@/lib/utils"
export const COMPONENT_ALIAS = "@/components/ui/"
/**
* this is the name of the key we are looking for, the following are the intended values to look for:
* - shadcn-ui property in package.json
* - .shadcn-uirc file in JSON or YAML format
* - .shadcn-uirc.json, .shadcn-uirc.yaml, .shadcn-uirc.yml, .shadcn-uirc.js, or .shadcn-uirc.cjs file
* - shadcn-uirc, shadcn-uirc.json, shadcn-uirc.yaml, shadcn-uirc.yml, shadcn-uirc.js or shadcn-uirc.cjs file inside a .config subdirectory
* - shadcn-ui.config.js or shadcn-ui.config.cjs CommonJS module exporting an object
*/
const explorer = cosmiconfig("shadcn-ui")
const configSchema = z.object({
componentsDirInstallation: z.string(),
askForDir: z.boolean(),
utilsLocation: z.string(),
componentDirAlias: z.string(),
})
export type Config = z.infer<typeof configSchema>
export async function getCliConfig(): Promise<Config> {
const defaultConfig: Config = {
componentsDirInstallation: COMPONENTS_DIR,
askForDir: true,
utilsLocation: UTILS_LOCATION,
componentDirAlias: COMPONENT_ALIAS,
}
const userDefinedConfig = await getConfigFromEverywhere()
return {
...defaultConfig,
...userDefinedConfig,
askForDir: !userDefinedConfig.componentsDirInstallation,
}
}
export async function getConfigFromEverywhere() {
try {
const configResult = await explorer.search()
if (!configResult) {
// we should always return an object so we can then merge with
// the base config
return {}
}
const { config } = configResult
const parsedConfig = configSchema.partial().parse(config)
return parsedConfig
} catch (e) {
// lets just show the error to the user to aware about the issue, but lets not handle it.
console.log(e)
return {}
}
}

View File

@@ -5,7 +5,6 @@ import fs from "fs-extra"
export async function getProjectInfo() {
const info = {
tsconfig: null,
alias: null,
srcDir: false,
appDir: false,
srcComponentsUiDir: false,
@@ -14,12 +13,9 @@ export async function getProjectInfo() {
try {
const tsconfig = await getTsConfig()
const paths = tsconfig?.compilerOptions?.paths
const alias = paths ? Object.keys(paths)[0].replace("*", "") : null
return {
tsconfig,
alias,
srcDir: existsSync(path.resolve("./src")),
appDir:
existsSync(path.resolve("./app")) ||

3
pnpm-lock.yaml generated
View File

@@ -317,6 +317,9 @@ importers:
commander:
specifier: ^10.0.0
version: 10.0.0
cosmiconfig:
specifier: ^8.1.3
version: 8.1.3
execa:
specifier: ^7.0.0
version: 7.0.0