gchcg-cli
Version:
1 lines • 11.4 kB
JavaScript
"use strict";const fs=require("fs"),path=require("path"),SimpleGit=require("simple-git"),fse=require("fs-extra"),userHome=require("user-home"),inquirer=require("inquirer").default,terminalLink=require("terminal-link"),semver=require("semver"),Listr=require("listr"),{Observable:Observable}=require("rxjs"),log=require("./log"),{readFile:readFile,writeFile:writeFile,spinnerStart:spinnerStart}=require("./utils"),Gitlab=require("./GitLab"),DEFAULT_CLI_HOME=".gchcg-cli",GIT_ROOT_DIR=".git",GIT_TOKEN_FILE=".git_token",GIT_IGNORE_FILE=".gitignore",VERSION_RELEASE="release",VERSION_DEVELOP="dev",COMPONENT_FILE=".componentrc";class Git{constructor({name:e,version:t,dir:i,pid:s,repoUrl:r,refreshToken:a=!1,type:n,ssh:o=!1,prod:h,del:c=!1}){if(e.startsWith("@")&&e.indexOf("/")>0){const t=e.split("/");this.name=t.join("_").replace("@","")}else this.name=e;this.version=t,this.dir=i,this.prod=h,this.git=SimpleGit(i),this.gitServer=null,this.homePath=null,this.pid=s,this.repoUrl=r,this.delete=c,this.user=null,this.orgs=null,this.owner=null,this.login=null,this.refreshToken=a,this.repo=null,this.ssh=o,this.branch=null,this.groupId=null,this.type=n}async prepare(){this.checkHomePath(),await this.checkGitServer(),await this.checkGitToken(),await this.checkRepo(),this.checkGitIgnore(),await this.init()}async checkComponent(){let e=this.isComponent();if(e){log.info("开始检查build结果"),this.buildCmd||(this.buildCmd="npm run build"),require("child_process").execSync(this.buildCmd,{cwd:this.dir});const t=path.resolve(this.dir,e.buildPath);if(!fs.existsSync(t))throw new Error(`构建结果:${t}不存在!`);const i=this.getPackageJson();if(!i.files||!i.files.includes(e.buildPath))throw new Error(`package.json中files属性未添加构建结果目录:[${e.buildPath}],请在package.json中手动添加!`);log.success("build结果检查通过!")}}isComponent(){const e=path.resolve(this.dir,".componentrc");return fs.existsSync(e)&&fse.readJsonSync(e)}async init(){await this.getRemote()||(await this.initAndAddRemote(),await this.initCommit())}async commit(){await this.getCorrectVersion(),await this.checkStash(),await this.checkConflicted(),await this.checkNotCommitted(),await this.checkoutBranch(this.branch),await this.pullRemoteMasterAndBranch(),await this.pushRemoteRepo(this.branch)}async publish(){this.prod&&await this.runCreateTagTask()}async uploadComponentToNpm(){this.isComponent()&&(log.info("开始发布NPM"),require("child_process").execSync("npm publish",{cwd:this.dir}),log.success("NPM发布成功"))}runCreateTagTask(){const e=e=>setTimeout(e,1e3),t=[{title:"创建Tag",task:()=>new Observable((t=>{t.next("正在创建Tag"),e((()=>{this.checkTag().then((()=>{t.complete()}))}))}))},{title:"切换分支到master",task:()=>new Observable((t=>{t.next("正在切换master分支"),e((()=>{this.checkoutBranch("master").then((()=>{t.complete()}))}))}))},{title:"将开发分支代码合并到master",task:()=>new Observable((t=>{t.next("正在合并到master分支"),e((()=>{this.mergeBranchToMaster("master").then((()=>{t.complete()}))}))}))},{title:"将代码推送到远程master",task:()=>new Observable((t=>{t.next("正在推送master分支"),e((()=>{this.pushRemoteRepo("master").then((()=>{t.complete()}))}))}))}];this.delete&&(t.push({title:"删除本地开发分支",task:()=>new Observable((t=>{t.next("正在删除本地开发分支"),e((()=>{this.deleteLocalBranch().then((()=>{t.complete()}))}))}))}),t.push({title:"删除远程开发分支",task:()=>new Observable((t=>{t.next("正在删除远程开发分支"),e((()=>{this.deleteRemoteBranch().then((()=>{t.complete()}))}))}))}));new Listr([{title:"自动生成远程仓库Tag",task:()=>new Listr(t)}]).run()}async deleteLocalBranch(){await this.git.deleteLocalBranch(this.branch)}async deleteRemoteBranch(){await this.git.push(["origin","--delete",this.branch])}async mergeBranchToMaster(){await this.git.mergeFromTo(this.branch,"master")}async checkTag(){const e=`release/${this.version}`;(await this.getRemoteBranchList("release")).includes(this.version)&&await this.git.push(["origin",`:refs/tags/${e}`]);(await this.git.tags()).all.includes(e)&&await this.git.tag(["-d",e]),await this.git.addTag(e),await this.git.pushTags("origin")}async uploadTemplate(){}getPackageJson(){const e=path.resolve(this.dir,"package.json");if(!fs.existsSync(e))throw new Error(`package.json 不存在!源码目录:${this.dir}`);return fse.readJsonSync(e)}async pullRemoteMasterAndBranch(){log.info(`合并 [master] -> [${this.branch}]`),await this.pullRemoteRepo("master"),log.success("合并远程 [master] 分支代码成功"),await this.checkConflicted(),log.info("检查远程开发分支");(await this.getRemoteBranchList()).indexOf(this.version)>=0?(log.info(`合并 [${this.branch}] -> [${this.branch}]`),await this.pullRemoteRepo(this.branch),log.success(`合并远程 [${this.branch}] 分支代码成功`),await this.checkConflicted()):log.success(`不存在远程分支 [${this.branch}]`)}async checkoutBranch(e){(await this.git.branchLocal()).all.indexOf(e)>=0?await this.git.checkout(e):await this.git.checkoutLocalBranch(e)}async checkStash(){log.info("检查stash记录");(await this.git.stashList()).all.length>0&&(await this.git.stash(["pop"]),log.success("stash pop成功"))}async getCorrectVersion(){log.info("获取代码分支");const e=await this.getRemoteBranchList("release");let t=null;e&&e.length>0&&(t=e[0]),log.verbose("线上最新版本号",t);const i=this.version;if(t)if(semver.gt(this.version,t))log.info("当前版本大于线上最新版本",`${i} >= ${t}`),this.branch=`dev/${i}`;else{log.info("当前线上版本大于本地版本",`${t} > ${i}`);const e=[{type:"list",name:"incType",message:"自动升级版本,请选择升级版本类型",default:"patch",choices:[{name:`小版本(${t} -> ${semver.inc(t,"patch")})`,value:"patch"},{name:`中版本(${t} -> ${semver.inc(t,"minor")})`,value:"minor"},{name:`大版本(${t} -> ${semver.inc(t,"major")})`,value:"major"}]}],s=(await inquirer.prompt(e)).incType,r=semver.inc(t,s);this.branch=`dev/${r}`,this.version=r}else this.branch=`dev/${i}`;log.verbose("本地开发分支",this.branch),this.syncVersionToPackageJson()}syncVersionToPackageJson(){const e=fse.readJsonSync(`${this.dir}/package.json`);e&&e.version!==this.version&&(e.version=this.version,fse.writeJsonSync(`${this.dir}/package.json`,e,{spaces:2}))}async getRemoteBranchList(e){const t=await this.git.listRemote(["--refs"]);let i;return i="release"===e?/.+?refs\/tags\/release\/(\d+\.\d+\.\d+)/g:/.+?refs\/heads\/dev\/(\d+\.\d+\.\d+)/g,t.split("\n").map((e=>{const t=i.exec(e);if(i.lastIndex=0,t&&semver.valid(t[1]))return t[1]})).filter((e=>e)).sort(((e,t)=>semver.lte(t,e)?e===t?0:-1:1))}async initCommit(){await this.checkConflicted(),await this.checkNotCommitted(),await this.checkRemoteMaster()?await this.pullRemoteRepo("master",{"--allow-unrelated-histories":null}):await this.pushRemoteRepo("master")}async pullRemoteRepo(e,t){log.info(`同步远程${e}分支代码`),await this.git.pull("origin",e,t).catch((e=>{log.error(e.message)}))}async pushRemoteRepo(e){await this.git.push("origin",e)}async checkRemoteMaster(){return(await this.git.listRemote(["--refs"])).indexOf("refs/heads/master")>=0}async checkNotCommitted(){const e=await this.git.status();if(e.not_added.length>0||e.created.length>0||e.deleted.length>0||e.modified.length>0||e.renamed.length>0){await this.git.add(e.not_added),await this.git.add(e.created),await this.git.add(e.deleted),await this.git.add(e.modified),await this.git.add(e.renamed);const t=require("child_process").execSync("git config user.name").toString().trim();if(!t)throw new Error('git用户名不存在,请先执行git config --global user.name "Your Name"');let i,s=["feat","fix","docs","style","refactor","perf","test","build","ci","chore","revert"].includes(this.type)?this.type:this.prod?"build":"fix";const r=[{type:"list",name:"type",message:"请选择commit类型",default:s,choices:[{name:"新功能(feat)",value:"feat"},{name:"修复bug(fix)",value:"fix"},{name:"文档变更(docs)",value:"docs"},{name:"无关代码的格式(style)",value:"style"},{name:"重构(refactor)",value:"refactor"},{name:"优化性能(perf)",value:"perf"},{name:"增加或更新测试(test)",value:"test"},{name:"构建系统或外部依赖更改(build)",value:"build"},{name:"CI配置或脚本变动(ci)",value:"ci"},{name:"不影响代码的其余变动(chore)",value:"chore"},{name:"回滚(revert)",value:"revert"}]}];for(s=(await inquirer.prompt(r)).type;!i;)i=(await inquirer.prompt([{type:"text",name:"message",message:"请输入commit信息:"}])).message;await this.git.commit(`${s}(${t}):${i}`),log.success("本次commit提交成功")}}async checkConflicted(){log.info("代码冲突检查");if((await this.git.status()).conflicted.length>0)throw new Error("当前代码存在冲突,请手动处理合并后执行:cg push!");log.success("代码冲突检查通过")}getRemote(){const e=path.resolve(this.dir,".git");if(this.remote=this.repo?this.ssh?this.repo.ssh_url_to_repo:this.repo.http_url_to_repo:this.repoUrl,fs.existsSync(e))return log.success("git已完成初始化"),!0}async initAndAddRemote(){await this.git.init(this.dir);if(!(await this.git.getRemotes()).find((e=>"origin"===e.name))){if(!this.remote)throw new Error("git remote 地址不存在,请先执行git remote add origin");await this.git.addRemote("origin",this.remote),log.warn("git remote 地址添加成功"+terminalLink("链接",this.remote))}}checkHomePath(){if(this.homePath=path.resolve(userHome,".gchcg-cli"),fse.ensureDirSync(this.homePath),!fs.existsSync(this.homePath))throw new Error("用户主目录获取失败!")}async checkGitServer(){this.gitServer=this.createGitServer()}async checkGitToken(){const e=this.createPath(".git_token");let t=readFile(e);if(!t||this.refreshToken){log.warn(this.gitServer.type+" token未生成","请先生成"+this.gitServer.type+" token,"+terminalLink("链接",this.gitServer.getTokenUrl()));const i=[{type:"password",name:"token",message:"请将token复制到这里",default:""}];t=(await inquirer.prompt(i)).token,writeFile(e,t),log.success("token写入成功",`${t} -> ${e}`)}else log.success("token获取成功",e);this.token=t,this.gitServer.setToken(t)}async getUserAndOrgs(){if(this.user=await this.gitServer.getUser(),!this.user)throw new Error("用户信息获取失败!");if(this.orgs=await this.gitServer.getOrg(this.user.login),!this.orgs)throw new Error("组织信息获取失败!");log.success(this.gitServer.type+" 用户和组织信息获取成功")}async checkRepo(){if(!this.pid)return;let e=await this.gitServer.getRepoGroupsInfo(this.name,this.pid);if(!e)throw new Error("远程仓库获取失败,请检查仓库是否存在,是否有读取权限");log.success("远程仓库信息获取成功"),this.repo=e}checkGitIgnore(){const e=path.resolve(this.dir,".gitignore");fs.existsSync(e)||(writeFile(e,".DS_Store\nnode_modules\n/dist\n\n\n# local env files\n.env.local\n.env.*.local\n\n# Log files\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\npnpm-debug.log*\n\n# Editor directories and files\n.idea\n.vscode\n*.suo\n*.ntvs*\n*.njsproj\n*.sln\n*.sw?"),log.success("自动写入.gitignore文件成功"))}createGitServer(){return new Gitlab}createPath(e){const t=path.resolve(this.homePath,".git"),i=path.resolve(t,e);return fse.ensureDirSync(t),i}}Git.Gitlab=Gitlab,module.exports=Git;