#!/usr/bin/env node
const{Command}=require("commander");constfs=require("fs-extra");constpath=require("path");constbabel=require("@babel/core");constpresetTypescript=require("@babel/preset-typescript");const{execSync}=require("child_process");constregistry=require("../registry.json");constprogram=newCommand();/* -----------------------------
INQUIRER (FIXED SAFE WRAPPER)
------------------------------*/constprompt=async (questions)=>{constinquirer=awaitimport("inquirer");returninquirer.default.prompt(questions);};/* =============================
ADD COMMAND
============================= */program.command("add <name>").description("Add components or utilities").action(async (name)=>{try{constcwd=process.cwd();console.log("\nπ Starting installation...\n");constisTsProject=fs.existsSync(path.join(cwd,"tsconfig.json"));lettemplatePath="";letoutputDir="";letextension="tsx";/* ---------------- COMPONENTS ---------------- */if (!name.startsWith("utils/")){templatePath=path.join(__dirname,`../templates/${name}.tsx`);outputDir=path.join(cwd,"src/components/pejay-ui");extension="tsx";}else{/* ---------------- UTILITIES ---------------- */constutilName=name.split("/")[1];templatePath=path.join(__dirname,`../utils/${utilName}.ts`);outputDir=path.join(cwd,"src/utils/pejay-ui");extension="ts";}if (!fs.existsSync(templatePath)){console.log(`β "${name}" not found`);return;}constcode=awaitfs.readFile(templatePath,"utf8");letfinalCode=code;letoutputExt=extension;/* ---------------- TRANSFORM ---------------- */if (!isTsProject){consttransformed=babel.transformSync(code,{presets:[presetTypescript],filename:`file.${extension}`,});if (!transformed?.code){thrownewError("Babel transform failed");}finalCode=transformed.code;outputExt=extension==="tsx"?"jsx":"js";}awaitfs.ensureDir(outputDir);constitemName=name.includes("/")?name.split("/")[1]:name;constoutputPath=path.join(outputDir,`${itemName}.${outputExt}`);awaitfs.writeFile(outputPath,finalCode);console.log(`β Created ${itemName}.${outputExt}`);/* ---------------- DEPENDENCIES ---------------- */constitemRegistry=registry[name];if (itemRegistry?.dependencies?.length){for (constdependencyofitemRegistry.dependencies){constdepName=dependency.includes("/")?dependency.split("/")[1]:dependency;constdepPath=path.join(__dirname,`../utils/${depName}.ts`);if (!fs.existsSync(depPath))continue;letdepCode=awaitfs.readFile(depPath,"utf8");if (!isTsProject){consttransformed=babel.transformSync(depCode,{presets:[presetTypescript],filename:`${depName}.ts`,});depCode=transformed.code;}constutilDir=path.join(cwd,"src/utils/pejay-ui");awaitfs.ensureDir(utilDir);constext=isTsProject?"ts":"js";constoutPath=path.join(utilDir,`${depName}.${ext}`);if (!fs.existsSync(outPath)){awaitfs.writeFile(outPath,depCode);console.log(`π¦ Added dependency ${depName}`);}}}/* ---------------- STATE SAVE ---------------- */conststatePath=path.join(cwd,".pejay-ui.json");letstate={};if (fs.existsSync(statePath)){state=JSON.parse(awaitfs.readFile(statePath,"utf8"));}state.installed=state.installed||{};state.installed[name]={files:[`src/components/pejay-ui/${itemName}.${outputExt}`],uses:itemRegistry?.dependencies?.map((d)=>d.includes("/")?d.split("/")[1]:d,)||[],};awaitfs.writeFile(statePath,JSON.stringify(state,null,2));console.log(`\nπ ${name} installed successfully\n`);}catch (err){console.error("\nβ Add failed\n",err);}});/* =============================
REMOVE COMMAND (SAFE VERSION)
============================= */program.command("remove <name>").description("Remove components or utilities safely").action(async (name)=>{try{constcwd=process.cwd();console.log("\nπ§Ή Starting removal...\n");constitemRegistry=registry[name];if (!itemRegistry){console.log(`β "${name}" not found in registry`);return;}constisTsProject=fs.existsSync(path.join(cwd,"tsconfig.json"));constitemName=name.includes("/")?name.split("/")[1]:name;constcomponentDir=path.join(cwd,"src/components/pejay-ui");constutilsDir=path.join(cwd,"src/utils/pejay-ui");letremovedAny=false;/* ---------------- REMOVE COMPONENT ---------------- */constexts=isTsProject?["tsx","ts"]:["jsx","js"];for (constextofexts){constfile=path.join(componentDir,`${itemName}.${ext}`);if (fs.existsSync(file)){awaitfs.remove(file);console.log(`ποΈ Removed ${file}`);removedAny=true;}}/* ---------------- LOAD STATE ---------------- */conststatePath=path.join(cwd,".pejay-ui.json");letstate={};if (fs.existsSync(statePath)){state=JSON.parse(awaitfs.readFile(statePath,"utf8"));}state.installed=state.installed||{};/* ---------------- BUILD USAGE MAP ---------------- */constusageMap=newMap();for (constcompofObject.values(state.installed)){for (constuofcomp.uses||[]){usageMap.set(u,(usageMap.get(u)||0)+1);}}/* ---------------- SAFE UTILITY REMOVE ---------------- */if (itemRegistry.dependencies?.length){for (constdepofitemRegistry.dependencies){constutilName=dep.includes("/")?dep.split("/")[1]:dep;constusage=usageMap.get(utilName)||0;if (usage>1){console.log(`β Skipping ${utilName} (used by other components)`);continue;}const{confirm}=awaitprompt([{type:"confirm",name:"confirm",message:`Remove utility "${utilName}"?`,default:false,},]);if (!confirm)continue;for (constextofexts){constfile=path.join(utilsDir,`${utilName}.${ext}`);if (fs.existsSync(file)){awaitfs.remove(file);console.log(`ποΈ Removed ${file}`);removedAny=true;}}}}/* ---------------- REMOVE PACKAGES ---------------- */// Only collect packages from deps that NO other component still usesconstpkgs=[];for (constdepofitemRegistry.dependencies||[]){constutilName=dep.includes("/")?dep.split("/")[1]:dep;constusage=usageMap.get(utilName)||0;// usage > 1 means another component still needs this dep β skip its packagesif (usage>1){console.log(`β Keeping packages for ${utilName} (used by other components)`);continue;}constreg=registry[dep];if (reg?.packages)pkgs.push(...reg.packages);}if (pkgs.length){constunique=[...newSet(pkgs)];const{ok}=awaitprompt([{type:"confirm",name:"ok",message:`Uninstall packages: ${unique.join(", ")}?`,default:false,},]);if (ok){execSync(`npm uninstall ${unique.join("")}`,{cwd,stdio:"inherit",});}}/* ---------------- CLEAN STATE ---------------- */if (state.installed[name]){deletestate.installed[name];awaitfs.writeFile(statePath,JSON.stringify(state,null,2));}console.log(`\nπ ${name} removed successfully\n`);}catch (err){console.error("\nβ Remove failed\n",err);}});program.parse();
{"name":"demo-dev2d-cli","version":"1.0.0","description":"CLI to install reusable React components like shadcn/ui","main":"bin/index.js","bin":{"demo-dev2d-cli":"./bin/index.js"},"files":["bin","templates","utils","registry.json"],"scripts":{"test":"echo \"Error: no test specified\" && exit 1"},"keywords":[],"author":"demo-auth","license":"ISC","dependencies":{"@babel/core":"^7.29.0","@babel/preset-react":"^7.28.5","@babel/preset-typescript":"^7.28.5","@types/react":"^19.2.14","clsx":"^2.1.1","commander":"^14.0.3","fs-extra":"^11.3.5","inquirer":"^13.4.3","react":"^19.2.6","tailwind-merge":"^3.6.0"}}
Top comments (0)