/
githubmirror
/
ui
Обзор
Документация
Войти
/
githubmirror
/
ui
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
packages/shadcn/src/registry/errors.ts
478 строк
14 KB
shadcn
feat(registry): accept config in addRegistryItems (#11307)
27 июл 2026, 21:25
Не верифицирован
27 июл 2026, 21:25
431da71
Код
Авторство
О чём код?
import { SHADCN_URL } from "@/src/registry/constants" import { z } from "zod" // Error codes for programmatic error handling export const RegistryErrorCode = { // Network errors NETWORK_ERROR: "NETWORK_ERROR", NOT_FOUND: "NOT_FOUND", GONE: "GONE", UNAUTHORIZED: "UNAUTHORIZED", FORBIDDEN: "FORBIDDEN", FETCH_ERROR: "FETCH_ERROR", // Configuration errors NOT_CONFIGURED: "NOT_CONFIGURED", INVALID_CONFIG: "INVALID_CONFIG", MISSING_ENV_VARS: "MISSING_ENV_VARS", // File system errors LOCAL_FILE_ERROR: "LOCAL_FILE_ERROR", // Parsing errors PARSE_ERROR: "PARSE_ERROR", VALIDATION_ERROR: "VALIDATION_ERROR", // Generic errors UNKNOWN_ERROR: "UNKNOWN_ERROR", } as const export type RegistryErrorCode = (typeof RegistryErrorCode)[keyof typeof RegistryErrorCode] export class RegistryError extends Error { public readonly code: RegistryErrorCode public readonly statusCode?: number public readonly context?: Record<string, unknown> public readonly suggestion?: string public readonly timestamp: Date public readonly cause?: unknown constructor( message: string, options: { code?: RegistryErrorCode statusCode?: number cause?: unknown context?: Record<string, unknown> suggestion?: string } = {} ) { super(message) this.name = "RegistryError" this.code = options.code || RegistryErrorCode.UNKNOWN_ERROR this.statusCode = options.statusCode this.cause = options.cause this.context = options.context this.suggestion = options.suggestion this.timestamp = new Date() if (Error.captureStackTrace) { Error.captureStackTrace(this, this.constructor) } } toJSON() { return { name: this.name, message: this.message, code: this.code, statusCode: this.statusCode, context: this.context, suggestion: this.suggestion, timestamp: this.timestamp, stack: this.stack, } } } export class RegistryNotFoundError extends RegistryError { constructor( public readonly url: string, cause?: unknown ) { const message = `The item at ${url} was not found. It may not exist at the registry.` super(message, { code: RegistryErrorCode.NOT_FOUND, statusCode: 404, cause, context: { url }, suggestion: "Check if the item name is correct and the registry URL is accessible.", }) this.name = "RegistryNotFoundError" } } export class RegistryGoneError extends RegistryError { constructor( public readonly url: string, cause?: unknown ) { const message = `The item at ${url} is no longer available. It may have been removed or expired.` super(message, { code: RegistryErrorCode.GONE, statusCode: 410, cause, context: { url }, suggestion: "This resource was previously available but has been permanently removed. Check if a newer version exists or contact the registry maintainer.", }) this.name = "RegistryGoneError" } } export class RegistryUnauthorizedError extends RegistryError { constructor( public readonly url: string, cause?: unknown ) { const message = `You are not authorized to access the item at ${url}. If this is a remote registry, you may need to authenticate.` super(message, { code: RegistryErrorCode.UNAUTHORIZED, statusCode: 401, cause, context: { url }, suggestion: "Check your authentication credentials and environment variables.", }) this.name = "RegistryUnauthorizedError" } } export class RegistryForbiddenError extends RegistryError { constructor( public readonly url: string, cause?: unknown ) { const message = `You are not authorized to access the item at ${url}. If this is a remote registry, you may need to authenticate.` super(message, { code: RegistryErrorCode.FORBIDDEN, statusCode: 403, cause, context: { url }, suggestion: "Check your authentication credentials and environment variables.", }) this.name = "RegistryForbiddenError" } } export class RegistryFetchError extends RegistryError { constructor( public readonly url: string, statusCode?: number, public readonly responseBody?: string, cause?: unknown ) { // Use the error detail from the server if available const baseMessage = statusCode ? `Failed to fetch from registry (${statusCode}): ${url}` : `Failed to fetch from registry: ${url}` const message = typeof cause === "string" && cause ? `${baseMessage} - ${cause}` : baseMessage let suggestion = "Check your network connection and try again." if (statusCode === 404) { suggestion = "The requested resource was not found. Check the URL or item name." } else if (statusCode === 500) { suggestion = "The registry server encountered an error. Try again later." } else if (statusCode && statusCode >= 400 && statusCode < 500) { suggestion = "There was a client error. Check your request parameters." } super(message, { code: RegistryErrorCode.FETCH_ERROR, statusCode, cause, context: { url, responseBody }, suggestion, }) this.name = "RegistryFetchError" } } export class RegistryNotConfiguredError extends RegistryError { constructor(public readonly registryName: string | null) { const message = registryName ? `Unknown registry "${registryName}". Make sure it is defined under "registries" in your components.json or package.json file: { "registries": { "${registryName}": "[URL_TO_REGISTRY]" } }` : `Unknown registry. Make sure it is defined under "registries" in your components.json or package.json file.` super(message, { code: RegistryErrorCode.NOT_CONFIGURED, context: { registryName }, suggestion: 'Add the registry configuration under "registries" in your components.json or package.json file.', }) this.name = "RegistryNotConfiguredError" } } export class RegistryLocalFileError extends RegistryError { constructor( public readonly filePath: string, cause?: unknown, options: { message?: string context?: Record<string, unknown> suggestion?: string } = {} ) { super( options.message ?? `Failed to read local registry file: ${filePath}`, { code: RegistryErrorCode.LOCAL_FILE_ERROR, cause, context: { filePath, ...options.context }, suggestion: options.suggestion ?? "Check if the file exists and you have read permissions.", } ) this.name = "RegistryLocalFileError" } } export class RegistrySourceFileError extends RegistryError { constructor( public readonly filePath: string, cause?: unknown, options: { message?: string context?: Record<string, unknown> suggestion?: string } = {} ) { super( options.message ?? `Failed to read registry source file: ${filePath}`, { code: RegistryErrorCode.FETCH_ERROR, cause, context: { filePath, ...options.context }, suggestion: options.suggestion ?? "Check if the source file exists and is accessible.", } ) this.name = "RegistrySourceFileError" } } export class RegistryParseError extends RegistryError { public readonly parseError: unknown constructor( public readonly item: string, parseError: unknown, options: { subject?: string context?: Record<string, unknown> suggestion?: string } = {} ) { const subject = options.subject ?? "registry item" let message = `Failed to parse ${subject}: ${item}` if (parseError instanceof z.ZodError) { message = `Failed to parse ${subject}: ${item}\n${parseError.errors .map((e) => ` - ${e.path.join(".")}: ${e.message}`) .join("\n")}` } super(message, { code: RegistryErrorCode.PARSE_ERROR, cause: parseError, context: { item, ...options.context }, suggestion: options.suggestion ?? `The registry item may be corrupted or have an invalid format. Please make sure it returns a valid JSON object. See ${SHADCN_URL}/schema/registry-item.json.`, }) this.parseError = parseError this.name = "RegistryParseError" } } export class RegistryValidationError extends RegistryError { constructor( message: string, options: { registryFile?: string cause?: unknown context?: Record<string, unknown> suggestion?: string } = {} ) { super(message, { code: RegistryErrorCode.VALIDATION_ERROR, cause: options.cause, context: { ...(options.registryFile ? { registryFile: options.registryFile } : {}), ...options.context, }, suggestion: options.suggestion ?? "Update the registry.json file and try running the command again.", }) this.name = "RegistryValidationError" } } export class RegistryItemNotFoundError extends RegistryError { constructor(public readonly itemName: string) { super(`Registry item "${itemName}" was not found.`, { code: RegistryErrorCode.NOT_FOUND, statusCode: 404, context: { itemName }, suggestion: "Check that the item name exists in the resolved registry catalog.", }) this.name = "RegistryItemNotFoundError" } } export class RegistryMissingEnvironmentVariablesError extends RegistryError { constructor( public readonly registryName: string, public readonly missingVars: string[] ) { const message = `Registry "${registryName}" requires the following environment variables:\n\n` + missingVars.map((v) => ` • ${v}`).join("\n") super(message, { code: RegistryErrorCode.MISSING_ENV_VARS, context: { registryName, missingVars }, suggestion: "Set the required environment variables to your .env or .env.local file.", }) this.name = "RegistryMissingEnvironmentVariablesError" } } export class RegistryInvalidNamespaceError extends RegistryError { constructor(public readonly name: string) { const message = `Invalid registry namespace: "${name}". Registry names must start with @ (e.g., @shadcn, @v0).` super(message, { code: RegistryErrorCode.VALIDATION_ERROR, context: { name }, suggestion: "Use a valid registry name starting with @ or provide a direct URL to the registry.", }) this.name = "RegistryInvalidNamespaceError" } } export class ConfigMissingError extends RegistryError { constructor(public readonly cwd: string) { const message = `No components.json found in ${cwd} or parent directories.` super(message, { code: RegistryErrorCode.NOT_CONFIGURED, context: { cwd }, suggestion: "Run 'npx shadcn@latest init' to create a components.json file, or check that you're in the correct directory.", }) this.name = "ConfigMissingError" } } export class ConfigParseError extends RegistryError { constructor( public readonly cwd: string, parseError: unknown, public readonly configFile: | "components.json" | "package.json" | "config" = "components.json" ) { const configName = configFile === "package.json" ? 'package.json "registries"' : configFile === "config" ? "provided config" : "components.json" let message = `Invalid ${configName} configuration in ${cwd}.` if (parseError instanceof z.ZodError) { message = `Invalid ${configName} configuration in ${cwd}:\n${parseError.errors .map((e) => ` - ${e.path.join(".")}: ${e.message}`) .join("\n")}` } super(message, { code: RegistryErrorCode.INVALID_CONFIG, cause: parseError, context: { cwd, configFile }, suggestion: configFile === "package.json" ? 'Check the "registries" field in your package.json file for invalid configuration.' : configFile === "config" ? "Pass a valid full project config, or omit resolvedPaths and provide only registry configuration." : "Check your components.json file for syntax errors or invalid configuration. Run 'npx shadcn@latest init' to regenerate a valid configuration.", }) this.name = "ConfigParseError" } } export class RegistriesIndexParseError extends RegistryError { public readonly parseError: unknown constructor(parseError: unknown) { let message = "Failed to parse registries index" if (parseError instanceof z.ZodError) { const invalidNamespaces = parseError.errors .filter((e) => e.path.length > 0) .map((e) => `"${e.path[0]}"`) .filter((v, i, arr) => arr.indexOf(v) === i) // remove duplicates if (invalidNamespaces.length > 0) { message = `Failed to parse registries index. Invalid registry namespace(s): ${invalidNamespaces.join( ", " )}\n${parseError.errors .map((e) => ` - ${e.path.join(".")}: ${e.message}`) .join("\n")}` } else { message = `Failed to parse registries index:\n${parseError.errors .map((e) => ` - ${e.path.join(".")}: ${e.message}`) .join("\n")}` } } super(message, { code: RegistryErrorCode.PARSE_ERROR, cause: parseError, context: { parseError }, suggestion: "The registries index may be corrupted or have invalid registry namespace format. Registry names must start with @ (e.g., @shadcn, @example).", }) this.parseError = parseError this.name = "RegistriesIndexParseError" } } export class InvalidConfigIconLibraryError extends RegistryError { constructor( public readonly iconLibrary: string, public readonly validOptions: string[] ) { const message = `Invalid icon library "${iconLibrary}". Valid options are: ${validOptions.join( ", " )}` super(message, { code: RegistryErrorCode.INVALID_CONFIG, context: { iconLibrary, validOptions }, suggestion: `Update the "iconLibrary" field in your components.json to one of: ${validOptions.join( ", " )}`, }) this.name = "InvalidConfigIconLibraryError" } }