الأدوات · فصل ١٢

الـ API البرمجي في shadcn

getRegistryItems · resolveRegistryItems · addRegistryItems · loadRegistry · registryItemSchema · encodePreset
🎯 الهدف: تبني أدوات بتاعتك فوق الـ registry، زي scripts أو MCP أو سيرفر، باستخدام الـ imports الرسمية.

الفكرة

الـ shadcn package فيه API برمجي غير الـ CLI. وكل جزء ليه subpath:

imports
import { getRegistryItems } from "shadcn/registry"import { registryItemSchema } from "shadcn/schema"import { encodePreset } from "shadcn/preset"

shadcn/registry — الـ options المشتركة

config

  • النوع: Partial<Config>.
  • الـ default: الـ registries المدمجة بس.
  • registries: بتربط كل namespace بـ URL، ومعاه الـ headers والـ env vars اللي محتاجها.
script.ts
const items = await getRegistryItems(["@nile/money-input"], {  config: { registries: { "@nile": "https://nile.dev/r/{name}.json" } },})

useCache

  • النوع: boolean، والـ default بتاعه true.
  • الـ cache في الـ memory طول عمر الـ process، والـ key بتاعه هو الـ URL بعد الحل.
  • الـ promise نفسه بيتخزن، فلو طلبين لنفس الـ URL في نفس الوقت، بيبقوا fetch واحد.
  • سيبه true في الـ scripts والـ CLI.
  • خليه false في الـ processes اللي شغالة على طول، زي السيرفرات والـ watchers والـ MCP server، لأن الـ registry ممكن تتغير بين الطلبات.

الدوال

getRegistriesConfig(cwd)

بتقرا components.json لو موجود. ولو مش موجود، بتقرا حقل registries من package.json.

getRegistry(name, opts)

  • بتجيب registry واحدة بالاسم (@nile)، أو بـ URL، أو بعنوان GitHub.
  • الفهرس لازم يكون flat: بترفض أي فهرس remote لسه فيه include.

getRegistryItems(names, opts)

  • بتجيب items بأسمائها الكاملة.
  • بترجّع array، وكل item جواه الـ files بالـ content.

resolveRegistryItems(names, opts)

  • بتمشي على registryDependencies لكل item.
  • بتعمل flatten لكل حاجة في object واحد يتسطب: dependencies وfiles وcssVars وdocs.

addRegistryItems(names, opts)

ده shadcn add بس من الكود: بيكتب الملفات، ويسطّب الـ packages، ويضيف الـ env vars والـ CSS والـ Tailwind config.

install.ts
import { addRegistryItems, getRegistriesConfig } from "shadcn/registry"const cwd = process.cwd()const config = await getRegistriesConfig(cwd)await addRegistryItems(["@nile/agent-rules"], { cwd, config, overwrite: false, silent: true })
  • مش بيقرا ملفات الـ config لوحده. لازم تديله config من getRegistriesConfig أو تكتبه بإيدك.
  • config فيه registries بس بيكفي لو كل الـ items والـ dependencies universal، يعني registry:item أو registry:file وكل ملفاتهم ليها target صريح.
  • الـ items التانية محتاجة config كامل للمشروع، فيه الـ aliases والـ resolvedPaths.
  • أي namespace مذكور في item أو dependency لازم يكون موجود في config.
  • بيرمي errors بدل ما يقفل الـ process، وعمره ما بيسأل المستخدم.
  • الملفات الموجودة بتتساب إلا لو overwrite: true.
  • مع npm بيستخدم --force لو فيه تعارض peer dependencies مع React 19.

getRegistries(opts)

بتجيب الـ Registry Directory كـ array، وكل عنصر فيه name وurl وhomepage.

searchRegistries(names, opts)

search.ts
const results = await searchRegistries(["@shadcn", "@nile"], {  query: "button",  types: ["registry:ui"],  limit: 100,  offset: 0,  config,  continueOnError: true, // لو registry وقعت، يكمّل بالباقي})
  • البحث fuzzy.
  • بترجّع pagination وitems، وكل item فيه registry وaddCommandArgument، زي @shadcn/button.

loadRegistry(opts) وloadRegistryItem(name, opts)

دول للـ local، يعني اللي بتستخدمهم في route handlers (فصل ٥):

loadRegistry

بيقرا registry.json من الـ disk ويحل الـ include. وبيرجّع الفهرس من غير محتوى الملفات. والـ options بتاعته cwd وregistryFile.

loadRegistryItem

بيبني item واحد من الـ source، ويقرا كل ملف من الـ disk ويحطه في content.

  • getRegistry وgetRegistryItems: remote من الشبكة، والفهرس لازم يكون flat.
  • loadRegistry وloadRegistryItem: local من الـ disk، وبيحلّوا include لوحدهم.

الـ errors

كل الـ errors بتورث من RegistryError، وتقدر تفرّق بينهم بـ instanceof أو بالـ enum RegistryErrorCode:

errors.ts
import { getRegistry, RegistryNotFoundError, RegistryUnauthorizedError } from "shadcn/registry"try {  await getRegistry("@nile")} catch (error) {  if (error instanceof RegistryNotFoundError) console.log("مش موجودة")  else if (error instanceof RegistryUnauthorizedError) console.log("الـ token غلط")  else throw error}
RegistryErrorRegistryNotFoundErrorRegistryUnauthorizedErrorRegistryForbiddenErrorRegistryFetchErrorRegistryNotConfiguredErrorRegistryLocalFileErrorRegistryParseErrorRegistryValidationErrorRegistryItemNotFoundErrorRegistriesIndexParseErrorRegistryMissingEnvironmentVariablesErrorRegistryInvalidNamespaceError

shadcn/schema — الـ Zod schemas

validate.ts
import { registryItemSchema } from "shadcn/schema"const result = registryItemSchema.safeParse(json)if (!result.success) console.error(result.error)
  • الـ schemas: registrySchema وregistryItemSchema وregistryItemFileSchema وregistryItemTypeSchema وregistryItemCssVarsSchema وregistryItemTailwindSchema وregistryBaseColorSchema وconfigSchema وpresetSchema.
  • الـ types: Registry وRegistryItem وRegistryBaseItem وRegistryFontItem وPreset وConfigJson.

shadcn/preset — الـ theme presets

الـ preset هو كود قصير بيختصر إعدادات الـ theme، زي style ولون أساسي وtheme وradius وخط.

preset.ts
import { encodePreset, decodePreset } from "shadcn/preset"const code = encodePreset({ style: "vega", baseColor: "stone", theme: "blue", radius: "large", font: "geist" })// كود قصير URL-safe وأوله رقم الإصدار، زي "bJ4FLU0"const config = decodePreset(code)  // كل الحقول، واللي ناقص بياخد الـ defaultdecodePreset("not-a-code")         // null
  • encodePreset: أي حقل مش موجود بياخد قيمته من DEFAULT_PRESET_CONFIG.
  • decodePreset: بيرجّع style وbaseColor وtheme وchartColor وiconLibrary وfont وfontHeading وradius وmenuAccent وmenuColor.
  • دوال تانية: isPresetCode وisValidPreset وgenerateRandomConfig وgenerateRandomPreset وtoBase62 وfromBase62.
  • constants: PRESET_BASES وPRESET_STYLES وPRESET_BASE_COLORS وPRESET_THEMES وPRESET_ICON_LIBRARIES وPRESET_FONTS وPRESET_FONT_HEADINGS وPRESET_RADII وPRESET_MENU_ACCENTS وPRESET_MENU_COLORS وPRESET_CHART_COLORS وDEFAULT_PRESET_CONFIG.

خلّصت الفصل لما

🧠 اختبر نفسك
١) MCP server بتاعك بيستخدم getRegistry. ليه useCache: false؟
لأنه شغال على طول، والـ cache بيفضل طول عمر الـ process، فمش هيشوف التغييرات.
٢) getRegistry على فهرس remote فيه include. هيحصل إيه؟
هيرفضه. الفهرس الـ remote لازم يكون flat.
٣) addRegistryItems من غير config لـ item نوعه registry:ui. هيشتغل؟
لأ. محتاج config كامل للمشروع فيه aliases وresolvedPaths. الـ registries بس بتكفي للـ universal items.
٤) مشروع مفيهوش components.json. getRegistriesConfig هتقرا منين؟
من حقل registries في package.json.