UNPKG

shadkit

Version:

CLI tool to bootstrap and manage reusable code for projects

47 lines (39 loc) 12.8 kB
#!/usr/bin/env node import V from"yargs";import{hideBin as q}from"yargs/helpers";import Q from"chalk";import M from"path";import x from"chalk";import{execa as A}from"execa";import m from"fs-extra";import l from"chalk";import C from"boxen";var n=class{static success(e){console.log(l.green(`\u2705 ${e}`))}static error(e,...r){console.log(l.red(`\u274C ${e}`),r)}static warn(e){console.log(l.yellow(`\u26A0\uFE0F ${e}`))}static info(e){console.log(l.blue(`\u2139\uFE0F ${e}`))}static running(e){console.log(l.gray(`\u2139\uFE0F ${e}`))}static bold(e,r){let o=l[e].bold;console.log(typeof o=="function"?o(`${r}`):r)}static box(e,r){console.log(C(e,{title:r,padding:1,margin:1,borderStyle:"round",borderColor:"cyan"}))}static log(e){console.log(e)}static divider(){console.log(l.gray("\u2500".repeat(50)))}static successBox(e,r){console.log(C(`${l.green.bold(e)} ${r}`,{padding:1,margin:1,borderStyle:"round",borderColor:"green"}))}static errorBox(e,r){console.log(C(`${l.red.bold(e)} ${r}`,{padding:1,margin:1,borderStyle:"round",borderColor:"red"}))}static displayTemplateInfo(e,r){console.log(l.blue(`Adding template: ${l.bold(e.title)}`)),console.log(l.gray(`Type: ${e.type}`)),console.log(l.gray(`Description: ${e.description}`)),console.log(l.gray(`Target: ${r}`)),e.dependencies&&e.dependencies.length>0&&console.log(l.gray(`Dependencies: ${e.dependencies.join(", ")}`)),console.log("")}};import T from"path";import{fileURLToPath as O}from"url";var w="myproject.config.json",z=".mycli.registry.json",S=O(import.meta.url),L=T.dirname(S);async function $(){try{return await m.pathExists(w)}catch{return!1}}async function v(t){let e=Object.values(t.folders);for(let r of e)try{await m.ensureDir(r),n.success(`Created directory: ${r}`);let o=`${r}/index.ts`;await m.pathExists(o)?n.info(`File already exists: ${o}`):(await m.writeFile(o,`export default {} `,"utf8"),n.success(`Created file: ${o}`))}catch(o){throw n.error(`Failed to create directory or file in: ${r}`,o),o}}var f=class{static async readProjectConfig(){try{return await m.pathExists(w)?await m.readJson(w):null}catch(e){return console.error(x.red("Error reading project config:"),e),null}}static async readTemplateRegistry(){try{let e=T.resolve(L,"../../.mycli.registry.json");return await m.pathExists(e)?await m.readJson(e):null}catch(e){return console.error(x.red("Error reading template registry:"),e),null}}static async writeProjectConfig(e){try{await m.writeJson(w,e,{spaces:2})}catch(r){throw console.error(x.red("Error writing project config:"),r),r}}static async writeTemplateRegistry(e){try{await m.writeJson(z,e,{spaces:2})}catch(r){throw console.error(x.red("Error writing template registry:"),r),r}}},y=class{static findTemplate(e,r){return e.templates.find(o=>o.title===r)||null}static replacePlaceholders(e,r){let o=e;for(let[i,c]of Object.entries(r)){let a=new RegExp(`__${i.toUpperCase()}__`,"g");o=o.replace(a,c)}return o}static toComponentName(e){return e.split("-").map(r=>r.charAt(0).toUpperCase()+r.slice(1)).join("")}static toHookName(e){return`use${this.toComponentName(e)}`}};async function E(){try{n.info("Running Prettier to format code..."),await A("npx",["prettier","--write","."],{stdio:"pipe"}),n.success("Code formatted successfully")}catch{n.warn("Warning: Could not run Prettier. Make sure it's installed."),n.running("You can manually format with: npx prettier --write .")}}function B(t){return{name:t,version:"1.0.0",folders:{components:"src/components",hooks:"src/hooks",providers:"src/providers",utils:"src/utils",constants:"src/constants"},createdAt:new Date().toISOString()}}async function P(t={}){try{!t.force&&await $()&&(n.errorBox("Project Already Initialized",`This directory already contains a mycli project. Use --force to reinitialize.`),process.exit(1));let e=t.name||M.basename(process.cwd());n.info(`Initializing mycli project: ${n.bold("blue",e)}`),n.divider();let r=B(e);n.info("Creating project configuration..."),await f.writeProjectConfig(r),n.success("Created myproject.config.json"),n.info("Creating project directories..."),await v(r),n.divider(),n.successBox("Project Initialized Successfully!",`Project "${e}" has been initialized with: \u2022 Configuration file: myproject.config.json \u2022 Template registry: .mycli.registry.json \u2022 ${Object.keys(r.folders).length} project directories Next steps: \u2022 Run "mycli add button" to add a button component \u2022 Run "mycli add use-auth" to add an auth hook \u2022 Customize templates in .mycli.registry.json`)}catch(e){n.errorBox("Initialization Failed",`An error occurred during project initialization: ${e instanceof Error?e.message:String(e)}`),process.exit(1)}}import j from"fs-extra";import b from"path";import{fileURLToPath as U}from"url";import R from"path";import g from"fs-extra";var I=async(t,e)=>{try{let r=t.type==="provider",o=["component","provider"].includes(t.type),i=R.join(e,o?"index.tsx":"index.ts"),c=R.join(e,`${t.title+(o?".tsx":".ts")}`),a=`./${t.title}`;if(await g.pathExists(i)||await g.writeFile(i,""),!await g.pathExists(c))throw new Error(`Generated file not found: ${c}`);if(r){D({templateName:t.name,generatedFilePath:c,importPath:a,indexPath:i});return}let p=await g.readFile(i,"utf8"),{defaultExportMatch:d,exportNames:s}=await F(t.name,c);if(s.size===0){console.warn(`\u26A0\uFE0F No exports found in ${c}`);return}if(!new RegExp(`from ['"]${a}['"]`).test(p)){let h="";d&&s.size===1?h=`export {deafult as ${t.name} }from '${a}';`:h=`export { ${[...s].join(", ")} } from '${a}';`,p=`${h} ${p}`}await g.writeFile(i,p,"utf8"),console.log(`\u2705 Index updated with exports from ${t.name}.ts`)}catch(r){throw console.error("\u274C implementationInIndex Error:",r),r}},F=async(t,e)=>{let r=await g.readFile(e,"utf8"),o=new Set,i=/export\s+(?:const|let|var|function|class)\s+(\w+)/g,c;for(;(c=i.exec(r))!==null;)o.add(c[1]);let a=/export\s*{([^}]+)}/g;for(;(c=a.exec(r))!==null;)c[1].split(",").map(s=>s.trim()).forEach(s=>s&&o.add(s));let p=r.match(/export\s+default\s+(\w+)/);return p&&o.add(t),{exportNames:o,defaultExportMatch:p}},D=async({generatedFilePath:t,importPath:e,indexPath:r,templateName:o})=>{let i=await g.readFile(r,"utf8"),{defaultExportMatch:c,exportNames:a}=await F(o,t);if(a.size===0){console.warn(`\u26A0\uFE0F No exports found in ${t}`);return}if(!new RegExp(`from ['"]${e}['"]`).test(i)){let s="";c&&a.size===1?s=`import ${o} from '${e}';`:s=`import { ${[...a].join(", ")} } from '${e}';`,i=`${s} ${i}`}let d=i.match(/return\s*\(\s*([\s\S]+?)\s*\);/m);if(d){let s=d[1].trim();if(!s.includes(`<${o}>`)){let u=`<${o}> ${s} </${o}>`;i=i.replace(d[0],`return ( ${u} );`)}}else if(d=i.match(/return\s+<([\s\S]+?)>;/m),d){let s=d[0],u=s.replace(/return\s+/,"").replace(/;$/,"").trim();if(!u.includes(`<${o}>`)){let h=`<${o}> ${u} </${o}>`;i=i.replace(s,`return ( ${h} );`)}}else console.warn("\u26A0\uFE0F No recognizable return JSX found to wrap.");await g.writeFile(r,i,"utf8"),console.log(`\u2705 Wrapped Provider with ${o}`)};var J=U(import.meta.url),H=b.dirname(J);function G(t,e){switch(t.type){case"component":return e.folders.components;case"hook":return e.folders.hooks;case"provider":return e.folders.providers;case"util":return e.folders.utils;case"constant":return e.folders.constants;default:throw new Error(`Unknown template type: ${t.type}`)}}function Y(t,e){let r=e||t.title;return["component","provider"].includes(t.type)?`${t.title}.tsx`:t.type==="hook"?`${r}.ts`:`${r}.ts`}function W(t,e){let r=e||t.title,o={};return t.placeholders&&t.placeholders.forEach(i=>{switch(i){case"COMPONENT_NAME":o.COMPONENT_NAME=y.toComponentName(r);break;case"HOOK_NAME":o.HOOK_NAME=y.toHookName(r);break;case"FILE_NAME":o.FILE_NAME=r;break;default:o[i]=r}}),o}async function X(t){try{let e=b.resolve(H,t.path);if(!await j.pathExists(e))return n.error(` Template file not found at path: ${e}`),null;let o=await import(e).then(i=>i.default);return n.info(`\u2705 Loaded template: ${t.title}`),o[t?.name].replace(/__NAME__/g,t.name)}catch(e){return n.error("\u274C Failed to read template file.",e),null}}async function K(t,e){if(await j.pathExists(t))if(e)n.warn(`Overwriting existing file: ${t}`);else return n.warn(`File already exists: ${t}`),n.running("Use --force to overwrite existing files"),!1;return!0}async function k(t){try{await $()||(n.errorBox("Project Not Initialized",`This directory is not a mycli project. Run "mycli init" first to initialize the project.`),process.exit(1));let e=await f.readProjectConfig(),r=await f.readTemplateRegistry();(!e||!r)&&(n.errorBox("Configuration Error",`Could not read project configuration or template registry. Try running "mycli init" to reinitialize.`),process.exit(1));let o=y.findTemplate(r,t.template);if(!o){let s=r.templates.map(u=>u.title).join(", ");n.errorBox("Template Not Found",`Template "${t.template}" not found in registry. Available templates: ${s}`),process.exit(1)}let i=G(o,e),c=Y(o,t.name),a=b.join(i,c);n.displayTemplateInfo(o,a),await K(a,t.force||!1)||process.exit(1);let p=W(o,t.name),d=await X(o);Object.keys(p).length>0&&(d=y.replacePlaceholders(d,p),n.running(`Applied ${Object.keys(p).length} placeholder replacements`)),await j.ensureDir(i),console.log("\u{1F680} ~ addCommand ~ targetPath:",a),await j.writeFile(a,d,"utf8"),n.success(`Created: ${a}`),await I(o,i),await E(),console.log(""),n.successBox("Template Added Successfully!",`Template "${o.title}" has been added to your project. File: ${a} Type: ${o.type} ${o.dependencies&&o.dependencies.length>0?`Don't forget to install dependencies: npm install ${o.dependencies.join(" ")} `:""}The file has been automatically formatted with Prettier.`)}catch(e){n.errorBox("Add Command Failed",`An error occurred while adding the template: ${e instanceof Error?e.message:String(e)}`),process.exit(1)}}async function N(){try{let t=await f.readTemplateRegistry();if(t||(n.errorBox("Registry Not Found","Could not read template registry. Make sure you're in a mycli project directory."),process.exit(1)),t.templates.length===0){n.warn("No templates found in registry.");return}n.bold("blue",`Available Templates (${t.templates.length})`),n.divider(),t.templates.forEach(e=>{n.success(`${e.title} (${e.type})`),n.running(e.description),e.dependencies&&e.dependencies.length>0&&n.running(` Dependencies: ${e.dependencies.join(", ")}`),console.log("")})}catch(t){n.errorBox("List Command Failed",`An error occurred while listing templates: ${t instanceof Error?t.message:String(t)}`),process.exit(1)}}var _="1.0.0";function Z(){console.log(Q.blue.bold(` \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510 \u2502 shadkit v${_} \u2502 \u2502 Manage Reusable Code Templates \u2502 \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518 `))}process.on("unhandledRejection",(t,e)=>{console.error(n.error("Unhandled Rejection at:"),e,n.error("reason:"),t),process.exit(1)});process.on("uncaughtException",t=>{n.error("Uncaught Exception: ",t),process.exit(1)});async function ee(){let t=await V(q(process.argv)).scriptName("shadkit").version(_).usage("$0 <command> [options]").help("help").alias("h","help").alias("v","version").wrap(Math.min(120,process.stdout.columns||80)).command("init [name]","Initialize a new shadkit project",e=>e.positional("name",{describe:"Project name (defaults to current directory name)",type:"string"}).option("force",{alias:"f",type:"boolean",description:"Force initialization even if project already exists",default:!1}),async e=>{Z(),await P({name:e.name,force:e.force})}).command("add <template> [name]","Add a template to your project",e=>e.positional("template",{describe:"Template name to add",type:"string",demandOption:!0}).positional("name",{describe:"Custom name for the generated file",type:"string"}).option("force",{alias:"f",type:"boolean",description:"Overwrite existing files",default:!1}),async e=>{await k({template:e.template,name:e.name,force:e.force})}).command("list","List all available templates",()=>{},async()=>{await N()}).example("$0 init my-project",'Initialize a new project called "my-project"').example("$0 add button","Add the button template to your project").example("$0 add button MyCustomButton","Add button template with custom name").example("$0 list","List all available templates").demandCommand(1,"You need at least one command before moving on").strict().recommendCommands().parse()}ee().catch(t=>{n.error("CLI Error: ",t),process.exit(1)});