UNPKG

@tianyio/quality-helper

Version:

A comprehensive quality helper tool for project scaffolding and management

3 lines (2 loc) 13.3 kB
"use strict";const e=require("chalk"),t=require("ora"),i=require("path"),o=require("./core-file-ops-CkY4H0C_.cjs"),a=require("./core-file-merger-CzI_IKXm.cjs"),r=require("./core-discovery-DoUt3gJ3.cjs"),n=require("prompts"),s=require("./utils-CuBC1baf.cjs"),c=require("fs/promises");class l{configMerger;constructor(){this.configMerger=new a.FileMerger}async determineUpdateMode(e,t,i){if(e.trim()===t.trim())return"skip";return"git-hooks"===i.group||[".gitconfig",".editorconfig"].includes(i.name)||!e.trim()?"overwrite":"merge"}async mergeConfigContent(e,t,i){return this.configMerger.mergeConfigContent(e,t,i)}}class g{mergeService;updatedMonorepoConfigs;constructor(){this.mergeService=new l,this.updatedMonorepoConfigs=new Set}isTsConfigSeparated(e){try{const t=JSON.parse(e);return(e=>{const t=/^(?:\.\.\/|\.\/|[\w-]+\/)*tsconfig\.json$/;if("string"==typeof e){const i=e.replace(/^["']|["']$/g,"");return t.test(i)}return!!Array.isArray(e)&&e.some(e=>{if("string"==typeof e){const i=e.replace(/^["']|["']$/g,"");return t.test(i)}return!1})})(t.extends??"")}catch{return!1}}isViteConfigSeparated(e){return/import\s+.*\s+from\s+["'][.\/]+.*vite\.config\.(?:ts|js)["']/.test(e)}async checkConfigStatus(e,t){const a="vite"===t?"vite.config.ts":"tsconfig.json";try{const r=i.join(e,a);if(!(await o.FileOperations.exists(r)))return"not-exists";const n=await o.FileOperations.readFile(r);let s;return s="vite"===t?this.isViteConfigSeparated(n):this.isTsConfigSeparated(n),s?"separated":"not-separated"}catch{return"not-exists"}}async process(e,t,i,o,a,r){switch(await this.checkConfigStatus(i,e)){case"not-exists":await this.handleProjectConfigNotExists(t,i,o,e,a,r);break;case"separated":await this.handleProjectConfigSeparated(t,i,o,e,a,r);break;case"not-separated":await this.handleProjectConfigNotSeparated(t,i,o,e,a,r)}}calculateRelativePath(e,t){const o=i.relative(e,t).replace(/\\/g,"/");return o.startsWith("./")||o.startsWith("../")?o:`./${o}`}async updateBaseConfigFile(e,t){const i=e;if(!this.updatedMonorepoConfigs.has(i)){const a=await o.FileOperations.readFile(t);await o.FileOperations.writeFile(e,a),this.updatedMonorepoConfigs.add(i)}}async mergeAndUpdateBaseConfigFile(e,t,i){const a=e;if(!this.updatedMonorepoConfigs.has(a)){const r=await o.FileOperations.readFile(t),n=await o.FileOperations.readFile(e),s=await this.mergeConfigContent(n,r,i);await o.FileOperations.writeFile(e,s),this.updatedMonorepoConfigs.add(a)}}async handleProjectConfigNotExists(e,t,a,r,n,s){await o.FileOperations.ensureDir(i.join(a,"configs"));const c="vite"===r?"vite.config.ts":"tsconfig.json",l=i.join(e,"configs","build-tools",c),g=i.join(a,"configs",c);if(await this.updateBaseConfigFile(g,l),"monorepo"===n&&"root"===s)return;const p=i.join(e,"configs","build-tools","lib"===s?"libs":"apps",c);let u=await o.FileOperations.readFile(p);const h=this.calculateRelativePath(t,i.join(a,"configs"));if("vite"===r)u=`import getBaseConfig from '${h}/vite.config'\n${u}`,u=u.replace(/return\s+config/g,"return mergeConfig(getBaseConfig(env), config)");else{const e=JSON.parse(u);e.extends=`${h}/tsconfig.json`,u=JSON.stringify(e,null,2)}await o.FileOperations.writeFile(i.join(t,c),u)}async handleProjectConfigSeparated(e,t,a,r,n,s){const c="vite"===r?"vite.config.ts":"tsconfig.json",l=i.join(a,"configs",c),g=i.join(e,"configs","build-tools",c);if("monorepo"===n)await this.mergeAndUpdateBaseConfigFile(l,g,c);else{const e=await o.FileOperations.readFile(g),t=await o.FileOperations.readFile(l),i=await this.mergeConfigContent(t,e,c);await o.FileOperations.writeFile(l,i)}if("monorepo"===n&&"root"===s)return;const p=i.join(e,"configs","build-tools","lib"===s?"libs":"apps",c),u=await o.FileOperations.readFile(p),h=i.join(t,c),f=await o.FileOperations.readFile(h),d=await this.mergeConfigContent(f,u,c);await o.FileOperations.writeFile(h,d)}async handleProjectConfigNotSeparated(e,t,a,r,n,s){const c="vite"===r?"vite.config.ts":"tsconfig.json",l=i.join(t,c),g=`${l}.backup`;await o.FileOperations.exists(l)&&(await o.FileOperations.rename(l,g),console.log(`原始配置文件已备份为: ${g}`)),await this.handleProjectConfigNotExists(e,t,a,r,n,s)}async mergeConfigContent(e,t,i){const o="vite.config.ts"===i?"// ":"/*",a="vite.config.ts"===i?" ":"*/";return this.mergeService.mergeConfigContent(e,t,{name:i,commentPrefix:o,commentSuffix:a})}}class p{templatesDir;mergeService;buildToolsService;constructor(e){this.templatesDir=e,this.mergeService=new l,this.buildToolsService=new g}async updateConfigFile(e,t,i,a,r){try{if(e.name.startsWith("vite.config"))return await this.buildToolsService.process("vite",this.templatesDir,t,i,a,r),!0;if("tsconfig.json"===e.name)return await this.buildToolsService.process("ts",this.templatesDir,t,i,a,r),!0;let n,s;return n=".pnpmrc"===e.name||".npmrc"===e.name?await this.getAutoUpdateModeOfNpmrc(e,a):await this.getAutoUpdateMode(e),"skip"===n.updateMode?!1:(s="overwrite"===n.updateMode?n.templateContent:await this.mergeService.mergeConfigContent(n.targetContent,n.templateContent,e),await o.FileOperations.writeFile(e.path,s,"git-hooks"===e.group?{mode:493}:"utf8"),!0)}catch(n){throw new Error(`更新配置文件失败 ${e.name}: ${n.message}`)}}async getAutoUpdateMode(e){const{templatePath:t,path:a}=e,r=await o.FileOperations.readFile(t);let n="";await o.FileOperations.exists(a)?n=await o.FileOperations.readFile(a):await o.FileOperations.ensureDir(i.dirname(a));return{updateMode:await this.mergeService.determineUpdateMode(n,r,e),targetContent:n,templateContent:r}}async getAutoUpdateModeOfNpmrc(e,t){const a=e.templatePath,r=i.join(this.templatesDir,"configs","npm",t,e.name.replace(/^\./,"")),n=await o.FileOperations.readFile(a),s=await o.FileOperations.readFile(r),c=await this.mergeService.mergeConfigContent(s,n,e);let l="";await o.FileOperations.exists(e.path)?l=await o.FileOperations.readFile(e.path):await o.FileOperations.ensureDir(i.dirname(e.path));return{updateMode:await this.mergeService.determineUpdateMode(l,c,e),targetContent:l,templateContent:c}}}class u{async browseForPackageJson(t){let o=t,a=null;for(;;){const{action:r,file:n}=await this.browseDirectory(o,t);if("cancel"===r)return console.log(e.yellow("⚠️ 操作已取消")),null;if("select"===r&&n){a=n;break}if("navigate"===r&&n&&(o=n),"back"===r){const e=i.dirname(o);e!==o&&(o=e)}}return a&&console.log(e.green(`✓ 已选择: ${i.basename(a)}`)),a}async browseDirectory(t,i){try{const o=await c.readdir(t,{withFileTypes:!0}),a=this.getRelativePath(t,i);console.log(e.blue(`\n📁 当前目录: ${a}`));const r=await this.buildChoices(o,t);if(1===r.length)return console.log(e.yellow("⚠️ 当前目录中没有找到 package.json 文件")),{action:"cancel"};const{selection:s}=await n({type:"select",name:"selection",message:"请选择操作:",choices:r,instructions:!1});return s??{action:"cancel"}}catch(o){const t=o instanceof Error?o.message:"未知错误";return console.log(e.red(`❌ 无法访问目录: ${t}`)),{action:"cancel"}}}async buildChoices(e,t){const o=[];i.dirname(t)!==t&&o.push({title:"📁 .. (返回上级目录)",value:{action:"back"},description:"返回上级目录"});return e.some(e=>e.isFile()&&"package.json"===e.name)&&o.push({title:"📄 package.json",value:{action:"select",file:i.join(t,"package.json")},description:"选择此文件"}),await this.addDirectoriesWithPackageJson(o,e,t),o.push({title:"❌ 取消",value:{action:"cancel"},description:"取消选择"}),o}async addDirectoriesWithPackageJson(e,t,o){(await this.getDirectoriesWithPackageJson(t,o)).forEach(t=>{e.push({title:`📁 ${t.name}/`,value:{action:"navigate",file:i.join(o,t.name)},description:"进入目录"})})}async getDirectoriesWithPackageJson(e,t){const o=[];for(const a of e)if(a.isDirectory()&&!a.name.startsWith("."))try{const e=await c.readdir(i.join(t,a.name),{withFileTypes:!0});e.some(e=>e.isFile()&&"package.json"===e.name)&&o.push({name:a.name})}catch{}return o.sort((e,t)=>e.name.localeCompare(t.name))}getRelativePath(e,t){return e.replace(t,"").replace(/^[\\/]/,"")||"."}}class h{fileBrowserService;constructor(){this.fileBrowserService=new u}async selectConfigFiles(t){if(0===t.length)return[];const i=s.ArrayUtils.groupBy(t,e=>e.group),o=[],a=Array.from(i.entries()),r=a.length;for(let c=0;c<r;c++){const t=s.ArrayUtils.safeGet(a,c);if(!t)continue;const[i,l]=t,g=c+1;console.log(`${e.cyan(`[${g}/${r}]`)} ${e.bold(i)} ${e.gray(`(${l.length} 个文件)`)}`);const p=l.map(e=>({title:e.name,value:e})),{selectedFiles:u}=await n({type:"multiselect",name:"selectedFiles",message:"选择要更新的文件:",choices:p,instructions:!1});u&&0!==u.length&&o.push(...u)}return 0===o.length?(console.log(e.yellow("⚠️ 未选择任何配置文件")),[]):(this.displaySelectionSummary(o),o)}async selectDependencyFile(t){console.log(e.blue("\n📦 依赖列表文件选择")),console.log(e.gray("您可以选择使用本地 package.json 文件,或使用项目默认的依赖列表"));const{useCustomFile:i}=await n({type:"confirm",name:"useCustomFile",message:"是否要使用自定义 package.json 文件?",initial:!1});return i?await this.fileBrowserService.browseForPackageJson(t):(console.log(e.green("✓ 使用项目默认依赖列表")),null)}displaySelectionSummary(t){console.log(`\n${e.blue("📋")} ${e.bold("最终选择汇总:")}${t.length} 个文件`);const i=s.ArrayUtils.groupBy(t,e=>e.group);Array.from(i.entries()).forEach(([t,i])=>{console.log(` ${e.green("✓")} ${t}: ${i.length} 个文件`)})}}exports.ConfigService=class{templatesDir;fileService;uiService;constructor(e){this.templatesDir=e,this.fileService=new p(e),this.uiService=new h}async updateConfig(i){const o=i?.silent?null:t("正在初始化配置更新...").start();try{let t,a;if(i?.projectPath)t=i.projectPath,a=i.rootPath;else{const{projectPath:e,rootPath:i}=await r.Discovery.findProjectRoot();t=e,a=i}if(!t||!a)throw o&&o.fail("未找到项目根目录(package.json)"),new Error("未找到项目根目录(package.json)");o&&(o.text="正在检测项目架构...");const n=i?.architectureType??await r.Discovery.detectArchitectureType(a),s=i?.projectType??await r.Discovery.detectProjectType(t);o&&o.succeed(e.gray(`📋 项目架构: ${n};项目类型: ${"app"===s?"应用":"lib"===s?"库":"根目录"}`));const c=await r.Discovery.getAvailableConfigFiles(this.templatesDir,t,i);if(0===c.length)return void(i?.silent||console.log(e.yellow("⚠️ 未找到可更新的配置文件")));const l=i?.silent?c:await this.uiService.selectConfigFiles(c);if(0===l.length)return;await this.updateSelectedConfigs(l,t,a,n,s,i?.silent)}catch(a){throw o&&o.fail("配置更新初始化失败"),a}}async updateSelectedConfigs(i,o,a,n,s,c){let l=c?null:t("正在更新配置文件...").start();try{let p=0,u=!1;for(const r of i)try{await this.fileService.updateConfigFile(r,o,a,n,s),p++,("env"===r.group||r.name.startsWith(".env"))&&(u=!0)}catch(g){!c&&l&&(l.warn(e.yellow(`⚠️ ${g.message}`)),l=t("正在更新配置文件...").start())}if(l){const t=i.length,o=t-p;let a=`配置更新完成!成功更新 ${p}/${t} 个文件`;o>0&&(a+=e.yellow(` (${o} 个文件更新失败)`)),u&&(a+=e.gray(" • 已更新环境变量配置")),l.succeed(e.green(a))}u&&await r.Discovery.updatePackageJsonScripts(o)}catch(g){throw l&&l.fail(e.red("配置文件更新失败")),g}}},exports.ProjectOptionsService=class{constructor(){}async getProjectOptions(e,t={}){console.log(),console.log();const i=await this.getProjectName(e),o=await this.getArchitectureType(t),a=await this.getFrameworkType(t),r=await this.getTargetPath();return await this.displayProjectSummary({projectName:i,architectureType:o,framework:a,targetPath:r}),{projectName:i,architectureType:o,framework:a,targetPath:r}}async getProjectName(e){if(e)return e;const t=await n({type:"text",name:"projectName",message:"请输入项目名称:",initial:"my-project",validate:e=>e.trim()?!!/^[a-zA-Z0-9-_]+$/.test(e)||"项目名称只能包含字母、数字、连字符和下划线":"项目名称不能为空"},{onCancel:()=>{process.exit(0)}});return t.projectName||process.exit(0),t.projectName}async getArchitectureType(e){if(e.architecture)return e.architecture;if(e.monorepo)return"monorepo";if(e.single)return"single";const t=await n({type:"select",name:"architecture",message:"选择项目架构:",choices:[{title:"单体仓库 (Single Repository)",value:"single",description:"适合小到中型项目,所有代码在一个仓库中"},{title:"Monorepo (多包管理)",value:"monorepo",description:"适合大型项目,支持多个包的统一管理"}],initial:0},{onCancel:()=>{process.exit(0)}});return t.architecture||process.exit(0),t.architecture}async getFrameworkType(e){return e.framework?e.framework:"vue3"}async getTargetPath(){return process.cwd()}async displayProjectSummary(t){console.log(),console.log(e.gray("─".repeat(50))),console.log(`${e.bold("项目名称:")} ${e.cyan(t.projectName)}`),console.log(`${e.bold("项目架构:")} ${e.cyan("monorepo"===t.architectureType?"Monorepo":"Single Repository")}`),console.log(`${e.bold("开发框架:")} ${e.cyan("Vue 3")}`),console.log(`${e.bold("创建路径:")} ${e.cyan(i.join(t.targetPath,t.projectName))}`),console.log(e.gray("─".repeat(50))),console.log()}},exports.UIService=h; //# sourceMappingURL=services-BHKno3F_.cjs.map