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

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

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.9, we looked at getRegistryStyles function, fetchRegistry function and stylesSchema.

In this article, we will understand the below concepts:

  1. getRegistryBaseColors function

  2. prompts

  3. Creating components.json

  4. resolveConfigPaths

getRegistryBaseColors

Unlike the getRegistryStyles function, getRegistryBaseColors does not use fetchRegistry function, it simply returns an array as shown below:

 export async function getRegistryBaseColors() {
  return [
    {
      name: "slate",
      label: "Slate",
    },
    {
      name: "gray",
      label: "Gray",
    },
    {
      name: "zinc",
      label: "Zinc",
    },
    {
      name: "neutral",
      label: "Neutral",
    },
    {
      name: "stone",
      label: "Stone",
    },
  ]
}

prompts

promptForMinimalConfig calls prompts with an array objects as in the below image.

Prompts is an npm package is an easy to use CLI prompts to enquire users for information. Prompts docs has a lot of examples, do check them out.

Based on the response that you provide in your CLI, it sets the style, baseColor and cssVariables.

const config = rawConfigSchema.parse({
    $schema: defaultConfig?.$schema,
    style,
    tailwind: {
      ...defaultConfig?.tailwind,
      baseColor,
      cssVariables,
    },
    rsc: defaultConfig?.rsc,
    tsx: defaultConfig?.tsx,
    aliases: defaultConfig?.aliases,
})

and these are used in setting the config.

Creating components.json

After setting the config, promptsForMinimalConfig creates components.json using this config.

// 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()

fs.writeFile asynchronously writes data to a file, replacing the file if it already exists. data can be a string, a buffer, an <AsyncIterable>, or an <Iterable> object. The promise is fulfilled with no arguments upon success.

JSON.stringify(config, null, 2)

We all have seen JSON.stringify(<some variable>) but what are these additional params, null and 2?

Reading the mdn docs for JSON.stringify, JSON.stringify has the below syntax:

JSON.stringify(value)
JSON.stringify(value, replacer)
JSON.stringify(value, replacer, space)

This example below demonstrates perfectly

function replacer(key, value) {
  // Filtering out properties
  if (typeof value === "string") {
    return undefined;
  }
  return value;
}

const foo = {
  foundation: "Mozilla",
  model: "box",
  week: 45,
  transport: "car",
  month: 7,
};
JSON.stringify(foo, null, 2);

resolveConfigPaths

export async function resolveConfigPaths(cwd: string, config: RawConfig) {
  // Read tsconfig.json.
  const tsConfig = await loadConfig(cwd)

  if (tsConfig.resultType === "failed") {
    throw new Error(
      `Failed to load ${config.tsx ? "tsconfig" : "jsconfig"}.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),
      ui: config.aliases["ui"]
        ? await resolveImport(config.aliases["ui"], tsConfig)
        : await resolveImport(config.aliases["components"], tsConfig),
    },
  })
}

resolveConfigPaths has the resolvedPaths object with some keys resolved using using path.resolve. Keys like tailwindConfig, tailwindCss, utils, components, ui are set.

Conclusion:

In this article, I discussed the following concepts:

  1. getRegistryBaseColors

Unlike the getRegistryStyles function, getRegistryBaseColors does not use fetchRegistry function, it simply returns an array

2. prompts

Prompts package lets you enquire users for information in the CLI. Prompts docs has a lot of examples, do check them out.

3. Creating components.json

promptsForMinimalConfig creates components.json using fs.writeFile

4. JSON.stringify(config, null, 2)

We all have seen JSON.stringify(<some variable>) but what are these additional params, null and 2 used in shadcn-ui/ui CLI source code?

 await fs.writeFile(targetPath, JSON.stringify(config, null, 2), "utf8")

From the mdn docs, JSON.stringify can have any of the following syntax:

JSON.stringify(value)
JSON.stringify(value, replacer)
JSON.stringify(value, replacer, space)

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/init.ts#L232

  2. https://github.com/shadcn-ui/ui/blob/main/packages/cli/src/utils/registry/index.ts#L39

  3. https://github.com/shadcn-ui/ui/blob/main/packages/cli/src/utils/get-config.ts#L65