refactor(cli): cleanup

This commit is contained in:
shadcn
2024-08-25 21:06:18 +04:00
parent 3a6eca12c0
commit 2f28d1c816
13 changed files with 144 additions and 97 deletions

View File

@@ -2,6 +2,7 @@ import { existsSync, promises as fs } from "fs"
import path from "path"
import { Config, getConfig } from "@/src/utils/get-config"
import { handleError } from "@/src/utils/handle-error"
import { highlighter } from "@/src/utils/highlighter"
import { logger } from "@/src/utils/logger"
import {
fetchTree,
@@ -13,7 +14,6 @@ import { registryIndexSchema } from "@/src/utils/registry/schema"
import { transform } from "@/src/utils/transformers"
import { Command } from "commander"
import { diffLines, type Change } from "diff"
import { green, red } from "kleur/colors"
import { z } from "zod"
const updateOptionsSchema = z.object({
@@ -50,7 +50,7 @@ export const diff = new Command()
const config = await getConfig(cwd)
if (!config) {
logger.warn(
`Configuration is missing. Please run ${green(
`Configuration is missing. Please run ${highlighter.success(
`init`
)} to create a components.json file.`
)
@@ -107,7 +107,9 @@ export const diff = new Command()
}
}
logger.break()
logger.info(`Run ${green(`diff <component>`)} to see the changes.`)
logger.info(
`Run ${highlighter.success(`diff <component>`)} to see the changes.`
)
process.exit(0)
}
@@ -118,7 +120,9 @@ export const diff = new Command()
if (!component) {
logger.error(
`The component ${green(options.component)} does not exist.`
`The component ${highlighter.success(
options.component
)} does not exist.`
)
process.exit(1)
}
@@ -200,10 +204,10 @@ async function printDiff(diff: Change[]) {
diff.forEach((part) => {
if (part) {
if (part.added) {
return process.stdout.write(green(part.value))
return process.stdout.write(highlighter.success(part.value))
}
if (part.removed) {
return process.stdout.write(red(part.value))
return process.stdout.write(highlighter.error(part.value))
}
return process.stdout.write(part.value)

View File

@@ -14,10 +14,10 @@ import {
} from "@/src/utils/get-config"
import { getProjectConfig } from "@/src/utils/get-project-info"
import { handleError } from "@/src/utils/handle-error"
import { highlighter } from "@/src/utils/highlighter"
import { logger } from "@/src/utils/logger"
import { getRegistryBaseColors, getRegistryStyles } from "@/src/utils/registry"
import { Command } from "commander"
import { cyan, green } from "kleur/colors"
import ora from "ora"
import prompts from "prompts"
import { z } from "zod"
@@ -57,7 +57,7 @@ export const init = new Command()
const { proceed } = await prompts({
type: "confirm",
name: "proceed",
message: `Write configuration to ${cyan(
message: `Write configuration to ${highlighter.info(
"components.json"
)}. Proceed?`,
initial: true,
@@ -85,8 +85,8 @@ export const init = new Command()
})
logger.info("")
logger.info(
`${green(
logger.log(
`${highlighter.success(
"Success!"
)} Project initialization completed.\nYou may now add components.`
)
@@ -108,7 +108,9 @@ async function promptForConfig(defaultConfig: Config | null = null) {
{
type: "toggle",
name: "typescript",
message: `Would you like to use ${cyan("TypeScript")} (recommended)?`,
message: `Would you like to use ${highlighter.info(
"TypeScript"
)} (recommended)?`,
initial: defaultConfig?.tsx ?? true,
active: "yes",
inactive: "no",
@@ -116,7 +118,7 @@ async function promptForConfig(defaultConfig: Config | null = null) {
{
type: "select",
name: "style",
message: `Which ${cyan("style")} would you like to use?`,
message: `Which ${highlighter.info("style")} would you like to use?`,
choices: styles.map((style) => ({
title: style.label,
value: style.name,
@@ -125,7 +127,7 @@ async function promptForConfig(defaultConfig: Config | null = null) {
{
type: "select",
name: "tailwindBaseColor",
message: `Which color would you like to use as the ${cyan(
message: `Which color would you like to use as the ${highlighter.info(
"base color"
)}?`,
choices: baseColors.map((color) => ({
@@ -136,13 +138,15 @@ async function promptForConfig(defaultConfig: Config | null = null) {
{
type: "text",
name: "tailwindCss",
message: `Where is your ${cyan("global CSS")} file?`,
message: `Where is your ${highlighter.info("global CSS")} file?`,
initial: defaultConfig?.tailwind.css ?? DEFAULT_TAILWIND_CSS,
},
{
type: "toggle",
name: "tailwindCssVariables",
message: `Would you like to use ${cyan("CSS variables")} for theming?`,
message: `Would you like to use ${highlighter.info(
"CSS variables"
)} for theming?`,
initial: defaultConfig?.tailwind.cssVariables ?? true,
active: "yes",
inactive: "no",
@@ -150,7 +154,7 @@ async function promptForConfig(defaultConfig: Config | null = null) {
{
type: "text",
name: "tailwindPrefix",
message: `Are you using a custom ${cyan(
message: `Are you using a custom ${highlighter.info(
"tailwind prefix eg. tw-"
)}? (Leave blank if not)`,
initial: "",
@@ -158,25 +162,29 @@ async function promptForConfig(defaultConfig: Config | null = null) {
{
type: "text",
name: "tailwindConfig",
message: `Where is your ${cyan("tailwind.config.js")} located?`,
message: `Where is your ${highlighter.info(
"tailwind.config.js"
)} located?`,
initial: defaultConfig?.tailwind.config ?? DEFAULT_TAILWIND_CONFIG,
},
{
type: "text",
name: "components",
message: `Configure the import alias for ${cyan("components")}:`,
message: `Configure the import alias for ${highlighter.info(
"components"
)}:`,
initial: defaultConfig?.aliases["components"] ?? DEFAULT_COMPONENTS,
},
{
type: "text",
name: "utils",
message: `Configure the import alias for ${cyan("utils")}:`,
message: `Configure the import alias for ${highlighter.info("utils")}:`,
initial: defaultConfig?.aliases["utils"] ?? DEFAULT_UTILS,
},
{
type: "toggle",
name: "rsc",
message: `Are you using ${cyan("React Server Components")}?`,
message: `Are you using ${highlighter.info("React Server Components")}?`,
initial: defaultConfig?.rsc ?? true,
active: "yes",
inactive: "no",
@@ -223,7 +231,7 @@ async function promptForMinimalConfig(
{
type: "select",
name: "style",
message: `Which ${cyan("style")} would you like to use?`,
message: `Which ${highlighter.info("style")} would you like to use?`,
choices: styles.map((style) => ({
title: style.label,
value: style.name,
@@ -233,7 +241,7 @@ async function promptForMinimalConfig(
{
type: "select",
name: "tailwindBaseColor",
message: `Which color would you like to use as the ${cyan(
message: `Which color would you like to use as the ${highlighter.info(
"base color"
)}?`,
choices: baseColors.map((color) => ({
@@ -244,7 +252,9 @@ async function promptForMinimalConfig(
{
type: "toggle",
name: "tailwindCssVariables",
message: `Would you like to use ${cyan("CSS variables")} for theming?`,
message: `Would you like to use ${highlighter.info(
"CSS variables"
)} for theming?`,
initial: defaultConfig?.tailwind.cssVariables,
active: "yes",
inactive: "no",

View File

@@ -2,9 +2,9 @@ import path from "path"
import { addOptionsSchema } from "@/src/commands/add"
import * as ERRORS from "@/src/utils/errors"
import { getConfig } from "@/src/utils/get-config"
import { highlighter } from "@/src/utils/highlighter"
import { logger } from "@/src/utils/logger"
import fs from "fs-extra"
import { cyan } from "kleur/colors"
import ora from "ora"
import { z } from "zod"
@@ -13,7 +13,7 @@ export async function preFlightAdd(options: z.infer<typeof addOptionsSchema>) {
let projectSpinner
if (options.verbose) {
logger.info("")
logger.break()
projectSpinner = ora(`Preflight checks.`).start()
}
@@ -39,27 +39,35 @@ export async function preFlightAdd(options: z.infer<typeof addOptionsSchema>) {
if (Object.keys(errors).length > 0) {
projectSpinner?.fail()
logger.info("")
logger.break()
if (errors[ERRORS.MISSING_DIR_OR_EMPTY_PROJECT]) {
logger.error(`The path ${cyan(options.cwd)} does not exist or is empty.`)
logger.error(
`The path ${highlighter.info(options.cwd)} does not exist or is empty.`
)
}
if (errors[ERRORS.MISSING_CONFIG]) {
logger.error(
`A ${cyan("components.json")} file was not found at ${cyan(
options.cwd
)}.\nBefore you can add components, you must create a ${cyan(
`A ${highlighter.info(
"components.json"
)} file by running the ${cyan("init")} command.`
)} file was not found at ${highlighter.info(
options.cwd
)}.\nBefore you can add components, you must create a ${highlighter.info(
"components.json"
)} file by running the ${highlighter.info("init")} command.`
)
logger.error(
`Learn more at ${cyan("https://ui.shadcn.com/docs/components-json")}.`
`Learn more at ${highlighter.info(
"https://ui.shadcn.com/docs/components-json"
)}.`
)
} else if (errors[ERRORS.FAILED_CONFIG_READ]) {
logger.error(`Failed to read the ${cyan("components.json")} file.`)
logger.error(
`Failed to read the ${highlighter.info("components.json")} file.`
)
}
logger.info("")
logger.break()
process.exit(1)
}

View File

@@ -2,16 +2,16 @@ import path from "path"
import { initOptionsSchema } from "@/src/commands/init"
import * as ERRORS from "@/src/utils/errors"
import { getProjectInfo } from "@/src/utils/get-project-info"
import { highlighter } from "@/src/utils/highlighter"
import { logger } from "@/src/utils/logger"
import fs from "fs-extra"
import { cyan } from "kleur/colors"
import ora from "ora"
import { z } from "zod"
export async function preFlightInit(
options: z.infer<typeof initOptionsSchema>
) {
logger.info("")
logger.break()
const errors: Record<string, boolean> = {}
// Ensure target directory exists.
@@ -24,7 +24,6 @@ export async function preFlightInit(
errors[ERRORS.MISSING_DIR_OR_EMPTY_PROJECT] = true
}
// Check for existing components.json file.
if (
fs.existsSync(path.resolve(options.cwd, "components.json")) &&
!options.force
@@ -35,48 +34,57 @@ export async function preFlightInit(
if (Object.keys(errors).length > 0) {
projectSpinner?.fail()
logger.info("")
logger.break()
if (errors[ERRORS.MISSING_DIR_OR_EMPTY_PROJECT]) {
logger.error(`The path ${cyan(options.cwd)} does not exist or is empty.`)
logger.error(
`The path ${highlighter.info(options.cwd)} does not exist or is empty.`
)
}
if (errors[ERRORS.EXISTING_CONFIG]) {
logger.error(
`A ${cyan("components.json")} file already exists at ${cyan(
options.cwd
)}.\nTo start over, remove the ${cyan(
`A ${highlighter.info(
"components.json"
)} file and run ${cyan("init")} again.`
)} file already exists at ${highlighter.info(
options.cwd
)}.\nTo start over, remove the ${highlighter.info(
"components.json"
)} file and run ${highlighter.info("init")} again.`
)
}
logger.info("")
logger.break()
process.exit(1)
}
projectSpinner?.succeed()
const projectInfo = await getProjectInfo(options.cwd)
const frameworkSpinner = ora(`Verifying framework.`).start()
if (projectInfo?.framework.name === "manual") {
const projectInfo = await getProjectInfo(options.cwd)
if (!projectInfo || projectInfo?.framework.name === "manual") {
errors[ERRORS.UNSUPPORTED_FRAMEWORK] = true
frameworkSpinner?.fail()
logger.info("")
logger.error(
`We could not detect a supported framework at ${cyan(options.cwd)}.\n` +
`Visit ${cyan(
projectInfo?.framework.links.installation
)} to manually configure your project.\nOnce configured, you can use the cli to add components.`
)
logger.info("")
logger.break()
if (projectInfo?.framework.links.installation) {
logger.error(
`We could not detect a supported framework at ${highlighter.info(
options.cwd
)}.\n` +
`Visit ${highlighter.info(
projectInfo?.framework.links.installation
)} to manually configure your project.\nOnce configured, you can use the cli to add components.`
)
}
logger.break()
process.exit(1)
} else {
frameworkSpinner?.succeed(
`Verifying framework: ${projectInfo?.framework.label}.`
)
}
frameworkSpinner?.succeed(
`Verifying framework. Found ${highlighter.info(
projectInfo.framework.label
)}.`
)
const tailwindSpinner = ora(`Validating Tailwind CSS.`).start()
if (!projectInfo?.tailwindConfigFile || !projectInfo?.tailwindCssFile) {
errors[ERRORS.TAILWIND_NOT_CONFIGURED] = true
@@ -95,30 +103,32 @@ export async function preFlightInit(
if (Object.keys(errors).length > 0) {
if (errors[ERRORS.TAILWIND_NOT_CONFIGURED]) {
logger.info("")
logger.break()
logger.error(
`Tailwind CSS is not configured. Install Tailwind CSS then run init again.`
)
if (projectInfo?.framework.links.tailwind) {
logger.error(
`Visit ${cyan(projectInfo?.framework.links.tailwind)} to get started.`
`Visit ${highlighter.info(
projectInfo?.framework.links.tailwind
)} to get started.`
)
}
}
if (errors[ERRORS.IMPORT_ALIAS_MISSING]) {
logger.info("")
logger.break()
logger.error(`No import alias found in your tsconfig.json file.`)
if (projectInfo?.framework.links.installation) {
logger.error(
`Visit ${cyan(
`Visit ${highlighter.info(
projectInfo?.framework.links.installation
)} to learn how to set an import alias.`
)
}
}
logger.info("")
logger.break()
process.exit(1)
}

View File

@@ -1,10 +1,10 @@
import { type Config } from "@/src/utils/get-config"
import { handleError } from "@/src/utils/handle-error"
import { registryResolveItemsTree } from "@/src/utils/registry"
import { updateCssVars } from "@/src/utils/updaters/update-css-vars"
import { updateDependencies } from "@/src/utils/updaters/update-dependencies"
import { updateFiles } from "@/src/utils/updaters/update-files"
import { updateTailwindConfig } from "@/src/utils/updaters/update-tailwind-config"
import { updateTailwindCss } from "@/src/utils/updaters/update-tailwind-css"
import ora from "ora"
export async function addComponents(
@@ -26,7 +26,7 @@ export async function addComponents(
registrySpinner?.succeed()
await updateTailwindConfig(tree.tailwind?.config, config)
await updateTailwindCss(tree.cssVars, config)
await updateCssVars(tree.cssVars, config)
await updateDependencies(tree.dependencies, config)
await updateFiles(tree.files, config, {

View File

@@ -1,5 +1,5 @@
import { highlighter } from "@/src/utils/highlighter"
import { logger } from "@/src/utils/logger"
import { cyan } from "kleur/colors"
import { z } from "zod"
export function handleError(error: unknown) {
@@ -10,7 +10,7 @@ export function handleError(error: unknown) {
logger.error("")
if (typeof error === "string") {
logger.error(error)
logger.error("\n")
logger.break()
process.exit(1)
}
@@ -18,18 +18,18 @@ export function handleError(error: unknown) {
console.log(error.issues)
logger.error("Validation failed:")
for (const [key, value] of Object.entries(error.flatten().fieldErrors)) {
logger.error(`- ${cyan(key)}: ${value}`)
logger.error(`- ${highlighter.info(key)}: ${value}`)
}
logger.error("\n")
logger.break()
process.exit(1)
}
if (error instanceof Error) {
logger.error(error.message)
logger.error("\n")
logger.break()
process.exit(1)
}
logger.error("\n")
logger.break()
process.exit(1)
}

View File

@@ -0,0 +1,8 @@
import { cyan, green, red, yellow } from "kleur/colors"
export const highlighter = {
error: red,
warn: yellow,
info: cyan,
success: green,
}

View File

@@ -1,17 +1,17 @@
import { cyan, green, red, yellow } from "kleur/colors"
import { highlighter } from "@/src/utils/highlighter"
export const logger = {
error(...args: unknown[]) {
console.log(red(args.join(" ")))
console.log(highlighter.error(args.join(" ")))
},
warn(...args: unknown[]) {
console.log(yellow(args.join(" ")))
console.log(highlighter.warn(args.join(" ")))
},
info(...args: unknown[]) {
console.log(cyan(args.join(" ")))
console.log(highlighter.info(args.join(" ")))
},
success(...args: unknown[]) {
console.log(green(args.join(" ")))
console.log(highlighter.success(args.join(" ")))
},
log(...args: unknown[]) {
console.log(args.join(" "))

View File

@@ -1,6 +1,7 @@
import path from "path"
import { Config } from "@/src/utils/get-config"
import { handleError } from "@/src/utils/handle-error"
import { highlighter } from "@/src/utils/highlighter"
import { logger } from "@/src/utils/logger"
import {
registryBaseColorSchema,
@@ -13,7 +14,6 @@ import {
import { buildTailwindThemeColorsFromCssVars } from "@/src/utils/updaters/update-tailwind-config"
import deepmerge from "deepmerge"
import { HttpsProxyAgent } from "https-proxy-agent"
import { cyan } from "kleur/colors"
import fetch from "node-fetch"
import { z } from "zod"
@@ -174,7 +174,9 @@ async function fetchRegistry(paths: string[]) {
500: "Internal server error",
}
const message = errorMessages[response.status] || response.statusText
throw new Error(`Failed to fetch from ${cyan(url)}. ${message}`)
throw new Error(
`Failed to fetch from ${highlighter.info(url)}. ${message}`
)
}
return response.json()

View File

@@ -1,8 +1,8 @@
import { promises as fs } from "fs"
import path from "path"
import { Config } from "@/src/utils/get-config"
import { highlighter } from "@/src/utils/highlighter"
import { registryItemCssVarsSchema } from "@/src/utils/registry/schema"
import { cyan } from "kleur/colors"
import ora from "ora"
import postcss from "postcss"
import AtRule from "postcss/lib/at-rule"
@@ -10,24 +10,29 @@ import Root from "postcss/lib/root"
import Rule from "postcss/lib/rule"
import { z } from "zod"
export async function updateTailwindCss(
export async function updateCssVars(
cssVars: z.infer<typeof registryItemCssVarsSchema> | undefined,
config: Config
) {
if (!cssVars || !Object.keys(cssVars).length) {
if (
!cssVars ||
!Object.keys(cssVars).length ||
!config.resolvedPaths.tailwindCss
) {
return
}
console.log(cssVars)
const tailwindCssFileRelativePath = path.relative(
const cssFilepath = config.resolvedPaths.tailwindCss
const cssFilepathRelative = path.relative(
config.resolvedPaths.cwd,
config.resolvedPaths.tailwindCss
cssFilepath
)
const spinner = ora(`Updating ${cyan(tailwindCssFileRelativePath)}`).start()
const raw = await fs.readFile(config.resolvedPaths.tailwindCss, "utf8")
const spinner = ora(
`Updating ${highlighter.info(cssFilepathRelative)}`
).start()
const raw = await fs.readFile(cssFilepath, "utf8")
let output = await transformTailwindCss(raw, cssVars)
await fs.writeFile(config.resolvedPaths.tailwindCss, output, "utf8")
await fs.writeFile(cssFilepath, output, "utf8")
spinner.succeed()
}
@@ -140,7 +145,6 @@ function updateCssVarsPlugin(
}
}
// Function to add or update variables for a given selector
function addOrUpdateVars(
baseLayer: AtRule,
selector: string,

View File

@@ -1,8 +1,8 @@
import { Config } from "@/src/utils/get-config"
import { getPackageManager } from "@/src/utils/get-package-manager"
import { highlighter } from "@/src/utils/highlighter"
import { RegistryItem } from "@/src/utils/registry/schema"
import { execa } from "execa"
import { cyan } from "kleur/colors"
import ora from "ora"
export async function updateDependencies(
@@ -14,7 +14,7 @@ export async function updateDependencies(
}
const dependenciesSpinner = ora(
`Installing ${dependencies.map((d) => cyan(d)).join(", ")}.`
`Installing ${dependencies.map((d) => highlighter.info(d)).join(", ")}.`
)?.start()
const packageManager = await getPackageManager(config.resolvedPaths.cwd)

View File

@@ -1,6 +1,7 @@
import { existsSync, promises as fs } from "fs"
import path, { basename } from "path"
import { Config } from "@/src/utils/get-config"
import { highlighter } from "@/src/utils/highlighter"
import { logger } from "@/src/utils/logger"
import {
getRegistryBaseColor,
@@ -12,7 +13,6 @@ import { transformCssVars } from "@/src/utils/transformers/transform-css-vars"
import { transformImport } from "@/src/utils/transformers/transform-import"
import { transformRsc } from "@/src/utils/transformers/transform-rsc"
import { transformTwPrefixes } from "@/src/utils/transformers/transform-tw-prefix"
import { cyan } from "kleur/colors"
import ora from "ora"
import prompts from "prompts"
@@ -52,7 +52,7 @@ export async function updateFiles(
const { overwrite } = await prompts({
type: "confirm",
name: "overwrite",
message: `The file ${cyan(
message: `The file ${highlighter.info(
fileName
)} already exists. Would you like to overwrite?`,
initial: false,

View File

@@ -2,10 +2,9 @@ import { promises as fs } from "fs"
import { tmpdir } from "os"
import path from "path"
import { Config } from "@/src/utils/get-config"
import { logger } from "@/src/utils/logger"
import { highlighter } from "@/src/utils/highlighter"
import { registryItemTailwindSchema } from "@/src/utils/registry/schema"
import deepmerge from "deepmerge"
import { cyan } from "kleur/colors"
import ora from "ora"
import objectToString from "stringify-object"
import { type Config as TailwindConfig } from "tailwindcss"
@@ -39,7 +38,9 @@ export async function updateTailwindConfig(
config.resolvedPaths.cwd,
config.resolvedPaths.tailwindConfig
)
const spinner = ora(`Updating ${cyan(tailwindFileRelativePath)}`).start()
const spinner = ora(
`Updating ${highlighter.info(tailwindFileRelativePath)}`
).start()
const raw = await fs.readFile(config.resolvedPaths.tailwindConfig, "utf8")
const output = await transformTailwindConfig(raw, tailwindConfig, config)
await fs.writeFile(config.resolvedPaths.tailwindConfig, output, "utf8")