mirror of
https://github.com/shadcn-ui/ui.git
synced 2026-06-27 06:34:12 +00:00
feat: refactor
This commit is contained in:
@@ -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 } =
|
||||
|
||||
@@ -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
|
||||
) {
|
||||
|
||||
@@ -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,
|
||||
})
|
||||
|
||||
|
||||
@@ -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]
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user