feat: refactor

This commit is contained in:
shadcn
2026-02-21 22:28:47 +04:00
parent 4fa2ef66ed
commit 751c520865
6 changed files with 137 additions and 59 deletions

View File

@@ -16,7 +16,11 @@ import { getProjectInfo } 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 { promptForPreset, resolveRegistryBaseConfig } from "@/src/utils/presets"
import {
promptForBase,
promptForPreset,
resolveRegistryBaseConfig,
} from "@/src/utils/presets"
import { ensureRegistriesInConfig } from "@/src/utils/registries"
import { updateAppIndex } from "@/src/utils/update-app-index"
import { Command } from "commander"
@@ -163,9 +167,11 @@ export const add = new Command()
projectInfo?.framework.name
)
// Prompt for preset.
// Prompt for base and preset.
const base = await promptForBase()
const { url: initUrl } = await promptForPreset({
rtl: false,
base,
template: inferredTemplate,
})
@@ -208,9 +214,11 @@ export const add = new Command()
options.cwd = path.resolve(options.cwd, "apps/web")
config = await getConfig(options.cwd)
} else {
// Prompt for preset.
// Prompt for base and preset.
const selectedBase = await promptForBase()
const { url: initUrl } = await promptForPreset({
rtl: false,
base: selectedBase,
template,
})
const { registryBaseConfig, installStyleIndex } =

View File

@@ -1,6 +1,6 @@
import { existsSync } from "fs"
import path from "path"
import { getConfig } from "@/src/utils/get-config"
import { getBase, getConfig } from "@/src/utils/get-config"
import {
formatMonorepoMessage,
getMonorepoTargets,
@@ -76,10 +76,6 @@ export const info = new Command()
}
})
function getBase(style: string | undefined) {
return style?.startsWith("base-") ? "base" : "radix"
}
function getRegistries(
registries: Record<string, string | { url: string }> | undefined
) {

View File

@@ -4,6 +4,7 @@ import { preFlightInit } from "@/src/preflights/preflight-init"
import { getRegistryBaseColors, getRegistryStyles } from "@/src/registry/api"
import { BUILTIN_REGISTRIES } from "@/src/registry/constants"
import { clearRegistryContext } from "@/src/registry/context"
import { registryConfigSchema } from "@/src/registry/schema"
import { isUrl } from "@/src/registry/utils"
import { rawConfigSchema } from "@/src/schema"
import { getTemplateForFramework, templates } from "@/src/templates/index"
@@ -43,6 +44,7 @@ import { logger } from "@/src/utils/logger"
import { decodePreset, isPresetCode } from "@/src/utils/preset"
import {
DEFAULT_PRESETS,
promptForBase,
promptForPreset,
resolveInitUrl,
resolveRegistryBaseConfig,
@@ -84,7 +86,7 @@ export const init = new Command()
"-t, --template <template>",
"the template to use. (next, start, vite, next-monorepo, react-router)"
)
.option("-b, --base <base>", "the primitive library to use. (radix, base)")
.option("-b, --base <base>", "the component library to use. (radix, base)")
.option("-p, --preset [name]", "use a preset configuration")
.option("-y, --yes", "skip confirmation prompt.", true)
.option(
@@ -126,10 +128,7 @@ export const init = new Command()
reinstall: opts.reinstall,
cwd: path.resolve(opts.cwd),
})
const presets = Object.values(DEFAULT_PRESETS)
const presetsByName = new Map(
presets.map((preset) => [preset.name, preset])
)
const presetsByName = new Map(Object.entries(DEFAULT_PRESETS))
let presetBase: string | undefined
@@ -155,7 +154,7 @@ export const init = new Command()
!isUrl(options.preset) &&
!isPresetCode(options.preset)
) {
const knownPresetNames = presets.map((preset) => preset.name)
const knownPresetNames = Array.from(presetsByName.keys())
if (!presetsByName.has(options.preset)) {
logger.error(
@@ -292,6 +291,11 @@ export const init = new Command()
}
}
// Prompt for base if not provided.
if (!options.base) {
options.base = await promptForBase()
}
// Show interactive preset list.
options.preset = true
}
@@ -303,7 +307,7 @@ export const init = new Command()
const result = await promptForPreset({
rtl: options.rtl ?? false,
template: options.template,
base: options.base,
base: options.base!,
})
components = [result.url, ...components]
presetBase = result.base
@@ -346,11 +350,12 @@ export const init = new Command()
initUrl = resolveInitUrl(
{
...preset,
base: options.base ?? "radix",
rtl: options.rtl ?? preset.rtl,
},
{ template: options.template }
)
presetBase = preset.base
presetBase = undefined
}
components = [initUrl, ...components]
@@ -368,27 +373,17 @@ export const init = new Command()
: "")
if (!resolvedBase) {
const { base } = await prompts({
type: "select",
name: "base",
message: `Which ${highlighter.info(
"primitive library"
)} would you like to use?`,
choices: [
{ title: "Radix", value: "radix" },
{ title: "Base", value: "base" },
],
})
if (!base) process.exit(0)
const base = await promptForBase()
resolvedBase = base
options.base = base
}
// Build the --defaults URL now that base is resolved.
if (options.defaults && components.length === 0) {
const presetName = resolvedBase === "base" ? "base-nova" : "radix-nova"
const initUrl = resolveInitUrl(
{
...DEFAULT_PRESETS[presetName],
...DEFAULT_PRESETS.nova,
base: resolvedBase,
rtl: options.rtl ?? false,
},
{ template: options.template }
@@ -450,7 +445,7 @@ export const init = new Command()
const { registryBaseConfig, installStyleIndex } =
await resolveRegistryBaseConfig(components[0], cwd, {
registries: existingConfig?.registries as
| Record<string, unknown>
| z.infer<typeof registryConfigSchema>
| undefined,
})

View File

@@ -216,6 +216,10 @@ export async function getTargetStyleFromConfig(cwd: string, fallback: string) {
return projectInfo?.tailwindVersion === "v4" ? "new-york-v4" : fallback
}
export function getBase(style: string | undefined) {
return style?.startsWith("base-") ? "base" : "radix"
}
export type DeepPartial<T> = {
[P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P]
}

View File

@@ -3,7 +3,6 @@ import { buildUrlAndHeadersForRegistryItem } from "@/src/registry/builder"
import { configWithDefaults } from "@/src/registry/config"
import { REGISTRY_URL } from "@/src/registry/constants"
import { type registryConfigSchema } from "@/src/registry/schema"
import { type Preset } from "@/src/schema"
import { createConfig } from "@/src/utils/get-config"
import { highlighter } from "@/src/utils/highlighter"
import { logger } from "@/src/utils/logger"
@@ -15,37 +14,72 @@ import { type z } from "zod"
const SHADCN_URL = REGISTRY_URL.replace(/\/r\/?$/, "")
export const DEFAULT_PRESETS = {
"radix-nova": {
name: "radix-nova",
title: "Radix",
description: "Nova / Lucide / Geist",
base: "radix",
nova: {
title: "Nova",
description: "Lucide / Geist",
style: "nova",
baseColor: "neutral",
theme: "neutral",
iconLibrary: "lucide",
font: "geist",
menuAccent: "subtle",
menuColor: "default",
menuAccent: "subtle" as const,
menuColor: "default" as const,
radius: "default",
rtl: false,
},
"base-nova": {
name: "base-nova",
title: "Base",
description: "Nova / Lucide / Geist",
base: "base",
style: "nova",
vega: {
title: "Vega",
description: "Lucide / Inter",
style: "vega",
baseColor: "neutral",
theme: "neutral",
iconLibrary: "lucide",
font: "geist",
menuAccent: "subtle",
menuColor: "default",
font: "inter",
menuAccent: "subtle" as const,
menuColor: "default" as const,
radius: "default",
rtl: false,
},
} satisfies Record<string, Preset>
maia: {
title: "Maia",
description: "Hugeicons / Figtree",
style: "maia",
baseColor: "neutral",
theme: "neutral",
iconLibrary: "hugeicons",
font: "figtree",
menuAccent: "subtle" as const,
menuColor: "default" as const,
radius: "default",
rtl: false,
},
lyra: {
title: "Lyra",
description: "Tabler / JetBrains Mono",
style: "lyra",
baseColor: "neutral",
theme: "neutral",
iconLibrary: "hugeicons",
font: "jetbrains-mono",
menuAccent: "subtle" as const,
menuColor: "default" as const,
radius: "default",
rtl: false,
},
mira: {
title: "Mira",
description: "Hugeicons / Inter",
style: "mira",
baseColor: "neutral",
theme: "neutral",
iconLibrary: "hugeicons",
font: "inter",
menuAccent: "subtle" as const,
menuColor: "default" as const,
radius: "default",
rtl: false,
},
}
export function resolveCreateUrl(
searchParams?: Partial<{
@@ -73,7 +107,18 @@ export function resolveCreateUrl(
}
export function resolveInitUrl(
preset: Omit<Preset, "name" | "title" | "description">,
preset: {
base: string
style: string
baseColor: string
theme: string
iconLibrary: string
font: string
rtl: boolean
menuAccent: string
menuColor: string
radius: string
},
options?: { template?: string }
) {
const params = new URLSearchParams({
@@ -96,24 +141,36 @@ export function resolveInitUrl(
return `${SHADCN_URL}/init?${params.toString()}`
}
export async function promptForBase() {
const { base } = await prompts({
type: "select",
name: "base",
message: `Select a ${highlighter.info("component library")}`,
choices: [
{ title: "Radix", value: "radix" },
{ title: "Base", value: "base" },
],
})
if (!base) process.exit(0)
return base as "radix" | "base"
}
export async function promptForPreset(options: {
rtl: boolean
base: string
template?: string
base?: string
}) {
const presets = Object.values(DEFAULT_PRESETS).filter(
(preset) => !options.base || preset.base === options.base
)
const presets = Object.entries(DEFAULT_PRESETS)
const { selectedPreset } = await prompts({
type: "select",
name: "selectedPreset",
message: `Which ${highlighter.info("preset")} would you like to use?`,
choices: [
...presets.map((preset) => ({
...presets.map(([name, preset]) => ({
title: preset.title,
description: preset.description,
value: preset.name,
value: name,
})),
{
title: "Custom",
@@ -131,6 +188,7 @@ export async function promptForPreset(options: {
const createUrl = resolveCreateUrl({
command: "init",
rtl: options.rtl,
base: options.base,
...(options.template && { template: options.template }),
})
logger.break()
@@ -156,19 +214,19 @@ export async function promptForPreset(options: {
process.exit(0)
}
const preset = presets.find((p) => p.name === selectedPreset)
const preset = DEFAULT_PRESETS[selectedPreset as keyof typeof DEFAULT_PRESETS]
if (!preset) {
process.exit(0)
}
return {
url: resolveInitUrl(
{ ...preset, rtl: options.rtl },
{ ...preset, base: options.base, rtl: options.rtl },
{
template: options.template,
}
),
base: preset.base,
base: options.base,
}
}

View File

@@ -3,6 +3,7 @@ import { describe, expect, test } from "vitest"
import {
createConfig,
getBase,
getConfig,
getRawConfig,
} from "../../src/utils/get-config"
@@ -197,6 +198,22 @@ test("get config", async () => {
})
})
describe("getBase", () => {
test("returns radix for radix styles", () => {
expect(getBase("radix-nova")).toBe("radix")
expect(getBase("radix-vega")).toBe("radix")
})
test("returns base for base styles", () => {
expect(getBase("base-nova")).toBe("base")
expect(getBase("base-vega")).toBe("base")
})
test("returns radix for undefined", () => {
expect(getBase(undefined)).toBe("radix")
})
})
describe("createConfig", () => {
test("creates default config when called without arguments", () => {
const config = createConfig()