Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

test(cli): Add unit tests for the add command #1292

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
302 changes: 155 additions & 147 deletions packages/cli/src/commands/add.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,175 +28,183 @@ const addOptionsSchema = z.object({
path: z.string().optional(),
})

export const add = new Command()
.name("add")
.description("add a component to your project")
.argument("[components...]", "the components to add")
.option("-y, --yes", "skip confirmation prompt.", true)
.option("-o, --overwrite", "overwrite existing files.", false)
.option(
"-c, --cwd <cwd>",
"the working directory. defaults to the current directory.",
process.cwd()
)
.option("-a, --all", "add all available components", false)
.option("-p, --path <path>", "the path to add the component to.")
.action(async (components, opts) => {
try {
const options = addOptionsSchema.parse({
components,
...opts,
})
export const runAdd = async (
components: string[],
opts: {
yes: boolean
overwrite: boolean
cwd: string
all: boolean
path?: string
}
) => {
try {
const options = addOptionsSchema.parse({
components,
...opts,
})

const cwd = path.resolve(options.cwd)

if (!existsSync(cwd)) {
logger.error(`The path ${cwd} does not exist. Please try again.`)
process.exit(1)
}

const cwd = path.resolve(options.cwd)
const config = await getConfig(cwd)
if (!config) {
logger.warn(
`Configuration is missing. Please run ${chalk.green(
`init`
)} to create a components.json file.`
)
process.exit(1)
}

if (!existsSync(cwd)) {
logger.error(`The path ${cwd} does not exist. Please try again.`)
process.exit(1)
}
const registryIndex = await getRegistryIndex()

let selectedComponents = options.all
? registryIndex.map((entry) => entry.name)
: options.components
if (!options.components?.length && !options.all) {
const { components } = await prompts({
type: "multiselect",
name: "components",
message: "Which components would you like to add?",
hint: "Space to select. A to toggle all. Enter to submit.",
instructions: false,
choices: registryIndex.map((entry) => ({
title: entry.name,
value: entry.name,
selected: options.all
? true
: options.components?.includes(entry.name),
})),
})
selectedComponents = components
}

const config = await getConfig(cwd)
if (!config) {
logger.warn(
`Configuration is missing. Please run ${chalk.green(
`init`
)} to create a components.json file.`
)
process.exit(1)
}
if (!selectedComponents?.length) {
logger.warn("No components selected. Exiting.")
process.exit(0)
}

const registryIndex = await getRegistryIndex()

let selectedComponents = options.all
? registryIndex.map((entry) => entry.name)
: options.components
if (!options.components?.length && !options.all) {
const { components } = await prompts({
type: "multiselect",
name: "components",
message: "Which components would you like to add?",
hint: "Space to select. A to toggle all. Enter to submit.",
instructions: false,
choices: registryIndex.map((entry) => ({
title: entry.name,
value: entry.name,
selected: options.all
? true
: options.components?.includes(entry.name),
})),
})
selectedComponents = components
}
const tree = await resolveTree(registryIndex, selectedComponents)
const payload = await fetchTree(config.style, tree)
const baseColor = await getRegistryBaseColor(config.tailwind.baseColor)

if (!selectedComponents?.length) {
logger.warn("No components selected. Exiting.")
process.exit(0)
}
if (!payload.length) {
logger.warn("Selected components not found. Exiting.")
process.exit(0)
}

const tree = await resolveTree(registryIndex, selectedComponents)
const payload = await fetchTree(config.style, tree)
const baseColor = await getRegistryBaseColor(config.tailwind.baseColor)
if (!options.yes) {
const { proceed } = await prompts({
type: "confirm",
name: "proceed",
message: `Ready to install components and dependencies. Proceed?`,
initial: true,
})

if (!payload.length) {
logger.warn("Selected components not found. Exiting.")
if (!proceed) {
process.exit(0)
}
}

if (!options.yes) {
const { proceed } = await prompts({
type: "confirm",
name: "proceed",
message: `Ready to install components and dependencies. Proceed?`,
initial: true,
})
const spinner = ora(`Installing components...`).start()
for (const item of payload) {
spinner.text = `Installing ${item.name}...`
const targetDir = await getItemTargetPath(
config,
item,
options.path ? path.resolve(cwd, options.path) : undefined
)

if (!targetDir) {
continue
}

if (!proceed) {
process.exit(0)
}
if (!existsSync(targetDir)) {
await fs.mkdir(targetDir, { recursive: true })
}

const spinner = ora(`Installing components...`).start()
for (const item of payload) {
spinner.text = `Installing ${item.name}...`
const targetDir = await getItemTargetPath(
config,
item,
options.path ? path.resolve(cwd, options.path) : undefined
)
const existingComponent = item.files.filter((file) =>
existsSync(path.resolve(targetDir, file.name))
)

if (existingComponent.length && !options.overwrite) {
if (selectedComponents.includes(item.name)) {
spinner.stop()
const { overwrite } = await prompts({
type: "confirm",
name: "overwrite",
message: `Component ${item.name} already exists. Would you like to overwrite?`,
initial: false,
})

if (!targetDir) {
if (!overwrite) {
logger.info(
`Skipped ${item.name}. To overwrite, run with the ${chalk.green(
"--overwrite"
)} flag.`
)
continue
}

spinner.start(`Installing ${item.name}...`)
} else {
continue
}
}

if (!existsSync(targetDir)) {
await fs.mkdir(targetDir, { recursive: true })
}
for (const file of item.files) {
let filePath = path.resolve(targetDir, file.name)

const existingComponent = item.files.filter((file) =>
existsSync(path.resolve(targetDir, file.name))
)
// Run transformers.
const content = await transform({
filename: file.name,
raw: file.content,
config,
baseColor,
})

if (existingComponent.length && !options.overwrite) {
if (selectedComponents.includes(item.name)) {
spinner.stop()
const { overwrite } = await prompts({
type: "confirm",
name: "overwrite",
message: `Component ${item.name} already exists. Would you like to overwrite?`,
initial: false,
})

if (!overwrite) {
logger.info(
`Skipped ${item.name}. To overwrite, run with the ${chalk.green(
"--overwrite"
)} flag.`
)
continue
}

spinner.start(`Installing ${item.name}...`)
} else {
continue
}
if (!config.tsx) {
filePath = filePath.replace(/\.tsx$/, ".jsx")
filePath = filePath.replace(/\.ts$/, ".js")
}

for (const file of item.files) {
let filePath = path.resolve(targetDir, file.name)

// Run transformers.
const content = await transform({
filename: file.name,
raw: file.content,
config,
baseColor,
})
await fs.writeFile(filePath, content)
}

if (!config.tsx) {
filePath = filePath.replace(/\.tsx$/, ".jsx")
filePath = filePath.replace(/\.ts$/, ".js")
// Install dependencies.
if (item.dependencies?.length) {
const packageManager = await getPackageManager(cwd)
await execa(
packageManager,
[packageManager === "npm" ? "install" : "add", ...item.dependencies],
{
cwd,
}

await fs.writeFile(filePath, content)
}

// Install dependencies.
if (item.dependencies?.length) {
const packageManager = await getPackageManager(cwd)
await execa(
packageManager,
[
packageManager === "npm" ? "install" : "add",
...item.dependencies,
],
{
cwd,
}
)
}
)
}
spinner.succeed(`Done.`)
} catch (error) {
handleError(error)
}
})
spinner.succeed(`Done.`)
} catch (error) {
handleError(error)
}
}

export const add = new Command()
.name("add")
.description("add a component to your project")
.argument("[components...]", "the components to add")
.option("-y, --yes", "skip confirmation prompt.", true)
.option("-o, --overwrite", "overwrite existing files.", false)
.option(
"-c, --cwd <cwd>",
"the working directory. defaults to the current directory.",
process.cwd()
)
.option("-a, --all", "add all available components", false)
.option("-p, --path <path>", "the path to add the component to.")
.action(runAdd)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't touch the core logic at all. Just extracted the core functionality to the runAdd and calling the func here

Loading