mirror of
https://github.com/shadcn-ui/ui.git
synced 2026-06-27 06:34:12 +00:00
wip
This commit is contained in:
@@ -1,5 +1,8 @@
|
||||
import path from "path"
|
||||
import { runInit } from "@/src/commands/init"
|
||||
import {
|
||||
getTemplateFromFrameworkName,
|
||||
runInit,
|
||||
} from "@/src/commands/init"
|
||||
import { preFlightAdd } from "@/src/preflights/preflight-add"
|
||||
import { getRegistryItems, getShadcnRegistryIndex } from "@/src/registry/api"
|
||||
import { DEPRECATED_COMPONENTS } from "@/src/registry/constants"
|
||||
@@ -13,6 +16,10 @@ import * as ERRORS from "@/src/utils/errors"
|
||||
import { createConfig, getConfig } from "@/src/utils/get-config"
|
||||
import { getProjectInfo } from "@/src/utils/get-project-info"
|
||||
import { handleError } from "@/src/utils/handle-error"
|
||||
import {
|
||||
promptForPreset,
|
||||
resolveRegistryBaseConfig,
|
||||
} from "@/src/utils/presets"
|
||||
import { highlighter } from "@/src/utils/highlighter"
|
||||
import { logger } from "@/src/utils/logger"
|
||||
import { ensureRegistriesInConfig } from "@/src/utils/registries"
|
||||
@@ -52,8 +59,8 @@ export const add = new Command()
|
||||
try {
|
||||
const options = addOptionsSchema.parse({
|
||||
components,
|
||||
cwd: path.resolve(opts.cwd),
|
||||
...opts,
|
||||
cwd: path.resolve(opts.cwd),
|
||||
})
|
||||
|
||||
await loadEnvFiles(options.cwd)
|
||||
@@ -159,6 +166,21 @@ export const add = new Command()
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
// Infer template from project framework.
|
||||
const inferredTemplate = getTemplateFromFrameworkName(
|
||||
projectInfo?.framework.name
|
||||
)
|
||||
|
||||
// Prompt for preset.
|
||||
const initUrl = await promptForPreset({
|
||||
rtl: false,
|
||||
template: inferredTemplate,
|
||||
})
|
||||
|
||||
// Resolve registry:base config.
|
||||
const { registryBaseConfig, installStyleIndex } =
|
||||
await resolveRegistryBaseConfig(initUrl, options.cwd)
|
||||
|
||||
config = await runInit({
|
||||
cwd: options.cwd,
|
||||
yes: true,
|
||||
@@ -169,8 +191,9 @@ export const add = new Command()
|
||||
isNewProject: false,
|
||||
cssVariables: options.cssVariables,
|
||||
rtl: false,
|
||||
installStyleIndex: shouldInstallStyleIndex,
|
||||
components: options.components,
|
||||
installStyleIndex,
|
||||
components: [initUrl, ...(options.components ?? [])],
|
||||
registryBaseConfig,
|
||||
})
|
||||
initHasRun = true
|
||||
}
|
||||
@@ -193,6 +216,11 @@ export const add = new Command()
|
||||
options.cwd = path.resolve(options.cwd, "apps/web")
|
||||
config = await getConfig(options.cwd)
|
||||
} else {
|
||||
// Prompt for preset.
|
||||
const initUrl = await promptForPreset({ rtl: false, template })
|
||||
const { registryBaseConfig, installStyleIndex } =
|
||||
await resolveRegistryBaseConfig(initUrl, options.cwd)
|
||||
|
||||
config = await runInit({
|
||||
cwd: options.cwd,
|
||||
yes: true,
|
||||
@@ -203,8 +231,9 @@ export const add = new Command()
|
||||
isNewProject: true,
|
||||
cssVariables: options.cssVariables,
|
||||
rtl: false,
|
||||
installStyleIndex: shouldInstallStyleIndex,
|
||||
components: options.components,
|
||||
installStyleIndex,
|
||||
components: [initUrl, ...(options.components ?? [])],
|
||||
registryBaseConfig,
|
||||
})
|
||||
initHasRun = true
|
||||
|
||||
|
||||
@@ -3,11 +3,8 @@ import path from "path"
|
||||
import { preFlightInit } from "@/src/preflights/preflight-init"
|
||||
import {
|
||||
getRegistryBaseColors,
|
||||
getRegistryItems,
|
||||
getRegistryStyles,
|
||||
} from "@/src/registry/api"
|
||||
import { buildUrlAndHeadersForRegistryItem } from "@/src/registry/builder"
|
||||
import { configWithDefaults } from "@/src/registry/config"
|
||||
import { BUILTIN_REGISTRIES } from "@/src/registry/constants"
|
||||
import { clearRegistryContext } from "@/src/registry/context"
|
||||
import { isUrl } from "@/src/registry/utils"
|
||||
@@ -43,15 +40,15 @@ import { highlighter } from "@/src/utils/highlighter"
|
||||
import { logger } from "@/src/utils/logger"
|
||||
import {
|
||||
DEFAULT_PRESETS,
|
||||
resolveCreateUrl,
|
||||
promptForPreset,
|
||||
resolveInitUrl,
|
||||
resolveRegistryBaseConfig,
|
||||
} from "@/src/utils/presets"
|
||||
import { ensureRegistriesInConfig } from "@/src/utils/registries"
|
||||
import { spinner } from "@/src/utils/spinner"
|
||||
import { Command } from "commander"
|
||||
import deepmerge from "deepmerge"
|
||||
import fsExtra from "fs-extra"
|
||||
import open from "open"
|
||||
import prompts from "prompts"
|
||||
import { z } from "zod"
|
||||
|
||||
@@ -212,68 +209,9 @@ export const init = new Command()
|
||||
const presetArg = options.preset === true ? true : options.preset
|
||||
|
||||
if (presetArg === true) {
|
||||
const { selectedPreset } = await prompts({
|
||||
type: "select",
|
||||
name: "selectedPreset",
|
||||
message: `Which ${highlighter.info(
|
||||
"preset"
|
||||
)} would you like to use?`,
|
||||
choices: [
|
||||
...presets.map((preset) => ({
|
||||
title: preset.title,
|
||||
description: preset.description,
|
||||
value: preset.name,
|
||||
})),
|
||||
{
|
||||
title: "Custom",
|
||||
description: "Build your own on https://ui.shadcn.com",
|
||||
value: "custom",
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
if (!selectedPreset) {
|
||||
process.exit(0)
|
||||
}
|
||||
|
||||
if (selectedPreset === "custom") {
|
||||
const createUrl = resolveCreateUrl({
|
||||
command: "init",
|
||||
rtl: options.rtl,
|
||||
...(options.template && { template: options.template }),
|
||||
})
|
||||
logger.break()
|
||||
logger.log(
|
||||
` Build your custom preset on ${highlighter.info(createUrl)}`
|
||||
)
|
||||
logger.log(
|
||||
` Then ${highlighter.info(
|
||||
"copy and run the command"
|
||||
)} from ui.shadcn.com.`
|
||||
)
|
||||
logger.break()
|
||||
|
||||
const { proceed } = await prompts({
|
||||
type: "confirm",
|
||||
name: "proceed",
|
||||
message: "Open in browser?",
|
||||
initial: true,
|
||||
})
|
||||
|
||||
if (proceed) {
|
||||
await open(createUrl)
|
||||
}
|
||||
|
||||
process.exit(0)
|
||||
}
|
||||
|
||||
const preset = presets.find((p) => p.name === selectedPreset)
|
||||
if (!preset) {
|
||||
process.exit(0)
|
||||
}
|
||||
const initUrl = resolveInitUrl({
|
||||
...preset,
|
||||
const initUrl = await promptForPreset({
|
||||
rtl: options.rtl,
|
||||
template: options.template,
|
||||
})
|
||||
components = [initUrl, ...components]
|
||||
}
|
||||
@@ -310,41 +248,14 @@ export const init = new Command()
|
||||
// We need to check if we're initializing with a new style.
|
||||
// This will allow us to determine if we need to install the base style.
|
||||
if (components.length > 0) {
|
||||
// We don't know the full config at this point.
|
||||
// So we'll use a shadow config to fetch the first item.
|
||||
let shadowConfig = configWithDefaults(
|
||||
createConfig({
|
||||
resolvedPaths: {
|
||||
cwd: parsedOptions.cwd,
|
||||
},
|
||||
})
|
||||
)
|
||||
|
||||
// Check if there's a components.json file.
|
||||
// If so, we'll merge with our shadow config.
|
||||
// Back up existing components.json if it exists.
|
||||
// Since components.json might not be valid at this point,
|
||||
// temporarily rename it to allow preflight to run.
|
||||
const componentsJsonPath = path.resolve(
|
||||
parsedOptions.cwd,
|
||||
"components.json"
|
||||
)
|
||||
if (fsExtra.existsSync(componentsJsonPath)) {
|
||||
const existingConfig = await fsExtra.readJson(componentsJsonPath)
|
||||
const config = rawConfigSchema.partial().parse(existingConfig)
|
||||
const baseConfig = createConfig({
|
||||
resolvedPaths: {
|
||||
cwd: parsedOptions.cwd,
|
||||
},
|
||||
})
|
||||
shadowConfig = configWithDefaults({
|
||||
...config,
|
||||
resolvedPaths: {
|
||||
...baseConfig.resolvedPaths,
|
||||
cwd: parsedOptions.cwd,
|
||||
},
|
||||
})
|
||||
|
||||
// Since components.json might not be valid at this point.
|
||||
// Temporarily rename components.json to allow preflight to run.
|
||||
// We'll rename it back after preflight.
|
||||
componentsJsonBackupPath =
|
||||
createFileBackup(componentsJsonPath) ?? undefined
|
||||
if (!componentsJsonBackupPath) {
|
||||
@@ -354,37 +265,16 @@ export const init = new Command()
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure all registries used in components are configured.
|
||||
const { config: updatedConfig } = await ensureRegistriesInConfig(
|
||||
components,
|
||||
shadowConfig,
|
||||
{
|
||||
silent: true,
|
||||
writeFile: false,
|
||||
}
|
||||
)
|
||||
shadowConfig = updatedConfig
|
||||
// Resolve registry:base config from the first component.
|
||||
const { registryBaseConfig, installStyleIndex } =
|
||||
await resolveRegistryBaseConfig(components[0], parsedOptions.cwd)
|
||||
|
||||
// This forces a shadowConfig validation early in the process.
|
||||
buildUrlAndHeadersForRegistryItem(components[0], shadowConfig)
|
||||
|
||||
const [item] = await getRegistryItems([components[0]], {
|
||||
config: shadowConfig,
|
||||
})
|
||||
|
||||
if (item?.extends === "none") {
|
||||
if (!installStyleIndex) {
|
||||
parsedOptions.installStyleIndex = false
|
||||
}
|
||||
|
||||
if (item?.type === "registry:base") {
|
||||
if (item.config) {
|
||||
// Merge config values into shadowConfig.
|
||||
shadowConfig = configWithDefaults(
|
||||
deepmerge(shadowConfig, item.config)
|
||||
)
|
||||
// Store config to be merged into components.json later.
|
||||
parsedOptions.registryBaseConfig = item.config
|
||||
}
|
||||
if (registryBaseConfig) {
|
||||
parsedOptions.registryBaseConfig = registryBaseConfig
|
||||
}
|
||||
}
|
||||
|
||||
@@ -730,7 +620,7 @@ async function promptForMinimalConfig(
|
||||
})
|
||||
}
|
||||
|
||||
function getTemplateFromFrameworkName(frameworkName?: string) {
|
||||
export function getTemplateFromFrameworkName(frameworkName?: string) {
|
||||
if (frameworkName === "next-app" || frameworkName === "next-pages") {
|
||||
return "next"
|
||||
}
|
||||
|
||||
@@ -1,5 +1,14 @@
|
||||
import { getRegistryItems } from "@/src/registry/api"
|
||||
import { buildUrlAndHeadersForRegistryItem } from "@/src/registry/builder"
|
||||
import { configWithDefaults } from "@/src/registry/config"
|
||||
import { REGISTRY_URL } from "@/src/registry/constants"
|
||||
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"
|
||||
import { ensureRegistriesInConfig } from "@/src/utils/registries"
|
||||
import open from "open"
|
||||
import prompts from "prompts"
|
||||
|
||||
const SHADCN_URL = REGISTRY_URL.replace(/\/r\/?$/, "")
|
||||
|
||||
@@ -79,3 +88,110 @@ export function resolveInitUrl(
|
||||
|
||||
return `${SHADCN_URL}/init?${params.toString()}`
|
||||
}
|
||||
|
||||
export async function promptForPreset(options: {
|
||||
rtl: boolean
|
||||
template?: string
|
||||
}) {
|
||||
const presets = Object.values(DEFAULT_PRESETS)
|
||||
|
||||
const { selectedPreset } = await prompts({
|
||||
type: "select",
|
||||
name: "selectedPreset",
|
||||
message: `Which ${highlighter.info("preset")} would you like to use?`,
|
||||
choices: [
|
||||
...presets.map((preset) => ({
|
||||
title: preset.title,
|
||||
description: preset.description,
|
||||
value: preset.name,
|
||||
})),
|
||||
{
|
||||
title: "Custom",
|
||||
description: "Build your own on https://ui.shadcn.com",
|
||||
value: "custom",
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
if (!selectedPreset) {
|
||||
process.exit(0)
|
||||
}
|
||||
|
||||
if (selectedPreset === "custom") {
|
||||
const createUrl = resolveCreateUrl({
|
||||
command: "init",
|
||||
rtl: options.rtl,
|
||||
...(options.template && { template: options.template }),
|
||||
})
|
||||
logger.break()
|
||||
logger.log(
|
||||
` Build your custom preset on ${highlighter.info(createUrl)}`
|
||||
)
|
||||
logger.log(
|
||||
` Then ${highlighter.info(
|
||||
"copy and run the command"
|
||||
)} from ui.shadcn.com.`
|
||||
)
|
||||
logger.break()
|
||||
|
||||
const { proceed } = await prompts({
|
||||
type: "confirm",
|
||||
name: "proceed",
|
||||
message: "Open in browser?",
|
||||
initial: true,
|
||||
})
|
||||
|
||||
if (proceed) {
|
||||
await open(createUrl)
|
||||
}
|
||||
|
||||
process.exit(0)
|
||||
}
|
||||
|
||||
const preset = presets.find((p) => p.name === selectedPreset)
|
||||
if (!preset) {
|
||||
process.exit(0)
|
||||
}
|
||||
|
||||
return resolveInitUrl({ ...preset, rtl: options.rtl })
|
||||
}
|
||||
|
||||
export async function resolveRegistryBaseConfig(
|
||||
initUrl: string,
|
||||
cwd: string
|
||||
) {
|
||||
// Use a shadow config to fetch the registry:base item.
|
||||
let shadowConfig = configWithDefaults(
|
||||
createConfig({
|
||||
resolvedPaths: {
|
||||
cwd,
|
||||
},
|
||||
})
|
||||
)
|
||||
|
||||
// Ensure all registries used in the init URL are configured.
|
||||
const { config: updatedConfig } = await ensureRegistriesInConfig(
|
||||
[initUrl],
|
||||
shadowConfig,
|
||||
{
|
||||
silent: true,
|
||||
writeFile: false,
|
||||
}
|
||||
)
|
||||
shadowConfig = updatedConfig
|
||||
|
||||
// This forces a shadowConfig validation early in the process.
|
||||
buildUrlAndHeadersForRegistryItem(initUrl, shadowConfig)
|
||||
|
||||
const [item] = await getRegistryItems([initUrl], {
|
||||
config: shadowConfig,
|
||||
})
|
||||
|
||||
return {
|
||||
registryBaseConfig:
|
||||
item?.type === "registry:base" && item.config
|
||||
? item.config
|
||||
: undefined,
|
||||
installStyleIndex: item?.extends !== "none",
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user