From 1fab95c9986af5b2cf8c7b77cd1386dfbe2e59da Mon Sep 17 00:00:00 2001 From: hngngn Date: Thu, 20 Jul 2023 12:23:09 +0700 Subject: [PATCH] feat(cli): check path aliases --- .changeset/brown-queens-agree.md | 6 + packages/cli/src/commands/init.ts | 437 ++++++++++++++------------- packages/cli/src/index.ts | 12 +- packages/cli/src/utils/get-config.ts | 117 ++++--- 4 files changed, 298 insertions(+), 274 deletions(-) create mode 100644 .changeset/brown-queens-agree.md diff --git a/.changeset/brown-queens-agree.md b/.changeset/brown-queens-agree.md new file mode 100644 index 00000000..38b01617 --- /dev/null +++ b/.changeset/brown-queens-agree.md @@ -0,0 +1,6 @@ +--- +"shadcn-solid": patch +--- + +- update default option. +- check and warning about path aliases to prevent creating folder with symbol. diff --git a/packages/cli/src/commands/init.ts b/packages/cli/src/commands/init.ts index b1619b18..e6fee8f5 100644 --- a/packages/cli/src/commands/init.ts +++ b/packages/cli/src/commands/init.ts @@ -1,20 +1,20 @@ import { - DEFAULT_COMPONENTS, - DEFAULT_TAILWIND_CONFIG, - DEFAULT_TAILWIND_CSS, - DEFAULT_UTILS, - getConfig, - rawConfigSchema, - resolveConfigPaths, - type Config, + DEFAULT_COMPONENTS, + DEFAULT_TAILWIND_CONFIG, + DEFAULT_TAILWIND_CSS, + DEFAULT_UTILS, + getConfig, + rawConfigSchema, + resolveConfigPaths, + type Config, } 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 { - getRegistryBaseColor, - getRegistryBaseColors, - getRegistryStyles, + getRegistryBaseColor, + getRegistryBaseColors, + getRegistryStyles, } from "@/src/utils/registry" import * as templates from "@/src/utils/templates" import chalk from "chalk" @@ -24,237 +24,256 @@ import { existsSync, promises as fs } from "fs" import ora from "ora" import path from "path" import prompts from "prompts" +import { loadConfig } from "tsconfig-paths" import * as z from "zod" const PROJECT_DEPENDENCIES = [ - "tailwindcss-animate", - "class-variance-authority", - "clsx", - "tailwind-merge", + "tailwindcss-animate", + "class-variance-authority", + "clsx", + "tailwind-merge", ] const initOptionsSchema = z.object({ - cwd: z.string(), - yes: z.boolean(), + cwd: z.string(), + yes: z.boolean(), }) export const init = new Command() - .name("init") - .description("initialize your project and install dependencies") - .option("-y, --yes", "skip confirmation prompt.", false) - .option( - "-c, --cwd ", - "the working directory. defaults to the current directory.", - process.cwd() - ) - .action(async (opts) => { - try { - const options = initOptionsSchema.parse(opts) - const cwd = path.resolve(options.cwd) + .name("init") + .description("initialize your project and install dependencies") + .option("-y, --yes", "skip confirmation prompt.", false) + .option( + "-c, --cwd ", + "the working directory. defaults to the current directory.", + process.cwd() + ) + .action(async (opts) => { + try { + const options = initOptionsSchema.parse(opts) + const cwd = path.resolve(options.cwd) - // Ensure target directory exists. - if (!existsSync(cwd)) { - logger.error( - `The path ${cwd} does not exist. Please try again.` - ) - process.exit(1) - } + // Ensure target directory exists. + if (!existsSync(cwd)) { + logger.error( + `The path ${cwd} does not exist. Please try again.` + ) + process.exit(1) + } - // Read config. - const existingConfig = await getConfig(cwd) - const config = await promptForConfig( - cwd, - existingConfig, - options.yes - ) + // Read config. + const existingConfig = await getConfig(cwd) - await runInit(cwd, config) + const tsConfig = loadConfig(cwd) - logger.info("") - logger.info( - `${chalk.green("Success!")} Project initialization completed.` - ) - logger.info("") - } catch (error) { - handleError(error) - } - }) + if (tsConfig.resultType === "failed") { + throw new Error( + `Failed to load tsconfig.json. ${ + tsConfig.message ?? "" + }`.trim() + ) + } + + if (tsConfig.paths["@/*"] === undefined) { + throw new Error( + `Please make sure to update your path aliases to '@'. For more information, please visit: https://shadcn-solid.vercel.app/docs/installation#path-aliases.` + ) + } + + const config = await promptForConfig( + cwd, + existingConfig, + options.yes + ) + + await runInit(cwd, config) + + logger.info("") + logger.info( + `${chalk.green("Success!")} Project initialization completed.` + ) + logger.info("") + } catch (error) { + handleError(error) + } + }) export async function promptForConfig( - cwd: string, - defaultConfig: Config | null = null, - skip = false + cwd: string, + defaultConfig: Config | null = null, + skip = false ) { - const highlight = (text: string) => chalk.cyan(text) + const highlight = (text: string) => chalk.cyan(text) - const styles = await getRegistryStyles() - const baseColors = await getRegistryBaseColors() + const styles = await getRegistryStyles() + const baseColors = await getRegistryBaseColors() - const options = await prompts([ - { - type: "select", - name: "style", - message: `Which ${highlight("style")} would you like to use?`, - choices: styles.map((style) => ({ - title: style.label, - value: style.name, - })), - }, - { - type: "select", - name: "tailwindBaseColor", - message: `Which color would you like to use as ${highlight( - "base color" - )}?`, - choices: baseColors.map((color) => ({ - title: color.label, - value: color.name, - })), - }, - { - type: "text", - name: "tailwindCss", - message: `Where is your ${highlight("global CSS")} file?`, - initial: defaultConfig?.tailwind.css ?? DEFAULT_TAILWIND_CSS, - }, - { - type: "toggle", - name: "tailwindCssVariables", - message: `Would you like to use ${highlight( - "CSS variables" - )} for colors?`, - initial: defaultConfig?.tailwind.cssVariables ?? true, - active: "yes", - inactive: "no", - }, - { - type: "text", - name: "tailwindConfig", - message: `Where is your ${highlight( - "tailwind.config.js" - )} located?`, - initial: defaultConfig?.tailwind.config ?? DEFAULT_TAILWIND_CONFIG, - }, - { - type: "text", - name: "components", - message: `Configure the import alias for ${highlight( - "components" - )}:`, - initial: defaultConfig?.aliases["components"] ?? DEFAULT_COMPONENTS, - }, - { - type: "text", - name: "utils", - message: `Configure the import alias for ${highlight("utils")}:`, - initial: defaultConfig?.aliases["utils"] ?? DEFAULT_UTILS, - }, - ]) + const options = await prompts([ + { + type: "select", + name: "style", + message: `Which ${highlight("style")} would you like to use?`, + choices: styles.map((style) => ({ + title: style.label, + value: style.name, + })), + }, + { + type: "select", + name: "tailwindBaseColor", + message: `Which color would you like to use as ${highlight( + "base color" + )}?`, + choices: baseColors.map((color) => ({ + title: color.label, + value: color.name, + })), + initial: 2, + }, + { + type: "text", + name: "tailwindCss", + message: `Where is your ${highlight("global CSS")} file?`, + initial: defaultConfig?.tailwind.css ?? DEFAULT_TAILWIND_CSS, + }, + { + type: "toggle", + name: "tailwindCssVariables", + message: `Would you like to use ${highlight( + "CSS variables" + )} for colors?`, + initial: defaultConfig?.tailwind.cssVariables ?? true, + active: "yes", + inactive: "no", + }, + { + type: "text", + name: "tailwindConfig", + message: `Where is your ${highlight( + "tailwind.config.cjs" + )} located?`, + initial: defaultConfig?.tailwind.config ?? DEFAULT_TAILWIND_CONFIG, + }, + { + type: "text", + name: "components", + message: `Configure the import alias for ${highlight( + "components" + )}:`, + initial: defaultConfig?.aliases["components"] ?? DEFAULT_COMPONENTS, + }, + { + type: "text", + name: "utils", + message: `Configure the import alias for ${highlight("utils")}:`, + initial: defaultConfig?.aliases["utils"] ?? DEFAULT_UTILS, + }, + ]) - const config = rawConfigSchema.parse({ - $schema: "https://shadcn-solid.vercel.app/schema.json", - style: options.style, - tailwind: { - config: options.tailwindConfig, - css: options.tailwindCss, - baseColor: options.tailwindBaseColor, - cssVariables: options.tailwindCssVariables, - }, - aliases: { - utils: options.utils, - components: options.components, - }, - }) + const config = rawConfigSchema.parse({ + $schema: "https://shadcn-solid.vercel.app/schema.json", + style: options.style, + tailwind: { + config: options.tailwindConfig, + css: options.tailwindCss, + baseColor: options.tailwindBaseColor, + cssVariables: options.tailwindCssVariables, + }, + aliases: { + utils: options.utils, + components: options.components, + }, + }) - if (!skip) { - const { proceed } = await prompts({ - type: "confirm", - name: "proceed", - message: `Write configuration to ${highlight( - "components.json" - )}. Proceed?`, - initial: true, - }) + if (!skip) { + const { proceed } = await prompts({ + type: "confirm", + name: "proceed", + message: `Write configuration to ${highlight( + "components.json" + )}. Proceed?`, + initial: true, + }) - if (!proceed) { - process.exit(0) - } - } + if (!proceed) { + process.exit(0) + } + } - // Write to file. - logger.info("") - const spinner = ora(`Writing components.json...`).start() - const targetPath = path.resolve(cwd, "components.json") - await fs.writeFile(targetPath, JSON.stringify(config, null, 2), "utf8") - spinner.succeed() + // Write to file. + logger.info("") + const spinner = ora(`Writing components.json...`).start() + const targetPath = path.resolve(cwd, "components.json") + await fs.writeFile(targetPath, JSON.stringify(config, null, 2), "utf8") + spinner.succeed() - return await resolveConfigPaths(cwd, config) + return await resolveConfigPaths(cwd, config) } export async function runInit(cwd: string, config: Config) { - const spinner = ora(`Initializing project...`)?.start() + const spinner = ora(`Initializing project...`)?.start() - // Ensure all resolved paths directories exist. - for (const [key, resolvedPath] of Object.entries(config.resolvedPaths)) { - // Determine if the path is a file or directory. - // TODO: is there a better way to do this? - let dirname = path.extname(resolvedPath) - ? path.dirname(resolvedPath) - : resolvedPath + // Ensure all resolved paths directories exist. + for (const [key, resolvedPath] of Object.entries(config.resolvedPaths)) { + // Determine if the path is a file or directory. + // TODO: is there a better way to do this? + let dirname = path.extname(resolvedPath) + ? path.dirname(resolvedPath) + : resolvedPath - // If the utils alias is set to something like "@/lib/utils", - // assume this is a file and remove the "utils" file name. - // TODO: In future releases we should add support for individual utils. - if (key === "utils" && resolvedPath.endsWith("/utils")) { - // Remove /utils at the end. - dirname = dirname.replace(/\/utils$/, "") - } + // If the utils alias is set to something like "@/lib/utils", + // assume this is a file and remove the "utils" file name. + // TODO: In future releases we should add support for individual utils. + if (key === "utils" && resolvedPath.endsWith("/utils")) { + // Remove /utils at the end. + dirname = dirname.replace(/\/utils$/, "") + } - if (!existsSync(dirname)) { - await fs.mkdir(dirname, { recursive: true }) - } - } + if (!existsSync(dirname)) { + await fs.mkdir(dirname, { recursive: true }) + } + } - // Write tailwind config. - await fs.writeFile( - config.resolvedPaths.tailwindConfig, - config.tailwind.cssVariables - ? templates.TAILWIND_CONFIG_WITH_VARIABLES - : templates.TAILWIND_CONFIG, - "utf8" - ) + // Write tailwind config. + await fs.writeFile( + config.resolvedPaths.tailwindConfig, + config.tailwind.cssVariables + ? templates.TAILWIND_CONFIG_WITH_VARIABLES + : templates.TAILWIND_CONFIG, + "utf8" + ) - // Write css file. - const baseColor = await getRegistryBaseColor(config.tailwind.baseColor) - if (baseColor) { - await fs.writeFile( - config.resolvedPaths.tailwindCss, - config.tailwind.cssVariables - ? baseColor.cssVarsTemplate - : baseColor.inlineColorsTemplate, - "utf8" - ) - } + // Write css file. + const baseColor = await getRegistryBaseColor(config.tailwind.baseColor) + if (baseColor) { + await fs.writeFile( + config.resolvedPaths.tailwindCss, + config.tailwind.cssVariables + ? baseColor.cssVarsTemplate + : baseColor.inlineColorsTemplate, + "utf8" + ) + } - // Write cn file. - await fs.writeFile( - `${config.resolvedPaths.utils}.ts`, - templates.UTILS, - "utf8" - ) + // Write cn file. + await fs.writeFile( + `${config.resolvedPaths.utils}.ts`, + templates.UTILS, + "utf8" + ) - spinner?.succeed() + spinner?.succeed() - // Install dependencies. - const dependenciesSpinner = ora(`Installing dependencies...`)?.start() - const packageManager = await getPackageManager(cwd) + // Install dependencies. + const dependenciesSpinner = ora(`Installing dependencies...`)?.start() + const packageManager = await getPackageManager(cwd) - await execa( - packageManager, - [packageManager === "npm" ? "install" : "add", ...PROJECT_DEPENDENCIES], - { - cwd, - } - ) - dependenciesSpinner?.succeed() + await execa( + packageManager, + [packageManager === "npm" ? "install" : "add", ...PROJECT_DEPENDENCIES], + { + cwd, + } + ) + dependenciesSpinner?.succeed() } diff --git a/packages/cli/src/index.ts b/packages/cli/src/index.ts index af79b426..fec1aef7 100644 --- a/packages/cli/src/index.ts +++ b/packages/cli/src/index.ts @@ -7,14 +7,14 @@ process.on("SIGINT", () => process.exit(0)) process.on("SIGTERM", () => process.exit(0)) async function main() { - const program = new Command() - .name("shadcn-solid") - .description("add components and dependencies to your project") - .version("0.1.0", "-v, --version", "display the version number") + const program = new Command() + .name("shadcn-solid") + .description("add components and dependencies to your project") + .version("0.1.3", "-v, --version", "display the version number") - program.addCommand(init).addCommand(add) + program.addCommand(init).addCommand(add) - program.parse() + program.parse() } main() diff --git a/packages/cli/src/utils/get-config.ts b/packages/cli/src/utils/get-config.ts index ddba8a54..c8daaddc 100644 --- a/packages/cli/src/utils/get-config.ts +++ b/packages/cli/src/utils/get-config.ts @@ -7,92 +7,91 @@ import * as z from "zod" export const DEFAULT_STYLE = "default" export const DEFAULT_COMPONENTS = "@/components" export const DEFAULT_UTILS = "@/lib/utils" -export const DEFAULT_TAILWIND_CSS = "src/globals.css" +export const DEFAULT_TAILWIND_CSS = "src/root.css" export const DEFAULT_TAILWIND_CONFIG = "tailwind.config.cjs" -export const DEFAULT_TAILWIND_BASE_COLOR = "slate" // 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"], + searchPlaces: ["components.json"], }) export const rawConfigSchema = z - .object({ - $schema: z.string().optional(), - style: z.string(), - tailwind: z.object({ - config: z.string(), - css: z.string(), - baseColor: z.string(), - cssVariables: z.boolean().default(true), - }), - aliases: z.object({ - components: z.string(), - utils: z.string(), - }), - }) - .strict() + .object({ + $schema: z.string().optional(), + style: z.string(), + tailwind: z.object({ + config: z.string(), + css: z.string(), + baseColor: z.string(), + cssVariables: z.boolean().default(true), + }), + aliases: z.object({ + components: z.string(), + utils: z.string(), + }), + }) + .strict() export type RawConfig = z.infer export const configSchema = rawConfigSchema.extend({ - resolvedPaths: z.object({ - tailwindConfig: z.string(), - tailwindCss: z.string(), - utils: z.string(), - components: z.string(), - }), + resolvedPaths: z.object({ + tailwindConfig: z.string(), + tailwindCss: z.string(), + utils: z.string(), + components: z.string(), + }), }) export type Config = z.infer export async function getConfig(cwd: string) { - const config = await getRawConfig(cwd) + const config = await getRawConfig(cwd) - if (!config) { - return null - } + if (!config) { + return null + } - return await resolveConfigPaths(cwd, config) + return await resolveConfigPaths(cwd, config) } export async function resolveConfigPaths(cwd: string, config: RawConfig) { - // Read tsconfig.json. - const tsConfig = loadConfig(cwd) + // Read tsconfig.json. + const tsConfig = loadConfig(cwd) - if (tsConfig.resultType === "failed") { - throw new Error( - `Failed to load tsconfig.json. ${tsConfig.message ?? ""}`.trim() - ) - } + if (tsConfig.resultType === "failed") { + throw new Error( + `Failed to load tsconfig.json. ${tsConfig.message ?? ""}`.trim() + ) + } - return configSchema.parse({ - ...config, - resolvedPaths: { - tailwindConfig: path.resolve(cwd, config.tailwind.config), - tailwindCss: path.resolve(cwd, config.tailwind.css), - utils: await resolveImport(config.aliases["utils"], tsConfig), - components: await resolveImport( - config.aliases["components"], - tsConfig - ), - }, - }) + return configSchema.parse({ + ...config, + resolvedPaths: { + tailwindConfig: path.resolve(cwd, config.tailwind.config), + tailwindCss: path.resolve(cwd, config.tailwind.css), + utils: await resolveImport(config.aliases["utils"], tsConfig), + components: await resolveImport( + config.aliases["components"], + tsConfig + ), + }, + }) } export async function getRawConfig(cwd: string): Promise { - try { - const configResult = await explorer.search(cwd) + try { + const configResult = await explorer.search(cwd) - if (!configResult) { - return null - } + if (!configResult) { + return null + } - return rawConfigSchema.parse(configResult.config) - } catch (error) { - throw new Error( - `Invalid configuration found in ${cwd}/components.json.` - ) - } + return rawConfigSchema.parse(configResult.config) + } catch (error) { + throw new Error( + `Invalid configuration found in ${cwd}/components.json.` + ) + } }