@seed-design/cli 0.0.0-alpha.1 → 0.0.1
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 +3 -3
- package/bin/index.mjs +2 -2
- package/package.json +9 -16
- package/src/commands/add.ts +112 -78
- package/src/commands/init.ts +61 -46
- package/src/constants.ts +2 -0
- package/src/index.ts +1 -1
- package/src/schema.ts +58 -16
- package/src/test/add-relative-registries.test.ts +182 -0
- package/src/utils/add-relative-registries.ts +43 -0
- package/src/utils/color.ts +3 -0
- package/src/utils/get-config.ts +34 -9
- package/src/utils/get-metadata.ts +64 -25
- package/src/utils/get-package-info.ts +2 -1
- package/src/utils/get-package-manager.ts +2 -2
- package/src/utils/install.ts +53 -0
- package/src/utils/transformers/index.ts +1 -2
- package/src/test/add-relative-components.test.ts +0 -67
- package/src/utils/add-relative-components.ts +0 -29
- package/src/utils/transformers/transform-css.ts +0 -16
package/README.md
CHANGED
|
@@ -2,6 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
### How to test
|
|
4
4
|
|
|
5
|
-
- Run `
|
|
6
|
-
- Run `
|
|
7
|
-
- You can test `
|
|
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
|
package/bin/index.mjs
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import{cosmiconfig as Z}from"cosmiconfig";import T from"path";import{z as u}from"zod";var $="seed-design",ee=Z($,{searchPlaces:[`${$}.json`]}),k=u.object({$schema:u.string().optional(),rsc:u.coerce.boolean().default(!1),tsx:u.coerce.boolean().default(!0),css:u.coerce.boolean().default(!0),path:u.string()}).strict(),te=k.extend({resolvedUIPaths:u.string()});async function O(e){let t=await ne(e);return t?await oe(e,t):null}async function oe(e,t){let n=T.resolve(e,t.path);return te.parse({...t,resolvedUIPaths:T.join(n,"ui")})}async function ne(e){try{let t=await ee.search(e);return t?k.parse(t.config):null}catch(t){throw console.log(t),new Error(`Invalid configuration found in ${e}/seed-design.json.`)}}import{z as s}from"zod";var A=s.object({name:s.string(),description:s.string().optional(),dependencies:s.array(s.string()).optional(),devDependencies:s.array(s.string()).optional(),innerDependencies:s.array(s.string()).optional(),snippets:s.array(s.string()),type:s.enum(["component"])}),E=s.array(A),re=A.omit({snippets:!0}),se=re.extend({registries:s.array(s.object({name:s.string(),content:s.string()}))}),De=s.array(se);var I="https://component.seed-design.io";async function M(e){try{return await Promise.all(e.map(async n=>await(await fetch(`${I}/registry/component/${n}.json`)).json()))}catch(t){throw console.log(t),new Error(`Failed to fetch registry from ${I}.`)}}async function D(){try{let[e]=await M(["index"]);return E.parse(e)}catch(e){throw console.log(e),new Error(`Failed to fetch components from ${I}.`)}}import{detect as ae}from"@antfu/ni";async function F(e){let t=await ae({programmatic:!0,cwd:e});return t==="yarn@berry"?"yarn":t==="pnpm@6"?"pnpm":t==="bun"?"bun":t??"npm"}import{promises as le}from"fs";import{tmpdir as fe}from"os";import J from"path";import{transformFromAstSync as ie}from"@babel/core";import ce from"@babel/plugin-transform-typescript";import*as v from"recast";import{parse as pe}from"@babel/parser";var me={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"]},z=async({sourceFile:e,config:t})=>{let n=e.getFullText();if(t.tsx)return n;let r=v.parse(n,{parser:{parse:c=>pe(c,me)}}),o=ie(r,n,{cloneInputAst:!1,code:!1,ast:!0,plugins:[ce],configFile:!1});if(!o||!o.ast)throw new Error("Failed to transform JSX");return v.print(o.ast).code};import{SyntaxKind as de}from"ts-morph";var N=async({sourceFile:e,config:t})=>{if(t.rsc)return e;let n=e.getFirstChildByKind(de.ExpressionStatement);return n?.getText()==='"use client";'&&n.remove(),e};var W=async({sourceFile:e,config:t})=>{if(t.css)return e;let r=e.getImportDeclarations().filter(o=>o.getModuleSpecifierValue().endsWith(".css"));for(let o of r)o.remove();return e};import{Project as ge,ScriptKind as ue}from"ts-morph";var he=[N,W],ye=new ge({compilerOptions:{}});async function xe(e){let t=await le.mkdtemp(J.join(fe(),"seed-deisgn-"));return J.join(t,e)}async function V(e){let t=await xe(e.filename),n=ye.createSourceFile(t,e.raw,{scriptKind:ue.TSX});for(let r of he)r({sourceFile:n,...e});return await z({sourceFile:n,...e})}import*as a from"@clack/prompts";import{execa as B}from"execa";import P from"fs-extra";import K from"path";import j from"picocolors";import{z as w}from"zod";function U(e,t){let n=new Set;function r(o){if(n.has(o))return;n.add(o);let c=t.find(d=>d.name===o);if(c&&c.innerDependencies)for(let d of c.innerDependencies)r(d)}for(let o of e)r(o);return Array.from(n)}var we=w.object({components:w.array(w.string()).optional(),cwd:w.string(),all:w.boolean()}),L=e=>{e.command("add [...components]","add component").option("-a, --all","Add all components",{default:!1}).option("-c, --cwd <cwd>","the working directory. defaults to the current directory.",{default:process.cwd()}).example("seed-design add box-button").example("seed-design add alert-dialog").action(async(t,n)=>{let r=we.parse({components:t,...n}),o=i=>j.cyan(i),c=r.cwd;P.existsSync(c)||(a.log.error(`The path ${c} does not exist. Please try again.`),process.exit(1));let d=await D(),f=r.all?d.map(i=>i.name):r.components;if(!r.components?.length&&!r.all){let i=await a.multiselect({message:"Select all components to add",options:d.map(l=>({label:l.name,value:l.name,hint:l.description}))});a.isCancel(i)&&(a.log.error("Aborted."),process.exit(0)),f=i}f?.length||(a.log.error("No components found."),process.exit(0));let y=U(f,d),S=y.filter(i=>!f.includes(i)),x=await O(c),H=await M(y);a.log.message(`Selection: ${o(f.join(", "))}`),S.length&&a.log.message(`Inner Dependencies: ${o(S.join(", "))} will be also added.`);for(let i of H){for(let m of i.registries){let g=x.resolvedUIPaths;P.existsSync(g)||await P.mkdir(g,{recursive:!0});let h=K.resolve(g,m.name),Q=await V({filename:m.name,config:x,raw:m.content});x.tsx||(h=h.replace(/\.tsx$/,".jsx"),h=h.replace(/\.ts$/,".js")),await P.writeFile(h,Q);let Y=K.relative(c,h);a.log.info(`Added ${o(m.name)} to ${o(Y)}`)}let l=await F(c),{start:b,stop:R}=a.spinner();if(i.dependencies?.length){b(j.gray("Installing dependencies"));let m=await B(l,[l==="npm"?"install":"add",...i.dependencies],{cwd:c});if(m.failed)console.error(m.all),process.exit(1);else{for(let g of i.dependencies)a.log.info(`- ${g}`);R("Dependencies installed.")}}if(i.devDependencies?.length){b(j.gray("Installing devDependencies"));let m=await B(l,[l==="npm"?"install":"add","-D",...i.devDependencies],{cwd:c});if(m.failed)console.error(m.all),process.exit(1);else{for(let g of i.devDependencies)a.log.info(`- ${g}`);R("Dependencies installed.")}}}a.outro("Components added.")})};import Ce from"findup-sync";import Se from"fs-extra";var ve="package.json";function Pe(){let e=Ce(ve);if(!e)throw new Error("No package.json file found in the project.");return e}function _(){return Se.readJSONSync(Pe())}import{cac as be}from"cac";import*as p from"@clack/prompts";import Ie from"fs-extra";import G from"path";import Me from"picocolors";import{z as X}from"zod";var je=X.object({cwd:X.string()}),q=e=>{e.command("init","initialize seed-design.json").option("-c, --cwd <cwd>","the working directory. defaults to the current directory.",{default:process.cwd()}).action(async t=>{let n=je.parse({...t}),r=x=>Me.cyan(x),o=await p.group({tsx:()=>p.confirm({message:`Would you like to use ${r("TypeScript")} (recommended)?`,initialValue:!0}),rsc:()=>p.confirm({message:`Are you using ${r("React Server Components")}?`,initialValue:!1}),css:()=>p.confirm({message:`Would you like to use ${r("CSS Modules")}? (If true, CSS import will be added in components)`,initialValue:!0}),path:()=>p.text({message:`Enter the path to your ${r("seed-design directory")}`,initialValue:"./seed-design",defaultValue:"./seed-design",placeholder:"./seed-design"})},{onCancel:()=>{p.cancel("Operation cancelled."),process.exit(0)}}),c={rsc:o.rsc,tsx:o.tsx,css:o.css,path:o.path},{start:d,stop:f}=p.spinner();d("Writing seed-design.json...");let y=G.resolve(n.cwd,"seed-design.json");await Ie.writeFile(y,`${JSON.stringify(c,null,2)}
|
|
3
|
-
`,"utf-8");let
|
|
2
|
+
import*as w from"@clack/prompts";import{cosmiconfig as ce}from"cosmiconfig";import{execa as pe}from"execa";import I from"fs";import D from"path";import{z as x}from"zod";import ie from"picocolors";var y=e=>ie.cyan(e);import{detect as ae}from"@antfu/ni";async function M(e){let t=await ae({programmatic:!0,cwd:e});return t==="yarn@berry"?"yarn":t==="pnpm@6"?"pnpm":t==="bun"?"bun":t==="deno"?"deno":t??"npm"}var F="seed-design",me=ce(F,{searchPlaces:[`${F}.json`]}),_=x.object({$schema:x.string().optional(),rsc:x.coerce.boolean().default(!1),tsx:x.coerce.boolean().default(!0),path:x.string()}).strict(),le=_.extend({resolvedUIPaths:x.string(),resolvedLibPaths:x.string()});async function J(e){let t=await ge(e);return t?await fe(e,t):null}async function fe(e,t){let r=D.resolve(e,t.path);I.existsSync(r)||I.mkdirSync(r,{recursive:!0});let n=D.join(r,"ui"),o=D.join(r,"lib");return I.existsSync(n)||I.mkdirSync(n,{recursive:!0}),I.existsSync(o)||I.mkdirSync(o,{recursive:!0}),le.parse({...t,resolvedUIPaths:n,resolvedLibPaths:o})}async function ge(e){try{let t=await me.search(e);return _.parse(t.config)}catch{if(w.log.error("\uD504\uB85C\uC81D\uD2B8 \uB8E8\uD2B8 \uACBD\uB85C\uC5D0 `seed-design.json` \uD30C\uC77C\uC774 \uC5C6\uC5B4\uC694."),await w.confirm({message:"seed-design.json \uD30C\uC77C\uC744 \uC0DD\uC131\uD558\uC2DC\uACA0\uC5B4\uC694?"})===!0){let r=await M(e);await pe(r,["seed-design","init","--default"],{cwd:e}),w.log.message("seed-design.json \uD30C\uC77C\uC774 \uC0DD\uC131\uB410\uC5B4\uC694.")}else w.outro(y("\uC791\uC5C5\uC774 \uCDE8\uC18C\uB410\uC5B4\uC694.")),process.exit(1)}}import*as R from"@clack/prompts";import{z as c}from"zod";var de=c.union([c.literal("ui"),c.literal("lib")]),N=c.object({name:c.string(),description:c.string().optional(),dependencies:c.array(c.string()).optional(),devDependencies:c.array(c.string()).optional(),innerDependencies:c.array(c.string()).optional(),files:c.array(c.string())}),U=c.array(N),ue=N.omit({files:!0}),ye=ue.extend({registries:c.array(c.object({name:c.string(),type:de,content:c.string()}))}),tt=c.array(ye);async function G(e,t,r="ui"){return await Promise.all(e.map(async o=>{try{return await(await fetch(`${t}/__registry__/${r}/${o}.json`)).json()}catch(s){let a=await fetch(`${t}/__registry__/${r}/index.json`).then(g=>g.json()),m=U.parse(a).map(g=>g.name);R.log.error(`${r}:${o} \uCEF4\uD3EC\uB10C\uD2B8\uB294 \uB808\uC9C0\uC2A4\uD2B8\uB9AC\uC5D0 \uC874\uC7AC\uD558\uC9C0 \uC54A\uC544\uC694.`),R.log.info(`\uC0AC\uC6A9 \uAC00\uB2A5\uD55C \uCEF4\uD3EC\uB10C\uD2B8: ${m.join(", ")}`),R.log.info(JSON.stringify({baseUrl:t,error:s.toString()},null,2)),process.exit(1)}}))}async function B(e,t,r="ui"){let[n]=await G([e],t,r);return n}async function K(e){try{let[t]=await G(["index"],e,"ui");return U.parse(t)}catch(t){R.log.error("\uB808\uC9C0\uC2A4\uD2B8\uB9AC \uC778\uB371\uC2A4\uB97C \uAC00\uC838\uC624\uB294 \uB370 \uC2E4\uD328\uD588\uC5B4\uC694."),R.log.info(JSON.stringify({baseUrl:e,error:t.toString()},null,2)),process.exit(1)}}async function V(e){let[t]=await G(["index"],e,"lib");return U.parse(t)}import{promises as Re}from"fs";import{tmpdir as be}from"os";import q from"path";import{transformFromAstSync as he}from"@babel/core";import xe from"@babel/plugin-transform-typescript";import*as $ from"recast";import{parse as we}from"@babel/parser";var Se={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"]},X=async({sourceFile:e,config:t})=>{let r=e.getFullText();if(t.tsx)return r;let n=$.parse(r,{parser:{parse:s=>we(s,Se)}}),o=he(n,r,{cloneInputAst:!1,code:!1,ast:!0,plugins:[xe],configFile:!1});if(!o||!o.ast)throw new Error("Failed to transform JSX");return $.print(o.ast).code};import{SyntaxKind as Ie}from"ts-morph";var Y=async({sourceFile:e,config:t})=>{if(t.rsc)return e;let r=e.getFirstChildByKind(Ie.ExpressionStatement);return r?.getText()==='"use client";'&&r.remove(),e};import{Project as Ce,ScriptKind as ve}from"ts-morph";var Pe=[Y],je=new Ce({compilerOptions:{}});async function ke(e){let t=await Re.mkdtemp(q.join(be(),"seed-deisgn-"));return q.join(t,e)}async function H(e){let t=await ke(e.filename),r=je.createSourceFile(t,e.raw,{scriptKind:ve.TSX});for(let n of Pe)n({sourceFile:r,...e});return await X({sourceFile:r,...e})}import*as l from"@clack/prompts";import z from"fs-extra";import ee from"path";import De from"picocolors";import{z as b}from"zod";var Q="https://v3.seed-design.io";function W({userSelects:e,uiRegistryIndex:t,libRegistryIndex:r}){let n=[];function o({registryName:s,type:a}){if(n.some(m=>m.name===s&&m.type===a))return;n.push({type:a,name:s});let d=a==="ui"?t.find(m=>m.name===s):r.find(m=>m.name===s);if(d&&d.innerDependencies)for(let m of d.innerDependencies){let[g,k]=m.split(":");o({registryName:k,type:g})}}for(let s of e)o({registryName:s,type:"ui"});return Array.from(n)}import*as Z from"@clack/prompts";import{execa as Ae}from"execa";import Te from"picocolors";import Me from"findup-sync";import Ue from"fs-extra";var $e="package.json";function Oe(){let e=Me($e);if(!e)throw new Error("No package.json file found in the project.");return e}function O(){let e=Oe();return Ue.readJSONSync(e)}async function L({cwd:e,deps:t,dev:r=!1}){let{start:n,stop:o}=Z.spinner(),s=await M(e),a=O(),d={...a.dependencies,...a.devDependencies},m=t.filter(h=>!d[h]),g=t.filter(h=>d[h]);if(!m.length)return{installed:new Set,filtered:new Set};n(Te.gray("\uC758\uC874\uC131 \uC124\uCE58\uC911..."));let A=[s==="npm"?"install":"add",r?"-D":null,...m].filter(Boolean);try{await Ae(s,A,{cwd:e})}catch(h){console.error(`\uC758\uC874\uC131 \uC124\uCE58 \uC2E4\uD328: ${h}`),process.exit(1)}return o("\uC758\uC874\uC131 \uC124\uCE58 \uC644\uB8CC."),{installed:m,filtered:g}}var Ge=b.object({components:b.array(b.string()).optional(),cwd:b.string(),all:b.boolean(),baseUrl:b.string().optional()}),te=e=>{e.command("add [...components]","add component").option("-a, --all","Add all components",{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:Q}).example("seed-design add action-button").example("seed-design add alert-dialog").action(async(t,r)=>{l.intro(De.bgCyan("seed-design add"));let n=Ge.parse({components:t,...r}),o=n.cwd,s=n.baseUrl,a=await J(o),d=await K(s),m=await V(s),g=n.all?d.map(f=>f.name):n.components;if(!n.components?.length&&!n.all){let f=await l.multiselect({message:"\uCD94\uAC00\uD560 \uCEF4\uD3EC\uB10C\uD2B8\uB97C \uC120\uD0DD\uD574\uC8FC\uC138\uC694 (\uC2A4\uD398\uC774\uC2A4 \uBC14\uB85C \uC5EC\uB7EC \uAC1C \uC120\uD0DD \uAC00\uB2A5)",options:d.map(i=>({label:i.name,value:i.name,hint:i.description}))});l.isCancel(f)&&(l.log.error("\uCDE8\uC18C\uB418\uC5C8\uC5B4\uC694."),process.exit(0)),g=f}g?.length||(l.log.error("\uCEF4\uD3EC\uB10C\uD2B8\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC5B4\uC694."),process.exit(0)),l.log.message(`\uC120\uD0DD\uB41C \uCEF4\uD3EC\uB10C\uD2B8: ${y(g.join(", "))}`);let k=W({userSelects:g,uiRegistryIndex:d,libRegistryIndex:m}),C=[],{start:A,stop:h}=l.spinner();A("Registry\uB97C \uAC00\uC838\uC624\uACE0 \uC788\uC5B4\uC694...");for(let f of k){let i=await B(f.name,s,f.type);C.push(i)}if(h(),C.length){let f=C.filter(i=>!g.includes(i.name));l.log.message(`\uCD94\uAC00\uB85C \uC124\uCE58\uB420 \uB808\uC9C0\uC2A4\uD2B8\uB9AC: ${y(f.map(i=>i.name).join(", "))}`)}let T=[],u={installed:new Set,filtered:new Set};for(let f of C){for(let i of f.registries){let v="";switch(i.type){case"ui":v=a.resolvedUIPaths;break;case"lib":v=a.resolvedLibPaths;break;default:break}z.existsSync(v)||await z.mkdir(v,{recursive:!0});let S=ee.resolve(v,i.name),oe=await H({filename:i.name,config:a,raw:i.content});a.tsx||(S=S.replace(/\.tsx$/,".jsx"),S=S.replace(/\.ts$/,".js")),await z.writeFile(S,oe);let se=ee.relative(o,S);T.push({name:i.name,path:se})}if(f.dependencies?.length){let i=await L({cwd:o,deps:f.dependencies});u.installed=new Set([...u.installed,...i.installed]),u.filtered=new Set([...u.filtered,...i.filtered])}if(f.devDependencies?.length){let i=await L({cwd:o,deps:f.devDependencies,dev:!0});u.installed=new Set([...u.installed,...i.installed]),u.filtered=new Set([...u.filtered,...i.filtered])}l.log.success(`${y(f.name)} \uAD00\uB828 \uD30C\uC77C \uCD94\uAC00 \uC644\uB8CC`)}if(u.installed.size&&l.log.message(`\uC124\uCE58\uB41C \uC758\uC874\uC131: ${y(Array.from(u.installed).join(", "))}`),u.filtered.size&&l.log.message(`\uC774\uBBF8 \uC124\uCE58\uB41C \uC758\uC874\uC131: ${y(Array.from(u.filtered).join(", "))}`),T.length)for(let f of T)l.log.message(`\uCD94\uAC00\uB41C \uD30C\uC77C: ${y(f.path)}`);l.outro("\uCEF4\uD3EC\uB10C\uD2B8 \uCD94\uAC00 \uC644\uB8CC.")})};import*as p from"@clack/prompts";import Le from"fs-extra";import re from"path";import P from"picocolors";import{z as E}from"zod";var ze=E.object({cwd:E.string(),yes:E.boolean().optional()}),ne=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 r=a=>P.cyan(a);p.intro(P.bgCyan("seed-design.json \uD30C\uC77C \uC0DD\uC131"));let n=ze.parse(t),o=n.yes,s={rsc:!1,tsx:!0,path:"./seed-design"};o||(s={...await p.group({tsx:()=>p.confirm({message:`${r("TypeScript")}\uB97C \uC0AC\uC6A9\uC911\uC774\uC2E0\uAC00\uC694?`,initialValue:!0}),rsc:()=>p.confirm({message:`${r("React Server Components")}\uB97C \uC0AC\uC6A9\uC911\uC774\uC2E0\uAC00\uC694?`,initialValue:!1}),path:()=>p.text({message:`${r("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:()=>{p.cancel("\uC791\uC5C5\uC774 \uCDE8\uC18C\uB410\uC5B4\uC694."),process.exit(0)}})});try{let{start:a,stop:d}=p.spinner();a("seed-design.json \uD30C\uC77C \uC0DD\uC131\uC911...");let m=re.resolve(n.cwd,"seed-design.json");await Le.writeFile(m,`${JSON.stringify(s,null,2)}
|
|
3
|
+
`,"utf-8");let g=re.relative(process.cwd(),m);d(`seed-design.json \uD30C\uC77C\uC774 ${r(g)}\uC5D0 \uC0DD\uC131\uB410\uC5B4\uC694.`),p.log.info(P.gray("seed-design add {component} \uBA85\uB839\uC5B4\uB85C \uCEF4\uD3EC\uB10C\uD2B8\uB97C \uCD94\uAC00\uD574\uBCF4\uC138\uC694!")),p.log.info(P.gray("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.")),p.outro("\uC791\uC5C5\uC774 \uC644\uB8CC\uB410\uC5B4\uC694.")}catch(a){p.log.error(`seed-design.json \uD30C\uC77C \uC0DD\uC131\uC5D0 \uC2E4\uD328\uD588\uC5B4\uC694. ${a}`),p.outro(P.bgRed("\uC791\uC5C5\uC774 \uCDE8\uC18C\uB410\uC5B4\uC694.")),process.exit(1)}})};import{cac as Ee}from"cac";var Fe="seed-design",j=Ee(Fe);async function _e(){let e=O();te(j),ne(j),j.version(e.version||"1.0.0","-v, --version"),j.help(),j.parse()}_e();
|
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@seed-design/cli",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
7
|
-
"url": "https://github.com/daangn/
|
|
7
|
+
"url": "git+https://github.com/daangn/seed-design.git",
|
|
8
8
|
"directory": "packages/cli"
|
|
9
9
|
},
|
|
10
10
|
"license": "MIT",
|
|
@@ -19,12 +19,13 @@
|
|
|
19
19
|
"node": ">=18"
|
|
20
20
|
},
|
|
21
21
|
"scripts": {
|
|
22
|
-
"build": "ENV=prod
|
|
23
|
-
"dev": "ENV=dev
|
|
24
|
-
"test": "
|
|
22
|
+
"build": "ENV=prod bun ./build.mjs",
|
|
23
|
+
"dev": "ENV=dev bun ./dev.mjs",
|
|
24
|
+
"test": "bun vitest",
|
|
25
|
+
"lint:publish": "bun publint"
|
|
25
26
|
},
|
|
26
27
|
"dependencies": {
|
|
27
|
-
"@antfu/ni": "^
|
|
28
|
+
"@antfu/ni": "^23.3.1",
|
|
28
29
|
"@babel/core": "^7.24.9",
|
|
29
30
|
"@babel/parser": "^7.24.8",
|
|
30
31
|
"@babel/plugin-transform-typescript": "^7.24.8",
|
|
@@ -43,19 +44,11 @@
|
|
|
43
44
|
"devDependencies": {
|
|
44
45
|
"@types/babel__core": "^7.20.5",
|
|
45
46
|
"@types/fs-extra": "^11.0.4",
|
|
46
|
-
"esbuild": "^0.
|
|
47
|
+
"esbuild": "^0.25.0",
|
|
47
48
|
"type-fest": "^4.23.0",
|
|
48
|
-
"typescript": "^5.4.5"
|
|
49
|
-
"ultra-runner": "^3.10.5",
|
|
50
|
-
"vitest": "^2.0.5"
|
|
49
|
+
"typescript": "^5.4.5"
|
|
51
50
|
},
|
|
52
51
|
"publishConfig": {
|
|
53
52
|
"access": "public"
|
|
54
|
-
},
|
|
55
|
-
"ultra": {
|
|
56
|
-
"concurrent": [
|
|
57
|
-
"dev",
|
|
58
|
-
"build"
|
|
59
|
-
]
|
|
60
53
|
}
|
|
61
54
|
}
|
package/src/commands/add.ts
CHANGED
|
@@ -1,21 +1,27 @@
|
|
|
1
1
|
import { getConfig } from "@/src/utils/get-config";
|
|
2
|
-
import {
|
|
3
|
-
|
|
2
|
+
import {
|
|
3
|
+
fetchRegistryItem,
|
|
4
|
+
getRegistryLibIndex,
|
|
5
|
+
getRegistryUIIndex,
|
|
6
|
+
} from "@/src/utils/get-metadata";
|
|
4
7
|
import { transform } from "@/src/utils/transformers";
|
|
5
8
|
import * as p from "@clack/prompts";
|
|
6
|
-
import { execa } from "execa";
|
|
7
9
|
import fs from "fs-extra";
|
|
8
10
|
import path from "path";
|
|
9
11
|
import color from "picocolors";
|
|
10
12
|
import { z } from "zod";
|
|
11
13
|
|
|
12
14
|
import type { CAC } from "cac";
|
|
13
|
-
import {
|
|
15
|
+
import { BASE_URL } from "../constants";
|
|
16
|
+
import { addRelativeRegistries } from "../utils/add-relative-registries";
|
|
17
|
+
import { highlight } from "../utils/color";
|
|
18
|
+
import { installDependencies } from "../utils/install";
|
|
14
19
|
|
|
15
20
|
const addOptionsSchema = z.object({
|
|
16
21
|
components: z.array(z.string()).optional(),
|
|
17
22
|
cwd: z.string(),
|
|
18
23
|
all: z.boolean(),
|
|
24
|
+
baseUrl: z.string().optional(),
|
|
19
25
|
// yes: z.boolean(),
|
|
20
26
|
// overwrite: z.boolean(),
|
|
21
27
|
// path: z.string().optional(),
|
|
@@ -30,25 +36,28 @@ export const addCommand = (cli: CAC) => {
|
|
|
30
36
|
.option("-c, --cwd <cwd>", "the working directory. defaults to the current directory.", {
|
|
31
37
|
default: process.cwd(),
|
|
32
38
|
})
|
|
33
|
-
.
|
|
39
|
+
.option(
|
|
40
|
+
"-u, --baseUrl <baseUrl>",
|
|
41
|
+
"the base url of the registry. defaults to the current directory.",
|
|
42
|
+
{
|
|
43
|
+
default: BASE_URL,
|
|
44
|
+
},
|
|
45
|
+
)
|
|
46
|
+
.example("seed-design add action-button")
|
|
34
47
|
.example("seed-design add alert-dialog")
|
|
35
48
|
.action(async (components, opts) => {
|
|
49
|
+
p.intro(color.bgCyan("seed-design add"));
|
|
36
50
|
const options = addOptionsSchema.parse({
|
|
37
51
|
components,
|
|
38
52
|
...opts,
|
|
39
53
|
});
|
|
40
|
-
const highlight = (text: string) => color.cyan(text);
|
|
41
54
|
const cwd = options.cwd;
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
const metadataIndex = await getMetadataIndex();
|
|
49
|
-
|
|
55
|
+
const baseUrl = options.baseUrl;
|
|
56
|
+
const config = await getConfig(cwd);
|
|
57
|
+
const registryComponentIndex = await getRegistryUIIndex(baseUrl);
|
|
58
|
+
const libRegistryIndex = await getRegistryLibIndex(baseUrl);
|
|
50
59
|
let selectedComponents: string[] = options.all
|
|
51
|
-
?
|
|
60
|
+
? registryComponentIndex.map((registry) => registry.name)
|
|
52
61
|
: options.components;
|
|
53
62
|
|
|
54
63
|
if (!options.components?.length && !options.all) {
|
|
@@ -56,8 +65,8 @@ export const addCommand = (cli: CAC) => {
|
|
|
56
65
|
{ label: string; value: string; hint: string }[],
|
|
57
66
|
string
|
|
58
67
|
>({
|
|
59
|
-
message: "
|
|
60
|
-
options:
|
|
68
|
+
message: "추가할 컴포넌트를 선택해주세요 (스페이스 바로 여러 개 선택 가능)",
|
|
69
|
+
options: registryComponentIndex.map((metadata) => {
|
|
61
70
|
return {
|
|
62
71
|
label: metadata.name,
|
|
63
72
|
value: metadata.name,
|
|
@@ -67,7 +76,7 @@ export const addCommand = (cli: CAC) => {
|
|
|
67
76
|
});
|
|
68
77
|
|
|
69
78
|
if (p.isCancel(selects)) {
|
|
70
|
-
p.log.error("
|
|
79
|
+
p.log.error("취소되었어요.");
|
|
71
80
|
process.exit(0);
|
|
72
81
|
}
|
|
73
82
|
|
|
@@ -75,31 +84,66 @@ export const addCommand = (cli: CAC) => {
|
|
|
75
84
|
}
|
|
76
85
|
|
|
77
86
|
if (!selectedComponents?.length) {
|
|
78
|
-
p.log.error("
|
|
87
|
+
p.log.error("컴포넌트를 찾을 수 없어요.");
|
|
79
88
|
process.exit(0);
|
|
80
89
|
}
|
|
81
90
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
const
|
|
85
|
-
|
|
91
|
+
p.log.message(`선택된 컴포넌트: ${highlight(selectedComponents.join(", "))}`);
|
|
92
|
+
|
|
93
|
+
const allRelativeRegistries = addRelativeRegistries({
|
|
94
|
+
userSelects: selectedComponents,
|
|
95
|
+
uiRegistryIndex: registryComponentIndex,
|
|
96
|
+
libRegistryIndex,
|
|
97
|
+
});
|
|
86
98
|
|
|
87
|
-
|
|
88
|
-
|
|
99
|
+
const allRegistryItems = [];
|
|
100
|
+
|
|
101
|
+
const { start, stop } = p.spinner();
|
|
102
|
+
start("Registry를 가져오고 있어요...");
|
|
103
|
+
|
|
104
|
+
for (const registry of allRelativeRegistries) {
|
|
105
|
+
const registryItem = await fetchRegistryItem(registry.name, baseUrl, registry.type);
|
|
106
|
+
allRegistryItems.push(registryItem);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
stop();
|
|
110
|
+
|
|
111
|
+
if (allRegistryItems.length) {
|
|
112
|
+
const filteredRegistryItems = allRegistryItems.filter(
|
|
113
|
+
(c) => !selectedComponents.includes(c.name),
|
|
114
|
+
);
|
|
89
115
|
p.log.message(
|
|
90
|
-
|
|
116
|
+
`추가로 설치될 레지스트리: ${highlight(
|
|
117
|
+
filteredRegistryItems.map((c) => c.name).join(", "),
|
|
118
|
+
)}`,
|
|
91
119
|
);
|
|
92
120
|
}
|
|
93
121
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
122
|
+
// 선택된 컴포넌트.json 레지스트리 파일 기반으로 컴포넌트를 추가합니다.
|
|
123
|
+
const registryResult = [];
|
|
124
|
+
const installResult = {
|
|
125
|
+
installed: new Set(),
|
|
126
|
+
filtered: new Set(),
|
|
127
|
+
};
|
|
128
|
+
for (const component of allRegistryItems) {
|
|
129
|
+
for (const registry of component.registries) {
|
|
130
|
+
let targetPath = "";
|
|
131
|
+
switch (registry.type) {
|
|
132
|
+
case "ui":
|
|
133
|
+
targetPath = config.resolvedUIPaths;
|
|
134
|
+
break;
|
|
135
|
+
case "lib":
|
|
136
|
+
targetPath = config.resolvedLibPaths;
|
|
137
|
+
break;
|
|
138
|
+
default:
|
|
139
|
+
break;
|
|
140
|
+
}
|
|
97
141
|
|
|
98
|
-
if (!fs.existsSync(
|
|
99
|
-
await fs.mkdir(
|
|
142
|
+
if (!fs.existsSync(targetPath)) {
|
|
143
|
+
await fs.mkdir(targetPath, { recursive: true });
|
|
100
144
|
}
|
|
101
145
|
|
|
102
|
-
let filePath = path.resolve(
|
|
146
|
+
let filePath = path.resolve(targetPath, registry.name);
|
|
103
147
|
|
|
104
148
|
const content = await transform({
|
|
105
149
|
filename: registry.name,
|
|
@@ -114,60 +158,50 @@ export const addCommand = (cli: CAC) => {
|
|
|
114
158
|
|
|
115
159
|
await fs.writeFile(filePath, content);
|
|
116
160
|
const relativePath = path.relative(cwd, filePath);
|
|
117
|
-
p.log.info(`Added ${highlight(registry.name)} to ${highlight(relativePath)}`);
|
|
118
|
-
}
|
|
119
161
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
162
|
+
registryResult.push({
|
|
163
|
+
name: registry.name,
|
|
164
|
+
path: relativePath,
|
|
165
|
+
});
|
|
166
|
+
}
|
|
123
167
|
|
|
124
168
|
// Install dependencies.
|
|
125
|
-
if (
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
packageManager,
|
|
130
|
-
[packageManager === "npm" ? "install" : "add", ...metadata.dependencies],
|
|
131
|
-
{
|
|
132
|
-
cwd,
|
|
133
|
-
},
|
|
134
|
-
);
|
|
135
|
-
|
|
136
|
-
if (result.failed) {
|
|
137
|
-
console.error(result.all);
|
|
138
|
-
process.exit(1);
|
|
139
|
-
} else {
|
|
140
|
-
for (const deps of metadata.dependencies) {
|
|
141
|
-
p.log.info(`- ${deps}`);
|
|
142
|
-
}
|
|
143
|
-
stop("Dependencies installed.");
|
|
144
|
-
}
|
|
169
|
+
if (component.dependencies?.length) {
|
|
170
|
+
const result = await installDependencies({ cwd, deps: component.dependencies });
|
|
171
|
+
installResult.installed = new Set([...installResult.installed, ...result.installed]);
|
|
172
|
+
installResult.filtered = new Set([...installResult.filtered, ...result.filtered]);
|
|
145
173
|
}
|
|
146
174
|
|
|
147
175
|
// Install devDependencies.
|
|
148
|
-
if (
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
176
|
+
if (component.devDependencies?.length) {
|
|
177
|
+
const result = await installDependencies({
|
|
178
|
+
cwd,
|
|
179
|
+
deps: component.devDependencies,
|
|
180
|
+
dev: true,
|
|
181
|
+
});
|
|
182
|
+
installResult.installed = new Set([...installResult.installed, ...result.installed]);
|
|
183
|
+
installResult.filtered = new Set([...installResult.filtered, ...result.filtered]);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
p.log.success(`${highlight(component.name)} 관련 파일 추가 완료`);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
if (installResult.installed.size) {
|
|
190
|
+
p.log.message(
|
|
191
|
+
`설치된 의존성: ${highlight(Array.from(installResult.installed).join(", "))}`,
|
|
192
|
+
);
|
|
193
|
+
}
|
|
194
|
+
if (installResult.filtered.size) {
|
|
195
|
+
p.log.message(
|
|
196
|
+
`이미 설치된 의존성: ${highlight(Array.from(installResult.filtered).join(", "))}`,
|
|
197
|
+
);
|
|
198
|
+
}
|
|
199
|
+
if (registryResult.length) {
|
|
200
|
+
for (const registry of registryResult) {
|
|
201
|
+
p.log.message(`추가된 파일: ${highlight(registry.path)}`);
|
|
168
202
|
}
|
|
169
203
|
}
|
|
170
204
|
|
|
171
|
-
p.outro("
|
|
205
|
+
p.outro("컴포넌트 추가 완료.");
|
|
172
206
|
});
|
|
173
207
|
};
|
package/src/commands/init.ts
CHANGED
|
@@ -10,63 +10,78 @@ import type { CAC } from "cac";
|
|
|
10
10
|
|
|
11
11
|
const initOptionsSchema = z.object({
|
|
12
12
|
cwd: z.string(),
|
|
13
|
+
yes: z.boolean().optional(),
|
|
13
14
|
});
|
|
14
15
|
|
|
15
16
|
export const initCommand = (cli: CAC) => {
|
|
16
17
|
cli
|
|
17
|
-
.command("init", "
|
|
18
|
-
.option("-c, --cwd <cwd>", "
|
|
18
|
+
.command("init", "seed-design.json 파일 생성")
|
|
19
|
+
.option("-c, --cwd <cwd>", "작업 디렉토리. 기본값은 현재 디렉토리.", {
|
|
19
20
|
default: process.cwd(),
|
|
20
21
|
})
|
|
22
|
+
.option("-y, --yes", "모든 질문에 대해 기본값으로 답변합니다.")
|
|
21
23
|
.action(async (opts) => {
|
|
22
|
-
const options = initOptionsSchema.parse({ ...opts });
|
|
23
24
|
const highlight = (text: string) => color.cyan(text);
|
|
25
|
+
p.intro(color.bgCyan("seed-design.json 파일 생성"));
|
|
24
26
|
|
|
25
|
-
const
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
process.exit(0);
|
|
27
|
+
const options = initOptionsSchema.parse(opts);
|
|
28
|
+
const isYesOption = options.yes;
|
|
29
|
+
let config: RawConfig = {
|
|
30
|
+
rsc: false,
|
|
31
|
+
tsx: true,
|
|
32
|
+
path: "./seed-design",
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
if (!isYesOption) {
|
|
36
|
+
const group = await p.group(
|
|
37
|
+
{
|
|
38
|
+
tsx: () =>
|
|
39
|
+
p.confirm({
|
|
40
|
+
message: `${highlight("TypeScript")}를 사용중이신가요?`,
|
|
41
|
+
initialValue: true,
|
|
42
|
+
}),
|
|
43
|
+
rsc: () =>
|
|
44
|
+
p.confirm({
|
|
45
|
+
message: `${highlight("React Server Components")}를 사용중이신가요?`,
|
|
46
|
+
initialValue: false,
|
|
47
|
+
}),
|
|
48
|
+
path: () =>
|
|
49
|
+
p.text({
|
|
50
|
+
message: `${highlight("seed-design 폴더")} 경로를 입력해주세요. (기본값은 프로젝트 루트에 생성됩니다.)`,
|
|
51
|
+
initialValue: "./seed-design",
|
|
52
|
+
defaultValue: "./seed-design",
|
|
53
|
+
placeholder: "./seed-design",
|
|
54
|
+
}),
|
|
54
55
|
},
|
|
55
|
-
|
|
56
|
-
|
|
56
|
+
{
|
|
57
|
+
onCancel: () => {
|
|
58
|
+
p.cancel("작업이 취소됐어요.");
|
|
59
|
+
process.exit(0);
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
);
|
|
57
63
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
path: group.path,
|
|
63
|
-
};
|
|
64
|
+
config = {
|
|
65
|
+
...group,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
64
68
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
69
|
+
try {
|
|
70
|
+
const { start, stop } = p.spinner();
|
|
71
|
+
start("seed-design.json 파일 생성중...");
|
|
72
|
+
const targetPath = path.resolve(options.cwd, "seed-design.json");
|
|
73
|
+
await fs.writeFile(targetPath, `${JSON.stringify(config, null, 2)}\n`, "utf-8");
|
|
74
|
+
const relativePath = path.relative(process.cwd(), targetPath);
|
|
75
|
+
stop(`seed-design.json 파일이 ${highlight(relativePath)}에 생성됐어요.`);
|
|
76
|
+
p.log.info(color.gray("seed-design add {component} 명령어로 컴포넌트를 추가해보세요!"));
|
|
77
|
+
p.log.info(
|
|
78
|
+
color.gray("seed-design add 명령어로 추가할 수 있는 모든 컴포넌트를 확인해보세요."),
|
|
79
|
+
);
|
|
80
|
+
p.outro("작업이 완료됐어요.");
|
|
81
|
+
} catch (error) {
|
|
82
|
+
p.log.error(`seed-design.json 파일 생성에 실패했어요. ${error}`);
|
|
83
|
+
p.outro(color.bgRed("작업이 취소됐어요."));
|
|
84
|
+
process.exit(1);
|
|
85
|
+
}
|
|
71
86
|
});
|
|
72
87
|
};
|
package/src/constants.ts
ADDED
package/src/index.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
import { addCommand } from "@/src/commands/add";
|
|
4
|
+
import { initCommand } from "@/src/commands/init";
|
|
4
5
|
import { getPackageInfo } from "@/src/utils/get-package-info";
|
|
5
6
|
import { cac } from "cac";
|
|
6
|
-
import { initCommand } from "./commands/init";
|
|
7
7
|
|
|
8
8
|
const NAME = "seed-design";
|
|
9
9
|
const CLI = cac(NAME);
|
package/src/schema.ts
CHANGED
|
@@ -1,34 +1,76 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
export const
|
|
3
|
+
export const registryType = z.union([z.literal("ui"), z.literal("lib")]);
|
|
4
|
+
|
|
5
|
+
export const registryUIItemSchema = z.object({
|
|
6
|
+
/**
|
|
7
|
+
* @description 레지스트리 이름
|
|
8
|
+
* @example chip-tabs, alert-dialog
|
|
9
|
+
*/
|
|
6
10
|
name: z.string(),
|
|
11
|
+
|
|
7
12
|
description: z.string().optional(),
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* @description 레지스트리 의존성
|
|
16
|
+
* @example @seed-design/react-tabs
|
|
17
|
+
*/
|
|
8
18
|
dependencies: z.array(z.string()).optional(),
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* @description 레지스트리 개발 의존성
|
|
22
|
+
*/
|
|
9
23
|
devDependencies: z.array(z.string()).optional(),
|
|
10
|
-
innerDependencies: z.array(z.string()).optional(),
|
|
11
|
-
snippets: z.array(z.string()),
|
|
12
|
-
type: z.enum(["component"]),
|
|
13
|
-
});
|
|
14
24
|
|
|
15
|
-
|
|
25
|
+
/**
|
|
26
|
+
* @description 레지스트리 내부의 Seed Design 컴포넌트 의존성
|
|
27
|
+
* * `:`를 기준으로 왼쪽은 {registryType}, 오른쪽은 파일 이름
|
|
28
|
+
* @example ui:action-button
|
|
29
|
+
* @example lib:manner-temp-level
|
|
30
|
+
*/
|
|
31
|
+
innerDependencies: z.array(z.string()).optional(),
|
|
16
32
|
|
|
17
|
-
|
|
33
|
+
/**
|
|
34
|
+
* @description
|
|
35
|
+
* 컴포넌트 코드 스니펫 경로, 여러 파일이 될 수 있어서 배열로 정의
|
|
36
|
+
* `:`를 기준으로 왼쪽은 {registryType}, 오른쪽은 파일 이름
|
|
37
|
+
* @example ui:alert-dialog.tsx
|
|
38
|
+
* @example ui:use-dismissible.ts
|
|
39
|
+
* @example lib:manner-temp-level.ts
|
|
40
|
+
*/
|
|
41
|
+
files: z.array(z.string()),
|
|
42
|
+
});
|
|
43
|
+
export const registryUISchema = z.array(registryUIItemSchema);
|
|
18
44
|
|
|
19
|
-
|
|
45
|
+
/**
|
|
46
|
+
* @description 머신이 생성한 registry component schema
|
|
47
|
+
*/
|
|
48
|
+
const omittedRegistryUISchema = registryUIItemSchema.omit({
|
|
49
|
+
files: true,
|
|
50
|
+
});
|
|
51
|
+
export const registryUIItemMachineGeneratedSchema = omittedRegistryUISchema.extend({
|
|
20
52
|
registries: z.array(
|
|
21
53
|
z.object({
|
|
22
54
|
name: z.string(),
|
|
55
|
+
type: registryType,
|
|
23
56
|
content: z.string(),
|
|
24
57
|
}),
|
|
25
58
|
),
|
|
26
59
|
});
|
|
60
|
+
export const registryComponentMachineGeneratedSchema = z.array(
|
|
61
|
+
registryUIItemMachineGeneratedSchema,
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
// NOTE: 현재는 lib이 ui와 타입이 동일하지만, 따로 가져가야한다면 타입을 변경해야해요.
|
|
65
|
+
export const registryLibItemMachineGeneratedSchema = registryUIItemMachineGeneratedSchema;
|
|
27
66
|
|
|
28
|
-
|
|
67
|
+
// NOTE: 현재는 lib이 ui와 타입이 동일하지만, 따로 가져가야한다면 타입을 변경해야해요.
|
|
68
|
+
export type RegistryLibItem = z.infer<typeof registryUIItemSchema>;
|
|
69
|
+
export type RegistryLib = z.infer<typeof registryUISchema>;
|
|
70
|
+
export type RegistryUIItem = z.infer<typeof registryUIItemSchema>;
|
|
71
|
+
export type RegistryUI = z.infer<typeof registryUISchema>;
|
|
29
72
|
|
|
30
|
-
export type
|
|
31
|
-
export type
|
|
32
|
-
export type
|
|
33
|
-
|
|
34
|
-
>;
|
|
73
|
+
export type RegistryUIItemMachineGenerated = z.infer<typeof registryUIItemMachineGeneratedSchema>;
|
|
74
|
+
export type RegistryUIMachineGenerated = z.infer<typeof registryComponentMachineGeneratedSchema>;
|
|
75
|
+
export type RegistryLibItemMachineGenerated = z.infer<typeof registryLibItemMachineGeneratedSchema>;
|
|
76
|
+
export type RegistryLibMachineGenerated = z.infer<typeof registryComponentMachineGeneratedSchema>;
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import { describe, expect, test } from "vitest";
|
|
2
|
+
import { addRelativeRegistries } from "../utils/add-relative-registries";
|
|
3
|
+
import type { RegistryLib, RegistryUI } from "@/src/schema";
|
|
4
|
+
|
|
5
|
+
const libConfig: RegistryLib = [
|
|
6
|
+
{
|
|
7
|
+
name: "a",
|
|
8
|
+
files: ["a.tsx"],
|
|
9
|
+
},
|
|
10
|
+
];
|
|
11
|
+
|
|
12
|
+
const uiConfig: RegistryUI = [
|
|
13
|
+
{
|
|
14
|
+
name: "a",
|
|
15
|
+
files: ["a.tsx"],
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
name: "b",
|
|
19
|
+
innerDependencies: ["ui:a"],
|
|
20
|
+
files: ["b.tsx"],
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
name: "c",
|
|
24
|
+
innerDependencies: ["ui:b"],
|
|
25
|
+
files: ["c.tsx"],
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
name: "d",
|
|
29
|
+
innerDependencies: ["ui:a", "ui:b"],
|
|
30
|
+
files: ["d.tsx"],
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
name: "e",
|
|
34
|
+
innerDependencies: ["ui:d"],
|
|
35
|
+
files: ["e.tsx"],
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
name: "f",
|
|
39
|
+
innerDependencies: ["lib:a"],
|
|
40
|
+
files: ["f.tsx"],
|
|
41
|
+
},
|
|
42
|
+
];
|
|
43
|
+
|
|
44
|
+
describe("addRelativeRegistries", () => {
|
|
45
|
+
test("4 deps test", () => {
|
|
46
|
+
const userSelects = ["e"];
|
|
47
|
+
const result = addRelativeRegistries({
|
|
48
|
+
userSelects,
|
|
49
|
+
uiRegistryIndex: uiConfig,
|
|
50
|
+
libRegistryIndex: [],
|
|
51
|
+
});
|
|
52
|
+
expect(result).toEqual(
|
|
53
|
+
expect.arrayContaining([
|
|
54
|
+
{
|
|
55
|
+
type: "ui",
|
|
56
|
+
name: "e",
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
type: "ui",
|
|
60
|
+
name: "d",
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
type: "ui",
|
|
64
|
+
name: "a",
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
type: "ui",
|
|
68
|
+
name: "b",
|
|
69
|
+
},
|
|
70
|
+
]),
|
|
71
|
+
);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
test("3 deps test", () => {
|
|
75
|
+
const userSelects = ["d"];
|
|
76
|
+
const result = addRelativeRegistries({
|
|
77
|
+
userSelects,
|
|
78
|
+
uiRegistryIndex: uiConfig,
|
|
79
|
+
libRegistryIndex: [],
|
|
80
|
+
});
|
|
81
|
+
expect(result).toEqual(
|
|
82
|
+
expect.arrayContaining([
|
|
83
|
+
{
|
|
84
|
+
type: "ui",
|
|
85
|
+
name: "d",
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
type: "ui",
|
|
89
|
+
name: "a",
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
type: "ui",
|
|
93
|
+
name: "b",
|
|
94
|
+
},
|
|
95
|
+
]),
|
|
96
|
+
);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
test("3 deps test", () => {
|
|
100
|
+
const userSelects = ["c"];
|
|
101
|
+
const result = addRelativeRegistries({
|
|
102
|
+
userSelects,
|
|
103
|
+
uiRegistryIndex: uiConfig,
|
|
104
|
+
libRegistryIndex: [],
|
|
105
|
+
});
|
|
106
|
+
expect(result).toEqual(
|
|
107
|
+
expect.arrayContaining([
|
|
108
|
+
{
|
|
109
|
+
type: "ui",
|
|
110
|
+
name: "c",
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
type: "ui",
|
|
114
|
+
name: "b",
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
type: "ui",
|
|
118
|
+
name: "a",
|
|
119
|
+
},
|
|
120
|
+
]),
|
|
121
|
+
);
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
test("2 deps test", () => {
|
|
125
|
+
const userSelects = ["b"];
|
|
126
|
+
const result = addRelativeRegistries({
|
|
127
|
+
userSelects,
|
|
128
|
+
uiRegistryIndex: uiConfig,
|
|
129
|
+
libRegistryIndex: [],
|
|
130
|
+
});
|
|
131
|
+
expect(result).toEqual(
|
|
132
|
+
expect.arrayContaining([
|
|
133
|
+
{
|
|
134
|
+
type: "ui",
|
|
135
|
+
name: "b",
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
type: "ui",
|
|
139
|
+
name: "a",
|
|
140
|
+
},
|
|
141
|
+
]),
|
|
142
|
+
);
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
test("1 deps test", () => {
|
|
146
|
+
const userSelects = ["a"];
|
|
147
|
+
const result = addRelativeRegistries({
|
|
148
|
+
userSelects,
|
|
149
|
+
uiRegistryIndex: uiConfig,
|
|
150
|
+
libRegistryIndex: [],
|
|
151
|
+
});
|
|
152
|
+
expect(result).toEqual(
|
|
153
|
+
expect.arrayContaining([
|
|
154
|
+
{
|
|
155
|
+
type: "ui",
|
|
156
|
+
name: "a",
|
|
157
|
+
},
|
|
158
|
+
]),
|
|
159
|
+
);
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
test("lib deps test", () => {
|
|
163
|
+
const userSelects = ["f"];
|
|
164
|
+
const result = addRelativeRegistries({
|
|
165
|
+
userSelects,
|
|
166
|
+
uiRegistryIndex: uiConfig,
|
|
167
|
+
libRegistryIndex: libConfig,
|
|
168
|
+
});
|
|
169
|
+
expect(result).toEqual(
|
|
170
|
+
expect.arrayContaining([
|
|
171
|
+
{
|
|
172
|
+
type: "ui",
|
|
173
|
+
name: "f",
|
|
174
|
+
},
|
|
175
|
+
{
|
|
176
|
+
type: "lib",
|
|
177
|
+
name: "a",
|
|
178
|
+
},
|
|
179
|
+
]),
|
|
180
|
+
);
|
|
181
|
+
});
|
|
182
|
+
});
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { RegistryLibMachineGenerated, RegistryUIMachineGenerated } from "@/src/schema";
|
|
2
|
+
|
|
3
|
+
interface AddRelativeComponentsProps {
|
|
4
|
+
userSelects: string[];
|
|
5
|
+
uiRegistryIndex: RegistryUIMachineGenerated;
|
|
6
|
+
libRegistryIndex: RegistryLibMachineGenerated;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function addRelativeRegistries({
|
|
10
|
+
userSelects,
|
|
11
|
+
uiRegistryIndex,
|
|
12
|
+
libRegistryIndex,
|
|
13
|
+
}: AddRelativeComponentsProps) {
|
|
14
|
+
const selectedComponents: { type: "ui" | "lib"; name: string }[] = [];
|
|
15
|
+
|
|
16
|
+
function addSeedDependencies({
|
|
17
|
+
registryName,
|
|
18
|
+
type,
|
|
19
|
+
}: { registryName: string; type: "ui" | "lib" }) {
|
|
20
|
+
if (selectedComponents.some((c) => c.name === registryName && c.type === type)) return;
|
|
21
|
+
|
|
22
|
+
selectedComponents.push({ type, name: registryName });
|
|
23
|
+
|
|
24
|
+
const registry =
|
|
25
|
+
type === "ui"
|
|
26
|
+
? uiRegistryIndex.find((c) => c.name === registryName)
|
|
27
|
+
: libRegistryIndex.find((c) => c.name === registryName);
|
|
28
|
+
if (!registry) return;
|
|
29
|
+
|
|
30
|
+
if (registry.innerDependencies) {
|
|
31
|
+
for (const dep of registry.innerDependencies) {
|
|
32
|
+
const [depType, depName] = dep.split(":");
|
|
33
|
+
addSeedDependencies({ registryName: depName, type: depType as "ui" | "lib" });
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
for (const registryName of userSelects) {
|
|
39
|
+
addSeedDependencies({ registryName, type: "ui" });
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return Array.from(selectedComponents);
|
|
43
|
+
}
|
package/src/utils/get-config.ts
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
|
+
import * as p from "@clack/prompts";
|
|
1
2
|
import { cosmiconfig } from "cosmiconfig";
|
|
3
|
+
import { execa } from "execa";
|
|
4
|
+
import fs from "fs";
|
|
2
5
|
import path from "path";
|
|
3
6
|
import { z } from "zod";
|
|
7
|
+
import { highlight } from "./color";
|
|
8
|
+
import { getPackageManager } from "./get-package-manager";
|
|
4
9
|
|
|
5
10
|
const MODULE_NAME = "seed-design";
|
|
6
11
|
|
|
@@ -13,7 +18,6 @@ export const rawConfigSchema = z
|
|
|
13
18
|
$schema: z.string().optional(),
|
|
14
19
|
rsc: z.coerce.boolean().default(false),
|
|
15
20
|
tsx: z.coerce.boolean().default(true),
|
|
16
|
-
css: z.coerce.boolean().default(true),
|
|
17
21
|
path: z.string(),
|
|
18
22
|
})
|
|
19
23
|
.strict();
|
|
@@ -22,6 +26,7 @@ export type RawConfig = z.infer<typeof rawConfigSchema>;
|
|
|
22
26
|
|
|
23
27
|
export const configSchema = rawConfigSchema.extend({
|
|
24
28
|
resolvedUIPaths: z.string(),
|
|
29
|
+
resolvedLibPaths: z.string(),
|
|
25
30
|
});
|
|
26
31
|
|
|
27
32
|
export async function getConfig(cwd: string) {
|
|
@@ -39,23 +44,43 @@ export type Config = z.infer<typeof configSchema>;
|
|
|
39
44
|
export async function resolveConfigPaths(cwd: string, config: RawConfig) {
|
|
40
45
|
const seedComponentRootPath = path.resolve(cwd, config.path);
|
|
41
46
|
|
|
47
|
+
if (!fs.existsSync(seedComponentRootPath)) {
|
|
48
|
+
fs.mkdirSync(seedComponentRootPath, { recursive: true });
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const resolvedUIPaths = path.join(seedComponentRootPath, "ui");
|
|
52
|
+
const resolvedLibPaths = path.join(seedComponentRootPath, "lib");
|
|
53
|
+
|
|
54
|
+
if (!fs.existsSync(resolvedUIPaths)) {
|
|
55
|
+
fs.mkdirSync(resolvedUIPaths, { recursive: true });
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (!fs.existsSync(resolvedLibPaths)) {
|
|
59
|
+
fs.mkdirSync(resolvedLibPaths, { recursive: true });
|
|
60
|
+
}
|
|
61
|
+
|
|
42
62
|
return configSchema.parse({
|
|
43
63
|
...config,
|
|
44
|
-
resolvedUIPaths
|
|
64
|
+
resolvedUIPaths,
|
|
65
|
+
resolvedLibPaths,
|
|
45
66
|
});
|
|
46
67
|
}
|
|
47
68
|
|
|
48
69
|
export async function getRawConfig(cwd: string): Promise<RawConfig | null> {
|
|
49
70
|
try {
|
|
50
71
|
const configResult = await explorer.search(cwd);
|
|
72
|
+
return rawConfigSchema.parse(configResult.config);
|
|
73
|
+
} catch {
|
|
74
|
+
p.log.error("프로젝트 루트 경로에 `seed-design.json` 파일이 없어요.");
|
|
51
75
|
|
|
52
|
-
|
|
53
|
-
|
|
76
|
+
const isConfirm = await p.confirm({ message: "seed-design.json 파일을 생성하시겠어요?" });
|
|
77
|
+
if (isConfirm === true) {
|
|
78
|
+
const packageManager = await getPackageManager(cwd);
|
|
79
|
+
await execa(packageManager, ["seed-design", "init", "--default"], { cwd });
|
|
80
|
+
p.log.message("seed-design.json 파일이 생성됐어요.");
|
|
81
|
+
} else {
|
|
82
|
+
p.outro(highlight("작업이 취소됐어요."));
|
|
83
|
+
process.exit(1);
|
|
54
84
|
}
|
|
55
|
-
|
|
56
|
-
return rawConfigSchema.parse(configResult.config);
|
|
57
|
-
} catch (error) {
|
|
58
|
-
console.log(error);
|
|
59
|
-
throw new Error(`Invalid configuration found in ${cwd}/seed-design.json.`);
|
|
60
85
|
}
|
|
61
86
|
}
|
|
@@ -1,36 +1,75 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
type ComponentMetadataWithRegistrySchema,
|
|
4
|
-
} from "@/src/schema";
|
|
1
|
+
import * as p from "@clack/prompts";
|
|
2
|
+
import { registryUISchema, type RegistryUIMachineGenerated } from "@/src/schema";
|
|
5
3
|
|
|
6
|
-
|
|
7
|
-
process.env.NODE_ENV === "prod" ? "https://component.seed-design.io" : "http://localhost:3000";
|
|
8
|
-
|
|
9
|
-
export async function fetchComponentMetadatas(
|
|
4
|
+
export async function fetchRegistryItems(
|
|
10
5
|
fileNames?: string[],
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
6
|
+
baseUrl?: string,
|
|
7
|
+
type: "ui" | "lib" = "ui",
|
|
8
|
+
): Promise<RegistryUIMachineGenerated> {
|
|
9
|
+
const results = await Promise.all(
|
|
10
|
+
fileNames.map(async (fileName) => {
|
|
11
|
+
try {
|
|
12
|
+
const response = await fetch(`${baseUrl}/__registry__/${type}/${fileName}.json`);
|
|
16
13
|
return await response.json();
|
|
17
|
-
})
|
|
18
|
-
|
|
14
|
+
} catch (error) {
|
|
15
|
+
const index = await fetch(`${baseUrl}/__registry__/${type}/index.json`).then((res) =>
|
|
16
|
+
res.json(),
|
|
17
|
+
);
|
|
18
|
+
const parsedIndex = registryUISchema.parse(index);
|
|
19
|
+
const availableComponents = parsedIndex.map((component) => component.name);
|
|
19
20
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
21
|
+
p.log.error(`${type}:${fileName} 컴포넌트는 레지스트리에 존재하지 않아요.`);
|
|
22
|
+
p.log.info(`사용 가능한 컴포넌트: ${availableComponents.join(", ")}`);
|
|
23
|
+
p.log.info(
|
|
24
|
+
JSON.stringify(
|
|
25
|
+
{
|
|
26
|
+
baseUrl,
|
|
27
|
+
error: error.toString(),
|
|
28
|
+
},
|
|
29
|
+
null,
|
|
30
|
+
2,
|
|
31
|
+
),
|
|
32
|
+
);
|
|
33
|
+
process.exit(1);
|
|
34
|
+
}
|
|
35
|
+
}),
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
return results;
|
|
25
39
|
}
|
|
26
40
|
|
|
27
|
-
export async function
|
|
41
|
+
export async function fetchRegistryItem(
|
|
42
|
+
fileName: string,
|
|
43
|
+
baseUrl: string,
|
|
44
|
+
type: "ui" | "lib" = "ui",
|
|
45
|
+
) {
|
|
46
|
+
const [result] = await fetchRegistryItems([fileName], baseUrl, type);
|
|
47
|
+
return result;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export async function getRegistryUIIndex(baseUrl?: string) {
|
|
28
51
|
try {
|
|
29
|
-
const [result] = await
|
|
52
|
+
const [result] = await fetchRegistryItems(["index"], baseUrl, "ui");
|
|
30
53
|
|
|
31
|
-
return
|
|
54
|
+
return registryUISchema.parse(result);
|
|
32
55
|
} catch (error) {
|
|
33
|
-
|
|
34
|
-
|
|
56
|
+
p.log.error("레지스트리 인덱스를 가져오는 데 실패했어요.");
|
|
57
|
+
p.log.info(
|
|
58
|
+
JSON.stringify(
|
|
59
|
+
{
|
|
60
|
+
baseUrl,
|
|
61
|
+
error: error.toString(),
|
|
62
|
+
},
|
|
63
|
+
null,
|
|
64
|
+
2,
|
|
65
|
+
),
|
|
66
|
+
);
|
|
67
|
+
process.exit(1);
|
|
35
68
|
}
|
|
36
69
|
}
|
|
70
|
+
|
|
71
|
+
export async function getRegistryLibIndex(baseUrl?: string) {
|
|
72
|
+
const [result] = await fetchRegistryItems(["index"], baseUrl, "lib");
|
|
73
|
+
|
|
74
|
+
return registryUISchema.parse(result);
|
|
75
|
+
}
|
|
@@ -2,12 +2,12 @@ import { detect } from "@antfu/ni";
|
|
|
2
2
|
|
|
3
3
|
export async function getPackageManager(
|
|
4
4
|
targetDir: string,
|
|
5
|
-
): Promise<"yarn" | "pnpm" | "bun" | "npm"> {
|
|
5
|
+
): Promise<"yarn" | "pnpm" | "bun" | "npm" | "deno"> {
|
|
6
6
|
const packageManager = await detect({ programmatic: true, cwd: targetDir });
|
|
7
7
|
|
|
8
8
|
if (packageManager === "yarn@berry") return "yarn";
|
|
9
9
|
if (packageManager === "pnpm@6") return "pnpm";
|
|
10
10
|
if (packageManager === "bun") return "bun";
|
|
11
|
-
|
|
11
|
+
if (packageManager === "deno") return "deno";
|
|
12
12
|
return packageManager ?? "npm";
|
|
13
13
|
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import * as p from "@clack/prompts";
|
|
2
|
+
import { execa } from "execa";
|
|
3
|
+
import color from "picocolors";
|
|
4
|
+
import { getPackageManager } from "./get-package-manager";
|
|
5
|
+
import { getPackageInfo } from "./get-package-info";
|
|
6
|
+
|
|
7
|
+
interface InstallDependenciesProps {
|
|
8
|
+
cwd: string;
|
|
9
|
+
deps: string[];
|
|
10
|
+
dev?: boolean;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export async function installDependencies({ cwd, deps, dev = false }: InstallDependenciesProps) {
|
|
14
|
+
const { start, stop } = p.spinner();
|
|
15
|
+
const packageManager = await getPackageManager(cwd);
|
|
16
|
+
const packageInfo = getPackageInfo();
|
|
17
|
+
|
|
18
|
+
// 이미 설치된 의존성 필터링
|
|
19
|
+
const existingDeps = {
|
|
20
|
+
...packageInfo.dependencies,
|
|
21
|
+
...packageInfo.devDependencies,
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const depsToInstall = deps.filter((dep) => !existingDeps[dep]);
|
|
25
|
+
const filteredDeps = deps.filter((dep) => existingDeps[dep]);
|
|
26
|
+
|
|
27
|
+
if (!depsToInstall.length) {
|
|
28
|
+
return {
|
|
29
|
+
installed: new Set(),
|
|
30
|
+
filtered: new Set(),
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
start(color.gray("의존성 설치중..."));
|
|
35
|
+
|
|
36
|
+
const isDev = dev ? "-D" : null;
|
|
37
|
+
const addCommand = packageManager === "npm" ? "install" : "add";
|
|
38
|
+
const command = [addCommand, isDev, ...depsToInstall].filter(Boolean);
|
|
39
|
+
|
|
40
|
+
try {
|
|
41
|
+
await execa(packageManager, command, { cwd });
|
|
42
|
+
} catch (error) {
|
|
43
|
+
console.error(`의존성 설치 실패: ${error}`);
|
|
44
|
+
process.exit(1);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
stop("의존성 설치 완료.");
|
|
48
|
+
|
|
49
|
+
return {
|
|
50
|
+
installed: depsToInstall,
|
|
51
|
+
filtered: filteredDeps,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
@@ -5,7 +5,6 @@ import path from "path";
|
|
|
5
5
|
|
|
6
6
|
import { transformJsx } from "@/src/utils/transformers/transform-jsx";
|
|
7
7
|
import { transformRsc } from "@/src/utils/transformers/transform-rsc";
|
|
8
|
-
import { transformCSS } from "./transform-css";
|
|
9
8
|
|
|
10
9
|
import { Project, ScriptKind, type SourceFile } from "ts-morph";
|
|
11
10
|
|
|
@@ -23,7 +22,7 @@ export type Transformer<Output = SourceFile> = (
|
|
|
23
22
|
},
|
|
24
23
|
) => Promise<Output>;
|
|
25
24
|
|
|
26
|
-
const transformers: Transformer[] = [transformRsc
|
|
25
|
+
const transformers: Transformer[] = [transformRsc];
|
|
27
26
|
|
|
28
27
|
const project = new Project({
|
|
29
28
|
compilerOptions: {},
|
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
import { describe, expect, test } from "vitest";
|
|
2
|
-
import { addRelativeComponents } from "../utils/add-relative-components";
|
|
3
|
-
import type { ComponentMetadataIndex } from "@/src/schema";
|
|
4
|
-
|
|
5
|
-
const config: ComponentMetadataIndex = [
|
|
6
|
-
{
|
|
7
|
-
name: "a",
|
|
8
|
-
snippets: ["a.tsx"],
|
|
9
|
-
type: "component",
|
|
10
|
-
},
|
|
11
|
-
{
|
|
12
|
-
name: "b",
|
|
13
|
-
innerDependencies: ["a"],
|
|
14
|
-
snippets: ["b.tsx"],
|
|
15
|
-
type: "component",
|
|
16
|
-
},
|
|
17
|
-
{
|
|
18
|
-
name: "c",
|
|
19
|
-
innerDependencies: ["b"],
|
|
20
|
-
snippets: ["c.tsx"],
|
|
21
|
-
type: "component",
|
|
22
|
-
},
|
|
23
|
-
{
|
|
24
|
-
name: "d",
|
|
25
|
-
innerDependencies: ["a", "b"],
|
|
26
|
-
snippets: ["d.tsx"],
|
|
27
|
-
type: "component",
|
|
28
|
-
},
|
|
29
|
-
{
|
|
30
|
-
name: "e",
|
|
31
|
-
innerDependencies: ["d"],
|
|
32
|
-
snippets: ["d.tsx"],
|
|
33
|
-
type: "component",
|
|
34
|
-
},
|
|
35
|
-
];
|
|
36
|
-
|
|
37
|
-
describe("addRelativeComponents", () => {
|
|
38
|
-
test("4 deps test", () => {
|
|
39
|
-
const userSelects = ["e"];
|
|
40
|
-
const result = addRelativeComponents(userSelects, config);
|
|
41
|
-
expect(result).toEqual(expect.arrayContaining(["a", "b", "d", "e"]));
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
test("3 deps test", () => {
|
|
45
|
-
const userSelects = ["d"];
|
|
46
|
-
const result = addRelativeComponents(userSelects, config);
|
|
47
|
-
expect(result).toEqual(expect.arrayContaining(["a", "b", "d"]));
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
test("3 deps test", () => {
|
|
51
|
-
const userSelects = ["c"];
|
|
52
|
-
const result = addRelativeComponents(userSelects, config);
|
|
53
|
-
expect(result).toEqual(expect.arrayContaining(["a", "b", "c"]));
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
test("2 deps test", () => {
|
|
57
|
-
const userSelects = ["b"];
|
|
58
|
-
const result = addRelativeComponents(userSelects, config);
|
|
59
|
-
expect(result).toEqual(expect.arrayContaining(["a", "b"]));
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
test("1 deps test", () => {
|
|
63
|
-
const userSelects = ["a"];
|
|
64
|
-
const result = addRelativeComponents(userSelects, config);
|
|
65
|
-
expect(result).toEqual(expect.arrayContaining(["a"]));
|
|
66
|
-
});
|
|
67
|
-
});
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
import type { ComponentMetadataIndex } from "@/src/schema";
|
|
2
|
-
|
|
3
|
-
export function addRelativeComponents(
|
|
4
|
-
userSelects: string[],
|
|
5
|
-
metadataIndex: ComponentMetadataIndex,
|
|
6
|
-
) {
|
|
7
|
-
const selectedComponents = new Set<string>();
|
|
8
|
-
|
|
9
|
-
function addSeedDependencies(componentName: string) {
|
|
10
|
-
if (selectedComponents.has(componentName)) return;
|
|
11
|
-
|
|
12
|
-
selectedComponents.add(componentName);
|
|
13
|
-
|
|
14
|
-
const component = metadataIndex.find((c) => c.name === componentName);
|
|
15
|
-
if (!component) return;
|
|
16
|
-
|
|
17
|
-
if (component.innerDependencies) {
|
|
18
|
-
for (const dep of component.innerDependencies) {
|
|
19
|
-
addSeedDependencies(dep);
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
for (const componentName of userSelects) {
|
|
25
|
-
addSeedDependencies(componentName);
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
return Array.from(selectedComponents);
|
|
29
|
-
}
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import type { Transformer } from "@/src/utils/transformers";
|
|
2
|
-
|
|
3
|
-
export const transformCSS: Transformer = async ({ sourceFile, config }) => {
|
|
4
|
-
if (config.css) {
|
|
5
|
-
return sourceFile;
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
const imports = sourceFile.getImportDeclarations();
|
|
9
|
-
const cssImports = imports.filter((i) => i.getModuleSpecifierValue().endsWith(".css"));
|
|
10
|
-
|
|
11
|
-
for (const cssImport of cssImports) {
|
|
12
|
-
cssImport.remove();
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
return sourceFile;
|
|
16
|
-
};
|