shadcn-ui/ui codebase analysis: How does shadcn-ui CLI work? — Part 3.0

Ramu NarasingaRamu Narasinga
4 min read

I wanted to find out how shadcn-ui CLI works. In this article, I discuss the code used to build the shadcn-ui/ui CLI.

In part 2.0 to 2.15, I discussed how npx shadcn-ui init works under the hood.

We will look at how npx shadcn-ui add <component> works in this part 3.x.

Since the packages/cli/src/commands/add.ts file is large, I will break this analysis down into parts and talk about code snippets and explain how stuff works.

In this article, we will look the concepts:

  1. Add command.

  2. Commander.js package

  3. How add command is registered?

  4. Argument and options

  5. addOptionsSchema

Want to learn how to build shadcn-ui/ui from scratch? Check out build-from-scratch

add command

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,
      })

We will begin with how add command is added. The above code snippet is picked from packages/cli/src/commands/add.ts

Commander.js package:

Command is imported from commander.js, a complete solution for node.js command-line interfaces.

How add command is registered?

The way add command is registered is that, if you open this src/commands/index.ts in a new tab, you will find this code as shown below

Commands are created separately in the folder named commands for maintainability purposes. If you were to fork this shadcn-ui/ui repo and want to add your own command, this is one way to do it.

Argument and options

When you write something like npx shadcn-ui add Button, Button here is an argument.

.argument("[components...]", "the components to add")

The above code snippet is picked from here.

You also have options that go with your add command as shown below:

.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.")

and then you have action

.action(async (components, opts) => {

addOptionsSchema

 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)
}

The above snippet is picked from add.ts

addOptionsSchema is declared just above the add function as shown below:

const addOptionsSchema = z.object({
  components: z.array(z.string()).optional(),
  yes: z.boolean(),
  overwrite: z.boolean(),
  cwd: z.string(),
  all: z.boolean(),
  path: z.string().optional(),
})

This schema basically ensures all the options and arguments are valid before processing them further.

Conclusion:

In Part 2.0 to 2.15, I discussed how npx shadcn-ui init works under the hood. It is time for a version bump to my articles. In 3.x articles, I will write about how npx shadcn-ui add works under the hood. Please note that semver is not applicable to my articles lol.

Command is imported from commander.js, a complete solution for node.js command-line interfaces. The way add command is registered with npx shadcn-ui CLI is that, if you open this src/commands/index.ts in a new tab, you will find this code as shown below:

program.addCommand(init).addCommand(add).addCommand(diff)

There’s an argument that accepts a single component name or an array of component names, that is why you would write something like npx shadcn-ui add Button

Buttonhere is an argument. add command also has few options such -y, -o, -c, -a, -p. Read more about these in the shadcn-ui CLI documentation.

addOptionsSchema ensures that arguments and the options passed to the add command are valid using zod

Want to learn how to build shadcn-ui/ui from scratch? Check out build-from-scratch

About me:

Website: https://ramunarasinga.com/

Linkedin: https://www.linkedin.com/in/ramu-narasinga-189361128/

Github: https://github.com/Ramu-Narasinga

Email: ramu.narasinga@gmail.com

Build shadcn-ui/ui from scratch

References

  1. https://github.com/shadcn-ui/ui/blob/main/packages/cli/src/commands/add.ts

  2. https://github.com/shadcn-ui/ui/blob/main/packages/cli/src/commands/add.ts#L31C1-L49C9

  3. https://github.com/shadcn-ui/ui/blob/main/packages/cli/src/index.ts#L24

  4. https://www.npmjs.com/package/commander

  5. https://github.com/shadcn-ui/ui/blob/main/packages/cli/src/commands/add.ts#L22

0
Subscribe to my newsletter

Read articles from Ramu Narasinga directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Ramu Narasinga
Ramu Narasinga