@seed-design/cli 1.0.0 → 1.1.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,13 @@
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 C from"@clack/prompts";import{cosmiconfig as xe}from"cosmiconfig";import{execa as be}from"execa";import{z as _}from"zod";import he from"picocolors";var a=e=>he.cyan(e);import{detect as we}from"@antfu/ni";async function z(e){let t=await we({programmatic:!0,cwd:e});return t==="yarn@berry"?"yarn":t==="pnpm@6"?"pnpm":t==="bun"?"bun":t==="deno"?"deno":t??"npm"}var te="seed-design",Ie=xe(te,{searchPlaces:[`${te}.json`]}),re=_.object({$schema:_.string().optional(),rsc:_.coerce.boolean().default(!1),tsx:_.coerce.boolean().default(!0),path:_.string(),telemetry:_.coerce.boolean().optional().default(!0)}).strict();async function D(e){let t=await $e(e);return t?re.parse(t):null}async function $e(e){try{let t=await Ie.search(e);return re.parse(t.config)}catch{C.log.error("\uD504\uB85C\uC81D\uD2B8 \uB8E8\uD2B8 \uACBD\uB85C\uC5D0 `seed-design.json` \uD30C\uC77C\uC774 \uC5C6\uC5B4\uC694."),await C.confirm({message:"seed-design.json \uD30C\uC77C\uC744 \uC0DD\uC131\uD558\uC2DC\uACA0\uC5B4\uC694?"})||(C.outro(a("\uC791\uC5C5\uC774 \uCDE8\uC18C\uB410\uC5B4\uC694.")),process.exit(1));let i=await z(e);await be(i,["seed-design","init","--default"],{cwd:e}),C.log.message("seed-design.json \uD30C\uC77C\uC774 \uC0DD\uC131\uB410\uC5B4\uC694.")}}function L({selectedItemKeys:e,publicRegistries:t}){let i=[],c=new Set;function p(r,n){let l=i.find(o=>o.registryId===r);if(!l?.items.some(o=>o.id===n.id)){if(l?l.items.push(n):i.push({registryId:r,items:[n]}),n.dependencies?.length)for(let o of n.dependencies)c.add(o);if(n.innerDependencies?.length)for(let o of n.innerDependencies)for(let u of o.itemIds){let w=t.find(R=>R.id===o.registryId)?.items.find(R=>R.id===u);if(!w)throw new Error(`Cannot find dependency item: ${o.registryId}:${u}`);p(o.registryId,w)}}}for(let r of e){let[n,...l]=r.split(":"),o=l.join(":");if(!n||!o)throw new Error(`Invalid snippet format: "${r}"`);let u=t.find(w=>w.id===n)?.items.find(w=>w.id===o);if(!u)throw new Error(`Cannot find snippet: "${r}"`);p(n,u)}return{registryItemsToAdd:i,npmDependenciesToAdd:c}}import*as Q 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()}))}),W=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()}))}))}),se=f.array(f.object({id:f.string()}));async function K({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 i=await t.json(),{success:c,data:p,error:r}=se.safeParse(i);if(!c)throw new Error(`Failed to parse registries: ${r?.message}`);return p}async function N({baseUrl:e,registryId:t}){let i=await fetch(`${e}/__registry__/${t}/index.json`);if(!i.ok)throw new Error(`Failed to fetch ${t} registry: ${i.status} ${i.statusText}`);let c=await i.json(),{success:p,data:r,error:n}=W.safeParse(c);if(!p)throw new Error(`Failed to parse ${t} registry: ${n?.message}`);return r}async function Pe({baseUrl:e,registryId:t,registryItemId:i}){let c=await fetch(`${e}/__registry__/${t}/${i}.json`);if(!c.ok)throw new Error(`Failed to fetch ${i}: ${c.status} ${c.statusText}`);let p=await c.json(),{success:r,data:n,error:l}=q.safeParse(p);if(!r)throw new Error(`Failed to parse ${i}: ${l?.message}`);return n}async function ie({baseUrl:e,registryId:t,registryItemIds:i}){return await Promise.all(i.map(async c=>{try{return await Pe({baseUrl:e,registryId:t,registryItemId:c})}catch(p){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 n=await r.json(),{success:l,data:o}=W.safeParse(n);throw l?(Q.log.error(`${c} \uC2A4\uB2C8\uD3AB\uC774 ${t} \uB808\uC9C0\uC2A4\uD2B8\uB9AC\uC5D0 \uC5C6\uC5B4\uC694.`),Q.log.info(`${t} \uB808\uC9C0\uC2A4\uD2B8\uB9AC\uC5D0 \uC874\uC7AC\uD558\uB294 \uC2A4\uB2C8\uD3AB:
3
+ ${o.items.map(u=>u.id).join(`
4
+ `)}`),p):new Error(`Failed to parse registry index for ${t}`)}}))}import{promises as Ce}from"fs";import{tmpdir as Ae}from"os";import ae from"path";import{transformFromAstSync as Se}from"@babel/core";import Re from"@babel/plugin-transform-typescript";import*as B from"recast";import{parse as Te}from"@babel/parser";var je={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"]},oe=async({sourceFile:e,config:t})=>{let i=e.getFullText();if(t.tsx)return i;let c=B.parse(i,{parser:{parse:r=>Te(r,je)}}),p=Se(c,i,{cloneInputAst:!1,code:!1,ast:!0,plugins:[Re],configFile:!1});if(!p||!p.ast)throw new Error("Failed to transform JSX");return B.print(p.ast).code};import{SyntaxKind as Ee}from"ts-morph";var ne=async({sourceFile:e,config:t})=>{if(t.rsc)return e;let i=e.getFirstChildByKind(Ee.ExpressionStatement);if(!i)return e;let c=i.getExpression();if(!c)return e;let p=c.getText().trim();if(p!=='"use client"'&&p!=="'use client'")return e;let r=i.getText(),n=i.getFullText();if(r.trim()===n.trim())return e;let o=n.replace(r,"").replace(/^\s*\n/,"").replace(/\n\s*$/,"");return i.replaceWithText(o),e};import{Project as ve,ScriptKind as _e}from"ts-morph";var De=[ne],Oe=new ve({compilerOptions:{}});async function ke(e){let t=await Ce.mkdtemp(ae.join(Ae(),"seed-design-"));return ae.join(t,e)}async function ce(e){let t=await ke(e.filename),i=Oe.createSourceFile(t,e.raw,{scriptKind:_e.TSX});for(let c of De)c({sourceFile:i,...e});return await oe({sourceFile:i,...e})}import*as pe from"@clack/prompts";import Z from"fs-extra";import J from"path";async function H({registryItemsToAdd:e,rootPath:t,cwd:i,baseUrl:c,config:p}){let r=[];for(let{registryId:n,items:l}of e){let o=J.join(t,n);Z.ensureDirSync(o);let u=await ie({baseUrl:c,registryId:n,registryItemIds:l.map(w=>w.id)});for(let{id:w,snippets:R}of u){let E=await Promise.all(R.map(async x=>{let $=await ce({filename:x.path,config:p,raw:x.content}),I=J.join(o,x.path);return p.tsx||(I=I.replace(/\.tsx$/,".jsx"),I=I.replace(/\.ts$/,".js")),{filePath:I,content:$,relativePath:J.relative(i,I),name:`${n}:${w}`}}));await Promise.all(E.map(async({filePath:x,content:$})=>{await Z.ensureDir(J.dirname(x)),await Z.writeFile(x,$)}));let b=E.map(({name:x,relativePath:$})=>({name:x,path:$}));r.push(...b),pe.log.success(`${a(`${n}:${w}`)} \uAD00\uB828 \uC2A4\uB2C8\uD3AB \uB2E4\uC6B4\uB85C\uB4DC \uC644\uB8CC: ${a(b.map(x=>x.path).join(", "))}`)}}}import*as d from"@clack/prompts";import Ge from"path";import{z as k}from"zod";var V="https://seed-design.io";import*as le from"@clack/prompts";import{execa as Le}from"execa";import Fe from"findup-sync";import Me from"fs-extra";var Ue="package.json";function ze(){let e=Fe(Ue);if(!e)throw new Error("No package.json file found in the project.");return e}function G(){let e=ze();return Me.readJSONSync(e)}async function Y({cwd:e,deps:t,dev:i=!1}){let{start:c,stop:p}=le.spinner(),r=await z(e),l={...G().dependencies},o=new Set(t.filter(b=>!l[b])),u=new Set(t.filter(b=>l[b]));if(!o.size)return{installed:new Set,filtered:o};c("\uC758\uC874\uC131 \uC124\uCE58\uC911...");let E=[r==="npm"?"install":"add",i?"-D":null,...o].filter(Boolean);try{await Le(r,E,{cwd:e})}catch(b){console.error(`\uC758\uC874\uC131 \uC124\uCE58 \uC2E4\uD328: ${b}`),process.exit(1)}return p("\uC758\uC874\uC131 \uC124\uCE58\uAC00 \uC644\uB8CC\uB410\uC5B4\uC694."),{installed:o,filtered:u}}import{randomUUID as Ke}from"node:crypto";import*as me from"@clack/prompts";var Ne="seed_cli";async function Be(e){if(process.env.DISABLE_TELEMETRY==="true"||process.env.SEED_DISABLE_TELEMETRY==="true")return!1;try{if((await D(e))?.telemetry===!1)return!1}catch{}return!0}function Je(){return Ke()}var He=Je(),de=!1;async function Ve(e,{event:t,properties:i={}}){if(!await Be(e))return;let p=`${Ne}.${t}`;de||(me.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",n={"Content-Type":"application/json"},l={api_key:"phc_seod8HhifElOP1R92KmvsQybrtUmkOTgZBsq0mfCelR",event:p,distinct_id:He,properties:{...i,$process_person_profile:!1},timestamp:new Date().toISOString()},o=new AbortController,u=setTimeout(()=>o.abort(),5e3);try{await fetch(r,{method:"POST",headers:n,body:JSON.stringify(l),signal:o.signal})}finally{clearTimeout(u)}}catch{}}var O={track:Ve};var Ye=k.object({itemIds:k.array(k.string()).optional(),all:k.boolean(),cwd:k.string(),baseUrl:k.string().optional()}),fe=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:V}).example("seed-design add ui:action-button").example("seed-design add ui:alert-dialog").action(async(t,i)=>{let c=Date.now();d.intro("seed-design add");let{success:p,data:{all:r,...n},error:l}=Ye.safeParse({itemIds:t,...i});p||(d.log.error(`\uC798\uBABB\uB41C \uC635\uC158\uC774\uC5D0\uC694: ${l?.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 o=n.cwd,u=n.baseUrl,w=await D(o),R=Ge.resolve(o,w.path),{start:E,stop:b}=d.spinner();E("Registry\uB97C \uAC00\uC838\uC624\uACE0 \uC788\uC5B4\uC694...");let x=await Promise.all((await K({baseUrl:u})).map(async({id:s})=>N({baseUrl:u,registryId:s})));b("Registry\uB97C \uAC00\uC838\uC654\uC5B4\uC694.");let $=await(async()=>{if(n.itemIds.length>0)return n.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:x.filter(({hideFromCLICatalog:m})=>!m).flatMap(({id:m,items:T})=>T.filter(({hideFromCLICatalog:S})=>!S).sort((S,P)=>S.id.localeCompare(P.id)).map(({id:S,description:P,deprecated:j})=>({label:`${j?"(deprecated) ":""}${a(m)}:${S}`,value:`${m}:${S}`,hint:P,deprecated:j,registryItemCount:T.length}))).sort((m,T)=>m.deprecated!==T.deprecated?m.deprecated?1:-1:T.registryItemCount-m.registryItemCount)});return d.isCancel(s)&&(d.log.error("\uCDE8\uC18C\uB418\uC5C8\uC5B4\uC694."),process.exit(0)),s})();$?.length||(d.log.error("\uD56D\uBAA9\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC5B4\uC694."),process.exit(0)),d.log.message(`\uC120\uD0DD\uB41C \uD56D\uBAA9: ${a($.join(", "))}`);let I=[];for(let s of $){let[m,...T]=s.split(":"),S=T.join(":");(!m||!S)&&(d.log.error(`${a(s)}: \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 P=x.find(j=>j.id===m)?.items.find(j=>j.id===S);if(P||(d.log.error(`${a(s)}: \uD56D\uBAA9\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC5B4\uC694.`),process.exit(1)),P.deprecated){let j=await d.confirm({message:`${a(P.id)}: deprecated \uB418\uC5C8\uC5B4\uC694. \uCD94\uAC00\uD560\uAE4C\uC694?`,initialValue:!1});if(j===!1||d.isCancel(j)){d.log.info(`${a(P.id)}: \uCD94\uAC00\uD558\uC9C0 \uC54A\uC744\uAC8C\uC694.`);continue}}I.push(s)}let{registryItemsToAdd:v,npmDependenciesToAdd:M}=L({selectedItemKeys:I,publicRegistries:x});d.log.info(`\uCD94\uAC00\uD560 \uD56D\uBAA9: ${a(v.map(s=>s.items.map(m=>`${s.registryId}:${m.id}`).join(", ")).join(", ")||"\uC5C6\uC74C")}
5
5
 
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();
6
+ \uC124\uCE58\uD560 \uC758\uC874\uC131: ${a(Array.from(M).join(", ")||"\uC5C6\uC74C")}`),await H({registryItemsToAdd:v,rootPath:R,cwd:o,baseUrl:u,config:w});try{let{installed:s,filtered:m}=await Y({cwd:o,deps:Array.from(M)});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: ${a(Array.from(s).join(", "))}`),m.size&&d.log.message(`\uC124\uCE58\uD558\uC9C0 \uC54A\uC740 \uC758\uC874\uC131 (\uC774\uBBF8 \uC124\uCE58\uB428): ${a(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(a("\uC791\uC5C5\uC774 \uCDE8\uC18C\uB410\uC5B4\uC694.")),process.exit(1)}let U=Date.now()-c,X=new Set(v.map(s=>s.registryId)),h=$.some(s=>{let[m,...T]=s.split(":"),S=T.join(":");return x.find(P=>P.id===m)?.items.find(P=>P.id===S)?.deprecated});await O.track(n.cwd,{event:"add",properties:{items_count:I.length,registries:Array.from(X),has_deprecated:h,dependencies_count:M.size,duration_ms:U}})})};import*as g from"@clack/prompts";import Xe from"path";import{z as A}from"zod";var qe=A.object({registryIds:A.array(A.string()).optional(),all:A.boolean(),includeDeprecated:A.boolean().optional(),cwd:A.string(),baseUrl:A.string().optional()}),ge=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:V}).example("seed-design add-all ui --include-deprecated").example("seed-design add-all ui lib breeze").action(async(t,i)=>{let c=Date.now();g.intro("seed-design add-all");let{success:p,data:r,error:n}=qe.safeParse({registryIds:t,...i});p||(g.log.error(`\uC798\uBABB\uB41C \uC635\uC158\uC774\uC5D0\uC694: ${n?.message}`),process.exit(1));let l=r.cwd,o=r.baseUrl,u=await D(l),w=Xe.resolve(l,u.path),{start:R,stop:E}=g.spinner();R("Registry\uB97C \uAC00\uC838\uC624\uACE0 \uC788\uC5B4\uC694...");let b=await Promise.all((await K({baseUrl:o})).map(async({id:h})=>N({baseUrl:o,registryId:h})));E("Registry\uB97C \uAC00\uC838\uC654\uC5B4\uC694.");let x=await(async()=>{if(r.all){let s=b.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: ${a(s.join(", "))}`),s}if(r.registryIds?.length){let s=b.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: ${a(r.registryIds.join(", "))}`),r.registryIds}let h=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:b.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(h)&&(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(h.join(", "))}`),h})(),$=b.filter(h=>x.includes(h.id)),I=$.flatMap(h=>h.items.filter(s=>s.deprecated?r.includeDeprecated:!0).map(s=>`${h.id}:${s.id}`)),v=$.flatMap(h=>h.items.filter(s=>s.deprecated).map(()=>1)).length;!r.includeDeprecated&&v>0&&g.log.info(`${v}\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.`),I.length||(g.log.error("\uCD94\uAC00\uD560 \uD56D\uBAA9\uC774 \uC5C6\uC5B4\uC694."),process.exit(0)),g.log.message(`\uCD1D ${a(I.length.toString())}\uAC1C\uC758 \uD56D\uBAA9\uC744 \uCD94\uAC00\uD569\uB2C8\uB2E4.`);let{registryItemsToAdd:M,npmDependenciesToAdd:U}=L({selectedItemKeys:I,publicRegistries:b});await H({registryItemsToAdd:M,rootPath:w,cwd:l,baseUrl:o,config:u});try{let{installed:h,filtered:s}=await Y({cwd:l,deps:Array.from(U)});h.size===0&&g.log.message("\uBAA8\uB4E0 \uC758\uC874\uC131\uC774 \uC774\uBBF8 \uC124\uCE58\uB418\uC5B4 \uC788\uC5B4\uC694."),h.size&&(g.log.message(`\uC758\uC874\uC131 \uC124\uCE58 \uC644\uB8CC: ${a(Array.from(h).join(", "))}`),s.size&&g.log.message(`\uC124\uCE58\uD558\uC9C0 \uC54A\uC740 \uC758\uC874\uC131 (\uC774\uBBF8 \uC124\uCE58\uB428): ${a(Array.from(s).join(", "))}`)),g.outro("\uC644\uB8CC\uD588\uC5B4\uC694.")}catch(h){g.log.error(`\uCD94\uAC00\uC5D0 \uC2E4\uD328\uD588\uC5B4\uC694. ${h}`),g.outro(a("\uC791\uC5C5\uC774 \uCDE8\uC18C\uB410\uC5B4\uC694.")),process.exit(1)}let X=Date.now()-c;await O.track(r.cwd,{event:"add-all",properties:{registries:x,items_count:I.length,include_deprecated:r.includeDeprecated||!1,dependencies_count:U.size,duration_ms:X}})})};import*as y from"@clack/prompts";import We from"fs-extra";import ue from"path";import{z as ee}from"zod";import Qe from"dedent";var Ze=ee.object({cwd:ee.string(),yes:ee.boolean().optional()}),ye=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 i=Date.now();y.intro("seed-design.json \uD30C\uC77C \uC0DD\uC131");let c=Ze.parse(t),p=c.yes,r={rsc:!1,tsx:!0,path:"./seed-design",telemetry:!0};p||(r={...await y.group({tsx:()=>y.confirm({message:`${a("TypeScript")}\uB97C \uC0AC\uC6A9\uC911\uC774\uC2E0\uAC00\uC694?`,initialValue:!0}),rsc:()=>y.confirm({message:`${a("React Server Components")}\uB97C \uC0AC\uC6A9\uC911\uC774\uC2E0\uAC00\uC694?`,initialValue:!1}),path:()=>y.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"}),telemetry:()=>y.confirm({message:`\uAC1C\uC120\uC744 \uC704\uD574 ${a("\uC775\uBA85 \uC0AC\uC6A9 \uB370\uC774\uD130")}\uB97C \uC218\uC9D1\uD560\uAE4C\uC694?`,initialValue:!0})},{onCancel:()=>{y.cancel("\uC791\uC5C5\uC774 \uCDE8\uC18C\uB410\uC5B4\uC694."),process.exit(0)}})});try{let{start:l,stop:o}=y.spinner();l("seed-design.json \uD30C\uC77C \uC0DD\uC131\uC911...");let u=ue.resolve(c.cwd,"seed-design.json");await We.writeFile(u,`${JSON.stringify(r,null,2)}
7
+ `,"utf-8");let w=ue.relative(process.cwd(),u);o(`seed-design.json \uD30C\uC77C\uC774 ${a(w)}\uC5D0 \uC0DD\uC131\uB410\uC5B4\uC694.`),y.log.info(a("seed-design add {component} \uBA85\uB839\uC5B4\uB85C \uCEF4\uD3EC\uB10C\uD2B8\uB97C \uCD94\uAC00\uD574\uBCF4\uC138\uC694!")),y.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.")),y.note(Qe(`SEED Design CLI\uB294 \uAC1C\uC120\uC744 \uC704\uD574 \uC775\uBA85 \uC0AC\uC6A9 \uB370\uC774\uD130\uB97C \uC218\uC9D1\uD574\uC694.
8
+
9
+ \uBE44\uD65C\uC131\uD654\uD558\uB824\uBA74:
10
+ \u2022 seed-design.json\uC5D0\uC11C ${a('"telemetry": false')}\uB85C \uC124\uC815
11
+ \u2022 ${a("DISABLE_TELEMETRY=true")} \uD658\uACBD \uBCC0\uC218 \uC124\uC815
12
+
13
+ \uC790\uC138\uD55C \uB0B4\uC6A9: https://seed-design.com/react/getting-started/cli/configuration#telemetry`),"Telemetry \uC548\uB0B4"),y.outro("\uC791\uC5C5\uC774 \uC644\uB8CC\uB410\uC5B4\uC694.")}catch(l){y.log.error(`seed-design.json \uD30C\uC77C \uC0DD\uC131\uC5D0 \uC2E4\uD328\uD588\uC5B4\uC694. ${l}`),y.outro(a("\uC791\uC5C5\uC774 \uCDE8\uC18C\uB410\uC5B4\uC694.")),process.exit(1)}let n=Date.now()-i;await O.track(c.cwd,{event:"init",properties:{tsx:r.tsx,rsc:r.rsc,telemetry:r.telemetry,yes_option:p,duration_ms:n}})})};import{cac as et}from"cac";var tt="seed-design",F=et(tt);async function rt(){let e=G();fe(F),ge(F),ye(F),F.version(e.version||"1.0.0","-v, --version"),F.help(),F.parse()}rt();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@seed-design/cli",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
4
4
  "type": "module",
5
5
  "repository": {
6
6
  "type": "git",
@@ -25,13 +25,14 @@
25
25
  "lint:publish": "bun publint"
26
26
  },
27
27
  "dependencies": {
28
- "@antfu/ni": "^26.0.0",
28
+ "@antfu/ni": "^27.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
33
  "cac": "^6.7.14",
34
34
  "cosmiconfig": "^9.0.0",
35
+ "dedent": "^1.7.0",
35
36
  "execa": "^9.5.2",
36
37
  "findup-sync": "^5.0.0",
37
38
  "fs-extra": "^11.3.0",
@@ -43,7 +44,8 @@
43
44
  "devDependencies": {
44
45
  "@types/babel__core": "^7.20.5",
45
46
  "@types/fs-extra": "^11.0.4",
46
- "esbuild": "^0.25.3",
47
+ "dotenv": "^17.2.3",
48
+ "esbuild": "^0.27.0",
47
49
  "type-fest": "^5.0.0",
48
50
  "typescript": "^5.9.2"
49
51
  },
@@ -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
 
@@ -39,6 +40,7 @@ export const addAllCommand = (cli: CAC) => {
39
40
  .example("seed-design add-all ui --include-deprecated")
40
41
  .example("seed-design add-all ui lib breeze")
41
42
  .action(async (registryIds, opts) => {
43
+ const startTime = Date.now();
42
44
  p.intro("seed-design add-all");
43
45
 
44
46
  const {
@@ -156,25 +158,44 @@ export const addAllCommand = (cli: CAC) => {
156
158
 
157
159
  await writeRegistryItemSnippets({ registryItemsToAdd, rootPath, cwd, baseUrl, config });
158
160
 
159
- const { installed, filtered } = await installDependencies({
160
- cwd,
161
- deps: Array.from(npmDependenciesToAdd),
162
- });
161
+ try {
162
+ const { installed, filtered } = await installDependencies({
163
+ cwd,
164
+ deps: Array.from(npmDependenciesToAdd),
165
+ });
163
166
 
164
- if (installed.size === 0) {
165
- p.log.message("모든 의존성이 이미 설치되어 있어요.");
166
- }
167
+ if (installed.size === 0) {
168
+ p.log.message("모든 의존성이 이미 설치되어 있어요.");
169
+ }
167
170
 
168
- if (installed.size) {
169
- p.log.message(`의존성 설치 완료: ${highlight(Array.from(installed).join(", "))}`);
171
+ if (installed.size) {
172
+ p.log.message(`의존성 설치 완료: ${highlight(Array.from(installed).join(", "))}`);
170
173
 
171
- if (filtered.size) {
172
- p.log.message(
173
- `설치하지 않은 의존성 (이미 설치됨): ${highlight(Array.from(filtered).join(", "))}`,
174
- );
174
+ if (filtered.size) {
175
+ p.log.message(
176
+ `설치하지 않은 의존성 (이미 설치됨): ${highlight(Array.from(filtered).join(", "))}`,
177
+ );
178
+ }
175
179
  }
180
+
181
+ p.outro("완료했어요.");
182
+ } catch (error) {
183
+ p.log.error(`추가에 실패했어요. ${error}`);
184
+ p.outro(highlight("작업이 취소됐어요."));
185
+ process.exit(1);
176
186
  }
177
187
 
178
- p.outro("완료했어요.");
188
+ // add-all 성공 이벤트 추적
189
+ const duration = Date.now() - startTime;
190
+ await analytics.track(options.cwd, {
191
+ event: "add-all",
192
+ properties: {
193
+ registries: selectedRegistryIds,
194
+ items_count: itemKeys.length,
195
+ include_deprecated: options.includeDeprecated || false,
196
+ dependencies_count: npmDependenciesToAdd.size,
197
+ duration_ms: duration,
198
+ },
199
+ });
179
200
  });
180
201
  };
@@ -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(),
@@ -38,6 +39,7 @@ export const addCommand = (cli: CAC) => {
38
39
  .example("seed-design add ui:action-button")
39
40
  .example("seed-design add ui:alert-dialog")
40
41
  .action(async (itemIds, opts) => {
42
+ const startTime = Date.now();
41
43
  p.intro("seed-design add");
42
44
 
43
45
  const {
@@ -180,25 +182,51 @@ export const addCommand = (cli: CAC) => {
180
182
  config,
181
183
  });
182
184
 
183
- const { installed, filtered } = await installDependencies({
184
- cwd,
185
- deps: Array.from(npmDependenciesToAdd),
186
- });
185
+ try {
186
+ const { installed, filtered } = await installDependencies({
187
+ cwd,
188
+ deps: Array.from(npmDependenciesToAdd),
189
+ });
187
190
 
188
- if (installed.size === 0) {
189
- p.log.message("모든 의존성이 이미 설치되어 있어요.");
190
- }
191
+ if (installed.size === 0) {
192
+ p.log.message("모든 의존성이 이미 설치되어 있어요.");
193
+ }
191
194
 
192
- if (installed.size) {
193
- p.log.message(`의존성 설치 완료: ${highlight(Array.from(installed).join(", "))}`);
195
+ if (installed.size) {
196
+ p.log.message(`의존성 설치 완료: ${highlight(Array.from(installed).join(", "))}`);
194
197
 
195
- if (filtered.size) {
196
- p.log.message(
197
- `설치하지 않은 의존성 (이미 설치됨): ${highlight(Array.from(filtered).join(", "))}`,
198
- );
198
+ if (filtered.size) {
199
+ p.log.message(
200
+ `설치하지 않은 의존성 (이미 설치됨): ${highlight(Array.from(filtered).join(", "))}`,
201
+ );
202
+ }
199
203
  }
204
+ p.outro("완료했어요.");
205
+ } catch (error) {
206
+ p.log.error(`추가에 실패했어요. ${error}`);
207
+ p.outro(highlight("작업이 취소됐어요."));
208
+ process.exit(1);
200
209
  }
201
210
 
202
- p.outro("완료했어요.");
211
+ // add 성공 이벤트 추적
212
+ const duration = Date.now() - startTime;
213
+ const uniqueRegistries = new Set(registryItemsToAdd.map((r) => r.registryId));
214
+ const hasDeprecated = selectedItemKeys.some((itemKey) => {
215
+ const [registryId, ...rest] = itemKey.split(":");
216
+ const itemId = rest.join(":");
217
+ return publicRegistries.find((r) => r.id === registryId)?.items.find((i) => i.id === itemId)
218
+ ?.deprecated;
219
+ });
220
+
221
+ await analytics.track(options.cwd, {
222
+ event: "add",
223
+ properties: {
224
+ items_count: filteredItemKeys.length,
225
+ registries: Array.from(uniqueRegistries),
226
+ has_deprecated: hasDeprecated,
227
+ dependencies_count: npmDependenciesToAdd.size,
228
+ duration_ms: duration,
229
+ },
230
+ });
203
231
  });
204
232
  };
@@ -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