Files
shadcn-ui/packages/shadcn/src/registry/errors.ts
shadcn 296feb28a2 feat(shadcn): new mcp server (#8012)
* feat(shadcn): add getRegistriesConfig api

* feat(shadcn): add new mcp command

* feat(shadcn): add get_item_examples_from_registries and get_add_command_for_items

* feat: remove getRegistriesConfig

* chore: changeset
2025-08-13 11:15:26 +04:00

288 lines
8.7 KiB
TypeScript

import { z } from "zod"
// Error codes for programmatic error handling
export const RegistryErrorCode = {
// Network errors
NETWORK_ERROR: "NETWORK_ERROR",
NOT_FOUND: "NOT_FOUND",
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 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 in components.json as follows:
{
"registries": {
"${registryName}": "[URL_TO_REGISTRY]"
}
}`
: `Unknown registry. Make sure it is defined in components.json under "registries".`
super(message, {
code: RegistryErrorCode.NOT_CONFIGURED,
context: { registryName },
suggestion:
"Add the registry configuration to your components.json file. Consult the registry documentation for the correct format.",
})
this.name = "RegistryNotConfiguredError"
}
}
export class RegistryLocalFileError extends RegistryError {
constructor(public readonly filePath: string, cause?: unknown) {
super(`Failed to read local registry file: ${filePath}`, {
code: RegistryErrorCode.LOCAL_FILE_ERROR,
cause,
context: { filePath },
suggestion: "Check if the file exists and you have read permissions.",
})
this.name = "RegistryLocalFileError"
}
}
export class RegistryParseError extends RegistryError {
public readonly parseError: unknown
constructor(public readonly item: string, parseError: unknown) {
let message = `Failed to parse registry item: ${item}`
if (parseError instanceof z.ZodError) {
message = `Failed to parse registry item: ${item}\n${parseError.errors
.map((e) => ` - ${e.path.join(".")}: ${e.message}`)
.join("\n")}`
}
super(message, {
code: RegistryErrorCode.PARSE_ERROR,
cause: parseError,
context: { item },
suggestion:
"The registry item may be corrupted or have an invalid format. Please make sure it returns a valid JSON object. See https://ui.shadcn.com/schema/registry-item.json.",
})
this.parseError = parseError
this.name = "RegistryParseError"
}
}
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) {
let message = `Invalid components.json configuration in ${cwd}.`
if (parseError instanceof z.ZodError) {
message = `Invalid components.json 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 },
suggestion:
"Check your components.json file for syntax errors or invalid configuration. Run 'npx shadcn@latest init' to regenerate a valid configuration.",
})
this.name = "ConfigParseError"
}
}