@seed-design/cli 1.0.0 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,7 +1,76 @@
1
1
  # @seed-design/cli
2
2
 
3
- ### How to test
3
+ SEED Design 컴포넌트를 프로젝트에 추가하기 위한 CLI 도구입니다.
4
4
 
5
- - Run `bun dev` in `@seed-design/component-docs` (snippet download)
6
- - Run `bun dev` here (watch mode)
7
- - You can test `bun seed-design <command>` in `@seed-design/example/cli` terminal
5
+ ## 개발 환경 설정
6
+
7
+ ### 의존성 설치
8
+
9
+ ```bash
10
+ bun install
11
+ ```
12
+
13
+ ### 환경 변수 설정 (선택사항)
14
+
15
+ PostHog 텔레메트리를 사용하려면 `.env` 파일을 생성하세요:
16
+
17
+ ```bash
18
+ # packages/cli/.env
19
+ POSTHOG_API_KEY=your-api-key
20
+ POSTHOG_HOST=https://us.i.posthog.com
21
+
22
+ # 텔레메트리 비활성화 (로컬 개발 시)
23
+ DISABLE_TELEMETRY=true
24
+ ```
25
+
26
+ **참고**: 환경 변수가 없어도 CLI는 정상적으로 동작합니다. 텔레메트리만 비활성화됩니다.
27
+
28
+ ## 개발
29
+
30
+ ### Dev 모드 실행
31
+
32
+ Watch 모드로 CLI를 실행합니다:
33
+
34
+ ```bash
35
+ bun dev
36
+ ```
37
+
38
+ Dev 모드에서는:
39
+ - 코드 변경 시 자동으로 재빌드됩니다
40
+ - `NODE_ENV=dev`로 설정되어 텔레메트리 이벤트가 콘솔에만 출력됩니다
41
+ - PostHog API 호출이 실제로 발생하지 않습니다
42
+
43
+ ### 로컬 테스트
44
+
45
+ 1. `@seed-design/docs`에서 `bun dev` 실행 (snippet 서버)
46
+ 2. `packages/cli`에서 `bun dev` 실행 (watch 모드)
47
+ 3. `bun run ./bin/index.mjs` 실행하여 CLI 명령어 테스트:
48
+
49
+ ```bash
50
+ bun run ./bin/index.mjs init
51
+ bun run ./bin/index.mjs add ui:action-button
52
+ ```
53
+
54
+ ## 빌드
55
+
56
+ 프로덕션 빌드:
57
+
58
+ ```bash
59
+ bun run build
60
+ ```
61
+
62
+ 빌드 결과물:
63
+ - `bin/index.mjs` - 번들링 및 minify된 CLI 실행 파일
64
+ - 환경 변수 (`POSTHOG_API_KEY`, `POSTHOG_HOST`)가 빌드 시 번들에 주입됩니다
65
+
66
+ ## 테스트
67
+
68
+ ```bash
69
+ bun test
70
+ ```
71
+
72
+ ## 배포
73
+
74
+ 이 패키지는 [Changesets](https://github.com/changesets/changesets)을 통해 자동으로 배포됩니다.
75
+
76
+ **참고**: 배포 시 GitHub Secrets에 `POSTHOG_API_KEY`와 `POSTHOG_HOST`가 설정되어 있어야 텔레메트리가 활성화됩니다.
package/bin/index.mjs CHANGED
@@ -1,7 +1,15 @@
1
1
  #!/usr/bin/env node
2
- import*as A from"@clack/prompts";import{cosmiconfig as ge}from"cosmiconfig";import{execa as ue}from"execa";import{z as O}from"zod";import de from"picocolors";var a=e=>de.cyan(e);import{detect as fe}from"@antfu/ni";async function _(e){let t=await fe({programmatic:!0,cwd:e});return t==="yarn@berry"?"yarn":t==="pnpm@6"?"pnpm":t==="bun"?"bun":t==="deno"?"deno":t??"npm"}var Q="seed-design",ye=ge(Q,{searchPlaces:[`${Q}.json`]}),Z=O.object({$schema:O.string().optional(),rsc:O.coerce.boolean().default(!1),tsx:O.coerce.boolean().default(!0),path:O.string()}).strict();async function U(e){let t=await he(e);return t?Z.parse(t):null}async function he(e){try{let t=await ye.search(e);return Z.parse(t.config)}catch{A.log.error("\uD504\uB85C\uC81D\uD2B8 \uB8E8\uD2B8 \uACBD\uB85C\uC5D0 `seed-design.json` \uD30C\uC77C\uC774 \uC5C6\uC5B4\uC694."),await A.confirm({message:"seed-design.json \uD30C\uC77C\uC744 \uC0DD\uC131\uD558\uC2DC\uACA0\uC5B4\uC694?"})||(A.outro(a("\uC791\uC5C5\uC774 \uCDE8\uC18C\uB410\uC5B4\uC694.")),process.exit(1));let r=await _(e);await ue(r,["seed-design","init","--default"],{cwd:e}),A.log.message("seed-design.json \uD30C\uC77C\uC774 \uC0DD\uC131\uB410\uC5B4\uC694.")}}function z({selectedItemKeys:e,publicRegistries:t}){let r=[],c=new Set;function i(s,o){let l=r.find(n=>n.registryId===s);if(!l?.items.some(n=>n.id===o.id)){if(l?l.items.push(o):r.push({registryId:s,items:[o]}),o.dependencies?.length)for(let n of o.dependencies)c.add(n);if(o.innerDependencies?.length)for(let n of o.innerDependencies)for(let w of n.itemIds){let x=t.find(j=>j.id===n.registryId)?.items.find(j=>j.id===w);if(!x)throw new Error(`Cannot find dependency item: ${n.registryId}:${w}`);i(n.registryId,x)}}}for(let s of e){let[o,...l]=s.split(":"),n=l.join(":");if(!o||!n)throw new Error(`Invalid snippet format: "${s}"`);let w=t.find(x=>x.id===o)?.items.find(x=>x.id===n);if(!w)throw new Error(`Cannot find snippet: "${s}"`);i(o,w)}return{registryItemsToAdd:r,npmDependenciesToAdd:c}}import*as Y from"@clack/prompts";import{z as d}from"zod";var X=d.object({id:d.string(),description:d.string().optional(),deprecated:d.boolean().optional(),hideFromCLICatalog:d.boolean().optional(),dependencies:d.array(d.string()).optional(),innerDependencies:d.array(d.object({registryId:d.string(),itemIds:d.array(d.string())})).optional(),snippets:d.array(d.object({path:d.string(),content:d.string()}))}),W=d.object({id:d.string(),hideFromCLICatalog:d.boolean().optional(),items:d.array(X.omit({snippets:!0}).extend({snippets:d.array(d.object({path:d.string()}))}))}),ee=d.array(d.object({id:d.string()}));async function M({baseUrl:e}){let t=await fetch(`${e}/__registry__/index.json`);if(!t.ok)throw new Error(`Failed to fetch registries: ${t.status} ${t.statusText}`);let r=await t.json(),{success:c,data:i,error:s}=ee.safeParse(r);if(!c)throw new Error(`Failed to parse registries: ${s?.message}`);return i}async function K({baseUrl:e,registryId:t}){let r=await fetch(`${e}/__registry__/${t}/index.json`);if(!r.ok)throw new Error(`Failed to fetch ${t} registry: ${r.status} ${r.statusText}`);let c=await r.json(),{success:i,data:s,error:o}=W.safeParse(c);if(!i)throw new Error(`Failed to parse ${t} registry: ${o?.message}`);return s}async function we({baseUrl:e,registryId:t,registryItemId:r}){let c=await fetch(`${e}/__registry__/${t}/${r}.json`);if(!c.ok)throw new Error(`Failed to fetch ${r}: ${c.status} ${c.statusText}`);let i=await c.json(),{success:s,data:o,error:l}=X.safeParse(i);if(!s)throw new Error(`Failed to parse ${r}: ${l?.message}`);return o}async function te({baseUrl:e,registryId:t,registryItemIds:r}){return await Promise.all(r.map(async c=>{try{return await we({baseUrl:e,registryId:t,registryItemId:c})}catch(i){let s=await fetch(`${e}/__registry__/${t}/index.json`);if(!s.ok)throw new Error(`${t} \uB808\uC9C0\uC2A4\uD2B8\uB9AC\uB97C \uAC00\uC838\uC624\uC9C0 \uBABB\uD588\uC5B4\uC694: ${s.status} ${s.statusText}`);let o=await s.json(),{success:l,data:n}=W.safeParse(o);throw l?(Y.log.error(`${c} \uC2A4\uB2C8\uD3AB\uC774 ${t} \uB808\uC9C0\uC2A4\uD2B8\uB9AC\uC5D0 \uC5C6\uC5B4\uC694.`),Y.log.info(`${t} \uB808\uC9C0\uC2A4\uD2B8\uB9AC\uC5D0 \uC874\uC7AC\uD558\uB294 \uC2A4\uB2C8\uD3AB:
3
- ${n.items.map(w=>w.id).join(`
4
- `)}`),i):new Error(`Failed to parse registry index for ${t}`)}}))}import{promises as Re}from"fs";import{tmpdir as je}from"os";import ie from"path";import{transformFromAstSync as xe}from"@babel/core";import be from"@babel/plugin-transform-typescript";import*as J from"recast";import{parse as Ie}from"@babel/parser";var $e={sourceType:"module",allowImportExportEverywhere:!0,allowReturnOutsideFunction:!0,startLine:1,tokens:!0,plugins:["asyncGenerators","bigInt","classPrivateMethods","classPrivateProperties","classProperties","classStaticBlock","decimal","decorators-legacy","doExpressions","dynamicImport","exportDefaultFrom","exportNamespaceFrom","functionBind","functionSent","importAssertions","importMeta","nullishCoalescingOperator","numericSeparator","objectRestSpread","optionalCatchBinding","optionalChaining",["pipelineOperator",{proposal:"minimal"}],["recordAndTuple",{syntaxType:"hash"}],"throwExpressions","topLevelAwait","v8intrinsic","typescript","jsx"]},re=async({sourceFile:e,config:t})=>{let r=e.getFullText();if(t.tsx)return r;let c=J.parse(r,{parser:{parse:s=>Ie(s,$e)}}),i=xe(c,r,{cloneInputAst:!1,code:!1,ast:!0,plugins:[be],configFile:!1});if(!i||!i.ast)throw new Error("Failed to transform JSX");return J.print(i.ast).code};import{SyntaxKind as Pe}from"ts-morph";var se=async({sourceFile:e,config:t})=>{if(t.rsc)return e;let r=e.getFirstChildByKind(Pe.ExpressionStatement);if(!r)return e;let c=r.getExpression();if(!c)return e;let i=c.getText().trim();if(i!=='"use client"'&&i!=="'use client'")return e;let s=r.getText(),o=r.getFullText();if(s.trim()===o.trim())return e;let n=o.replace(s,"").replace(/^\s*\n/,"").replace(/\n\s*$/,"");return r.replaceWithText(n),e};import{Project as Ce,ScriptKind as Se}from"ts-morph";var Ae=[se],ve=new Ce({compilerOptions:{}});async function Te(e){let t=await Re.mkdtemp(ie.join(je(),"seed-design-"));return ie.join(t,e)}async function oe(e){let t=await Te(e.filename),r=ve.createSourceFile(t,e.raw,{scriptKind:Se.TSX});for(let c of Ae)c({sourceFile:r,...e});return await re({sourceFile:r,...e})}import*as ne from"@clack/prompts";import q from"fs-extra";import N from"path";async function L({registryItemsToAdd:e,rootPath:t,cwd:r,baseUrl:c,config:i}){let s=[];for(let{registryId:o,items:l}of e){let n=N.join(t,o);q.ensureDirSync(n);let w=await te({baseUrl:c,registryId:o,registryItemIds:l.map(x=>x.id)});for(let{id:x,snippets:j}of w){let $=await Promise.all(j.map(async h=>{let P=await oe({filename:h.path,config:i,raw:h.content}),R=N.join(n,h.path);return i.tsx||(R=R.replace(/\.tsx$/,".jsx"),R=R.replace(/\.ts$/,".js")),{filePath:R,content:P,relativePath:N.relative(r,R),name:`${o}:${x}`}}));await Promise.all($.map(async({filePath:h,content:P})=>{await q.ensureDir(N.dirname(h)),await q.writeFile(h,P)}));let I=$.map(({name:h,relativePath:P})=>({name:h,path:P}));s.push(...I),ne.log.success(`${a(`${o}:${x}`)} \uAD00\uB828 \uC2A4\uB2C8\uD3AB \uB2E4\uC6B4\uB85C\uB4DC \uC644\uB8CC: ${a(I.map(h=>h.path).join(", "))}`)}}}import*as f from"@clack/prompts";import _e from"path";import{z as E}from"zod";var B="https://seed-design.io";import*as ae from"@clack/prompts";import{execa as Fe}from"execa";import Ee from"findup-sync";import ke from"fs-extra";var De="package.json";function Oe(){let e=Ee(De);if(!e)throw new Error("No package.json file found in the project.");return e}function V(){let e=Oe();return ke.readJSONSync(e)}async function G({cwd:e,deps:t,dev:r=!1}){let{start:c,stop:i}=ae.spinner(),s=await _(e),l={...V().dependencies},n=new Set(t.filter(I=>!l[I])),w=new Set(t.filter(I=>l[I]));if(!n.size)return{installed:new Set,filtered:n};c("\uC758\uC874\uC131 \uC124\uCE58\uC911...");let $=[s==="npm"?"install":"add",r?"-D":null,...n].filter(Boolean);try{await Fe(s,$,{cwd:e})}catch(I){console.error(`\uC758\uC874\uC131 \uC124\uCE58 \uC2E4\uD328: ${I}`),process.exit(1)}return i("\uC758\uC874\uC131 \uC124\uCE58\uAC00 \uC644\uB8CC\uB410\uC5B4\uC694."),{installed:n,filtered:w}}var Ue=E.object({itemIds:E.array(E.string()).optional(),all:E.boolean(),cwd:E.string(),baseUrl:E.string().optional()}),ce=e=>{e.command("add [...item-ids]","add items").option("-a, --all","[Deprecated] Add all items",{default:!1}).option("-c, --cwd <cwd>","the working directory. defaults to the current directory.",{default:process.cwd()}).option("-u, --baseUrl <baseUrl>","the base url of the registry. defaults to the current directory.",{default:B}).example("seed-design add ui:action-button").example("seed-design add ui:alert-dialog").action(async(t,r)=>{f.intro("seed-design add");let{success:c,data:{all:i,...s},error:o}=Ue.safeParse({itemIds:t,...r});c||(f.log.error(`\uC798\uBABB\uB41C \uC635\uC158\uC774\uC5D0\uC694: ${o?.message}`),process.exit(1)),i&&(f.log.error("`--all` \uC635\uC158\uC740 \uB354 \uC774\uC0C1 \uC9C0\uC6D0\uB418\uC9C0 \uC54A\uC544\uC694. \uB300\uC2E0 `seed-design add-all` \uBA85\uB839\uC5B4\uB97C \uC0AC\uC6A9\uD574\uC8FC\uC138\uC694."),process.exit(1));let l=s.cwd,n=s.baseUrl,w=await U(l),x=_e.resolve(l,w.path),{start:j,stop:$}=f.spinner();j("Registry\uB97C \uAC00\uC838\uC624\uACE0 \uC788\uC5B4\uC694...");let I=await Promise.all((await M({baseUrl:n})).map(async({id:b})=>K({baseUrl:n,registryId:b})));$("Registry\uB97C \uAC00\uC838\uC654\uC5B4\uC694.");let h=await(async()=>{if(s.itemIds.length>0)return s.itemIds;let b=await f.multiselect({message:"\uCD94\uAC00\uD560 \uD56D\uBAA9\uC744 \uC120\uD0DD\uD574\uC8FC\uC138\uC694 (\uC2A4\uD398\uC774\uC2A4 \uBC14\uB85C \uC5EC\uB7EC \uAC1C \uC120\uD0DD \uAC00\uB2A5)",options:I.filter(({hideFromCLICatalog:m})=>!m).flatMap(({id:m,items:p})=>p.filter(({hideFromCLICatalog:y})=>!y).sort((y,S)=>y.id.localeCompare(S.id)).map(({id:y,description:S,deprecated:C})=>({label:`${C?"(deprecated) ":""}${a(m)}:${y}`,value:`${m}:${y}`,hint:S,deprecated:C,registryItemCount:p.length}))).sort((m,p)=>m.deprecated!==p.deprecated?m.deprecated?1:-1:p.registryItemCount-m.registryItemCount)});return f.isCancel(b)&&(f.log.error("\uCDE8\uC18C\uB418\uC5C8\uC5B4\uC694."),process.exit(0)),b})();h?.length||(f.log.error("\uD56D\uBAA9\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC5B4\uC694."),process.exit(0)),f.log.message(`\uC120\uD0DD\uB41C \uD56D\uBAA9: ${a(h.join(", "))}`);let P=[];for(let b of h){let[m,...p]=b.split(":"),y=p.join(":");(!m||!y)&&(f.log.error(`${a(b)}: \uD56D\uBAA9 \uC774\uB984\uC774 \uC798\uBABB\uB418\uC5C8\uC5B4\uC694. ${a("ui:action-button")}\uACFC \uAC19\uC740 \uD615\uC2DD\uC73C\uB85C \uC785\uB825\uD574\uBCF4\uC138\uC694.`),process.exit(1));let S=I.find(C=>C.id===m)?.items.find(C=>C.id===y);if(S||(f.log.error(`${a(b)}: \uD56D\uBAA9\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC5B4\uC694.`),process.exit(1)),S.deprecated){let C=await f.confirm({message:`${a(S.id)}: deprecated \uB418\uC5C8\uC5B4\uC694. \uCD94\uAC00\uD560\uAE4C\uC694?`,initialValue:!1});if(C===!1||f.isCancel(C)){f.log.info(`${a(S.id)}: \uCD94\uAC00\uD558\uC9C0 \uC54A\uC744\uAC8C\uC694.`);continue}}P.push(b)}let{registryItemsToAdd:R,npmDependenciesToAdd:F}=z({selectedItemKeys:P,publicRegistries:I});f.log.info(`\uCD94\uAC00\uD560 \uD56D\uBAA9: ${a(R.map(b=>b.items.map(m=>`${b.registryId}:${m.id}`).join(", ")).join(", ")||"\uC5C6\uC74C")}
2
+ import*as k from"@clack/prompts";import{cosmiconfig as $e}from"cosmiconfig";import{execa as Ie}from"execa";import{z}from"zod";import be from"picocolors";var i=e=>be.cyan(e);import{detect as xe}from"@antfu/ni";async function B(e){let t=await xe({programmatic:!0,cwd:e});return t==="yarn@berry"?"yarn":t==="pnpm@6"?"pnpm":t==="bun"?"bun":t==="deno"?"deno":t??"npm"}var se="seed-design",Pe=$e(se,{searchPlaces:[`${se}.json`]}),oe=z.object({$schema:z.string().optional(),rsc:z.coerce.boolean().default(!1),tsx:z.coerce.boolean().default(!0),path:z.string(),telemetry:z.coerce.boolean().optional().default(!0)}).strict();async function M(e){let t=await Se(e);return t?oe.parse(t):null}async function Se(e){try{let t=await Pe.search(e);return oe.parse(t.config)}catch{k.log.error("\uD504\uB85C\uC81D\uD2B8 \uB8E8\uD2B8 \uACBD\uB85C\uC5D0 `seed-design.json` \uD30C\uC77C\uC774 \uC5C6\uC5B4\uC694."),await k.confirm({message:"seed-design.json \uD30C\uC77C\uC744 \uC0DD\uC131\uD558\uC2DC\uACA0\uC5B4\uC694?"})||(k.outro(i("\uC791\uC5C5\uC774 \uCDE8\uC18C\uB410\uC5B4\uC694.")),process.exit(1));let o=await B(e);await Ie(o,["seed-design","init","--default"],{cwd:e}),k.log.message("seed-design.json \uD30C\uC77C\uC774 \uC0DD\uC131\uB410\uC5B4\uC694.")}}function J({selectedItemKeys:e,publicRegistries:t}){let o=[],c=new Set;function l(r,a){let p=o.find(n=>n.registryId===r);if(!p?.items.some(n=>n.id===a.id)){if(p?p.items.push(a):o.push({registryId:r,items:[a]}),a.dependencies?.length)for(let n of a.dependencies)c.add(n);if(a.innerDependencies?.length)for(let n of a.innerDependencies)for(let u of n.itemIds){let $=t.find(S=>S.id===n.registryId)?.items.find(S=>S.id===u);if(!$)throw new Error(`Cannot find dependency item: ${n.registryId}:${u}`);l(n.registryId,$)}}}for(let r of e){let[a,...p]=r.split(":"),n=p.join(":");if(!a||!n)throw new Error(`Invalid snippet format: "${r}"`);let u=t.find($=>$.id===a)?.items.find($=>$.id===n);if(!u)throw new Error(`Cannot find snippet: "${r}"`);l(a,u)}return{registryItemsToAdd:o,npmDependenciesToAdd:c}}import*as ee from"@clack/prompts";import{z as f}from"zod";var Q=f.object({id:f.string(),description:f.string().optional(),deprecated:f.boolean().optional(),hideFromCLICatalog:f.boolean().optional(),dependencies:f.array(f.string()).optional(),innerDependencies:f.array(f.object({registryId:f.string(),itemIds:f.array(f.string())})).optional(),snippets:f.array(f.object({path:f.string(),content:f.string()}))}),Z=f.object({id:f.string(),hideFromCLICatalog:f.boolean().optional(),items:f.array(Q.omit({snippets:!0}).extend({snippets:f.array(f.object({path:f.string()}))}))}),ie=f.array(f.object({id:f.string()}));async function H({baseUrl:e}){let t=await fetch(`${e}/__registry__/index.json`);if(!t.ok)throw new Error(`Failed to fetch registries: ${t.status} ${t.statusText}`);let o=await t.json(),{success:c,data:l,error:r}=ie.safeParse(o);if(!c)throw new Error(`Failed to parse registries: ${r?.message}`);return l}async function V({baseUrl:e,registryId:t}){let o=await fetch(`${e}/__registry__/${t}/index.json`);if(!o.ok)throw new Error(`Failed to fetch ${t} registry: ${o.status} ${o.statusText}`);let c=await o.json(),{success:l,data:r,error:a}=Z.safeParse(c);if(!l)throw new Error(`Failed to parse ${t} registry: ${a?.message}`);return r}async function ve({baseUrl:e,registryId:t,registryItemId:o}){let c=await fetch(`${e}/__registry__/${t}/${o}.json`);if(!c.ok)throw new Error(`Failed to fetch ${o}: ${c.status} ${c.statusText}`);let l=await c.json(),{success:r,data:a,error:p}=Q.safeParse(l);if(!r)throw new Error(`Failed to parse ${o}: ${p?.message}`);return a}async function ne({baseUrl:e,registryId:t,registryItemIds:o}){return await Promise.all(o.map(async c=>{try{return await ve({baseUrl:e,registryId:t,registryItemId:c})}catch(l){let r=await fetch(`${e}/__registry__/${t}/index.json`);if(!r.ok)throw new Error(`${t} \uB808\uC9C0\uC2A4\uD2B8\uB9AC\uB97C \uAC00\uC838\uC624\uC9C0 \uBABB\uD588\uC5B4\uC694: ${r.status} ${r.statusText}`);let a=await r.json(),{success:p,data:n}=Z.safeParse(a);throw p?(ee.log.error(`${c} \uC2A4\uB2C8\uD3AB\uC774 ${t} \uB808\uC9C0\uC2A4\uD2B8\uB9AC\uC5D0 \uC5C6\uC5B4\uC694.`),ee.log.info(`${t} \uB808\uC9C0\uC2A4\uD2B8\uB9AC\uC5D0 \uC874\uC7AC\uD558\uB294 \uC2A4\uB2C8\uD3AB:
3
+ ${n.items.map(u=>u.id).join(`
4
+ `)}`),l):new Error(`Failed to parse registry index for ${t}`)}}))}import{promises as Ae}from"fs";import{tmpdir as _e}from"os";import pe from"path";import{transformFromAstSync as Re}from"@babel/core";import Te from"@babel/plugin-transform-typescript";import*as G from"recast";import{parse as je}from"@babel/parser";var Ce={sourceType:"module",allowImportExportEverywhere:!0,allowReturnOutsideFunction:!0,startLine:1,tokens:!0,plugins:["asyncGenerators","bigInt","classPrivateMethods","classPrivateProperties","classProperties","classStaticBlock","decimal","decorators-legacy","doExpressions","dynamicImport","exportDefaultFrom","exportNamespaceFrom","functionBind","functionSent","importAssertions","importMeta","nullishCoalescingOperator","numericSeparator","objectRestSpread","optionalCatchBinding","optionalChaining",["pipelineOperator",{proposal:"minimal"}],["recordAndTuple",{syntaxType:"hash"}],"throwExpressions","topLevelAwait","v8intrinsic","typescript","jsx"]},ae=async({sourceFile:e,config:t})=>{let o=e.getFullText();if(t.tsx)return o;let c=G.parse(o,{parser:{parse:r=>je(r,Ce)}}),l=Re(c,o,{cloneInputAst:!1,code:!1,ast:!0,plugins:[Te],configFile:!1});if(!l||!l.ast)throw new Error("Failed to transform JSX");return G.print(l.ast).code};import{SyntaxKind as Ee}from"ts-morph";var ce=async({sourceFile:e,config:t})=>{if(t.rsc)return e;let o=e.getFirstChildByKind(Ee.ExpressionStatement);if(!o)return e;let c=o.getExpression();if(!c)return e;let l=c.getText().trim();if(l!=='"use client"'&&l!=="'use client'")return e;let r=o.getText(),a=o.getFullText();if(r.trim()===a.trim())return e;let n=a.replace(r,"").replace(/^\s*\n/,"").replace(/\n\s*$/,"");return o.replaceWithText(n),e};import{Project as De,ScriptKind as Oe}from"ts-morph";var ke=[ce],Fe=new De({compilerOptions:{}});async function ze(e){let t=await Ae.mkdtemp(pe.join(_e(),"seed-design-"));return pe.join(t,e)}async function le(e){let t=await ze(e.filename),o=Fe.createSourceFile(t,e.raw,{scriptKind:Oe.TSX});for(let c of ke)c({sourceFile:o,...e});return await ae({sourceFile:o,...e})}import*as E from"@clack/prompts";import U from"fs-extra";import A from"path";import{createPatch as Me}from"diff";import Ue from"@npmcli/disparity-colors";async function Y({registryItemsToAdd:e,rootPath:t,cwd:o,baseUrl:c,config:l,overwrite:r=!1}){let a=[];for(let{registryId:p,items:n}of e){let u=A.join(t,p);U.ensureDirSync(u);let $=await ne({baseUrl:c,registryId:p,registryItemIds:n.map(S=>S.id)});for(let{id:S,snippets:D}of $){let I=await Promise.all(D.map(async x=>{let y=await le({filename:x.path,config:l,raw:x.content}),b=A.join(u,x.path);return l.tsx||(b=b.replace(/\.tsx$/,".jsx"),b=b.replace(/\.ts$/,".js")),{filePath:b,content:y,relativePath:A.relative(o,b),name:`${p}:${S}`}})),T=[];for(let x of I){let{filePath:y,content:b,relativePath:j}=x;if(await U.ensureDir(A.dirname(y)),U.existsSync(y)){let O=await U.readFile(y,"utf-8");if(O===b){E.log.info(`${i(j)}: \uC774\uBBF8 \uCD5C\uC2E0 \uC0C1\uD0DC\uC608\uC694.`);continue}if(!r){let N=Me(j,O,b),w=Ue(N);E.log.message(`
5
+ ${i(j)}: \uD604\uC7AC \uD30C\uC77C\uACFC \uBC1B\uC73C\uB824\uB294 \uD30C\uC77C\uC758 \uB0B4\uC6A9\uC774 \uB2EC\uB77C\uC694.
6
+ `),E.log.message(w);let s=A.basename(y),m=A.extname(y),C=A.basename(y,m),v=Date.now(),P=`legacy-${C}-${v}${m}`,R=await E.select({message:"\uD604\uC7AC \uD30C\uC77C\uC5D0 \uC2A4\uD0C0\uC77C \uBCC0\uACBD, \uB85C\uAE45 \uB4F1 \uCEE4\uC2A4\uD130\uB9C8\uC774\uC9D5\uC774 \uC801\uC6A9\uB418\uC5B4 \uC788\uB294 \uACBD\uC6B0 \uC2E0\uADDC \uD30C\uC77C\uC5D0 \uB3D9\uC77C\uD55C \uCEE4\uC2A4\uD130\uB9C8\uC774\uC9D5\uC744 \uC801\uC6A9\uD558\uB294 \uAC83\uC744 \uAC80\uD1A0\uD574\uBCF4\uC138\uC694.",options:[{value:"overwrite",label:`${s} \uB36E\uC5B4\uC4F0\uAE30`},{value:"backup",label:`\uAE30\uC874 \uD30C\uC77C \uB0B4\uC6A9\uC744 ${P}\uC73C\uB85C \uC62E\uAE30\uACE0 ${s} \uBC1B\uAE30`},{value:"skip",label:"\uC0C8 \uD30C\uC77C \uBC1B\uC9C0 \uC54A\uACE0 \uADF8\uB300\uB85C \uB450\uAE30"}]});if(E.isCancel(R)||R==="skip"){E.log.info(`${i(j)}: \uD30C\uC77C\uC744 \uBC1B\uC9C0 \uC54A\uACE0 \uAC74\uB108\uB6F0\uC5C8\uC5B4\uC694.`);continue}if(R==="backup"){let we=A.dirname(y),re=A.join(we,P);await U.rename(y,re),E.log.info(`${i(j)}: \uAE30\uC874 \uD30C\uC77C\uC744 ${i(A.relative(o,re))}\uB85C \uC62E\uACBC\uC5B4\uC694.`)}}}await U.writeFile(y,b),T.push(x)}if(T.length>0){let x=T.map(({name:y,relativePath:b})=>({name:y,path:b}));a.push(...x),E.log.success(`${i(`${p}:${S}`)} \uAD00\uB828 \uC2A4\uB2C8\uD3AB \uB2E4\uC6B4\uB85C\uB4DC \uC644\uB8CC: ${i(x.map(y=>y.path).join(", "))}`)}}}}import*as d from"@clack/prompts";import We from"path";import{z as F}from"zod";var X="https://seed-design.io";import*as me from"@clack/prompts";import{execa as Je}from"execa";import Le from"findup-sync";import Ke from"fs-extra";var Ne="package.json";function Be(){let e=Le(Ne);if(!e)throw new Error("No package.json file found in the project.");return e}function q(){let e=Be();return Ke.readJSONSync(e)}async function W({cwd:e,deps:t,dev:o=!1}){let{start:c,stop:l}=me.spinner(),r=await B(e),p={...q().dependencies},n=new Set(t.filter(I=>!p[I])),u=new Set(t.filter(I=>p[I]));if(!n.size)return{installed:new Set,filtered:n};c("\uC758\uC874\uC131 \uC124\uCE58\uC911...");let D=[r==="npm"?"install":"add",o?"-D":null,...n].filter(Boolean);try{await Je(r,D,{cwd:e})}catch(I){console.error(`\uC758\uC874\uC131 \uC124\uCE58 \uC2E4\uD328: ${I}`),process.exit(1)}return l("\uC758\uC874\uC131 \uC124\uCE58\uAC00 \uC644\uB8CC\uB410\uC5B4\uC694."),{installed:n,filtered:u}}import{randomUUID as He}from"node:crypto";import*as fe from"@clack/prompts";var Ve="seed_cli";async function Ge(e){if(process.env.DISABLE_TELEMETRY==="true"||process.env.SEED_DISABLE_TELEMETRY==="true")return!1;try{if((await M(e))?.telemetry===!1)return!1}catch{}return!0}function Ye(){return He()}var Xe=Ye(),de=!1;async function qe(e,{event:t,properties:o={}}){if(!await Ge(e))return;let l=`${Ve}.${t}`;de||(fe.log.info("\u{1F4CA} \uC0AC\uC6A9 \uB370\uC774\uD130 \uC218\uC9D1 \uC911 (\uBE44\uD65C\uC131\uD654: seed-design.json \uB610\uB294 DISABLE_TELEMETRY \uD658\uACBD \uBCC0\uC218)"),de=!0);try{let r="https://us.i.posthog.com/capture",a={"Content-Type":"application/json"},p={api_key:"phc_seod8HhifElOP1R92KmvsQybrtUmkOTgZBsq0mfCelR",event:l,distinct_id:Xe,properties:{...o,$process_person_profile:!1},timestamp:new Date().toISOString()},n=new AbortController,u=setTimeout(()=>n.abort(),5e3);try{await fetch(r,{method:"POST",headers:a,body:JSON.stringify(p),signal:n.signal})}finally{clearTimeout(u)}}catch{}}var L={track:qe};var Qe=F.object({itemIds:F.array(F.string()).optional(),all:F.boolean(),cwd:F.string(),baseUrl:F.string().optional(),overwrite:F.boolean().optional()}),ge=e=>{e.command("add [...item-ids]","add items").option("-a, --all","[Deprecated] Add all items",{default:!1}).option("-c, --cwd <cwd>","the working directory. defaults to the current directory.",{default:process.cwd()}).option("-u, --baseUrl <baseUrl>","the base url of the registry. defaults to the current directory.",{default:X}).option("--overwrite","Overwrite existing files without confirmation",{default:!1}).example("seed-design add ui:action-button").example("seed-design add ui:alert-dialog").action(async(t,o)=>{let c=Date.now();d.intro("seed-design add");let{success:l,data:{all:r,...a},error:p}=Qe.safeParse({itemIds:t,...o});l||(d.log.error(`\uC798\uBABB\uB41C \uC635\uC158\uC774\uC5D0\uC694: ${p?.message}`),process.exit(1)),r&&(d.log.error("`--all` \uC635\uC158\uC740 \uB354 \uC774\uC0C1 \uC9C0\uC6D0\uB418\uC9C0 \uC54A\uC544\uC694. \uB300\uC2E0 `seed-design add-all` \uBA85\uB839\uC5B4\uB97C \uC0AC\uC6A9\uD574\uC8FC\uC138\uC694."),process.exit(1));let n=a.cwd,u=a.baseUrl,$=await M(n),S=We.resolve(n,$.path),{start:D,stop:I}=d.spinner();D("Registry\uB97C \uAC00\uC838\uC624\uACE0 \uC788\uC5B4\uC694...");let T=await Promise.all((await H({baseUrl:u})).map(async({id:s})=>V({baseUrl:u,registryId:s})));I("Registry\uB97C \uAC00\uC838\uC654\uC5B4\uC694.");let x=await(async()=>{if(a.itemIds.length>0)return a.itemIds;let s=await d.multiselect({message:"\uCD94\uAC00\uD560 \uD56D\uBAA9\uC744 \uC120\uD0DD\uD574\uC8FC\uC138\uC694 (\uC2A4\uD398\uC774\uC2A4 \uBC14\uB85C \uC5EC\uB7EC \uAC1C \uC120\uD0DD \uAC00\uB2A5)",options:T.filter(({hideFromCLICatalog:m})=>!m).flatMap(({id:m,items:C})=>C.filter(({hideFromCLICatalog:v})=>!v).sort((v,P)=>v.id.localeCompare(P.id)).map(({id:v,description:P,deprecated:R})=>({label:`${R?"(deprecated) ":""}${i(m)}:${v}`,value:`${m}:${v}`,hint:P,deprecated:R,registryItemCount:C.length}))).sort((m,C)=>m.deprecated!==C.deprecated?m.deprecated?1:-1:C.registryItemCount-m.registryItemCount)});return d.isCancel(s)&&(d.log.error("\uCDE8\uC18C\uB418\uC5C8\uC5B4\uC694."),process.exit(0)),s})();x?.length||(d.log.error("\uD56D\uBAA9\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC5B4\uC694."),process.exit(0)),d.log.message(`\uC120\uD0DD\uB41C \uD56D\uBAA9: ${i(x.join(", "))}`);let y=[];for(let s of x){let[m,...C]=s.split(":"),v=C.join(":");(!m||!v)&&(d.log.error(`${i(s)}: \uD56D\uBAA9 \uC774\uB984\uC774 \uC798\uBABB\uB418\uC5C8\uC5B4\uC694. ${i("ui:action-button")}\uACFC \uAC19\uC740 \uD615\uC2DD\uC73C\uB85C \uC785\uB825\uD574\uBCF4\uC138\uC694.`),process.exit(1));let P=T.find(R=>R.id===m)?.items.find(R=>R.id===v);if(P||(d.log.error(`${i(s)}: \uD56D\uBAA9\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC5B4\uC694.`),process.exit(1)),P.deprecated){let R=await d.confirm({message:`${i(P.id)}: deprecated \uB418\uC5C8\uC5B4\uC694. \uCD94\uAC00\uD560\uAE4C\uC694?`,initialValue:!1});if(R===!1||d.isCancel(R)){d.log.info(`${i(P.id)}: \uCD94\uAC00\uD558\uC9C0 \uC54A\uC744\uAC8C\uC694.`);continue}}y.push(s)}let{registryItemsToAdd:b,npmDependenciesToAdd:j}=J({selectedItemKeys:y,publicRegistries:T});d.log.info(`\uCD94\uAC00\uD560 \uD56D\uBAA9: ${i(b.map(s=>s.items.map(m=>`${s.registryId}:${m.id}`).join(", ")).join(", ")||"\uC5C6\uC74C")}
5
7
 
6
- \uC124\uCE58\uD560 \uC758\uC874\uC131: ${a(Array.from(F).join(", ")||"\uC5C6\uC74C")}`),await L({registryItemsToAdd:R,rootPath:x,cwd:l,baseUrl:n,config:w});let{installed:D,filtered:T}=await G({cwd:l,deps:Array.from(F)});D.size===0&&f.log.message("\uBAA8\uB4E0 \uC758\uC874\uC131\uC774 \uC774\uBBF8 \uC124\uCE58\uB418\uC5B4 \uC788\uC5B4\uC694."),D.size&&(f.log.message(`\uC758\uC874\uC131 \uC124\uCE58 \uC644\uB8CC: ${a(Array.from(D).join(", "))}`),T.size&&f.log.message(`\uC124\uCE58\uD558\uC9C0 \uC54A\uC740 \uC758\uC874\uC131 (\uC774\uBBF8 \uC124\uCE58\uB428): ${a(Array.from(T).join(", "))}`)),f.outro("\uC644\uB8CC\uD588\uC5B4\uC694.")})};import*as g from"@clack/prompts";import ze from"path";import{z as v}from"zod";var Me=v.object({registryIds:v.array(v.string()).optional(),all:v.boolean(),includeDeprecated:v.boolean().optional(),cwd:v.string(),baseUrl:v.string().optional()}),pe=e=>{e.command("add-all [...registry-ids]","add all items from registries").option("-a, --all","Add all items from all registries",{default:!1}).option("--include-deprecated","Include deprecated items when used with `--all`",{default:!1}).option("-c, --cwd <cwd>","the working directory. defaults to the current directory.",{default:process.cwd()}).option("-u, --baseUrl <baseUrl>","the base url of the registry. defaults to the current directory.",{default:B}).example("seed-design add-all ui --include-deprecated").example("seed-design add-all ui lib breeze").action(async(t,r)=>{g.intro("seed-design add-all");let{success:c,data:i,error:s}=Me.safeParse({registryIds:t,...r});c||(g.log.error(`\uC798\uBABB\uB41C \uC635\uC158\uC774\uC5D0\uC694: ${s?.message}`),process.exit(1));let o=i.cwd,l=i.baseUrl,n=await U(o),w=ze.resolve(o,n.path),{start:x,stop:j}=g.spinner();x("Registry\uB97C \uAC00\uC838\uC624\uACE0 \uC788\uC5B4\uC694...");let $=await Promise.all((await M({baseUrl:l})).map(async({id:m})=>K({baseUrl:l,registryId:m})));j("Registry\uB97C \uAC00\uC838\uC654\uC5B4\uC694.");let I=await(async()=>{if(i.all){let p=$.map(y=>y.id);return g.log.message(`\uBAA8\uB4E0 \uB808\uC9C0\uC2A4\uD2B8\uB9AC\uC758 \uBAA8\uB4E0 \uD56D\uBAA9\uC744 \uCD94\uAC00\uD569\uB2C8\uB2E4: ${a(p.join(", "))}`),p}if(i.registryIds?.length){let p=$.map(y=>y.id);for(let y of i.registryIds)p.includes(y)||(g.log.error(`\uB808\uC9C0\uC2A4\uD2B8\uB9AC '${y}'\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC5B4\uC694.`),g.log.info(`\uC0AC\uC6A9 \uAC00\uB2A5\uD55C \uB808\uC9C0\uC2A4\uD2B8\uB9AC: ${p.join(", ")}`),process.exit(1));return g.log.message(`\uC120\uD0DD\uB41C \uB808\uC9C0\uC2A4\uD2B8\uB9AC\uC758 \uBAA8\uB4E0 \uD56D\uBAA9\uC744 \uCD94\uAC00\uD569\uB2C8\uB2E4: ${a(i.registryIds.join(", "))}`),i.registryIds}let m=await g.multiselect({message:"\uCD94\uAC00\uD560 \uB808\uC9C0\uC2A4\uD2B8\uB9AC\uB97C \uC120\uD0DD\uD574\uC8FC\uC138\uC694 (\uC2A4\uD398\uC774\uC2A4 \uBC14\uB85C \uC5EC\uB7EC \uAC1C \uC120\uD0DD \uAC00\uB2A5)",options:$.filter(({hideFromCLICatalog:p})=>!p).sort((p,y)=>y.items.length-p.items.length).map(p=>({label:p.id,value:p.id,hint:`${p.items.length}\uAC1C \uD56D\uBAA9 (${p.items[0].id} \uB4F1)`}))});return g.isCancel(m)&&(g.log.error("\uCDE8\uC18C\uB418\uC5C8\uC5B4\uC694."),process.exit(0)),g.log.message(`\uC120\uD0DD\uB41C \uB808\uC9C0\uC2A4\uD2B8\uB9AC\uC758 \uD56D\uBAA9\uC744 \uCD94\uAC00\uD569\uB2C8\uB2E4: ${a(m.join(", "))}`),m})(),h=$.filter(m=>I.includes(m.id)),P=h.flatMap(m=>m.items.filter(p=>p.deprecated?i.includeDeprecated:!0).map(p=>`${m.id}:${p.id}`)),R=h.flatMap(m=>m.items.filter(p=>p.deprecated).map(()=>1)).length;!i.includeDeprecated&&R>0&&g.log.info(`${R}\uAC1C\uC758 deprecated \uD56D\uBAA9\uC740 \uC81C\uC678\uB418\uC5C8\uC5B4\uC694. --include-deprecated \uC635\uC158\uC744 \uC0AC\uC6A9\uD558\uBA74 \uCD94\uAC00\uD560 \uC218 \uC788\uC5B4\uC694.`),P.length||(g.log.error("\uCD94\uAC00\uD560 \uD56D\uBAA9\uC774 \uC5C6\uC5B4\uC694."),process.exit(0)),g.log.message(`\uCD1D ${a(P.length.toString())}\uAC1C\uC758 \uD56D\uBAA9\uC744 \uCD94\uAC00\uD569\uB2C8\uB2E4.`);let{registryItemsToAdd:F,npmDependenciesToAdd:D}=z({selectedItemKeys:P,publicRegistries:$});await L({registryItemsToAdd:F,rootPath:w,cwd:o,baseUrl:l,config:n});let{installed:T,filtered:b}=await G({cwd:o,deps:Array.from(D)});T.size===0&&g.log.message("\uBAA8\uB4E0 \uC758\uC874\uC131\uC774 \uC774\uBBF8 \uC124\uCE58\uB418\uC5B4 \uC788\uC5B4\uC694."),T.size&&(g.log.message(`\uC758\uC874\uC131 \uC124\uCE58 \uC644\uB8CC: ${a(Array.from(T).join(", "))}`),b.size&&g.log.message(`\uC124\uCE58\uD558\uC9C0 \uC54A\uC740 \uC758\uC874\uC131 (\uC774\uBBF8 \uC124\uCE58\uB428): ${a(Array.from(b).join(", "))}`)),g.outro("\uC644\uB8CC\uD588\uC5B4\uC694.")})};import*as u from"@clack/prompts";import Ke from"fs-extra";import le from"path";import{z as H}from"zod";var Je=H.object({cwd:H.string(),yes:H.boolean().optional()}),me=e=>{e.command("init","seed-design.json \uD30C\uC77C \uC0DD\uC131").option("-c, --cwd <cwd>","\uC791\uC5C5 \uB514\uB809\uD1A0\uB9AC. \uAE30\uBCF8\uAC12\uC740 \uD604\uC7AC \uB514\uB809\uD1A0\uB9AC.",{default:process.cwd()}).option("-y, --yes","\uBAA8\uB4E0 \uC9C8\uBB38\uC5D0 \uB300\uD574 \uAE30\uBCF8\uAC12\uC73C\uB85C \uB2F5\uBCC0\uD569\uB2C8\uB2E4.").action(async t=>{u.intro("seed-design.json \uD30C\uC77C \uC0DD\uC131");let r=Je.parse(t),c=r.yes,i={rsc:!1,tsx:!0,path:"./seed-design"};c||(i={...await u.group({tsx:()=>u.confirm({message:`${a("TypeScript")}\uB97C \uC0AC\uC6A9\uC911\uC774\uC2E0\uAC00\uC694?`,initialValue:!0}),rsc:()=>u.confirm({message:`${a("React Server Components")}\uB97C \uC0AC\uC6A9\uC911\uC774\uC2E0\uAC00\uC694?`,initialValue:!1}),path:()=>u.text({message:`${a("seed-design \uD3F4\uB354")} \uACBD\uB85C\uB97C \uC785\uB825\uD574\uC8FC\uC138\uC694. (\uAE30\uBCF8\uAC12\uC740 \uD504\uB85C\uC81D\uD2B8 \uB8E8\uD2B8\uC5D0 \uC0DD\uC131\uB429\uB2C8\uB2E4.)`,initialValue:"./seed-design",defaultValue:"./seed-design",placeholder:"./seed-design"})},{onCancel:()=>{u.cancel("\uC791\uC5C5\uC774 \uCDE8\uC18C\uB410\uC5B4\uC694."),process.exit(0)}})});try{let{start:s,stop:o}=u.spinner();s("seed-design.json \uD30C\uC77C \uC0DD\uC131\uC911...");let l=le.resolve(r.cwd,"seed-design.json");await Ke.writeFile(l,`${JSON.stringify(i,null,2)}
7
- `,"utf-8");let n=le.relative(process.cwd(),l);o(`seed-design.json \uD30C\uC77C\uC774 ${a(n)}\uC5D0 \uC0DD\uC131\uB410\uC5B4\uC694.`),u.log.info(a("seed-design add {component} \uBA85\uB839\uC5B4\uB85C \uCEF4\uD3EC\uB10C\uD2B8\uB97C \uCD94\uAC00\uD574\uBCF4\uC138\uC694!")),u.log.info(a("seed-design add \uBA85\uB839\uC5B4\uB85C \uCD94\uAC00\uD560 \uC218 \uC788\uB294 \uBAA8\uB4E0 \uCEF4\uD3EC\uB10C\uD2B8\uB97C \uD655\uC778\uD574\uBCF4\uC138\uC694.")),u.outro("\uC791\uC5C5\uC774 \uC644\uB8CC\uB410\uC5B4\uC694.")}catch(s){u.log.error(`seed-design.json \uD30C\uC77C \uC0DD\uC131\uC5D0 \uC2E4\uD328\uD588\uC5B4\uC694. ${s}`),u.outro(a("\uC791\uC5C5\uC774 \uCDE8\uC18C\uB410\uC5B4\uC694.")),process.exit(1)}})};import{cac as Ne}from"cac";var Le="seed-design",k=Ne(Le);async function Be(){let e=V();ce(k),pe(k),me(k),k.version(e.version||"1.0.0","-v, --version"),k.help(),k.parse()}Be();
8
+ \uC124\uCE58\uD560 \uC758\uC874\uC131: ${i(Array.from(j).join(", ")||"\uC5C6\uC74C")}`),await Y({registryItemsToAdd:b,rootPath:S,cwd:n,baseUrl:u,config:$,overwrite:a.overwrite});try{let{installed:s,filtered:m}=await W({cwd:n,deps:Array.from(j)});s.size===0&&d.log.message("\uBAA8\uB4E0 \uC758\uC874\uC131\uC774 \uC774\uBBF8 \uC124\uCE58\uB418\uC5B4 \uC788\uC5B4\uC694."),s.size&&(d.log.message(`\uC758\uC874\uC131 \uC124\uCE58 \uC644\uB8CC: ${i(Array.from(s).join(", "))}`),m.size&&d.log.message(`\uC124\uCE58\uD558\uC9C0 \uC54A\uC740 \uC758\uC874\uC131 (\uC774\uBBF8 \uC124\uCE58\uB428): ${i(Array.from(m).join(", "))}`)),d.outro("\uC644\uB8CC\uD588\uC5B4\uC694.")}catch(s){d.log.error(`\uCD94\uAC00\uC5D0 \uC2E4\uD328\uD588\uC5B4\uC694. ${s}`),d.outro(i("\uC791\uC5C5\uC774 \uCDE8\uC18C\uB410\uC5B4\uC694.")),process.exit(1)}let O=Date.now()-c,N=new Set(b.map(s=>s.registryId)),w=x.some(s=>{let[m,...C]=s.split(":"),v=C.join(":");return T.find(P=>P.id===m)?.items.find(P=>P.id===v)?.deprecated});await L.track(a.cwd,{event:"add",properties:{items_count:y.length,registries:Array.from(N),has_deprecated:w,dependencies_count:j.size,duration_ms:O}})})};import*as g from"@clack/prompts";import Ze from"path";import{z as _}from"zod";var et=_.object({registryIds:_.array(_.string()).optional(),all:_.boolean(),includeDeprecated:_.boolean().optional(),cwd:_.string(),baseUrl:_.string().optional(),overwrite:_.boolean().optional()}),ue=e=>{e.command("add-all [...registry-ids]","add all items from registries").option("-a, --all","Add all items from all registries",{default:!1}).option("--include-deprecated","Include deprecated items when used with `--all`",{default:!1}).option("-c, --cwd <cwd>","the working directory. defaults to the current directory.",{default:process.cwd()}).option("-u, --baseUrl <baseUrl>","the base url of the registry. defaults to the current directory.",{default:X}).option("--overwrite","Overwrite existing files without confirmation",{default:!1}).example("seed-design add-all ui --include-deprecated").example("seed-design add-all ui lib breeze").action(async(t,o)=>{let c=Date.now();g.intro("seed-design add-all");let{success:l,data:r,error:a}=et.safeParse({registryIds:t,...o});l||(g.log.error(`\uC798\uBABB\uB41C \uC635\uC158\uC774\uC5D0\uC694: ${a?.message}`),process.exit(1));let p=r.cwd,n=r.baseUrl,u=await M(p),$=Ze.resolve(p,u.path),{start:S,stop:D}=g.spinner();S("Registry\uB97C \uAC00\uC838\uC624\uACE0 \uC788\uC5B4\uC694...");let I=await Promise.all((await H({baseUrl:n})).map(async({id:w})=>V({baseUrl:n,registryId:w})));D("Registry\uB97C \uAC00\uC838\uC654\uC5B4\uC694.");let T=await(async()=>{if(r.all){let s=I.map(m=>m.id);return g.log.message(`\uBAA8\uB4E0 \uB808\uC9C0\uC2A4\uD2B8\uB9AC\uC758 \uBAA8\uB4E0 \uD56D\uBAA9\uC744 \uCD94\uAC00\uD569\uB2C8\uB2E4: ${i(s.join(", "))}`),s}if(r.registryIds?.length){let s=I.map(m=>m.id);for(let m of r.registryIds)s.includes(m)||(g.log.error(`\uB808\uC9C0\uC2A4\uD2B8\uB9AC '${m}'\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC5B4\uC694.`),g.log.info(`\uC0AC\uC6A9 \uAC00\uB2A5\uD55C \uB808\uC9C0\uC2A4\uD2B8\uB9AC: ${s.join(", ")}`),process.exit(1));return g.log.message(`\uC120\uD0DD\uB41C \uB808\uC9C0\uC2A4\uD2B8\uB9AC\uC758 \uBAA8\uB4E0 \uD56D\uBAA9\uC744 \uCD94\uAC00\uD569\uB2C8\uB2E4: ${i(r.registryIds.join(", "))}`),r.registryIds}let w=await g.multiselect({message:"\uCD94\uAC00\uD560 \uB808\uC9C0\uC2A4\uD2B8\uB9AC\uB97C \uC120\uD0DD\uD574\uC8FC\uC138\uC694 (\uC2A4\uD398\uC774\uC2A4 \uBC14\uB85C \uC5EC\uB7EC \uAC1C \uC120\uD0DD \uAC00\uB2A5)",options:I.filter(({hideFromCLICatalog:s})=>!s).sort((s,m)=>m.items.length-s.items.length).map(s=>({label:s.id,value:s.id,hint:`${s.items.length}\uAC1C \uD56D\uBAA9 (${s.items[0].id} \uB4F1)`}))});return g.isCancel(w)&&(g.log.error("\uCDE8\uC18C\uB418\uC5C8\uC5B4\uC694."),process.exit(0)),g.log.message(`\uC120\uD0DD\uB41C \uB808\uC9C0\uC2A4\uD2B8\uB9AC\uC758 \uD56D\uBAA9\uC744 \uCD94\uAC00\uD569\uB2C8\uB2E4: ${i(w.join(", "))}`),w})(),x=I.filter(w=>T.includes(w.id)),y=x.flatMap(w=>w.items.filter(s=>s.deprecated?r.includeDeprecated:!0).map(s=>`${w.id}:${s.id}`)),b=x.flatMap(w=>w.items.filter(s=>s.deprecated).map(()=>1)).length;!r.includeDeprecated&&b>0&&g.log.info(`${b}\uAC1C\uC758 deprecated \uD56D\uBAA9\uC740 \uC81C\uC678\uB418\uC5C8\uC5B4\uC694. --include-deprecated \uC635\uC158\uC744 \uC0AC\uC6A9\uD558\uBA74 \uCD94\uAC00\uD560 \uC218 \uC788\uC5B4\uC694.`),y.length||(g.log.error("\uCD94\uAC00\uD560 \uD56D\uBAA9\uC774 \uC5C6\uC5B4\uC694."),process.exit(0)),g.log.message(`\uCD1D ${i(y.length.toString())}\uAC1C\uC758 \uD56D\uBAA9\uC744 \uCD94\uAC00\uD569\uB2C8\uB2E4.`);let{registryItemsToAdd:j,npmDependenciesToAdd:O}=J({selectedItemKeys:y,publicRegistries:I});await Y({registryItemsToAdd:j,rootPath:$,cwd:p,baseUrl:n,config:u,overwrite:r.overwrite});try{let{installed:w,filtered:s}=await W({cwd:p,deps:Array.from(O)});w.size===0&&g.log.message("\uBAA8\uB4E0 \uC758\uC874\uC131\uC774 \uC774\uBBF8 \uC124\uCE58\uB418\uC5B4 \uC788\uC5B4\uC694."),w.size&&(g.log.message(`\uC758\uC874\uC131 \uC124\uCE58 \uC644\uB8CC: ${i(Array.from(w).join(", "))}`),s.size&&g.log.message(`\uC124\uCE58\uD558\uC9C0 \uC54A\uC740 \uC758\uC874\uC131 (\uC774\uBBF8 \uC124\uCE58\uB428): ${i(Array.from(s).join(", "))}`)),g.outro("\uC644\uB8CC\uD588\uC5B4\uC694.")}catch(w){g.log.error(`\uCD94\uAC00\uC5D0 \uC2E4\uD328\uD588\uC5B4\uC694. ${w}`),g.outro(i("\uC791\uC5C5\uC774 \uCDE8\uC18C\uB410\uC5B4\uC694.")),process.exit(1)}let N=Date.now()-c;await L.track(r.cwd,{event:"add-all",properties:{registries:T,items_count:y.length,include_deprecated:r.includeDeprecated||!1,dependencies_count:O.size,duration_ms:N}})})};import*as h from"@clack/prompts";import tt from"fs-extra";import ye from"path";import{z as te}from"zod";import rt from"dedent";var st=te.object({cwd:te.string(),yes:te.boolean().optional()}),he=e=>{e.command("init","seed-design.json \uD30C\uC77C \uC0DD\uC131").option("-c, --cwd <cwd>","\uC791\uC5C5 \uB514\uB809\uD1A0\uB9AC. \uAE30\uBCF8\uAC12\uC740 \uD604\uC7AC \uB514\uB809\uD1A0\uB9AC.",{default:process.cwd()}).option("-y, --yes","\uBAA8\uB4E0 \uC9C8\uBB38\uC5D0 \uB300\uD574 \uAE30\uBCF8\uAC12\uC73C\uB85C \uB2F5\uBCC0\uD569\uB2C8\uB2E4.").action(async t=>{let o=Date.now();h.intro("seed-design.json \uD30C\uC77C \uC0DD\uC131");let c=st.parse(t),l=c.yes,r={rsc:!1,tsx:!0,path:"./seed-design",telemetry:!0};l||(r={...await h.group({tsx:()=>h.confirm({message:`${i("TypeScript")}\uB97C \uC0AC\uC6A9\uC911\uC774\uC2E0\uAC00\uC694?`,initialValue:!0}),rsc:()=>h.confirm({message:`${i("React Server Components")}\uB97C \uC0AC\uC6A9\uC911\uC774\uC2E0\uAC00\uC694?`,initialValue:!1}),path:()=>h.text({message:`${i("seed-design \uD3F4\uB354")} \uACBD\uB85C\uB97C \uC785\uB825\uD574\uC8FC\uC138\uC694. (\uAE30\uBCF8\uAC12\uC740 \uD504\uB85C\uC81D\uD2B8 \uB8E8\uD2B8\uC5D0 \uC0DD\uC131\uB429\uB2C8\uB2E4.)`,initialValue:"./seed-design",defaultValue:"./seed-design",placeholder:"./seed-design"}),telemetry:()=>h.confirm({message:`\uAC1C\uC120\uC744 \uC704\uD574 ${i("\uC775\uBA85 \uC0AC\uC6A9 \uB370\uC774\uD130")}\uB97C \uC218\uC9D1\uD560\uAE4C\uC694?`,initialValue:!0})},{onCancel:()=>{h.cancel("\uC791\uC5C5\uC774 \uCDE8\uC18C\uB410\uC5B4\uC694."),process.exit(0)}})});try{let{start:p,stop:n}=h.spinner();p("seed-design.json \uD30C\uC77C \uC0DD\uC131\uC911...");let u=ye.resolve(c.cwd,"seed-design.json");await tt.writeFile(u,`${JSON.stringify(r,null,2)}
9
+ `,"utf-8");let $=ye.relative(process.cwd(),u);n(`seed-design.json \uD30C\uC77C\uC774 ${i($)}\uC5D0 \uC0DD\uC131\uB410\uC5B4\uC694.`),h.log.info(i("seed-design add {component} \uBA85\uB839\uC5B4\uB85C \uCEF4\uD3EC\uB10C\uD2B8\uB97C \uCD94\uAC00\uD574\uBCF4\uC138\uC694!")),h.log.info(i("seed-design add \uBA85\uB839\uC5B4\uB85C \uCD94\uAC00\uD560 \uC218 \uC788\uB294 \uBAA8\uB4E0 \uCEF4\uD3EC\uB10C\uD2B8\uB97C \uD655\uC778\uD574\uBCF4\uC138\uC694.")),h.note(rt(`SEED Design CLI\uB294 \uAC1C\uC120\uC744 \uC704\uD574 \uC775\uBA85 \uC0AC\uC6A9 \uB370\uC774\uD130\uB97C \uC218\uC9D1\uD574\uC694.
10
+
11
+ \uBE44\uD65C\uC131\uD654\uD558\uB824\uBA74:
12
+ \u2022 seed-design.json\uC5D0\uC11C ${i('"telemetry": false')}\uB85C \uC124\uC815
13
+ \u2022 ${i("DISABLE_TELEMETRY=true")} \uD658\uACBD \uBCC0\uC218 \uC124\uC815
14
+
15
+ \uC790\uC138\uD55C \uB0B4\uC6A9: https://seed-design.com/react/getting-started/cli/configuration#telemetry`),"Telemetry \uC548\uB0B4"),h.outro("\uC791\uC5C5\uC774 \uC644\uB8CC\uB410\uC5B4\uC694.")}catch(p){h.log.error(`seed-design.json \uD30C\uC77C \uC0DD\uC131\uC5D0 \uC2E4\uD328\uD588\uC5B4\uC694. ${p}`),h.outro(i("\uC791\uC5C5\uC774 \uCDE8\uC18C\uB410\uC5B4\uC694.")),process.exit(1)}let a=Date.now()-o;await L.track(c.cwd,{event:"init",properties:{tsx:r.tsx,rsc:r.rsc,telemetry:r.telemetry,yes_option:l,duration_ms:a}})})};import{cac as ot}from"cac";var it="seed-design",K=ot(it);async function nt(){let e=q();ge(K),ue(K),he(K),K.version(e.version||"1.0.0","-v, --version"),K.help(),K.parse()}nt();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@seed-design/cli",
3
- "version": "1.0.0",
3
+ "version": "1.2.0",
4
4
  "type": "module",
5
5
  "repository": {
6
6
  "type": "git",
@@ -25,13 +25,16 @@
25
25
  "lint:publish": "bun publint"
26
26
  },
27
27
  "dependencies": {
28
- "@antfu/ni": "^26.0.0",
28
+ "@antfu/ni": "^28.0.0",
29
29
  "@babel/core": "^7.26.10",
30
30
  "@babel/parser": "^7.27.0",
31
31
  "@babel/plugin-transform-typescript": "^7.27.0",
32
32
  "@clack/prompts": "^0.11.0",
33
+ "@npmcli/disparity-colors": "^3.0.1",
33
34
  "cac": "^6.7.14",
34
35
  "cosmiconfig": "^9.0.0",
36
+ "dedent": "^1.7.0",
37
+ "diff": "^8.0.3",
35
38
  "execa": "^9.5.2",
36
39
  "findup-sync": "^5.0.0",
37
40
  "fs-extra": "^11.3.0",
@@ -43,7 +46,8 @@
43
46
  "devDependencies": {
44
47
  "@types/babel__core": "^7.20.5",
45
48
  "@types/fs-extra": "^11.0.4",
46
- "esbuild": "^0.25.3",
49
+ "dotenv": "^17.2.3",
50
+ "esbuild": "^0.27.0",
47
51
  "type-fest": "^5.0.0",
48
52
  "typescript": "^5.9.2"
49
53
  },
@@ -1,6 +1,6 @@
1
+ import { fetchAvailableRegistries, fetchRegistry } from "@/src/utils/fetch";
1
2
  import { getConfig } from "@/src/utils/get-config";
2
3
  import { resolveDependencies } from "@/src/utils/resolve-dependencies";
3
- import { fetchAvailableRegistries, fetchRegistry } from "@/src/utils/fetch";
4
4
  import { writeRegistryItemSnippets } from "@/src/utils/write";
5
5
  import * as p from "@clack/prompts";
6
6
  import path from "path";
@@ -8,6 +8,7 @@ import { z } from "zod";
8
8
 
9
9
  import type { CAC } from "cac";
10
10
  import { BASE_URL } from "../constants";
11
+ import { analytics } from "../utils/analytics";
11
12
  import { highlight } from "../utils/color";
12
13
  import { installDependencies } from "../utils/install";
13
14
 
@@ -17,6 +18,7 @@ const addAllOptionsSchema = z.object({
17
18
  includeDeprecated: z.boolean().optional(),
18
19
  cwd: z.string(),
19
20
  baseUrl: z.string().optional(),
21
+ overwrite: z.boolean().optional(),
20
22
  });
21
23
 
22
24
  export const addAllCommand = (cli: CAC) => {
@@ -36,9 +38,13 @@ export const addAllCommand = (cli: CAC) => {
36
38
  "the base url of the registry. defaults to the current directory.",
37
39
  { default: BASE_URL },
38
40
  )
41
+ .option("--overwrite", "Overwrite existing files without confirmation", {
42
+ default: false,
43
+ })
39
44
  .example("seed-design add-all ui --include-deprecated")
40
45
  .example("seed-design add-all ui lib breeze")
41
46
  .action(async (registryIds, opts) => {
47
+ const startTime = Date.now();
42
48
  p.intro("seed-design add-all");
43
49
 
44
50
  const {
@@ -154,27 +160,53 @@ export const addAllCommand = (cli: CAC) => {
154
160
  publicRegistries,
155
161
  });
156
162
 
157
- await writeRegistryItemSnippets({ registryItemsToAdd, rootPath, cwd, baseUrl, config });
158
-
159
- const { installed, filtered } = await installDependencies({
163
+ await writeRegistryItemSnippets({
164
+ registryItemsToAdd,
165
+ rootPath,
160
166
  cwd,
161
- deps: Array.from(npmDependenciesToAdd),
167
+ baseUrl,
168
+ config,
169
+ overwrite: options.overwrite,
162
170
  });
163
171
 
164
- if (installed.size === 0) {
165
- p.log.message("모든 의존성이 이미 설치되어 있어요.");
166
- }
172
+ try {
173
+ const { installed, filtered } = await installDependencies({
174
+ cwd,
175
+ deps: Array.from(npmDependenciesToAdd),
176
+ });
167
177
 
168
- if (installed.size) {
169
- p.log.message(`의존성 설치 완료: ${highlight(Array.from(installed).join(", "))}`);
178
+ if (installed.size === 0) {
179
+ p.log.message("모든 의존성이 이미 설치되어 있어요.");
180
+ }
170
181
 
171
- if (filtered.size) {
172
- p.log.message(
173
- `설치하지 않은 의존성 (이미 설치됨): ${highlight(Array.from(filtered).join(", "))}`,
174
- );
182
+ if (installed.size) {
183
+ p.log.message(`의존성 설치 완료: ${highlight(Array.from(installed).join(", "))}`);
184
+
185
+ if (filtered.size) {
186
+ p.log.message(
187
+ `설치하지 않은 의존성 (이미 설치됨): ${highlight(Array.from(filtered).join(", "))}`,
188
+ );
189
+ }
175
190
  }
191
+
192
+ p.outro("완료했어요.");
193
+ } catch (error) {
194
+ p.log.error(`추가에 실패했어요. ${error}`);
195
+ p.outro(highlight("작업이 취소됐어요."));
196
+ process.exit(1);
176
197
  }
177
198
 
178
- p.outro("완료했어요.");
199
+ // add-all 성공 이벤트 추적
200
+ const duration = Date.now() - startTime;
201
+ await analytics.track(options.cwd, {
202
+ event: "add-all",
203
+ properties: {
204
+ registries: selectedRegistryIds,
205
+ items_count: itemKeys.length,
206
+ include_deprecated: options.includeDeprecated || false,
207
+ dependencies_count: npmDependenciesToAdd.size,
208
+ duration_ms: duration,
209
+ },
210
+ });
179
211
  });
180
212
  };
@@ -10,6 +10,7 @@ import type { CAC } from "cac";
10
10
  import { BASE_URL } from "../constants";
11
11
  import { highlight } from "../utils/color";
12
12
  import { installDependencies } from "../utils/install";
13
+ import { analytics } from "../utils/analytics";
13
14
 
14
15
  const addOptionsSchema = z.object({
15
16
  itemIds: z.array(z.string()).optional(),
@@ -19,6 +20,7 @@ const addOptionsSchema = z.object({
19
20
  all: z.boolean(),
20
21
  cwd: z.string(),
21
22
  baseUrl: z.string().optional(),
23
+ overwrite: z.boolean().optional(),
22
24
  });
23
25
 
24
26
  export const addCommand = (cli: CAC) => {
@@ -35,9 +37,13 @@ export const addCommand = (cli: CAC) => {
35
37
  "the base url of the registry. defaults to the current directory.",
36
38
  { default: BASE_URL },
37
39
  )
40
+ .option("--overwrite", "Overwrite existing files without confirmation", {
41
+ default: false,
42
+ })
38
43
  .example("seed-design add ui:action-button")
39
44
  .example("seed-design add ui:alert-dialog")
40
45
  .action(async (itemIds, opts) => {
46
+ const startTime = Date.now();
41
47
  p.intro("seed-design add");
42
48
 
43
49
  const {
@@ -178,27 +184,54 @@ export const addCommand = (cli: CAC) => {
178
184
  cwd,
179
185
  baseUrl,
180
186
  config,
187
+ overwrite: options.overwrite,
181
188
  });
182
189
 
183
- const { installed, filtered } = await installDependencies({
184
- cwd,
185
- deps: Array.from(npmDependenciesToAdd),
186
- });
190
+ try {
191
+ const { installed, filtered } = await installDependencies({
192
+ cwd,
193
+ deps: Array.from(npmDependenciesToAdd),
194
+ });
187
195
 
188
- if (installed.size === 0) {
189
- p.log.message("모든 의존성이 이미 설치되어 있어요.");
190
- }
196
+ if (installed.size === 0) {
197
+ p.log.message("모든 의존성이 이미 설치되어 있어요.");
198
+ }
191
199
 
192
- if (installed.size) {
193
- p.log.message(`의존성 설치 완료: ${highlight(Array.from(installed).join(", "))}`);
200
+ if (installed.size) {
201
+ p.log.message(`의존성 설치 완료: ${highlight(Array.from(installed).join(", "))}`);
194
202
 
195
- if (filtered.size) {
196
- p.log.message(
197
- `설치하지 않은 의존성 (이미 설치됨): ${highlight(Array.from(filtered).join(", "))}`,
198
- );
203
+ if (filtered.size) {
204
+ p.log.message(
205
+ `설치하지 않은 의존성 (이미 설치됨): ${highlight(Array.from(filtered).join(", "))}`,
206
+ );
207
+ }
199
208
  }
209
+ p.outro("완료했어요.");
210
+ } catch (error) {
211
+ p.log.error(`추가에 실패했어요. ${error}`);
212
+ p.outro(highlight("작업이 취소됐어요."));
213
+ process.exit(1);
200
214
  }
201
215
 
202
- p.outro("완료했어요.");
216
+ // add 성공 이벤트 추적
217
+ const duration = Date.now() - startTime;
218
+ const uniqueRegistries = new Set(registryItemsToAdd.map((r) => r.registryId));
219
+ const hasDeprecated = selectedItemKeys.some((itemKey) => {
220
+ const [registryId, ...rest] = itemKey.split(":");
221
+ const itemId = rest.join(":");
222
+ return publicRegistries.find((r) => r.id === registryId)?.items.find((i) => i.id === itemId)
223
+ ?.deprecated;
224
+ });
225
+
226
+ await analytics.track(options.cwd, {
227
+ event: "add",
228
+ properties: {
229
+ items_count: filteredItemKeys.length,
230
+ registries: Array.from(uniqueRegistries),
231
+ has_deprecated: hasDeprecated,
232
+ dependencies_count: npmDependenciesToAdd.size,
233
+ duration_ms: duration,
234
+ },
235
+ });
203
236
  });
204
237
  };
@@ -1,12 +1,14 @@
1
1
  import * as p from "@clack/prompts";
2
2
  import fs from "fs-extra";
3
3
  import path from "path";
4
- import { highlight } from "../utils/color";
5
4
  import { z } from "zod";
5
+ import { analytics } from "../utils/analytics";
6
+ import { highlight } from "../utils/color";
6
7
 
7
8
  import type { Config } from "@/src/utils/get-config";
8
9
 
9
10
  import type { CAC } from "cac";
11
+ import dedent from "dedent";
10
12
 
11
13
  const initOptionsSchema = z.object({
12
14
  cwd: z.string(),
@@ -21,6 +23,7 @@ export const initCommand = (cli: CAC) => {
21
23
  })
22
24
  .option("-y, --yes", "모든 질문에 대해 기본값으로 답변합니다.")
23
25
  .action(async (opts) => {
26
+ const startTime = Date.now();
24
27
  p.intro("seed-design.json 파일 생성");
25
28
 
26
29
  const options = initOptionsSchema.parse(opts);
@@ -29,6 +32,7 @@ export const initCommand = (cli: CAC) => {
29
32
  rsc: false,
30
33
  tsx: true,
31
34
  path: "./seed-design",
35
+ telemetry: true,
32
36
  };
33
37
 
34
38
  if (!isYesOption) {
@@ -51,6 +55,11 @@ export const initCommand = (cli: CAC) => {
51
55
  defaultValue: "./seed-design",
52
56
  placeholder: "./seed-design",
53
57
  }),
58
+ telemetry: () =>
59
+ p.confirm({
60
+ message: `개선을 위해 ${highlight("익명 사용 데이터")}를 수집할까요?`,
61
+ initialValue: true,
62
+ }),
54
63
  },
55
64
  {
56
65
  onCancel: () => {
@@ -72,15 +81,41 @@ export const initCommand = (cli: CAC) => {
72
81
  await fs.writeFile(targetPath, `${JSON.stringify(config, null, 2)}\n`, "utf-8");
73
82
  const relativePath = path.relative(process.cwd(), targetPath);
74
83
  stop(`seed-design.json 파일이 ${highlight(relativePath)}에 생성됐어요.`);
84
+
75
85
  p.log.info(highlight("seed-design add {component} 명령어로 컴포넌트를 추가해보세요!"));
76
86
  p.log.info(
77
87
  highlight("seed-design add 명령어로 추가할 수 있는 모든 컴포넌트를 확인해보세요."),
78
88
  );
89
+
90
+ p.note(
91
+ dedent(`SEED Design CLI는 개선을 위해 익명 사용 데이터를 수집해요.
92
+
93
+ 비활성화하려면:
94
+ • seed-design.json에서 ${highlight('"telemetry": false')}로 설정
95
+ • ${highlight("DISABLE_TELEMETRY=true")} 환경 변수 설정
96
+
97
+ 자세한 내용: https://seed-design.com/react/getting-started/cli/configuration#telemetry`),
98
+ "Telemetry 안내",
99
+ );
100
+
79
101
  p.outro("작업이 완료됐어요.");
80
102
  } catch (error) {
81
103
  p.log.error(`seed-design.json 파일 생성에 실패했어요. ${error}`);
82
104
  p.outro(highlight("작업이 취소됐어요."));
83
105
  process.exit(1);
84
106
  }
107
+
108
+ // init 성공 이벤트 추적
109
+ const duration = Date.now() - startTime;
110
+ await analytics.track(options.cwd, {
111
+ event: "init",
112
+ properties: {
113
+ tsx: config.tsx,
114
+ rsc: config.rsc,
115
+ telemetry: config.telemetry,
116
+ yes_option: isYesOption,
117
+ duration_ms: duration,
118
+ },
119
+ });
85
120
  });
86
121
  };
package/src/env.d.ts ADDED
@@ -0,0 +1,13 @@
1
+ declare global {
2
+ namespace NodeJS {
3
+ interface ProcessEnv {
4
+ NODE_ENV: "dev" | "prod";
5
+ POSTHOG_API_KEY?: string;
6
+ POSTHOG_HOST?: string;
7
+ DISABLE_TELEMETRY?: "true" | "false";
8
+ SEED_DISABLE_TELEMETRY?: "true" | "false";
9
+ }
10
+ }
11
+ }
12
+
13
+ export {};
@@ -29,7 +29,7 @@ describe("resolveDependencies", () => {
29
29
  {
30
30
  id: "button",
31
31
  description: "Button component",
32
- files: [{ path: "button.tsx" }],
32
+ snippets: [{ path: "button.tsx" }],
33
33
  },
34
34
  ],
35
35
  });
@@ -105,7 +105,7 @@ describe("resolveDependencies", () => {
105
105
  {
106
106
  id: "dialog",
107
107
  description: "Dialog component",
108
- files: [{ path: "dialog.tsx" }],
108
+ snippets: [{ path: "dialog.tsx" }],
109
109
  innerDependencies: [
110
110
  {
111
111
  registryId: "breeze",
@@ -121,7 +121,7 @@ describe("resolveDependencies", () => {
121
121
  {
122
122
  id: "animate-number",
123
123
  description: "Animate number utility",
124
- files: [{ path: "animate-number.ts" }],
124
+ snippets: [{ path: "animate-number.ts" }],
125
125
  dependencies: ["framer-motion"],
126
126
  },
127
127
  ],
@@ -0,0 +1,119 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import * as p from "@clack/prompts";
3
+ import { getConfig } from "./get-config";
4
+
5
+ const EVENT_PREFIX = "seed_cli";
6
+
7
+ interface TrackOptions {
8
+ event: string;
9
+ properties?: Record<string, unknown>;
10
+ }
11
+
12
+ /**
13
+ * 텔레메트리 활성화 여부를 확인합니다.
14
+ * 우선순위:
15
+ * 1. 환경 변수 DISABLE_TELEMETRY
16
+ * 2. 환경 변수 SEED_DISABLE_TELEMETRY
17
+ * 3. seed-design.json의 telemetry 설정
18
+ * 4. 기본값 true (Opt-out)
19
+ */
20
+ async function isTelemetryEnabled(cwd: string): Promise<boolean> {
21
+ // 1. 환경 변수 체크
22
+ if (process.env.DISABLE_TELEMETRY === "true") return false;
23
+ if (process.env.SEED_DISABLE_TELEMETRY === "true") return false;
24
+
25
+ // 2. seed-design.json 체크
26
+ try {
27
+ const config = await getConfig(cwd);
28
+ if (config?.telemetry === false) return false;
29
+ } catch {
30
+ // 설정 파일이 없거나 읽기 실패 시 기본값 사용
31
+ }
32
+
33
+ // 3. 기본값
34
+ return true;
35
+ }
36
+
37
+ /**
38
+ * 익명 세션 ID를 생성합니다.
39
+ * 각 CLI 실행마다 새로운 UUID가 생성됩니다.
40
+ */
41
+ function generateSessionId(): string {
42
+ return randomUUID();
43
+ }
44
+
45
+ // 세션당 한 번만 생성
46
+ const sessionId = generateSessionId();
47
+
48
+ // 세션당 한 번만 메시지 표시
49
+ let hasShownMessage = false;
50
+
51
+ /**
52
+ * PostHog에 이벤트를 전송합니다.
53
+ */
54
+ async function track(cwd: string, { event, properties = {} }: TrackOptions): Promise<void> {
55
+ const enabled = await isTelemetryEnabled(cwd);
56
+
57
+ if (!enabled) {
58
+ return;
59
+ }
60
+
61
+ const fullEvent = `${EVENT_PREFIX}.${event}`;
62
+
63
+ // Dev 모드: 콘솔에만 출력
64
+ if (process.env.NODE_ENV === "dev") {
65
+ console.log(`📊 [Telemetry] ${fullEvent}`, properties);
66
+ return;
67
+ }
68
+
69
+ // 사용자에게 텔레메트리 수집 중임을 알림 (세션당 한 번만)
70
+ if (!hasShownMessage) {
71
+ p.log.info(
72
+ "📊 사용 데이터 수집 중 (비활성화: seed-design.json 또는 DISABLE_TELEMETRY 환경 변수)",
73
+ );
74
+ hasShownMessage = true;
75
+ }
76
+
77
+ // PostHog API 호출 (fire-and-forget)
78
+ try {
79
+ if (!process.env.POSTHOG_HOST || !process.env.POSTHOG_API_KEY) {
80
+ console.warn("[Analytics] Missing POSTHOG_HOST or POSTHOG_API_KEY");
81
+ return;
82
+ }
83
+
84
+ const url = `${process.env.POSTHOG_HOST}/capture`;
85
+ const headers = {
86
+ "Content-Type": "application/json",
87
+ };
88
+
89
+ const payload = {
90
+ api_key: process.env.POSTHOG_API_KEY,
91
+ event: fullEvent,
92
+ distinct_id: sessionId,
93
+ properties: {
94
+ ...properties,
95
+ $process_person_profile: false,
96
+ },
97
+ timestamp: new Date().toISOString(),
98
+ };
99
+ // 5초 타임아웃 설정
100
+ const controller = new AbortController();
101
+ const timeout = setTimeout(() => controller.abort(), 5000);
102
+ try {
103
+ await fetch(url, {
104
+ method: "POST",
105
+ headers,
106
+ body: JSON.stringify(payload),
107
+ signal: controller.signal,
108
+ });
109
+ } finally {
110
+ clearTimeout(timeout);
111
+ }
112
+ } catch {
113
+ // 에러 발생 시 조용히 무시 (CLI 블로킹 방지)
114
+ }
115
+ }
116
+
117
+ export const analytics = {
118
+ track,
119
+ };
@@ -17,6 +17,7 @@ export const configSchema = z
17
17
  rsc: z.coerce.boolean().default(false),
18
18
  tsx: z.coerce.boolean().default(true),
19
19
  path: z.string(),
20
+ telemetry: z.coerce.boolean().optional().default(true),
20
21
  })
21
22
  .strict();
22
23
 
@@ -3,6 +3,8 @@ import { transform } from "@/src/utils/transformers";
3
3
  import * as p from "@clack/prompts";
4
4
  import fs from "fs-extra";
5
5
  import path from "path";
6
+ import { createPatch } from "diff";
7
+ import colorize from "@npmcli/disparity-colors";
6
8
  import { highlight } from "./color";
7
9
  import type { Config } from "@/src/utils/get-config";
8
10
  import type { PublicRegistry } from "@/src/schema";
@@ -13,12 +15,14 @@ export async function writeRegistryItemSnippets({
13
15
  cwd,
14
16
  baseUrl,
15
17
  config,
18
+ overwrite = false,
16
19
  }: {
17
20
  registryItemsToAdd: { registryId: string; items: PublicRegistry["items"] }[];
18
21
  rootPath: string;
19
22
  cwd: string;
20
23
  baseUrl: string;
21
24
  config: Config;
25
+ overwrite?: boolean;
22
26
  }) {
23
27
  const registryResult: { name: string; path: string }[] = [];
24
28
 
@@ -53,23 +57,85 @@ export async function writeRegistryItemSnippets({
53
57
  }),
54
58
  );
55
59
 
56
- await Promise.all(
57
- transformedSnippets.map(async ({ filePath, content }) => {
58
- await fs.ensureDir(path.dirname(filePath));
59
- await fs.writeFile(filePath, content);
60
- }),
61
- );
60
+ const writtenSnippets: typeof transformedSnippets = [];
62
61
 
63
- const snippetResults = transformedSnippets.map(({ name, relativePath }) => ({
64
- name,
65
- path: relativePath,
66
- }));
62
+ for (const snippet of transformedSnippets) {
63
+ const { filePath, content, relativePath } = snippet;
67
64
 
68
- registryResult.push(...snippetResults);
65
+ await fs.ensureDir(path.dirname(filePath));
69
66
 
70
- p.log.success(
71
- `${highlight(`${registryId}:${id}`)} 관련 스니펫 다운로드 완료: ${highlight(snippetResults.map((r) => r.path).join(", "))}`,
72
- );
67
+ // 파일 존재 여부 확인
68
+ if (fs.existsSync(filePath)) {
69
+ const existingContent = await fs.readFile(filePath, "utf-8");
70
+
71
+ // 내용이 동일하면 스킵
72
+ if (existingContent === content) {
73
+ p.log.info(`${highlight(relativePath)}: 이미 최신 상태예요.`);
74
+ continue;
75
+ }
76
+
77
+ // diff가 있는 경우
78
+ if (!overwrite) {
79
+ // diff 생성 및 색상 적용
80
+ const patch = createPatch(relativePath, existingContent, content);
81
+ const coloredDiff = colorize(patch);
82
+
83
+ p.log.message(
84
+ `\n${highlight(relativePath)}: 현재 파일과 받으려는 파일의 내용이 달라요.\n`,
85
+ );
86
+ p.log.message(coloredDiff);
87
+
88
+ const filename = path.basename(filePath);
89
+ const ext = path.extname(filePath);
90
+ const base = path.basename(filePath, ext);
91
+ const timestamp = Date.now();
92
+ const legacyFilename = `legacy-${base}-${timestamp}${ext}`;
93
+
94
+ const action = await p.select({
95
+ message:
96
+ "현재 파일에 스타일 변경, 로깅 등 커스터마이징이 적용되어 있는 경우 신규 파일에 동일한 커스터마이징을 적용하는 것을 검토해보세요.",
97
+ options: [
98
+ { value: "overwrite", label: `${filename} 덮어쓰기` },
99
+ {
100
+ value: "backup",
101
+ label: `기존 파일 내용을 ${legacyFilename}으로 옮기고 ${filename} 받기`,
102
+ },
103
+ { value: "skip", label: "새 파일 받지 않고 그대로 두기" },
104
+ ],
105
+ });
106
+
107
+ if (p.isCancel(action) || action === "skip") {
108
+ p.log.info(`${highlight(relativePath)}: 파일을 받지 않고 건너뛰었어요.`);
109
+ continue;
110
+ }
111
+
112
+ if (action === "backup") {
113
+ const dir = path.dirname(filePath);
114
+ const legacyPath = path.join(dir, legacyFilename);
115
+ await fs.rename(filePath, legacyPath);
116
+ p.log.info(
117
+ `${highlight(relativePath)}: 기존 파일을 ${highlight(path.relative(cwd, legacyPath))}로 옮겼어요.`,
118
+ );
119
+ }
120
+ }
121
+ }
122
+
123
+ await fs.writeFile(filePath, content);
124
+ writtenSnippets.push(snippet);
125
+ }
126
+
127
+ if (writtenSnippets.length > 0) {
128
+ const snippetResults = writtenSnippets.map(({ name, relativePath }) => ({
129
+ name,
130
+ path: relativePath,
131
+ }));
132
+
133
+ registryResult.push(...snippetResults);
134
+
135
+ p.log.success(
136
+ `${highlight(`${registryId}:${id}`)} 관련 스니펫 다운로드 완료: ${highlight(snippetResults.map((r) => r.path).join(", "))}`,
137
+ );
138
+ }
73
139
  }
74
140
  }
75
141
  }