Skip to content

feat: add shadcn CLI compatible registry#1175

Draft
te6-in wants to merge 3 commits intodevfrom
feat/shadcn-registry
Draft

feat: add shadcn CLI compatible registry#1175
te6-in wants to merge 3 commits intodevfrom
feat/shadcn-registry

Conversation

@te6-in
Copy link
Copy Markdown
Member

@te6-in te6-in commented Jan 23, 2026

Summary

  • shadcn CLI(npx shadcn@latest add)로 SEED 컴포넌트 설치 지원
  • /r/ 디렉토리에 shadcn 호환 JSON 파일 생성
  • innerDependencies → registryDependencies 변환 처리

Usage

# URL 직접 지정
npx shadcn@latest add "https://seed-design.io/r/action-button.json"

# components.json에 registry 설정 후
npx shadcn@latest add seed/action-button

Test plan

  • bun generate:registry 실행 확인
  • 생성된 JSON 파일 스키마 검증
  • shadcn CLI로 설치 테스트

🤖 Generated with Claude Code

Summary by CodeRabbit

  • 새로운 기능
    • shadcn 호환 레지스트리 출력 추가 — 퍼블릭 경로에 shadcn 형식의 통합 레지스트리 파일 생성
    • 각 항목별 JSON 파일 자동 생성으로 구성요소별 개별 접근성 제공
    • 기존 레지스트리 생성 흐름 유지 및 완료 로그에 shadcn 관련 생성 정보(경로·항목 수) 표시

✏️ Tip: You can customize this high-level summary in your review settings.

- Add shadcn-converter.ts for SEED → shadcn format conversion
- Generate /r/ directory with shadcn-compatible JSON files
- Support `npx shadcn@latest add` command for SEED components
- Handle innerDependencies → registryDependencies conversion
- Remove "use client" directive (shadcn CLI adds it automatically)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@changeset-bot
Copy link
Copy Markdown

changeset-bot bot commented Jan 23, 2026

⚠️ No Changeset found

Latest commit: baf483c

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Jan 23, 2026

📝 Walkthrough

Walkthrough

기존 레지스트리 생성 흐름을 유지하면서, 수집된 항목들을 shadcn 형식으로 변환해 public/r 하위에 개별 {itemId}.json 파일과 통합 registry.json을 생성하도록 기능을 추가했습니다.

Changes

Cohort / File(s) 변경 사항
shadcn 레지스트리 생성
docs/scripts/generate-registry.ts
shadcn 변환 유틸리티를 통합하여 모든 레지스트리 항목을 평탄화하고 public/r 디렉토리 생성, 각 항목별 {itemId}.json 출력 및 통합 registry.json 생성; 기존 레지스트리 출력은 유지
shadcn 변환 유틸리티
docs/scripts/shadcn-converter.ts
신규 모듈 추가: convertToShadcnItem, convertToShadcnRegistry 및 관련 타입/도우미 구현(파일 타입 결정, 경로 변환, 의존성 변환, use client 제거 등)

Sequence Diagram(s)

sequenceDiagram
    participant Gen as generate-registry.ts
    participant Coll as Registry Collector
    participant Conv as shadcn-converter
    participant FS as File System

    Gen->>Coll: 모든 레지스트리 항목 수집
    Coll-->>Gen: items 맵, registryIdMap 반환

    loop 각 항목
        Gen->>Conv: convertToShadcnItem(item, {registryId, baseUrl})
        Conv-->>Gen: ShadcnRegistryItem
        Gen->>FS: write /public/r/{itemId}.json
    end

    Gen->>Conv: convertToShadcnRegistry(registries, {name, homepage, baseUrl})
    Conv-->>Gen: registry.json 구조
    Gen->>FS: write /public/r/registry.json
    Gen->>Gen: 로그 출력 (완료, 경로, 항목수)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰
씨앗 모아 찰칵찰칵, JSON 춤추네 🌱
경로 맞추고 타입 붙여, 하나둘 빛나고
작은 파일들 모여 지도 하나 되고
토끼가 껑충, 레지스트리 완성 축하해! 🎉

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed 제목이 PR의 주요 변경 사항을 명확하게 요약합니다. shadcn CLI 호환 레지스트리 생성이라는 핵심 기능 추가를 잘 표현하고 있습니다.
Docstring Coverage ✅ Passed Docstring coverage is 92.31% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@cloudflare-workers-and-pages
Copy link
Copy Markdown

cloudflare-workers-and-pages bot commented Jan 23, 2026

Deploying seed-design-v3 with  Cloudflare Pages  Cloudflare Pages

Latest commit: baf483c
Status: ✅  Deploy successful!
Preview URL: https://28ef0306.seed-design.pages.dev
Branch Preview URL: https://feat-shadcn-registry.seed-design.pages.dev

View logs

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@docs/scripts/generate-registry.ts`:
- Around line 96-105: The code silently overwrites items when item.id collides
across registries; update the loop that builds allShadcnItems and registryIdMap
to detect duplicates: inside the for (const { index, items } of registries) ...
for (const item of items) block, check if allShadcnItems.has(item.id) and if so
either throw a clear error (including item.id, existing
registryIdMap.get(item.id) and current index.id) to fail the generation, or
apply a deterministic renaming strategy (e.g., prefix or suffix using index.id)
and use the new unique id for both allShadcnItems.set and registryIdMap.set;
ensure whichever approach you pick is applied consistently so duplicate ids
cannot silently overwrite existing entries.

In `@docs/scripts/shadcn-converter.ts`:
- Around line 169-184: convertInnerDependencies currently ignores
registryId/baseUrl and always emits `@seed/`${itemId}; update it to follow the
documented behavior: for each innerDeps entry (function
convertInnerDependencies, parameter innerDeps, and ConvertOptions options) if
registryId equals the current registry id (options.registry.id) emit just the
itemId (e.g., "loading-indicator"), otherwise emit the full registry URL using
the provided base URL (e.g., `${options.baseUrl}/r/lib/${registryId}.json`) for
that registry's dependency; ensure you still return undefined for empty input
and preserve the final return type string[] | undefined.
🧹 Nitpick comments (2)
docs/scripts/shadcn-converter.ts (2)

139-160: 불필요한 분기 제거를 권장합니다.
두 분기 모두 동일한 객체를 반환하므로 가독성만 떨어집니다.

♻️ 제안 변경
-  // registry:page와 registry:file은 target 필수
-  if (fileType === "registry:page" || fileType === "registry:file") {
-    return {
-      path: buildFilePath(snippet.path, registryId),
-      type: fileType,
-      content: removeUseClient(snippet.content),
-      target: buildTargetPath(snippet.path),
-    };
-  }
-
-  return {
-    path: buildFilePath(snippet.path, registryId),
-    type: fileType,
-    content: removeUseClient(snippet.content),
-    target: buildTargetPath(snippet.path),
-  };
+  return {
+    path: buildFilePath(snippet.path, registryId),
+    type: fileType,
+    content: removeUseClient(snippet.content),
+    target: buildTargetPath(snippet.path),
+  };

197-226: 메타 빌더 중복 제거를 권장합니다.
동일 로직이 두 번 반복됩니다.

♻️ 제안 변경
-function buildMeta(item: GeneratedRegistryItem): Record<string, unknown> | undefined {
+function buildMetaBase(
+  item: { deprecated?: boolean; hideFromCLICatalog?: boolean },
+): Record<string, unknown> | undefined {
   const meta: Record<string, unknown> = {};
 
   if (item.deprecated) {
     meta.deprecated = true;
   }
   if (item.hideFromCLICatalog) {
     meta.hideFromCLICatalog = true;
   }
 
   return Object.keys(meta).length > 0 ? meta : undefined;
 }
+
+function buildMeta(item: GeneratedRegistryItem): Record<string, unknown> | undefined {
+  return buildMetaBase(item);
+}
@@
 function buildMetaFromIndexItem(
   item: GeneratedRegistry["items"][number],
 ): Record<string, unknown> | undefined {
-  const meta: Record<string, unknown> = {};
-
-  if (item.deprecated) {
-    meta.deprecated = true;
-  }
-  if (item.hideFromCLICatalog) {
-    meta.hideFromCLICatalog = true;
-  }
-
-  return Object.keys(meta).length > 0 ? meta : undefined;
+  return buildMetaBase(item);
 }

Comment on lines +96 to +105
// 모든 아이템을 수집하여 플랫 구조로 저장
const allShadcnItems: Map<string, GeneratedRegistryItem> = new Map();
const registryIdMap: Map<string, string> = new Map(); // itemId -> registryId

for (const { index, items } of registries) {
for (const item of items) {
allShadcnItems.set(item.id, item);
registryIdMap.set(item.id, index.id);
}
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

아이템 ID 충돌 시 조용히 덮어쓰는 문제가 있습니다.
item.id가 레지스트리 간 중복되면 이전 항목이 소실되어 잘못된 JSON이 생성됩니다. 중복을 감지해 실패시키거나 네이밍 규칙을 변경해야 합니다.

🐛 제안 변경
   for (const { index, items } of registries) {
     for (const item of items) {
+      if (allShadcnItems.has(item.id)) {
+        throw new Error(
+          `Duplicate registry item id: ${item.id} (registries: ${registryIdMap.get(item.id)} and ${index.id})`,
+        );
+      }
       allShadcnItems.set(item.id, item);
       registryIdMap.set(item.id, index.id);
     }
   }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// 모든 아이템을 수집하여 플랫 구조로 저장
const allShadcnItems: Map<string, GeneratedRegistryItem> = new Map();
const registryIdMap: Map<string, string> = new Map(); // itemId -> registryId
for (const { index, items } of registries) {
for (const item of items) {
allShadcnItems.set(item.id, item);
registryIdMap.set(item.id, index.id);
}
}
// 모든 아이템을 수집하여 플랫 구조로 저장
const allShadcnItems: Map<string, GeneratedRegistryItem> = new Map();
const registryIdMap: Map<string, string> = new Map(); // itemId -> registryId
for (const { index, items } of registries) {
for (const item of items) {
if (allShadcnItems.has(item.id)) {
throw new Error(
`Duplicate registry item id: ${item.id} (registries: ${registryIdMap.get(item.id)} and ${index.id})`,
);
}
allShadcnItems.set(item.id, item);
registryIdMap.set(item.id, index.id);
}
}
🤖 Prompt for AI Agents
In `@docs/scripts/generate-registry.ts` around lines 96 - 105, The code silently
overwrites items when item.id collides across registries; update the loop that
builds allShadcnItems and registryIdMap to detect duplicates: inside the for
(const { index, items } of registries) ... for (const item of items) block,
check if allShadcnItems.has(item.id) and if so either throw a clear error
(including item.id, existing registryIdMap.get(item.id) and current index.id) to
fail the generation, or apply a deterministic renaming strategy (e.g., prefix or
suffix using index.id) and use the new unique id for both allShadcnItems.set and
registryIdMap.set; ensure whichever approach you pick is applied consistently so
duplicate ids cannot silently overwrite existing entries.

Comment on lines +169 to +184
function convertInnerDependencies(
innerDeps: GeneratedRegistryItem["innerDependencies"],
options: ConvertOptions,
): string[] | undefined {
if (!innerDeps || innerDeps.length === 0) return undefined;

const result: string[] = [];

for (const { registryId, itemIds } of innerDeps) {
for (const itemId of itemIds) {
result.push(`@seed/${itemId}`);
}
}

return result.length > 0 ? result : undefined;
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

fd shadcn-converter.ts

Repository: daangn/seed-design

Length of output: 94


🏁 Script executed:

cat -n docs/scripts/shadcn-converter.ts | head -200 | tail -100

Repository: daangn/seed-design

Length of output: 3379


🏁 Script executed:

# Get the full context of the function and surrounding code
cat -n docs/scripts/shadcn-converter.ts | sed -n '150,210p'

Repository: daangn/seed-design

Length of output: 1981


🏁 Script executed:

# Search for ConvertOptions interface definition
rg "interface ConvertOptions|type ConvertOptions" -A 10 docs/scripts/

Repository: daangn/seed-design

Length of output: 661


🏁 Script executed:

# Search for references to registryDependencies in the codebase
rg "registryDependencies|innerDependencies" docs/scripts/ -B 2 -A 2

Repository: daangn/seed-design

Length of output: 3046


🏁 Script executed:

# Search for how baseUrl and registryId are used elsewhere
rg "baseUrl|registryId" docs/scripts/shadcn-converter.ts -B 2 -A 2

Repository: daangn/seed-design

Length of output: 2497


🌐 Web query:

shadcn UI registryDependencies format cross-registry dependencies URL

💡 Result:

In a registry-item.json, registryDependencies is an array where each entry can be:

  • A shadcn/ui built-in item name (same registry): "button", "input", etc.
  • A namespaced registry item (cross-registry by namespace): "@acme/input-form"
  • A full URL to a remote registry item JSON (cross-registry by URL): "https://example.com/r/editor.json"

Example:

{
  "registryDependencies": [
    "button",
    "@acme/input-form",
    "https://example.com/r/editor.json"
  ]
}

The shadcn CLI will automatically resolve remote (URL) registry dependencies. [1]

Sources: [1] (ui.shadcn.com)

Citations:


registryDependencies 변환 로직이 함수 문서 및 shadcn 표준과 불일치합니다.

함수의 공식 문서(163-168줄)에서는 다음을 명시합니다:

  • 동일 registry 내 의존성: 항목명만 (예: "loading-indicator")
  • 다른 registry 의존성: 전체 URL (예: "https://seed-design.io/r/lib/manner-temp-level.json")

그러나 현재 구현(177-181줄)은 registryIdbaseUrl을 무시하고 모든 의존성을 @seed/${itemId} 형식으로 생성하므로, 다른 레지스트리 의존성이 정상적으로 해석되지 않습니다. 함수의 문서화된 동작에 맞추도록 수정이 필요합니다.

🐛 제안 변경
 function convertInnerDependencies(
   innerDeps: GeneratedRegistryItem["innerDependencies"],
   options: ConvertOptions,
 ): string[] | undefined {
   if (!innerDeps || innerDeps.length === 0) return undefined;
 
   const result: string[] = [];
 
   for (const { registryId, itemIds } of innerDeps) {
     for (const itemId of itemIds) {
-      result.push(`@seed/${itemId}`);
+      if (registryId === options.registryId) {
+        result.push(itemId);
+      } else {
+        result.push(`${options.baseUrl}/r/${itemId}.json`);
+      }
     }
   }
 
   return result.length > 0 ? result : undefined;
 }
🤖 Prompt for AI Agents
In `@docs/scripts/shadcn-converter.ts` around lines 169 - 184,
convertInnerDependencies currently ignores registryId/baseUrl and always emits
`@seed/`${itemId}; update it to follow the documented behavior: for each innerDeps
entry (function convertInnerDependencies, parameter innerDeps, and
ConvertOptions options) if registryId equals the current registry id
(options.registry.id) emit just the itemId (e.g., "loading-indicator"),
otherwise emit the full registry URL using the provided base URL (e.g.,
`${options.baseUrl}/r/lib/${registryId}.json`) for that registry's dependency;
ensure you still return undefined for empty input and preserve the final return
type string[] | undefined.

@te6-in te6-in marked this pull request as draft March 11, 2026 06:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant