cli-z-develop 0.0.4 → 0.0.6
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/dist/index.js +2371 -1
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -1 +1,2371 @@
|
|
|
1
|
-
import{program as e}from"commander";import t from"chalk";import i from"shelljs";import n from"fs-extra";import o from"minimist";import s from"node:fs";import r from"node:path";import a from"node:os";import c,{AxiosError as l}from"axios";import u from"dayjs";import d from"inquirer";import m from"inquirer/lib/prompts/base.js";import p from"inquirer/lib/utils/paginator.js";import f from"inquirer/lib/utils/events.js";import"inquirer/lib/objects/choices.js";import{map as h,takeUntil as g,filter as y}from"rxjs";import v from"figures";import b from"ora";import j from"child_process";import $ from"lint-staged";var w={name:"cli-z-develop",version:"0.0.4",description:"前端本地开发命令行工具",main:"dist/index.js",bin:{z:"./bin/z.js","z-develop":"./bin/z.js"},scripts:{prepare:"husky",dev:"rollup -c -w --environment DEBUG:true",build:"rollup -c",eslint:"eslint '**/*.{ts,js}' --fix",prettier:"prettier -wu .",upload:"npm version patch && npm run build && npm publish --access public --registry https://registry.npmjs.org/"},type:"module",repository:{type:"git",url:"http://git.cxlqd.com/fe-base/cli-z-develop.git"},author:"z",devDependencies:{"@commitlint/cli":"^19.3.0","@commitlint/config-conventional":"^19.2.2","@rollup/plugin-json":"^6.1.0","@rollup/plugin-node-resolve":"^15.2.3","@rollup/plugin-replace":"^5.0.5","@rollup/plugin-terser":"^0.4.4","@types/fs-extra":"^11.0.4","@types/inquirer":"^9.0.7","@types/minimist":"^1.2.5","@types/node":"^20.12.12","@types/shelljs":"^0.8.15","@types/tough-cookie":"^4.0.5","@typescript-eslint/eslint-plugin":"^7.9.0","@typescript-eslint/parser":"^7.9.0",eslint:"^8.57.0","eslint-config-alloy":"^5.1.2","eslint-config-prettier":"^9.1.0",husky:"^9.0.11","lint-staged":"^15.2.2",prettier:"^3.2.5",rollup:"^4.17.2","rollup-plugin-typescript2":"^0.36.0",tslib:"^2.6.2",typescript:"^5.4.5"},dependencies:{axios:"^1.7.1",chalk:"^5.3.0",commander:"^12.1.0",dayjs:"^1.11.11",figures:"^6.1.0","fs-extra":"^11.2.0",inquirer:"^9.2.22",minimist:"^1.2.8",ora:"^8.0.1",rxjs:"^7.8.1",shelljs:"^0.8.5"}};function k(e,t,i,n){return new(i||(i=Promise))((function(o,s){function r(e){try{c(n.next(e))}catch(e){s(e)}}function a(e){try{c(n.throw(e))}catch(e){s(e)}}function c(e){var t;e.done?o(e.value):(t=e.value,t instanceof i?t:new i((function(e){e(t)}))).then(r,a)}c((n=n.apply(e,t||[])).next())}))}"function"==typeof SuppressedError&&SuppressedError;const x='{\n "printWidth": 120,\n "tabWidth": 2,\n "useTabs": false,\n "semi": true,\n "singleQuote": false,\n "quoteProps": "as-needed",\n "jsxSingleQuote": false,\n "trailingComma": "all",\n "bracketSpacing": true,\n "bracketSameLine": false,\n "arrowParens": "always",\n "requirePragma": false,\n "insertPragma": false,\n "proseWrap": "preserve",\n "htmlWhitespaceSensitivity": "css",\n "vueIndentScriptAndStyle": false,\n "endOfLine": "lf",\n "embeddedLanguageFormatting": "auto",\n "singleAttributePerLine": false\n}\n',S="# 系统 信息文件\n.DS_Store/\nThumbs.db\n\n# 编辑器配置文件\n.idea/\n.vscode/\n\n# 输出目录\ndist/\n\n# Log files\n*.log\n\n# 本地配置文件\n*.local\n\n# 缓存文件\n*.cache\n\n# 垃圾文件\n.Trashes\n",z={Java:{"**/*.{java}":"./run.sh pmd -d ../../../src/main/java/ -f text -R rulesets/java/quickstart.xml"},JavaScript:{"**/*.{vue,js,jsx,cjs,mjs,ts,tsx,cts,mts}":"eslint --fix","*":"prettier -wu"}},O=[{name:"开发环境 - dev",value:"dev"},{name:"测试环境 - test",value:"test"},{name:"预发环境 - release",value:"release"},{name:"正式环境 - production",value:"production"}],E=[{name:"网页 - h5",value:"h5"},{name:"支付宝小程序 - alipay",value:"alipay"},{name:"微信小程序 - weapp",value:"weapp"},{name:"node module - npm",value:"npm"}],N=["main","master"],q=["dev","test","release"],D=["fix","feat","refactor"],T={1:"研发",2:"测试",3:"产品",4:"设计",5:"运营",6:"销售",7:"行政",8:"财务",9:"其他"},C="http://git.cxlqd.com";function I(e){if(!e)return"不能为空";if(!/^[a-z0-9-]+$/.test(e))return"格式为小写字母、中横线(可选)、数字(不推荐)。如apple, apple-tree";return!K().includes(e)||"当前目录下已存在同名文件夹,请先处理。"}function L(e){return!!e||"不能为空"}function J(e){return e?!!/^[a-zA-Z0-9]+$/.test(e)||"格式为大小写字母、数字,小驼峰命名。如userInfo、systemRouter3":"不能为空"}const{red:P,green:A,blue:U,magenta:B}=t;function W(...e){console.log(A(...e))}function _(e,t=!1){let i=e;e instanceof Error?(i=e.message,c.isAxiosError(e)&&(i=`请求失败:${e.message}`),console.log(P(i)),console.log(B(e.stack))):console.log(P(e)),t||process.exit(1)}function R(e){return!!s.existsSync(e)&&s.lstatSync(e).isDirectory()}function K(e=process.cwd()){if(R(e)){return s.readdirSync(e).filter((t=>R(r.resolve(e,t))))}return[]}function F(){return r.resolve(a.homedir(),".z")}function V(){return r.resolve(F(),"develop-config.json")}function M(){return s.existsSync(F())&&s.existsSync(V())}function H(e){return k(this,arguments,void 0,(function*(e,t={removeTailLinkBreak:!0,silent:!0}){let n=yield new Promise(((n,o)=>{try{n(i.exec(e,{silent:t.silent}))}catch(e){o(e)}}));return n=n.toString(),t.removeTailLinkBreak&&(n=n.replace(/\n$/,"")),n}))}function G(){return u(Date.now()).format("YYMMDD")}const Q={profile:{ldapAccount:"",ldapPassword:"",gitToken:"",gitUserId:0,gitName:"",gitEnglishName:"",gitEmail:"",weWorkName:"",weWorkUserId:"",jobType:"fe",zenTaoToken:""},main:{version:w.version,latestCheckVersionTimestamp:0,versionCheckDuring:3,weWorkListCache:[]},constants:{ZenTaoDomain:"",FEServerDomain:"",jenkinsDomain:"",jenkinsUsername:"",jenkinsPassword:"",OSSEndpoint:"",OSSAccessKeyID:"",OSSAccessKeySecret:"",OSSBucketName:""}};function Z(e,t){void 0!==t?Q.profile[e]=t:Q.profile=Object.assign(Object.assign({},Q.profile),e)}function Y(e,t){Q.constants=Object.assign(Object.assign({},Q.constants),e)}function X(e,t){void 0!==t?Q.main[e]=t:Q.main=Object.assign(Object.assign({},Q.main),e)}function ee(e){return e?Q.profile[e]:Q.profile}function te(e){return e?Q.main[e]:Q.main}function ie(e){return e?Q.constants[e]:Q.constants}let ne=null,oe=null;const se="package.json",re=".z/project.json";function ae(){if(ne)return ne;try{ne=n.readJsonSync(se)}catch(e){_(`当前目录(${i.pwd()})不存在${se}文件,请在项目根目录执行该命令。`)}return ne}function ce(){if(oe)return oe;try{oe=n.readJsonSync(re)}catch(e){_(`当前目录(${i.pwd()})不存在${re}文件,请在项目根目录执行该命令,或者初始化项目(z init .)。`)}return oe}function le(e){oe=oe?Object.assign(Object.assign({},oe),e):e,n.writeJSONSync(re,oe,{spaces:2})}const ue={id:0,name:"",sourceBranch:"",mergeRequestUrl:""};function de(){return k(this,void 0,void 0,(function*(){if(!ue.name){const e=ae();ue.name=e.name}if(!ue.id)try{const[t]=yield(e=ue.name,ye({url:`${$e()}/projects`,data:{search:e}}));ue.mergeRequestUrl=t.web_url+"/merge_requests",ue.id=t.id}catch(e){_(e)}var e;return ue.sourceBranch||(ue.sourceBranch=yield H("git branch --show-current")),ue}))}function me(){n.writeJSONSync(V(),{main:te(),profile:ee(),constants:ie()},{spaces:2})}function pe(e,t){const i=[];t.forEach((t=>{e.includes(t.value)&&i.push(t)}));const n=te("weWorkListCache"),o=n.map((e=>e.value));i.forEach((e=>{const t=o.indexOf(e.value);t>-1?n[t].usageCount+=1:n.push(Object.assign(Object.assign({},e),{usageCount:1}))})),X({weWorkListCache:n.sort(((e,t)=>t.usageCount-e.usageCount))}),me()}function fe(){return k(this,void 0,void 0,(function*(){const{data:e}=yield je({url:"/user/list"}),t=e.filter((e=>[1,2,3].includes(e.title))).map((e=>({name:`${e.nick} - ${T[e.title]}`,value:e.weWorkUserId}))),i=te("weWorkListCache"),n=i.map((e=>e.value)),o=[];return t.forEach((e=>{const t=n.indexOf(e.value);o.push(Object.assign(Object.assign({},e),{usageCount:t>-1?i[t].usageCount:0}))})),o.sort(((e,t)=>t.usageCount-e.usageCount))}))}function he(e){return k(this,void 0,void 0,(function*(){var i;const n=e.method||"get",o=e.headers||{},s=e.data||{},r=e.dataKey||["GET","get"].includes(n)?"params":"data",a=e.auth||void 0;try{const i={url:e.url,method:n,[r]:s,headers:o,auth:a};t.magenta(i.method.toUpperCase()),t.yellow(i.url),JSON.stringify(i.headers),JSON.stringify(i.auth||{}),t.gray(JSON.stringify(i[r],null,2));const l=yield c(i);return t.green("Response"),t.grey(JSON.stringify(l.data,null,2)),Promise.resolve(l.data)}catch(e){return e instanceof l&&(t.red("Error"),t.grey(JSON.stringify(null===(i=null==e?void 0:e.response)||void 0===i?void 0:i.data))),Promise.reject(e)}}))}function ge(){return k(this,void 0,void 0,(function*(){const e=ee("gitToken");if(e)return e;{const{access_token:e}=yield he({url:`${C}/oauth/token`,method:"post",data:{grant_type:"password",username:ee("ldapAccount"),password:ee("ldapPassword")}}),t=`Bearer ${e}`;return Z("gitToken",t),me(),t}}))}function ye(e){return k(this,void 0,void 0,(function*(){return he(Object.assign(Object.assign({},e),{headers:{Authorization:yield ge()}}))}))}function ve(){return k(this,void 0,void 0,(function*(){const e=ee("zenTaoToken");if(e)return e;{const{token:e}=yield he({url:`${ie("ZenTaoDomain")}/api.php/v1/tokens`,method:"post",data:{account:ee("ldapAccount"),password:ee("ldapPassword")}});return Z("zenTaoToken",e),me(),e}}))}function be(e){return k(this,void 0,void 0,(function*(){const t=yield he(Object.assign(Object.assign({},e),{headers:{Token:yield ve()}}));if("string"==typeof t){const e=JSON.parse(t.slice(0,t.indexOf("<script>")));return JSON.parse(e.data)}if("object"==typeof t)return"Unauthorized"===t.error||t.data.indexOf("<script>")<0?(Z("zenTaoToken",""),be(Object.assign({},e))):t}))}function je(e){return k(this,void 0,void 0,(function*(){const t=`${ie("FEServerDomain")}/api`,{data:i}=yield he({url:`${t}/auth/z-develop/login`,method:"post"});return he({url:t+e.url,headers:{Authorization:`Bearer ${i}`},data:e.data,method:e.method||"post"})}))}const $e=()=>`${C}/api/v4`,we=e=>$e()+"/projects/100/repository/files/"+encodeURIComponent(e)+"/raw?ref=master";function ke(){return ye({url:we("src/data/z-develop-config.json")})}function xe(e){return ye({url:`${$e()}/projects/${e}/repository/branches`})}const Se=e=>`${ie("ZenTaoDomain")}${e}`;function ze(e,i){let n="";return e.forEach(((e,o)=>{if(e.disabled)n=`${n} - ${e.name} (Disabled)`;else{n+=o===i?t.cyan(v.pointer)+" ":" ",n+=(e.checked||!1?t.green(v.radioOn):v.radioOff)+" "+e.name}n+="\n"})),n.replace(/\n$/,"")}class Oe extends m{constructor(e,t,i){super(e,t,i),this.pointer=0,this.opt.choices||this.throwParamError("choices"),this.opt.choices.forEach(((e,t)=>{const i=e;i.checked=!1,i.id=t}));if(this.opt.choices.find((e=>"separator"===e.type)))throw new Error("Separator is not allowed in choices.");Array.isArray(this.opt.default)&&this.opt.choices.forEach((e=>{"separator"!==e.type&&this.opt.default.includes(e.value)&&(e.checked=!0)})),this.pointer=0,this.selection=[],this.done=e=>{console.log(e)},this.opt.default=null,this.paginator=new p(this.screen,{isInfinite:!0}),this.filterList=this.allList=this.opt.choices.choices}_run(e){this.done=e;const t=f(this.rl),i=this.handleSubmitEvents(t.line.pipe(h(this.getCurrentValue.bind(this))));i.success.forEach(this.onEnd.bind(this)),i.error.forEach(this.onError.bind(this)),t.normalizedUpKey.pipe(g(i.success)).forEach(this.onUpKey.bind(this)),t.normalizedDownKey.pipe(g(i.success)).forEach(this.onDownKey.bind(this)),t.spaceKey.pipe(g(i.success)).forEach(this.onSpaceKey.bind(this));const n=["up","down","space"];return t.keypress.pipe(y((e=>!e.key.ctrl&&!n.includes(e.key.name||"")))).pipe(g(i.success)).forEach(this.onKeyPress.bind(this)),this.render(),this}render(e){let i=this.getQuestion(),n="";if(this.getCurrentValue(),"answered"===this.status)i+=t.cyan(this.selection.join(","));else{this.selection.length?i+=t.magenta(`[已选: ${this.selection.join(",")}]`):i+="("+t.blue.bold("空格键")+"选择,"+t.blue.bold("回车键")+"提交,"+t.blue.bold("输入中文")+"搜索)",i+=t.bgYellowBright(t.black(this.rl.line));const e=ze(this.filterList,this.pointer);n=this.paginator.paginate(e,this.pointer,this.opt.pageSize)}e&&(n=t.red(">> ")+e),this.screen.render(i,n)}getCurrentValue(){const e=this.allList.filter((e=>e.checked&&!e.disabled));return this.selection=e.map((e=>e.short)),e.map((e=>e.value))}toggleChoice(e){const t=this.filterList[e];t&&(this.allList[t.id].checked=!t.checked)}filterChoices(){this.filterList=this.allList.filter((e=>{var t;return null===(t=e.name)||void 0===t?void 0:t.includes(this.rl.line.trim())}))}onSpaceKey(){this.toggleChoice(this.pointer),this.render(),this.rl.resume()}onDownKey(){const e=this.filterList.length;this.pointer=this.pointer<e-1?this.pointer+1:0,this.render()}onUpKey(){const e=this.filterList.length;this.pointer=this.pointer>0?this.pointer-1:e-1,this.render()}onKeyPress(){this.pointer=0,this.filterChoices(),this.render()}onEnd(e){this.status="answered",this.render(),this.screen.done(),this.done(e.value)}onError(e){this.render(e.isValid)}}function Ee(){return k(this,void 0,void 0,(function*(){const e=o(process.argv.slice(2));if(!["init","i"].includes(e._[0]))if(M()){const e=n.readJSONSync(V());Z(e.profile),X(e.main),Y(e.constants),yield function(){return k(this,void 0,void 0,(function*(){const e=te("latestCheckVersionTimestamp"),i=te("versionCheckDuring");if(Date.now()-Number(e)>24*Number(i)*3600*1e3){Y(yield ke());const e=yield H(`npm view ${w.name} version`);if(e!==w.version){console.log(`${t.blue(w.name)}本地版本为${w.version},低于线上版本${e},开始升级`);try{yield H(`npm i -g ${w.name}`),X("latestCheckVersionTimestamp",Date.now()),me(),console.log(t.green("升级完成!")),process.exit(0)}catch(e){console.log("升级出错!请重试,或者手动升级"),_(e)}}}}))}()}else e.h||e.help||e.v||e.version||_("请先初始化z-develop(执行 z i)。更多见https://zxf-fe.yuque.com/org-wiki-zxf-fe-ex4bve/qi89vg/us8lgdb5cq4kf3aa#PJK7q")}))}function Ne(){return k(this,void 0,void 0,(function*(){try{if(M()){const{isRemove:e}=yield d.prompt([{type:"confirm",name:"isRemove",message:"系统中已存在z的配置文件,确认重新配置?"}]);e?i.rm("-rf",V()):process.exit(0)}const{jobType:e,account:t,password:n}=yield d.prompt([{type:"list",name:"jobType",message:"请选择你要创建的类型",choices:[{name:"前端",value:"fe"},{name:"后端",value:"be"}]},{type:"input",name:"account",message:"请输入LDAP账号:"},{type:"input",name:"password",message:"请输入LDAP密码:"}]);Z("jobType",e),Z("ldapAccount",t),Z("ldapPassword",n);Y(yield ke());const o=yield fe(),{weWork:s}=yield d.prompt([{type:"search-checkbox",name:"weWork",message:"请选择你自己(用于企微通知):",choices:o,validate:e=>e.length>1?"只能选一个":!(e.length<1)||"请选一个"}]),{name:r,value:a}=o.find((e=>e.value===s[0]));Z({weWorkName:r,weWorkUserId:a})}catch(e){_(e)}const e=b("配置信息初始化中").start();try{const t=yield ye({url:`${$e()}/user`});Z({gitUserId:t.id,gitName:t.name,gitEnglishName:t.username,gitEmail:t.email}),X("latestCheckVersionTimestamp",Date.now()),i.mkdir("-p",F()),me(),e.succeed("配置信息初始化完成")}catch(t){e.fail("配置信息初始化失败"),c.isAxiosError(t)&&_("请检查你的域名及令牌配置"),_(t)}}))}const qe=".z",De=(e="")=>r.join(qe,e);let Te;function Ce(){return k(this,void 0,void 0,(function*(){if(s.existsSync(".git")||_("当前项目不是git项目,或不在根目录。请先执行git init,或切换到根目录"),s.existsSync(qe)){const{isRemove:e}=yield d.prompt([{type:"confirm",name:"isRemove",message:`当前项目中已存在配置文件夹${qe},确认重新配置?`}]);e?i.rm("-rf",".z"):process.exit(0)}yield function(){return k(this,void 0,void 0,(function*(){s.mkdirSync(De(),{recursive:!0});const e=yield H("git remote get-url --push origin");e||_("获取项目远程地址失败,请配置后重试");const{language:t}=yield d.prompt([{type:"list",name:"language",message:"请选择项目语言",choices:[{name:"JavaScript",value:"JavaScript"},{name:"Java",value:"Java"}]}]);Te=b("项目初始化中").start(),le({language:t,"lint-staged":z[t],repository:{url:e}})}))}(),yield function(){return k(this,void 0,void 0,(function*(){s.mkdirSync(De(),{recursive:!0}),j.spawnSync("git",["config","core.hooksPath",qe]),s.writeFileSync(De(".commit-msg-tpl"),"",{mode:493}),j.spawnSync("git",["config","commit.template",r.join(qe,".commit-msg-tpl")]),s.writeFileSync(De("commit-msg"),"z lint commit-msg",{mode:493}),s.writeFileSync(De("pre-commit"),"z lint commit-files",{mode:493}),s.writeFileSync(De(".prettierrc.json"),x,{mode:493}),s.writeFileSync(De(".prettierignore"),S,{mode:493}),s.writeFileSync(De(".gitignore"),".commit-msg-tpl\n")}))}(),null==Te||Te.succeed("初始化完成。")}))}function Ie(){return k(this,void 0,void 0,(function*(){try{const s={},r=()=>k(this,void 0,void 0,(function*(){const e=K(),{list:t}=yield ye({url:we("src/data/template-projects.json")}),i=t.map((t=>{const i={name:`${t.name} [${t.desc}]`,value:t.name,short:t.name,disabled:!1};return e.includes(t.name)&&(i.disabled="目录下已存在同名文件夹"),i})),{project:n}=yield d.prompt([{type:"list",name:"project",message:"请选择一个项目模板",choices:i}]),o=t.find((e=>e.name===n));s.tplName=o.name,s.tplUrl=o.url})),a=()=>k(this,void 0,void 0,(function*(){const e=yield ye({url:`${$e()}/groups`}),{list:t}=yield ye({url:we("src/data/project-groups.json")}),{group:i}=yield d.prompt([{type:"list",name:"group",message:"请选择一个分组",choices:t.map((e=>({name:`${e.name} [${e.desc}]`,value:e.name,short:e.name,disabled:!1})))}]),n=(o=i,e.find((e=>e.name===o)));var o;s.group=i,s.groupId=n.id})),c=()=>k(this,void 0,void 0,(function*(){const{name:e,desc:t}=yield d.prompt([{type:"input",name:"name",message:"请输入项目名称",validate:I},{type:"input",name:"desc",message:"请输入项目描述",validate:L}]);s.projectName=e,s.projectDesc=t}));yield r(),yield a(),yield c();const l=`${C}/${s.group}/${s.projectName}.git`,u=b("模版初始化中").start();yield H(`git clone --depth=1 ${s.tplUrl}`),i.mv(s.tplName,s.projectName),i.cd(s.projectName),i.rm("-rf",".git"),yield H("git init --initial-branch=master"),yield H(`git remote add origin ${l}`),yield H(`git config user.name ${ee("gitName")}`),yield H(`git config user.email ${ee("gitEmail")}`),o={name:s.projectName,description:s.projectDesc},ne=ne?Object.assign(Object.assign({},ne),o):o,n.writeJSONSync(se,ne,{spaces:2}),le({repository:{url:l},"lint-staged":z.JavaScript,language:"JavaScript"}),yield n.writeFile("README.md",function(e="项目中文名",t="项目描述"){return`\n# ${e}\n${t}\n\n## 快速开始\n### 安装依赖\n\`\`\`bash\n npm i\n\`\`\`\n\n### 本地开发\n\`\`\`bash\n npm run dev:{environment}:{platform}\n\`\`\`\n\n> 查看更多关于 [platform](https://zxf-fe.yuque.com/org-wiki-zxf-fe-ex4bve/qi89vg/nttvbeif4a62gtpz#faWqt) 和 [environment](https://zxf-fe.yuque.com/org-wiki-zxf-fe-ex4bve/qi89vg/nttvbeif4a62gtpz#faWqt)\n\n### 打包代码\n\`\`\`bash\n npm run build:{environment}:{platform}\n\`\`\`\n\n### 格式化、修复代码语法\n\`\`\`bash\n z lint prettier\n\`\`\`\n\n> [ESLint相关规范](https://zxf-fe.yuque.com/org-wiki-zxf-fe-ex4bve/qi89vg/ul914hdkqz8fr02g#AkvQ4)\n\n### 格式化文档\n\`\`\`bash\n z lint prettier\n\`\`\`\n\n> [Prettier相关规范](https://zxf-fe.yuque.com/org-wiki-zxf-fe-ex4bve/qi89vg/ul914hdkqz8fr02g#ojpwI)\n\n## 常见问题\n列出开发中常见的问题和解决方案\n\n## 了解更多\n在此处放入语雀文档链接。请在[前端团队-项目手册](https://zxf-fe.yuque.com/org-wiki-zxf-fe-ex4bve/qi89vg/bfrcicwzosoyaifg)中按照分类新建文档。\n`}(s.projectName,s.projectDesc)),u.succeed("模版初始化完成");const m=b("依赖安装中").start();yield H("npm install"),m.succeed("依赖安装完成");const p=b("远程项目创建中").start(),f=yield(e={name:s.projectName,description:s.projectDesc,path:s.projectName,visibility:"private",namespace_id:s.groupId},ye({url:`${$e()}/projects`,method:"post",data:e}));p.succeed("远程项目创建成功");const h=b("项目推送中").start();yield H('git add . && git commit -m "chore: init"'),yield H("git push -u origin master"),h.succeed(`项目已推送到远程,地址: ${t.blue(l)}`);const g=b("初始化分支中").start(),y=`feat_init_${G()}}`,v=[...q,y],j=yield Promise.allSettled(v.map((e=>function(e){return ye({url:`${$e()}/projects/${e.id}/repository/branches`,method:"post",data:e})}({id:f.id,branch:e,ref:"master"})))),$=[];j.forEach((({status:e},t)=>{"fulfilled"===e&&$.push(v[t])})),yield H("git fetch origin"),$.includes(y)?(yield H(`git checkout -b ${y} origin/${y}`),g.succeed(`项目已切换到初始分支: ${t.blue(y)}`)):g.warn("开发分支检出失败!项目当前在主分支,请自行检出开发分支。");const w=`cd ${s.projectName} && z start`;console.log(`输入 ${t.green(w)} 开始开发吧~`)}catch(e){_(e)}var e,o}))}function Le(e){return 0!==e.length||"请选择项"}function Je(){return k(this,void 0,void 0,(function*(){const{type:e}=yield d.prompt({type:"list",name:"type",message:"请选择你要创建的模板类型",choices:[{name:"feat - 业务需求开发",value:"feat"},{name:"fix - bug修复",value:"fix"},{name:"refactor - 技术内部需求",value:"refactor"},{name:"chore - 其他",value:"chore"}]});if(["feat","refactor"].includes(e)){const e=yield function(){return k(this,void 0,void 0,(function*(){const e=yield be({url:Se("/my-work-task.json?tid=mrrferp8"),method:"get"});return null==e?void 0:e.tasks.map((({id:e,name:t})=>({value:{name:t,value:e},name:t})))}))}(),{tasks:t}=yield d.prompt({type:"checkbox",name:"tasks",message:"请关联开发任务(可多选):",validate:Le,choices:e.map((e=>Object.assign(Object.assign({},e),{name:`[${e.value.value}]: ${e.name}`})))});Pe("feat",t)}else if("fix"===e){const e=yield function(){return k(this,void 0,void 0,(function*(){const e=yield be({url:Se("/my-work-bug.json?tid=mrrferp8"),method:"get"});return null==e?void 0:e.bugs.map((({id:e,title:t})=>({name:t,value:{name:t,value:e}})))}))}(),{bugs:t}=yield d.prompt({type:"checkbox",name:"bugs",message:"请关联Bug(可多选):",validate:Le,choices:e.map((e=>Object.assign(Object.assign({},e),{name:`[${e.value.value}]: ${e.name}`})))});Pe("fix",t)}else if("chore"===e){const{msg:e}=yield d.prompt({type:"input",name:"msg",message:"请输入commit msg:",validate:e=>0!==e.length||"请输入commit msg"});Pe("chore",[e])}}))}function Pe(e,i){let n="";if(["feat","refactor","fix"].includes(e)){n=`${e}(${i.map((e=>e.value)).join(",")}): ${i.map((e=>e.name)).join(";")}`}"chore"===e&&(n=`${e}: ${i[0]}`),s.writeFileSync("./.z/.commit-msg-tpl",n,"utf-8"),console.log(t.yellow(n)),W("commit msg模板写入成功,可以进行提交了")}function Ae(e,t){return k(this,void 0,void 0,(function*(){const i=function(e){const{pathname:t}=new URL(e),i=t.match(/\/(.+)\.git/);if(i)return i[1];_("GitLab URL有误")}(e),{originBranch:n,environment:o,platform:s,notify:r,processId:a}=t;var c;yield(c={params:{command:"build",params:JSON.stringify({originBranch:n,environment:o,platform:s,profile:{weWorkName:ee("weWorkName"),weWorkUserId:ee("weWorkUserId")}}),options:JSON.stringify({processId:a,notify:r||[]})},jobName:i},he({url:`${ie("jenkinsDomain")}/job/${c.jobName}/buildWithParameters`,method:"post",dataKey:"params",data:c.params,auth:{username:ie("jenkinsUsername"),password:ie("jenkinsPassword")}}))}))}function Ue(e){return k(this,void 0,void 0,(function*(){try{const{originBranch:t,environment:i,platform:n,notify:o,processId:s}=e,r=ce();!function(e,t){var i,n,o,s,r;const a=ae(),c=ce();(null===(i=null==c?void 0:c.repository)||void 0===i?void 0:i.url)||_("项目package.json文件中缺少repository.url");const l=`build:${e}:${t}`;(null==a?void 0:a.scripts[l])||_(`项目package.json文件scripts不存在命令${l}。`),["alipay","weapp"].includes(t)&&(a.mini||_("请在package.json配置mini字段"),"alipay"===t&&((null===(o=null===(n=a.mini)||void 0===n?void 0:n.alipay)||void 0===o?void 0:o.appid)||_("请在package.json配置mini.alipay.appid字段")),"weapp"===t&&((null===(r=null===(s=a.mini)||void 0===s?void 0:s.weapp)||void 0===r?void 0:r.appid)||_("请在package.json配置mini.weapp.appid字段")))}(i,n),yield H("git fetch origin"),yield function(e,t){return k(this,void 0,void 0,(function*(){const i=ce(),n=yield H(`git remote show ${i.repository.url}`),[,o]=n.match(/HEAD branch: (.+)\n/),s=yield H(`git log -b origin/${o} -1 --format=%H`),r=`origin/${e}`;if((yield H(`git branch --contains ${s} -r ${r}`))||_(`请先将最新的主分支(${o})代码合并到${r},再进行部署。`),"prod"!==t)return;const a=yield H(`git log -b origin/${e} -1 --format=%H`),c=(yield H(`git log ${s}...${a} -b ${r}`)).split("\n");c.length||_(`分支${e}上没有要发布的commit`);const l=yield H("git log -b origin/test -1 --format=%H");(function(e,t){for(let i of t)if(!e.includes(i))return!1;return!0})((yield H(`git log ${s}...${l} -b origin/test`)).split("\n"),c)||_("请先在测试环境发布要部署的代码")}))}(t,i),yield Ae(r.repository.url,{originBranch:t,environment:i,platform:n,notify:o,processId:s})}catch(e){_(e)}}))}function Be(){if(s.existsSync(r.resolve(".z/project.json"))){const e=n.readJsonSync(r.resolve(".z/project.json"));if(e["lint-staged"])return e["lint-staged"];throw new Error("未找到lint配置")}throw new Error("请先初始化项目(z i .)。")}e.command("init").alias("i").argument("[type]","非必填。可选值:.。不传为初始化工具配置;传 . 为初始化当前项目。").description("初始化工具配置、项目配置").action((function(e){return k(this,void 0,void 0,(function*(){e?"."===e?yield Ce():_("参数错误。执行 z init -h 查看帮助。"):yield Ne()}))})),e.command("create").alias("c").argument("[type]","可选值为project|p, branch|b, commit-msg|cm。传 p 为创建项目;传 b 为创建分支;传 cm 为创建提交信息").description("创建项目/分支/提交信息").action((function(e){return k(this,void 0,void 0,(function*(){let t;if(e)["project","p"].includes(e)?t="project":["branch","b"].includes(e)?t="branch":["commit-msg","cm"].includes(e)?t="commit-msg":_("参数输入错误");else{t=(yield d.prompt({type:"list",name:"type",message:"请选择你要创建的类型",choices:[{name:"项目",value:"project",disabled:!1},{name:"分支",value:"branch",disabled:!1},{name:"提交信息",value:"commit-msg",disabled:!1}]})).type}"project"===t?yield Ie():"branch"===t?yield function(){return k(this,void 0,void 0,(function*(){""!==(yield H("git status -s"))&&_("请先提交代码变动,再进行分支创建");const{type:e,purpose:t}=yield d.prompt([{name:"type",type:"list",message:"请选择创建分支的类型",choices:[{name:"开发新功能(feat)",value:"feat"},{name:"修复BUG(fix)",value:"fix"},{name:"重构/优化代码(refactor)",value:"refactor"}]},{name:"purpose",type:"input",message:"请输入创建分支的目的(大小写字母、数字,小驼峰式命名。如userInfo)",validate:J}]),i=`${e}_${t}_${G()}`,n=ce(),o=yield H(`git remote show ${n.repository.url}`),[,s]=o.match(/HEAD branch: (.+)\n/);yield H("git fetch origin"),yield H(`git checkout -b ${i} origin/${s}`)}))}():"commit-msg"===t&&(yield Je())}))})),e.command("merge").alias("m").description("合并当前分支到指定远程分支,并部署。").action((function(){return k(this,void 0,void 0,(function*(){var e,i;const n=yield de(),o=yield xe(n.id),s=[...q],r=o.filter((e=>![...N,n.sourceBranch].includes(e.name))).map((e=>{let i=e.name;return s.includes(i)&&(i=t.bold.blue(i)),{name:i,value:e.name}})),{target:a}=yield d.prompt([{type:"list",name:"target",message:"请选择要合并到的目标分支:",choices:r}]),l=s.includes(a);let u="h5",m=[];if(l){const e=yield fe(),t=yield d.prompt([{type:"list",name:"platform",message:"请选择平台",choices:[{name:"网页 - h5",value:"h5"},{name:"支付宝小程序 - alipay",value:"alipay"},{name:"微信小程序 - weapp",value:"weapp"}]},{type:"search-checkbox",name:"notify",message:"请选择部署成功要通知的人员:",choices:e,validate:e=>!(e.length<1)||"至少选一个"}]);pe(m,e),u=t.platform,m=t.notify}""!==(yield H("git status -s"))&&_("请先提交代码变动,再进行分支合并。"),yield H("git fetch origin"),yield H(`git push -u origin ${n.sourceBranch}`);let p=0;try{const{iid:e}=yield(f={title:"MR by z-develop",id:n.id,source_branch:n.sourceBranch,target_branch:a},ye({url:`${$e()}/projects/${f.id}/merge_requests`,method:"post",data:f}));p=e}catch(t){if(c.isAxiosError(t)){409===(null===(e=null==t?void 0:t.response)||void 0===e?void 0:e.status)&&_(`存在重复的合并请求,前往查看${n.mergeRequestUrl}`)}_(t)}var f;try{yield function(e){return ye({url:`${$e()}/projects/${e.id}/merge_requests/${e.iid}/merge`,method:"put"})}({id:n.id,iid:p})}catch(e){if(c.isAxiosError(e)){405===(null===(i=null==e?void 0:e.response)||void 0===i?void 0:i.status)&&(yield function(e){return ye({url:`${$e()}/projects/${e.id}/merge_requests/${e.iid}`,method:"put",data:e})}({id:n.id,iid:p,state_event:"close"}),_("合并过程中出现冲突,MR已关闭。建议在本地完成合并。"))}_(e)}l&&(yield Ue({originBranch:a,environment:a,platform:u,notify:m,processId:""}))}))})),e.command("deploy").alias("d").description("发布指定远程分支").action((function(){return k(this,void 0,void 0,(function*(){const e=yield fe(),{environment:t,platform:i,notify:n}=yield d.prompt([{type:"list",name:"environment",message:"请选择部署环境",choices:O},{type:"list",name:"platform",message:"请选择平台",choices:E},{type:"search-checkbox",name:"notify",message:"请选择部署成功要通知的人员:",choices:e,validate:e=>!(e.length<1)||"至少选一个"}]);pe(n,e);let o=t,s="";if("prod"===t){const e=yield de(),t=(yield xe(e.id)).filter((e=>![...N,...q].includes(e.name))).map((e=>({name:e.name,value:e.name}))),n=yield d.prompt([{type:"list",name:"branchName",message:"请输入要部署的分支:",choices:t},{type:"input",name:"processId",message:"请输入本次部署的企微审批编号"}]);o=n.branchName,s=n.processId,function(e){const t=e.split("_");if(4!==t.length)return!1;const[i,n,o]=t;return!!D.includes(i)&&!0===J(n)&&8===o.length&&!isNaN(Number(o))}(o)||_("分支命名不符合规范,参见https://zhongshitech.yuque.com/nx4ugr/guide/mg8t44#bQhu4");const{data:{state:a,content:c}}=yield(r={businessId:s},je({url:"/approval/findByBusinessId",data:r}));1===a&&_("当前审批流程状态为审批中,审批通过才能进行发布。"),3===a&&_("当前审批流程状态为已驳回,请重新提交审批。"),4===a&&_("当前审批流程状态为已发布,如需再次发布,请重新提交审批。");const{formComponentValues:l}=JSON.parse(c),u={};l.forEach((e=>{u[e.name]=e.value})),u["申请人"]!==ee("weWorkName")&&_("发布审批申请人与当前发布人不一致!"),u["项目名称"]!==ae().name&&_("发布审批项目名称与当前发布项目名称不一致!"),u["发布分支"]!==o&&_("发布审批发布分支与当前发布分支不一致!"),u["发布平台"]!==i&&_("发布审批发布平台与当前发布平台不一致!")}var r;yield Ue({originBranch:o,environment:t,platform:i,notify:n,processId:s})}))})),e.command("start").alias("s").description("启动本地开发环境").action((function(){return k(this,void 0,void 0,(function*(){const e=ae(),{environment:t,platform:n}=yield d.prompt([{type:"list",name:"environment",message:"请选择环境",choices:O},{type:"list",name:"platform",message:"请选择平台",choices:E}]),o=`dev:${t}:${n}`;if(e.scripts[o])try{i.exec(`npm run ${o}`,{silent:!1})}catch(e){_(e)}else _(`项目中(package.josn > scripts)不存在命令${o},请先添加!`)}))})),e.command("lint").alias("l").argument("[type]","非必填。可选值为commit-msg|cm, commit-files|cf, prettier|p, eslint|e。传 cm 为执行 lint commit-msg;传 cf 为执行 lint commit-files;传 p 为全部文件执行prettier;传 e 为全部文件执行eslint。cm 和 cf属于内部命令,会在git hooks中自动执行。").description("执行 lint 脚本。包含 lint staged 、 lint commit message、 prettier、 eslint。").action((function(e){return k(this,void 0,void 0,(function*(){let t;if(e)["commit-msg","cm"].includes(e)?t="commit-msg":["commit-files","cf"].includes(e)?t="commit-files":["prettier","p"].includes(e)?t="prettier":["eslint","e"].includes(e)?t="eslint":_("参数输入错误");else{t=(yield d.prompt({type:"list",name:"type",message:"请选择你要执行的操作",choices:[{name:"全局执行 prettier",value:"prettier"},{name:"全局执行 eslint",value:"eslint"}]})).type}if("commit-msg"===t)yield function(){return k(this,void 0,void 0,(function*(){const e=function(e){const t=s.readFileSync("./.git/COMMIT_EDITMSG","utf-8").split(": ");if(1===t.length)return"body前缺少「:」";{const e=t[0];return/^(feat|fix|refactor)/.test(e)?!!/\(\d+(?:,\d+)*\)/.test(e)||"ID缺少或者格式不正确。":/^chore/.test(e)?"chore"===e||"chore类型无需填写ID。":"不存在的提交类型。"}}();!0===e?W("commit msg validate success."):_(`commit msg格式错误。${e}`)}))}();else if("commit-files"===t)yield function(){return k(this,void 0,void 0,(function*(){(yield $({concurrent:!1,debug:void 0,config:Be()}))?W("代码风格检测通过!"):_("代码风格检测未通过!")}))}();else if("prettier"===t){s.existsSync("node_modules/.bin/prettier")||_("该项目未安装prettier,请安装后重试");try{yield H("prettier --write . --config .z/.prettierrc.json --ignore-path .z/.prettierignore --ignore-unknown --no-error-on-unmatched-pattern",{silent:!1}),W("prettier执行成功")}catch(e){_("prettier校验出错")}}else if("eslint"===t){s.existsSync("node_modules/.bin/eslint")||_("该项目未安装eslint,请安装后重试");try{yield H("eslint '**/*.{vue,js,jsx,cjs,mjs,ts,tsx,cts,mts}' --fix",{silent:!1}),W("eslint执行成功")}catch(e){_("eslint校验出错")}}}))})),function(){k(this,void 0,void 0,(function*(){process.on("unhandledRejection",(e=>{})),process.on("uncaughtException",(e=>{})),i.config.fatal=!0,i.config.silent=!0,i.config.verbose=!1,yield Ee(),d.registerPrompt("search-checkbox",Oe)}))}(),e.name("z-develop").alias("z").description(`z-develop, 开发流程管理工具。\n了解更多: ${t.blue("https://zxf-fe.yuque.com/org-wiki-zxf-fe-ex4bve/qi89vg/us8lgdb5cq4kf3aa")}`).usage("<command> [options]").version(w.version,"-v, --version","当前版本号").helpOption("-h, --help","帮助").showHelpAfterError("可以使用z -h查看帮助。"),e.parse();
|
|
1
|
+
import { program } from 'commander';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import shelljs from 'shelljs';
|
|
4
|
+
import fse from 'fs-extra';
|
|
5
|
+
import minimist from 'minimist';
|
|
6
|
+
import fs from 'node:fs';
|
|
7
|
+
import path from 'node:path';
|
|
8
|
+
import os from 'node:os';
|
|
9
|
+
import axios, { AxiosError } from 'axios';
|
|
10
|
+
import dayjs from 'dayjs';
|
|
11
|
+
import inquirer from 'inquirer';
|
|
12
|
+
import Base from 'inquirer/lib/prompts/base.js';
|
|
13
|
+
import Paginator from 'inquirer/lib/utils/paginator.js';
|
|
14
|
+
import observe from 'inquirer/lib/utils/events.js';
|
|
15
|
+
import 'inquirer/lib/objects/choices.js';
|
|
16
|
+
import { map, takeUntil, filter } from 'rxjs';
|
|
17
|
+
import figures from 'figures';
|
|
18
|
+
import ora from 'ora';
|
|
19
|
+
import child_process from 'child_process';
|
|
20
|
+
import lintStaged from 'lint-staged';
|
|
21
|
+
|
|
22
|
+
var name = "cli-z-develop";
|
|
23
|
+
var version = "0.0.6";
|
|
24
|
+
var description = "前端本地开发命令行工具";
|
|
25
|
+
var main = "dist/index.js";
|
|
26
|
+
var bin = {
|
|
27
|
+
z: "./bin/z.js",
|
|
28
|
+
"z-develop": "./bin/z.js"
|
|
29
|
+
};
|
|
30
|
+
var scripts = {
|
|
31
|
+
prepare: "husky",
|
|
32
|
+
dev: "rollup -c -w --environment DEBUG:true",
|
|
33
|
+
build: "rollup -c",
|
|
34
|
+
eslint: "eslint '**/*.{ts,js}' --fix",
|
|
35
|
+
prettier: "prettier -wu .",
|
|
36
|
+
upload: "npm version patch && npm run build && npm publish --access public --registry https://registry.npmjs.org/"
|
|
37
|
+
};
|
|
38
|
+
var type = "module";
|
|
39
|
+
var repository = {
|
|
40
|
+
type: "git",
|
|
41
|
+
url: "http://git.cxlqd.com/fe-base/cli-z-develop.git"
|
|
42
|
+
};
|
|
43
|
+
var author = "z";
|
|
44
|
+
var devDependencies = {
|
|
45
|
+
"@commitlint/cli": "^19.3.0",
|
|
46
|
+
"@commitlint/config-conventional": "^19.2.2",
|
|
47
|
+
"@rollup/plugin-json": "^6.1.0",
|
|
48
|
+
"@rollup/plugin-node-resolve": "^15.2.3",
|
|
49
|
+
"@rollup/plugin-replace": "^5.0.5",
|
|
50
|
+
"@rollup/plugin-terser": "^0.4.4",
|
|
51
|
+
"@types/fs-extra": "^11.0.4",
|
|
52
|
+
"@types/inquirer": "^9.0.7",
|
|
53
|
+
"@types/minimist": "^1.2.5",
|
|
54
|
+
"@types/node": "^20.12.12",
|
|
55
|
+
"@types/shelljs": "^0.8.15",
|
|
56
|
+
"@types/tough-cookie": "^4.0.5",
|
|
57
|
+
"@typescript-eslint/eslint-plugin": "^7.9.0",
|
|
58
|
+
"@typescript-eslint/parser": "^7.9.0",
|
|
59
|
+
eslint: "^8.57.0",
|
|
60
|
+
"eslint-config-alloy": "^5.1.2",
|
|
61
|
+
"eslint-config-prettier": "^9.1.0",
|
|
62
|
+
husky: "^9.0.11",
|
|
63
|
+
prettier: "^3.2.5",
|
|
64
|
+
rollup: "^4.17.2",
|
|
65
|
+
"rollup-plugin-typescript2": "^0.36.0",
|
|
66
|
+
tslib: "^2.6.2",
|
|
67
|
+
typescript: "^5.4.5"
|
|
68
|
+
};
|
|
69
|
+
var dependencies = {
|
|
70
|
+
axios: "^1.7.1",
|
|
71
|
+
chalk: "^5.3.0",
|
|
72
|
+
commander: "^12.1.0",
|
|
73
|
+
dayjs: "^1.11.11",
|
|
74
|
+
figures: "^6.1.0",
|
|
75
|
+
"fs-extra": "^11.2.0",
|
|
76
|
+
inquirer: "^9.2.22",
|
|
77
|
+
minimist: "^1.2.8",
|
|
78
|
+
ora: "^8.0.1",
|
|
79
|
+
rxjs: "^7.8.1",
|
|
80
|
+
shelljs: "^0.8.5",
|
|
81
|
+
"lint-staged": "^15.2.2"
|
|
82
|
+
};
|
|
83
|
+
var pkg = {
|
|
84
|
+
name: name,
|
|
85
|
+
version: version,
|
|
86
|
+
description: description,
|
|
87
|
+
main: main,
|
|
88
|
+
bin: bin,
|
|
89
|
+
scripts: scripts,
|
|
90
|
+
type: type,
|
|
91
|
+
repository: repository,
|
|
92
|
+
author: author,
|
|
93
|
+
devDependencies: devDependencies,
|
|
94
|
+
dependencies: dependencies
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
/******************************************************************************
|
|
98
|
+
Copyright (c) Microsoft Corporation.
|
|
99
|
+
|
|
100
|
+
Permission to use, copy, modify, and/or distribute this software for any
|
|
101
|
+
purpose with or without fee is hereby granted.
|
|
102
|
+
|
|
103
|
+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
|
104
|
+
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
|
105
|
+
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
|
106
|
+
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
|
107
|
+
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
|
108
|
+
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
|
109
|
+
PERFORMANCE OF THIS SOFTWARE.
|
|
110
|
+
***************************************************************************** */
|
|
111
|
+
/* global Reflect, Promise, SuppressedError, Symbol */
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
function __awaiter(thisArg, _arguments, P, generator) {
|
|
115
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
116
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
117
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
118
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
119
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
120
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
|
|
125
|
+
var e = new Error(message);
|
|
126
|
+
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
const PRETTIER_RC = `{
|
|
130
|
+
"printWidth": 120,
|
|
131
|
+
"tabWidth": 2,
|
|
132
|
+
"useTabs": false,
|
|
133
|
+
"semi": true,
|
|
134
|
+
"singleQuote": false,
|
|
135
|
+
"quoteProps": "as-needed",
|
|
136
|
+
"jsxSingleQuote": false,
|
|
137
|
+
"trailingComma": "all",
|
|
138
|
+
"bracketSpacing": true,
|
|
139
|
+
"bracketSameLine": false,
|
|
140
|
+
"arrowParens": "always",
|
|
141
|
+
"requirePragma": false,
|
|
142
|
+
"insertPragma": false,
|
|
143
|
+
"proseWrap": "preserve",
|
|
144
|
+
"htmlWhitespaceSensitivity": "css",
|
|
145
|
+
"vueIndentScriptAndStyle": false,
|
|
146
|
+
"endOfLine": "lf",
|
|
147
|
+
"embeddedLanguageFormatting": "auto",
|
|
148
|
+
"singleAttributePerLine": false
|
|
149
|
+
}
|
|
150
|
+
`;
|
|
151
|
+
const PRETTIER_IGNORE = `# 系统 信息文件
|
|
152
|
+
.DS_Store/
|
|
153
|
+
Thumbs.db
|
|
154
|
+
|
|
155
|
+
# 编辑器配置文件
|
|
156
|
+
.idea/
|
|
157
|
+
.vscode/
|
|
158
|
+
|
|
159
|
+
# 输出目录
|
|
160
|
+
dist/
|
|
161
|
+
|
|
162
|
+
# Log files
|
|
163
|
+
*.log
|
|
164
|
+
|
|
165
|
+
# 本地配置文件
|
|
166
|
+
*.local
|
|
167
|
+
|
|
168
|
+
# 缓存文件
|
|
169
|
+
*.cache
|
|
170
|
+
|
|
171
|
+
# 垃圾文件
|
|
172
|
+
.Trashes
|
|
173
|
+
`;
|
|
174
|
+
const LINT_STAGED_CONFIG = {
|
|
175
|
+
Java: {
|
|
176
|
+
// TODO: 需要优化
|
|
177
|
+
"**/*.{java}": "./run.sh pmd -d ../../../src/main/java/ -f text -R rulesets/java/quickstart.xml",
|
|
178
|
+
},
|
|
179
|
+
JavaScript: {
|
|
180
|
+
"**/*.{vue,js,jsx,cjs,mjs,ts,tsx,cts,mts}": "eslint --fix",
|
|
181
|
+
"*": "prettier -wu",
|
|
182
|
+
},
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
const ENVIRONMENT_CHOICES = [
|
|
186
|
+
{
|
|
187
|
+
name: `开发环境 - ${"dev" /* EEnv.DEV */}`,
|
|
188
|
+
value: "dev" /* EEnv.DEV */,
|
|
189
|
+
},
|
|
190
|
+
{
|
|
191
|
+
name: `测试环境 - ${"test" /* EEnv.TEST */}`,
|
|
192
|
+
value: "test" /* EEnv.TEST */,
|
|
193
|
+
},
|
|
194
|
+
{
|
|
195
|
+
name: `预发环境 - ${"release" /* EEnv.RELEASE */}`,
|
|
196
|
+
value: "release" /* EEnv.RELEASE */,
|
|
197
|
+
},
|
|
198
|
+
{
|
|
199
|
+
name: `正式环境 - ${"production" /* EEnv.PROD */}`,
|
|
200
|
+
value: "production" /* EEnv.PROD */,
|
|
201
|
+
},
|
|
202
|
+
];
|
|
203
|
+
const PLATFORM_CHOICES = [
|
|
204
|
+
{
|
|
205
|
+
name: `网页 - ${"h5" /* EPlatform.H5 */}`,
|
|
206
|
+
value: "h5" /* EPlatform.H5 */,
|
|
207
|
+
},
|
|
208
|
+
{
|
|
209
|
+
name: `支付宝小程序 - ${"alipay" /* EPlatform.ALIPAY */}`,
|
|
210
|
+
value: "alipay" /* EPlatform.ALIPAY */,
|
|
211
|
+
},
|
|
212
|
+
{
|
|
213
|
+
name: `微信小程序 - ${"weapp" /* EPlatform.WEAPP */}`,
|
|
214
|
+
value: "weapp" /* EPlatform.WEAPP */,
|
|
215
|
+
},
|
|
216
|
+
{
|
|
217
|
+
name: `node module - ${"npm" /* EPlatform.NPM */}`,
|
|
218
|
+
value: "npm" /* EPlatform.NPM */,
|
|
219
|
+
},
|
|
220
|
+
];
|
|
221
|
+
// 线上分支
|
|
222
|
+
const PROD_BRANCH = ["main", "master"];
|
|
223
|
+
// 非正式环境的主要分支
|
|
224
|
+
const INFORM_MAIN_BRANCHES = ["dev" /* EEnv.DEV */, "test" /* EEnv.TEST */, "release" /* EEnv.RELEASE */];
|
|
225
|
+
const BRANCH_TYPES = ["fix" /* EBranchType.FIX */, "feat" /* EBranchType.FEAT */, "refactor" /* EBranchType.REFACTOR */];
|
|
226
|
+
const USER_TITLE = {
|
|
227
|
+
1: "研发",
|
|
228
|
+
2: "测试",
|
|
229
|
+
3: "产品",
|
|
230
|
+
4: "设计",
|
|
231
|
+
5: "运营",
|
|
232
|
+
6: "销售",
|
|
233
|
+
7: "行政",
|
|
234
|
+
8: "财务",
|
|
235
|
+
9: "其他",
|
|
236
|
+
};
|
|
237
|
+
const GIT_DOMAIN = "http://git.cxlqd.com";
|
|
238
|
+
|
|
239
|
+
function validateProjectName(input) {
|
|
240
|
+
if (!input) {
|
|
241
|
+
return "不能为空";
|
|
242
|
+
}
|
|
243
|
+
if (!/^[a-z0-9-]+$/.test(input)) {
|
|
244
|
+
return "格式为小写字母、中横线(可选)、数字(不推荐)。如apple, apple-tree";
|
|
245
|
+
}
|
|
246
|
+
const curDirList = listDirs();
|
|
247
|
+
if (curDirList.includes(input)) {
|
|
248
|
+
return "当前目录下已存在同名文件夹,请先处理。";
|
|
249
|
+
}
|
|
250
|
+
return true;
|
|
251
|
+
}
|
|
252
|
+
function validateDesc(input) {
|
|
253
|
+
if (!input) {
|
|
254
|
+
return "不能为空";
|
|
255
|
+
}
|
|
256
|
+
return true;
|
|
257
|
+
}
|
|
258
|
+
function validateBranchPurpose(input) {
|
|
259
|
+
if (!input) {
|
|
260
|
+
return "不能为空";
|
|
261
|
+
}
|
|
262
|
+
if (!/^[a-zA-Z0-9]+$/.test(input)) {
|
|
263
|
+
return "格式为大小写字母、数字,小驼峰命名。如userInfo、systemRouter3";
|
|
264
|
+
}
|
|
265
|
+
return true;
|
|
266
|
+
}
|
|
267
|
+
function validateBranchName(name) {
|
|
268
|
+
const nameArr = name.split("_");
|
|
269
|
+
if (nameArr.length !== 4) {
|
|
270
|
+
return false;
|
|
271
|
+
}
|
|
272
|
+
const [type, purpose, date] = nameArr;
|
|
273
|
+
if (!BRANCH_TYPES.includes(type)) {
|
|
274
|
+
return false;
|
|
275
|
+
}
|
|
276
|
+
if (validateBranchPurpose(purpose) !== true) {
|
|
277
|
+
return false;
|
|
278
|
+
}
|
|
279
|
+
if (date.length !== 8 || isNaN(Number(date))) {
|
|
280
|
+
return false;
|
|
281
|
+
}
|
|
282
|
+
return true;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* 生成README.md内容
|
|
287
|
+
* @param projectName
|
|
288
|
+
* @param projectDesc
|
|
289
|
+
*/
|
|
290
|
+
function generateReadMeTemplate(projectName = "项目中文名", projectDesc = "项目描述") {
|
|
291
|
+
return `
|
|
292
|
+
# ${projectName}
|
|
293
|
+
${projectDesc}
|
|
294
|
+
|
|
295
|
+
## 快速开始
|
|
296
|
+
### 安装依赖
|
|
297
|
+
\`\`\`bash
|
|
298
|
+
npm i
|
|
299
|
+
\`\`\`
|
|
300
|
+
|
|
301
|
+
### 本地开发
|
|
302
|
+
\`\`\`bash
|
|
303
|
+
npm run dev:{environment}:{platform}
|
|
304
|
+
\`\`\`
|
|
305
|
+
|
|
306
|
+
> 查看更多关于 [platform](https://zxf-fe.yuque.com/org-wiki-zxf-fe-ex4bve/qi89vg/nttvbeif4a62gtpz#faWqt) 和 [environment](https://zxf-fe.yuque.com/org-wiki-zxf-fe-ex4bve/qi89vg/nttvbeif4a62gtpz#faWqt)
|
|
307
|
+
|
|
308
|
+
### 打包代码
|
|
309
|
+
\`\`\`bash
|
|
310
|
+
npm run build:{environment}:{platform}
|
|
311
|
+
\`\`\`
|
|
312
|
+
|
|
313
|
+
### 格式化、修复代码语法
|
|
314
|
+
\`\`\`bash
|
|
315
|
+
z lint prettier
|
|
316
|
+
\`\`\`
|
|
317
|
+
|
|
318
|
+
> [ESLint相关规范](https://zxf-fe.yuque.com/org-wiki-zxf-fe-ex4bve/qi89vg/ul914hdkqz8fr02g#AkvQ4)
|
|
319
|
+
|
|
320
|
+
### 格式化文档
|
|
321
|
+
\`\`\`bash
|
|
322
|
+
z lint prettier
|
|
323
|
+
\`\`\`
|
|
324
|
+
|
|
325
|
+
> [Prettier相关规范](https://zxf-fe.yuque.com/org-wiki-zxf-fe-ex4bve/qi89vg/ul914hdkqz8fr02g#ojpwI)
|
|
326
|
+
|
|
327
|
+
## 常见问题
|
|
328
|
+
列出开发中常见的问题和解决方案
|
|
329
|
+
|
|
330
|
+
## 了解更多
|
|
331
|
+
在此处放入语雀文档链接。请在[前端团队-项目手册](https://zxf-fe.yuque.com/org-wiki-zxf-fe-ex4bve/qi89vg/bfrcicwzosoyaifg)中按照分类新建文档。
|
|
332
|
+
`;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
const { red, green, blue, magenta } = chalk;
|
|
336
|
+
function log(...args) {
|
|
337
|
+
}
|
|
338
|
+
function success(...args) {
|
|
339
|
+
console.log(green(...args));
|
|
340
|
+
}
|
|
341
|
+
/**
|
|
342
|
+
* 提示错误
|
|
343
|
+
* @param message
|
|
344
|
+
* @param stopBubble 是否阻止冒泡。默认false,不阻止
|
|
345
|
+
*/
|
|
346
|
+
function error(message, stopBubble = false) {
|
|
347
|
+
let msg = message;
|
|
348
|
+
if (message instanceof Error) {
|
|
349
|
+
msg = message.message;
|
|
350
|
+
if (axios.isAxiosError(message)) {
|
|
351
|
+
msg = `请求失败:${message.message}`;
|
|
352
|
+
}
|
|
353
|
+
console.log(red(msg));
|
|
354
|
+
console.log(magenta(message.stack));
|
|
355
|
+
}
|
|
356
|
+
else {
|
|
357
|
+
console.log(red(message));
|
|
358
|
+
}
|
|
359
|
+
if (!stopBubble) {
|
|
360
|
+
process.exit(1);
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
/**
|
|
364
|
+
* 是否是文件夹
|
|
365
|
+
* @param input
|
|
366
|
+
*/
|
|
367
|
+
function isDir(input) {
|
|
368
|
+
return fs.existsSync(input) ? fs.lstatSync(input).isDirectory() : false;
|
|
369
|
+
}
|
|
370
|
+
/**
|
|
371
|
+
* 列出路径下所有文件夹
|
|
372
|
+
* @param {string?} input
|
|
373
|
+
*/
|
|
374
|
+
function listDirs(input = process.cwd()) {
|
|
375
|
+
if (isDir(input)) {
|
|
376
|
+
const dirs = fs.readdirSync(input);
|
|
377
|
+
return dirs.filter((dir) => isDir(path.resolve(input, dir)));
|
|
378
|
+
}
|
|
379
|
+
return [];
|
|
380
|
+
}
|
|
381
|
+
/**
|
|
382
|
+
* 获取配置文件夹地址
|
|
383
|
+
*/
|
|
384
|
+
function getConfigDir() {
|
|
385
|
+
return path.resolve(os.homedir(), ".z");
|
|
386
|
+
}
|
|
387
|
+
function getConfigFile() {
|
|
388
|
+
return path.resolve(getConfigDir(), "develop-config.json");
|
|
389
|
+
}
|
|
390
|
+
function isConfigDirExist() {
|
|
391
|
+
return fs.existsSync(getConfigDir());
|
|
392
|
+
}
|
|
393
|
+
function isConfigFileExist() {
|
|
394
|
+
return isConfigDirExist() && fs.existsSync(getConfigFile());
|
|
395
|
+
}
|
|
396
|
+
/**
|
|
397
|
+
* 异步执行命令
|
|
398
|
+
* @param command
|
|
399
|
+
* @param options
|
|
400
|
+
*/
|
|
401
|
+
function asyncExec(command_1) {
|
|
402
|
+
return __awaiter(this, arguments, void 0, function* (command, options = {
|
|
403
|
+
removeTailLinkBreak: true,
|
|
404
|
+
silent: true,
|
|
405
|
+
}) {
|
|
406
|
+
let res = yield new Promise((resolve, reject) => {
|
|
407
|
+
try {
|
|
408
|
+
const resp = shelljs.exec(command, {
|
|
409
|
+
silent: options.silent,
|
|
410
|
+
});
|
|
411
|
+
resolve(resp);
|
|
412
|
+
}
|
|
413
|
+
catch (e) {
|
|
414
|
+
reject(e);
|
|
415
|
+
}
|
|
416
|
+
});
|
|
417
|
+
res = res.toString();
|
|
418
|
+
if (options.removeTailLinkBreak) {
|
|
419
|
+
res = res.replace(/\n$/, "");
|
|
420
|
+
}
|
|
421
|
+
return res;
|
|
422
|
+
});
|
|
423
|
+
}
|
|
424
|
+
/**
|
|
425
|
+
* 获取分支命名格式的日期
|
|
426
|
+
*/
|
|
427
|
+
function getBranchNameDate() {
|
|
428
|
+
return dayjs(Date.now()).format("YYMMDD");
|
|
429
|
+
}
|
|
430
|
+
/**
|
|
431
|
+
* 从gitlab地址链接中获取Jenkins job名称
|
|
432
|
+
* @param url
|
|
433
|
+
*/
|
|
434
|
+
function getJenkinsJobNameFromGitLabURL(url) {
|
|
435
|
+
const { pathname } = new URL(url);
|
|
436
|
+
const result = pathname.match(/\/(.+)\.git/);
|
|
437
|
+
if (result) {
|
|
438
|
+
return result[1];
|
|
439
|
+
}
|
|
440
|
+
error("GitLab URL有误");
|
|
441
|
+
}
|
|
442
|
+
/**
|
|
443
|
+
* 第一个传入的数组是否包含第二个数组的所有数组项
|
|
444
|
+
* @param arr1
|
|
445
|
+
* @param arr2
|
|
446
|
+
*/
|
|
447
|
+
function isInclude(arr1, arr2) {
|
|
448
|
+
for (let item of arr2) {
|
|
449
|
+
if (!arr1.includes(item)) {
|
|
450
|
+
return false;
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
return true;
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
const STATE = {
|
|
457
|
+
// 个人数据
|
|
458
|
+
profile: {
|
|
459
|
+
ldapAccount: "",
|
|
460
|
+
ldapPassword: "",
|
|
461
|
+
gitToken: "",
|
|
462
|
+
gitUserId: 0,
|
|
463
|
+
gitName: "",
|
|
464
|
+
gitEnglishName: "",
|
|
465
|
+
gitEmail: "",
|
|
466
|
+
weWorkName: "",
|
|
467
|
+
weWorkUserId: "",
|
|
468
|
+
jobType: "fe",
|
|
469
|
+
zenTaoToken: "",
|
|
470
|
+
},
|
|
471
|
+
// 包数据
|
|
472
|
+
main: {
|
|
473
|
+
version: pkg.version,
|
|
474
|
+
latestCheckVersionTimestamp: 0,
|
|
475
|
+
// 3天检查一次
|
|
476
|
+
versionCheckDuring: 3,
|
|
477
|
+
// 企微用户数据使用缓存
|
|
478
|
+
weWorkListCache: [],
|
|
479
|
+
},
|
|
480
|
+
// 一些静态数据
|
|
481
|
+
constants: {
|
|
482
|
+
ZenTaoDomain: "",
|
|
483
|
+
FEServerDomain: "",
|
|
484
|
+
jenkinsDomain: "",
|
|
485
|
+
jenkinsUsername: "",
|
|
486
|
+
jenkinsPassword: "",
|
|
487
|
+
OSSEndpoint: "",
|
|
488
|
+
OSSAccessKeyID: "",
|
|
489
|
+
OSSAccessKeySecret: "",
|
|
490
|
+
OSSBucketName: "",
|
|
491
|
+
},
|
|
492
|
+
};
|
|
493
|
+
|
|
494
|
+
function setProfileConfig(objOrKey, value) {
|
|
495
|
+
if (value !== undefined) {
|
|
496
|
+
STATE.profile[objOrKey] = value;
|
|
497
|
+
}
|
|
498
|
+
else {
|
|
499
|
+
STATE.profile = Object.assign(Object.assign({}, STATE.profile), objOrKey);
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
function setConstantsConfig(objOrKey, value) {
|
|
503
|
+
{
|
|
504
|
+
STATE.constants = Object.assign(Object.assign({}, STATE.constants), objOrKey);
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
function setMainConfig(objOrKey, value) {
|
|
508
|
+
if (value !== undefined) {
|
|
509
|
+
STATE.main[objOrKey] = value;
|
|
510
|
+
}
|
|
511
|
+
else {
|
|
512
|
+
STATE.main = Object.assign(Object.assign({}, STATE.main), objOrKey);
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
function getProfileConfig(key) {
|
|
517
|
+
if (key) {
|
|
518
|
+
return STATE.profile[key];
|
|
519
|
+
}
|
|
520
|
+
return STATE.profile;
|
|
521
|
+
}
|
|
522
|
+
function getMainConfig(key) {
|
|
523
|
+
if (key) {
|
|
524
|
+
return STATE.main[key];
|
|
525
|
+
}
|
|
526
|
+
return STATE.main;
|
|
527
|
+
}
|
|
528
|
+
function getConstantsConfig(key) {
|
|
529
|
+
if (key) {
|
|
530
|
+
return STATE.constants[key];
|
|
531
|
+
}
|
|
532
|
+
return STATE.constants;
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
let packageJSON = null;
|
|
536
|
+
let projectJSON = null;
|
|
537
|
+
const packageJSONPath = "package.json";
|
|
538
|
+
const projectJSONPath = ".z/project.json";
|
|
539
|
+
function getProjectPackageJSON() {
|
|
540
|
+
if (packageJSON) {
|
|
541
|
+
return packageJSON;
|
|
542
|
+
}
|
|
543
|
+
try {
|
|
544
|
+
packageJSON = fse.readJsonSync(packageJSONPath);
|
|
545
|
+
}
|
|
546
|
+
catch (e) {
|
|
547
|
+
error(`当前目录(${shelljs.pwd()})不存在${packageJSONPath}文件,请在项目根目录执行该命令。`);
|
|
548
|
+
}
|
|
549
|
+
return packageJSON;
|
|
550
|
+
}
|
|
551
|
+
function getProjectJSON() {
|
|
552
|
+
if (projectJSON) {
|
|
553
|
+
return projectJSON;
|
|
554
|
+
}
|
|
555
|
+
try {
|
|
556
|
+
projectJSON = fse.readJsonSync(projectJSONPath);
|
|
557
|
+
}
|
|
558
|
+
catch (e) {
|
|
559
|
+
error(`当前目录(${shelljs.pwd()})不存在${projectJSONPath}文件,请在项目根目录执行该命令,或者初始化项目(z init .)。`);
|
|
560
|
+
}
|
|
561
|
+
return projectJSON;
|
|
562
|
+
}
|
|
563
|
+
function setProjectJSON(json) {
|
|
564
|
+
if (projectJSON) {
|
|
565
|
+
projectJSON = Object.assign(Object.assign({}, projectJSON), json);
|
|
566
|
+
}
|
|
567
|
+
else {
|
|
568
|
+
projectJSON = json;
|
|
569
|
+
}
|
|
570
|
+
fse.writeJSONSync(projectJSONPath, projectJSON, { spaces: 2 });
|
|
571
|
+
}
|
|
572
|
+
function setProjectPackageJSON(json) {
|
|
573
|
+
if (packageJSON) {
|
|
574
|
+
packageJSON = Object.assign(Object.assign({}, packageJSON), json);
|
|
575
|
+
}
|
|
576
|
+
else {
|
|
577
|
+
packageJSON = json;
|
|
578
|
+
}
|
|
579
|
+
fse.writeJSONSync(packageJSONPath, packageJSON, { spaces: 2 });
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
/**
|
|
583
|
+
* 检测要部署的代码是否有效
|
|
584
|
+
* @param originBranch
|
|
585
|
+
* @param environment
|
|
586
|
+
*/
|
|
587
|
+
function checkVersionCodeValid(originBranch, environment) {
|
|
588
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
589
|
+
const project = getProjectJSON();
|
|
590
|
+
// 获取本项目的主分支
|
|
591
|
+
const projectInfo = yield asyncExec(`git remote show ${project.repository.url}`);
|
|
592
|
+
const [, mainBranch] = projectInfo.match(/HEAD branch: (.+)\n/);
|
|
593
|
+
// 取得远程主分支上最新一次commit的hash
|
|
594
|
+
const mainBranchNewestCommit = yield asyncExec(`git log -b origin/${mainBranch} -1 --format=%H`);
|
|
595
|
+
const currentBranchRemote = `origin/${originBranch}`;
|
|
596
|
+
// 要发布分支上是否有主分支上的最新一次commit
|
|
597
|
+
const hasMainBranchNewestCommit = yield asyncExec(`git branch --contains ${mainBranchNewestCommit} -r ${currentBranchRemote}`);
|
|
598
|
+
if (!hasMainBranchNewestCommit) {
|
|
599
|
+
error(`请先将最新的主分支(${mainBranch})代码合并到${currentBranchRemote},再进行部署。`);
|
|
600
|
+
}
|
|
601
|
+
if (environment !== "production" /* EEnv.PROD */) {
|
|
602
|
+
return;
|
|
603
|
+
}
|
|
604
|
+
// 取要发布分支的最新commit
|
|
605
|
+
const originBranchNewestCommit = yield asyncExec(`git log -b origin/${originBranch} -1 --format=%H`);
|
|
606
|
+
// 获取本次所有要发布的commit
|
|
607
|
+
const releaseCommitListStr = yield asyncExec(`git log ${mainBranchNewestCommit}...${originBranchNewestCommit} -b ${currentBranchRemote}`);
|
|
608
|
+
const releaseCommitList = releaseCommitListStr.split("\n");
|
|
609
|
+
if (!releaseCommitList.length) {
|
|
610
|
+
error(`分支${originBranch}上没有要发布的commit`);
|
|
611
|
+
}
|
|
612
|
+
// TODO: 正式环境发布,检测要发布的所有commit是否在测试或者预发环境的分支上。目前只检测测试环境,也没有加上标签的校验(用于识别合并成功但是未发布成功的情况)
|
|
613
|
+
const testBranchNewestCommit = yield asyncExec(`git log -b origin/${"test" /* EEnv.TEST */} -1 --format=%H`);
|
|
614
|
+
const testReleaseCommitListStr = yield asyncExec(`git log ${mainBranchNewestCommit}...${testBranchNewestCommit} -b origin/${"test" /* EEnv.TEST */}`);
|
|
615
|
+
const testReleaseCommitList = testReleaseCommitListStr.split("\n");
|
|
616
|
+
if (!isInclude(testReleaseCommitList, releaseCommitList)) {
|
|
617
|
+
error("请先在测试环境发布要部署的代码");
|
|
618
|
+
}
|
|
619
|
+
});
|
|
620
|
+
}
|
|
621
|
+
/**
|
|
622
|
+
* 检测项目package
|
|
623
|
+
* @param environment
|
|
624
|
+
* @param platform
|
|
625
|
+
*/
|
|
626
|
+
function checkPackageJSON(environment, platform) {
|
|
627
|
+
var _a, _b, _c, _d, _e;
|
|
628
|
+
const pkg = getProjectPackageJSON();
|
|
629
|
+
const project = getProjectJSON();
|
|
630
|
+
if (!((_a = project === null || project === void 0 ? void 0 : project.repository) === null || _a === void 0 ? void 0 : _a.url)) {
|
|
631
|
+
error("项目package.json文件中缺少repository.url");
|
|
632
|
+
}
|
|
633
|
+
const script = `build:${environment}:${platform}`;
|
|
634
|
+
if (!(pkg === null || pkg === void 0 ? void 0 : pkg.scripts[script])) {
|
|
635
|
+
error(`项目package.json文件scripts不存在命令${script}。`);
|
|
636
|
+
}
|
|
637
|
+
if (["alipay" /* EPlatform.ALIPAY */, "weapp" /* EPlatform.WEAPP */].includes(platform)) {
|
|
638
|
+
if (!pkg.mini) {
|
|
639
|
+
error("请在package.json配置mini字段");
|
|
640
|
+
}
|
|
641
|
+
if ("alipay" /* EPlatform.ALIPAY */ === platform) {
|
|
642
|
+
if (!((_c = (_b = pkg.mini) === null || _b === void 0 ? void 0 : _b.alipay) === null || _c === void 0 ? void 0 : _c.appid)) {
|
|
643
|
+
error("请在package.json配置mini.alipay.appid字段");
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
if ("weapp" /* EPlatform.WEAPP */ === platform) {
|
|
647
|
+
if (!((_e = (_d = pkg.mini) === null || _d === void 0 ? void 0 : _d.weapp) === null || _e === void 0 ? void 0 : _e.appid)) {
|
|
648
|
+
error("请在package.json配置mini.weapp.appid字段");
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
const currentProject = {
|
|
654
|
+
id: 0,
|
|
655
|
+
name: "",
|
|
656
|
+
sourceBranch: "",
|
|
657
|
+
mergeRequestUrl: "",
|
|
658
|
+
};
|
|
659
|
+
/**
|
|
660
|
+
* 获取当前项目的信息
|
|
661
|
+
*/
|
|
662
|
+
function getCurrentProject() {
|
|
663
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
664
|
+
if (!currentProject.name) {
|
|
665
|
+
const pkg = getProjectPackageJSON();
|
|
666
|
+
currentProject.name = pkg.name;
|
|
667
|
+
}
|
|
668
|
+
if (!currentProject.id) {
|
|
669
|
+
try {
|
|
670
|
+
// 通过项目名称获取项目ID
|
|
671
|
+
const [detail] = yield getProjectDetailByNameApi(currentProject.name);
|
|
672
|
+
currentProject.mergeRequestUrl = detail.web_url + "/merge_requests";
|
|
673
|
+
currentProject.id = detail.id;
|
|
674
|
+
}
|
|
675
|
+
catch (e) {
|
|
676
|
+
error(e);
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
if (!currentProject.sourceBranch) {
|
|
680
|
+
currentProject.sourceBranch = yield asyncExec("git branch --show-current");
|
|
681
|
+
}
|
|
682
|
+
return currentProject;
|
|
683
|
+
});
|
|
684
|
+
}
|
|
685
|
+
/**
|
|
686
|
+
* 将配置写入配置文件
|
|
687
|
+
*/
|
|
688
|
+
function writeConfigFile() {
|
|
689
|
+
fse.writeJSONSync(getConfigFile(), {
|
|
690
|
+
main: getMainConfig(),
|
|
691
|
+
profile: getProfileConfig(),
|
|
692
|
+
constants: getConstantsConfig(),
|
|
693
|
+
}, {
|
|
694
|
+
spaces: 2,
|
|
695
|
+
});
|
|
696
|
+
}
|
|
697
|
+
/**
|
|
698
|
+
* 记录企微用户的使用次数
|
|
699
|
+
* @param userIdList
|
|
700
|
+
* @param allUserList
|
|
701
|
+
*/
|
|
702
|
+
function recordWeWorkUserUsage(userIdList, allUserList) {
|
|
703
|
+
const recordUserList = [];
|
|
704
|
+
allUserList.forEach((i) => {
|
|
705
|
+
if (userIdList.includes(i.value)) {
|
|
706
|
+
recordUserList.push(i);
|
|
707
|
+
}
|
|
708
|
+
});
|
|
709
|
+
const cacheList = getMainConfig("weWorkListCache");
|
|
710
|
+
const cacheIdList = cacheList.map((i) => i.value);
|
|
711
|
+
recordUserList.forEach((i) => {
|
|
712
|
+
const idx = cacheIdList.indexOf(i.value);
|
|
713
|
+
if (idx > -1) {
|
|
714
|
+
cacheList[idx].usageCount += 1;
|
|
715
|
+
}
|
|
716
|
+
else {
|
|
717
|
+
cacheList.push(Object.assign(Object.assign({}, i), { usageCount: 1 }));
|
|
718
|
+
}
|
|
719
|
+
});
|
|
720
|
+
setMainConfig({
|
|
721
|
+
weWorkListCache: cacheList.sort((a, b) => b.usageCount - a.usageCount),
|
|
722
|
+
});
|
|
723
|
+
writeConfigFile();
|
|
724
|
+
}
|
|
725
|
+
/**
|
|
726
|
+
* 获取企微列表并根据使用记录排序
|
|
727
|
+
*/
|
|
728
|
+
function getSortedWeWorkUserList() {
|
|
729
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
730
|
+
// 获取企微用户列表
|
|
731
|
+
const { data } = yield getWeWorkUserListApi();
|
|
732
|
+
const weWorkUserList = data
|
|
733
|
+
.filter((i) => [1, 2, 3].includes(i.title))
|
|
734
|
+
.map((i) => {
|
|
735
|
+
return {
|
|
736
|
+
name: `${i.nick} - ${USER_TITLE[i.title]}`,
|
|
737
|
+
value: i.weWorkUserId,
|
|
738
|
+
};
|
|
739
|
+
});
|
|
740
|
+
const cacheList = getMainConfig("weWorkListCache");
|
|
741
|
+
const cacheIdList = cacheList.map((i) => i.value);
|
|
742
|
+
const sortedList = [];
|
|
743
|
+
weWorkUserList.forEach((i) => {
|
|
744
|
+
const idx = cacheIdList.indexOf(i.value);
|
|
745
|
+
sortedList.push(Object.assign(Object.assign({}, i), { usageCount: idx > -1 ? cacheList[idx].usageCount : 0 }));
|
|
746
|
+
});
|
|
747
|
+
return sortedList.sort((a, b) => b.usageCount - a.usageCount);
|
|
748
|
+
});
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
/**
|
|
752
|
+
* 请求
|
|
753
|
+
* @param requestOption
|
|
754
|
+
* @constructor
|
|
755
|
+
*/
|
|
756
|
+
function request(requestOption) {
|
|
757
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
758
|
+
var _a;
|
|
759
|
+
const method = requestOption.method || "get";
|
|
760
|
+
const headers = requestOption.headers || {};
|
|
761
|
+
const data = requestOption.data || {};
|
|
762
|
+
const dataKey = requestOption.dataKey || ["GET", "get"].includes(method) ? "params" : "data";
|
|
763
|
+
const auth = requestOption.auth || undefined;
|
|
764
|
+
try {
|
|
765
|
+
const option = {
|
|
766
|
+
url: requestOption.url,
|
|
767
|
+
method,
|
|
768
|
+
[dataKey]: data,
|
|
769
|
+
headers,
|
|
770
|
+
auth,
|
|
771
|
+
};
|
|
772
|
+
log(chalk.magenta(option.method.toUpperCase()), chalk.yellow(option.url), JSON.stringify(option.headers), JSON.stringify(option.auth || {}));
|
|
773
|
+
log(chalk.gray(JSON.stringify(option[dataKey], null, 2)));
|
|
774
|
+
const response = yield axios(option);
|
|
775
|
+
log(chalk.green("Response"), chalk.grey(JSON.stringify(response.data, null, 2)));
|
|
776
|
+
return Promise.resolve(response.data);
|
|
777
|
+
}
|
|
778
|
+
catch (err) {
|
|
779
|
+
if (err instanceof AxiosError) {
|
|
780
|
+
log(chalk.red("Error"), chalk.grey(JSON.stringify((_a = err === null || err === void 0 ? void 0 : err.response) === null || _a === void 0 ? void 0 : _a.data)));
|
|
781
|
+
}
|
|
782
|
+
return Promise.reject(err);
|
|
783
|
+
}
|
|
784
|
+
});
|
|
785
|
+
}
|
|
786
|
+
function getGitToken() {
|
|
787
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
788
|
+
const token = getProfileConfig("gitToken");
|
|
789
|
+
if (token) {
|
|
790
|
+
return token;
|
|
791
|
+
}
|
|
792
|
+
else {
|
|
793
|
+
const { access_token } = yield request({
|
|
794
|
+
url: `${GIT_DOMAIN}/oauth/token`,
|
|
795
|
+
method: "post",
|
|
796
|
+
data: {
|
|
797
|
+
grant_type: "password",
|
|
798
|
+
username: getProfileConfig("ldapAccount"),
|
|
799
|
+
password: getProfileConfig("ldapPassword"),
|
|
800
|
+
},
|
|
801
|
+
});
|
|
802
|
+
const _token = `Bearer ${access_token}`;
|
|
803
|
+
setProfileConfig("gitToken", _token);
|
|
804
|
+
writeConfigFile();
|
|
805
|
+
return _token;
|
|
806
|
+
}
|
|
807
|
+
});
|
|
808
|
+
}
|
|
809
|
+
function requestForGitlab(requestOption) {
|
|
810
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
811
|
+
return request(Object.assign(Object.assign({}, requestOption), { headers: {
|
|
812
|
+
Authorization: yield getGitToken(),
|
|
813
|
+
} }));
|
|
814
|
+
});
|
|
815
|
+
}
|
|
816
|
+
function getZenTaoToken() {
|
|
817
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
818
|
+
const token = getProfileConfig("zenTaoToken");
|
|
819
|
+
if (token) {
|
|
820
|
+
return token;
|
|
821
|
+
}
|
|
822
|
+
else {
|
|
823
|
+
const { token: _token } = yield request({
|
|
824
|
+
url: `${getConstantsConfig("ZenTaoDomain")}/api.php/v1/tokens`,
|
|
825
|
+
method: "post",
|
|
826
|
+
data: {
|
|
827
|
+
account: getProfileConfig("ldapAccount"),
|
|
828
|
+
password: getProfileConfig("ldapPassword"),
|
|
829
|
+
},
|
|
830
|
+
});
|
|
831
|
+
setProfileConfig("zenTaoToken", _token);
|
|
832
|
+
writeConfigFile();
|
|
833
|
+
return _token;
|
|
834
|
+
}
|
|
835
|
+
});
|
|
836
|
+
}
|
|
837
|
+
function requestForZenTao(requestOption) {
|
|
838
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
839
|
+
const resp = yield request(Object.assign(Object.assign({}, requestOption), { headers: {
|
|
840
|
+
Token: yield getZenTaoToken(),
|
|
841
|
+
} }));
|
|
842
|
+
// get page data
|
|
843
|
+
if (typeof resp === "string") {
|
|
844
|
+
const data = JSON.parse(resp.slice(0, resp.indexOf("<script>")));
|
|
845
|
+
return JSON.parse(data.data);
|
|
846
|
+
}
|
|
847
|
+
// 登录失效
|
|
848
|
+
if (typeof resp === "object") {
|
|
849
|
+
if (resp.error === "Unauthorized" || resp.data.indexOf("<script>") < 0) {
|
|
850
|
+
setProfileConfig("zenTaoToken", "");
|
|
851
|
+
return requestForZenTao(Object.assign({}, requestOption));
|
|
852
|
+
}
|
|
853
|
+
return resp;
|
|
854
|
+
}
|
|
855
|
+
});
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
function getPaiRequest(req) {
|
|
859
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
860
|
+
const base = `${getConstantsConfig("FEServerDomain")}/api`;
|
|
861
|
+
const { data } = yield request({
|
|
862
|
+
url: `${base}/auth/z-develop/login`,
|
|
863
|
+
method: "post",
|
|
864
|
+
});
|
|
865
|
+
return request({
|
|
866
|
+
url: base + req.url,
|
|
867
|
+
headers: {
|
|
868
|
+
Authorization: `Bearer ${data}`,
|
|
869
|
+
},
|
|
870
|
+
data: req.data,
|
|
871
|
+
method: req.method || "post",
|
|
872
|
+
});
|
|
873
|
+
});
|
|
874
|
+
}
|
|
875
|
+
/**
|
|
876
|
+
* 获取企微用户列表
|
|
877
|
+
*/
|
|
878
|
+
function getWeWorkUserListApi() {
|
|
879
|
+
return getPaiRequest({
|
|
880
|
+
url: "/user/list",
|
|
881
|
+
});
|
|
882
|
+
}
|
|
883
|
+
/**
|
|
884
|
+
* 获取企微审批流程状态
|
|
885
|
+
* @param data
|
|
886
|
+
*/
|
|
887
|
+
function getProcessStateApi(data) {
|
|
888
|
+
return getPaiRequest({
|
|
889
|
+
url: "/approval/findByBusinessId",
|
|
890
|
+
data,
|
|
891
|
+
});
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
const gitLabApiV4 = () => `${GIT_DOMAIN}/api/v4`;
|
|
895
|
+
const gitLabDataPath = (path) => gitLabApiV4() + "/projects/100/repository/files/" + encodeURIComponent(path) + "/raw?ref=master";
|
|
896
|
+
/**
|
|
897
|
+
* 获取模版项目列表
|
|
898
|
+
*/
|
|
899
|
+
function getProjectListApi() {
|
|
900
|
+
return requestForGitlab({
|
|
901
|
+
url: gitLabDataPath("src/data/template-projects.json"),
|
|
902
|
+
});
|
|
903
|
+
}
|
|
904
|
+
/**
|
|
905
|
+
* 获取项目分组列表
|
|
906
|
+
*/
|
|
907
|
+
function getProjectGroupListApi() {
|
|
908
|
+
return requestForGitlab({
|
|
909
|
+
url: gitLabDataPath("src/data/project-groups.json"),
|
|
910
|
+
});
|
|
911
|
+
}
|
|
912
|
+
/**
|
|
913
|
+
* 获取静态配置
|
|
914
|
+
*/
|
|
915
|
+
function getConstantsConfigApi() {
|
|
916
|
+
return requestForGitlab({
|
|
917
|
+
url: gitLabDataPath("src/data/z-develop-config.json"),
|
|
918
|
+
});
|
|
919
|
+
}
|
|
920
|
+
/**
|
|
921
|
+
* 获取用户信息
|
|
922
|
+
*/
|
|
923
|
+
function getUserInfoApi() {
|
|
924
|
+
return requestForGitlab({
|
|
925
|
+
url: `${gitLabApiV4()}/user`,
|
|
926
|
+
});
|
|
927
|
+
}
|
|
928
|
+
/**
|
|
929
|
+
* 获取群组列表
|
|
930
|
+
*/
|
|
931
|
+
function getGroupListApi() {
|
|
932
|
+
return requestForGitlab({
|
|
933
|
+
url: `${gitLabApiV4()}/groups`,
|
|
934
|
+
});
|
|
935
|
+
}
|
|
936
|
+
/**
|
|
937
|
+
* 创建项目
|
|
938
|
+
* @param data
|
|
939
|
+
*/
|
|
940
|
+
function createProjectApi(data) {
|
|
941
|
+
return requestForGitlab({
|
|
942
|
+
url: `${gitLabApiV4()}/projects`,
|
|
943
|
+
method: "post",
|
|
944
|
+
data,
|
|
945
|
+
});
|
|
946
|
+
}
|
|
947
|
+
/**
|
|
948
|
+
* 通过项目名称获取项目详情
|
|
949
|
+
* @param {string} search
|
|
950
|
+
*/
|
|
951
|
+
function getProjectDetailByNameApi(search) {
|
|
952
|
+
return requestForGitlab({
|
|
953
|
+
url: `${gitLabApiV4()}/projects`,
|
|
954
|
+
data: {
|
|
955
|
+
search,
|
|
956
|
+
},
|
|
957
|
+
});
|
|
958
|
+
}
|
|
959
|
+
/**
|
|
960
|
+
* 创建一个Merge Request
|
|
961
|
+
* @param data
|
|
962
|
+
*/
|
|
963
|
+
function createMRApi(data) {
|
|
964
|
+
return requestForGitlab({
|
|
965
|
+
url: `${gitLabApiV4()}/projects/${data.id}/merge_requests`,
|
|
966
|
+
method: "post",
|
|
967
|
+
data,
|
|
968
|
+
});
|
|
969
|
+
}
|
|
970
|
+
/**
|
|
971
|
+
* 合并一个Merge Request
|
|
972
|
+
* @param data
|
|
973
|
+
*/
|
|
974
|
+
function mergeMRApi(data) {
|
|
975
|
+
return requestForGitlab({
|
|
976
|
+
url: `${gitLabApiV4()}/projects/${data.id}/merge_requests/${data.iid}/merge`,
|
|
977
|
+
method: "put",
|
|
978
|
+
});
|
|
979
|
+
}
|
|
980
|
+
/**
|
|
981
|
+
* 合并一个Merge Request
|
|
982
|
+
* @param data
|
|
983
|
+
*/
|
|
984
|
+
function updateMRApi(data) {
|
|
985
|
+
return requestForGitlab({
|
|
986
|
+
url: `${gitLabApiV4()}/projects/${data.id}/merge_requests/${data.iid}`,
|
|
987
|
+
method: "put",
|
|
988
|
+
data,
|
|
989
|
+
});
|
|
990
|
+
}
|
|
991
|
+
/**
|
|
992
|
+
* 创建项目远程分支
|
|
993
|
+
*/
|
|
994
|
+
function createRemoteBranchApi(data) {
|
|
995
|
+
return requestForGitlab({
|
|
996
|
+
url: `${gitLabApiV4()}/projects/${data.id}/repository/branches`,
|
|
997
|
+
method: "post",
|
|
998
|
+
data,
|
|
999
|
+
});
|
|
1000
|
+
}
|
|
1001
|
+
/**
|
|
1002
|
+
* 获取项目远程分支列表
|
|
1003
|
+
*/
|
|
1004
|
+
function getRemoteBranchListApi(id) {
|
|
1005
|
+
return requestForGitlab({
|
|
1006
|
+
url: `${gitLabApiV4()}/projects/${id}/repository/branches`,
|
|
1007
|
+
});
|
|
1008
|
+
}
|
|
1009
|
+
|
|
1010
|
+
// https://www.zentao.net/book/api/664.html
|
|
1011
|
+
const zenTaoURL = (url) => `${getConstantsConfig("ZenTaoDomain")}${url}`;
|
|
1012
|
+
/**
|
|
1013
|
+
* 获取禅道任务列表
|
|
1014
|
+
*/
|
|
1015
|
+
function getZenTaoTaskListApi() {
|
|
1016
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
1017
|
+
const res = yield requestForZenTao({
|
|
1018
|
+
url: zenTaoURL("/my-work-task.json?tid=mrrferp8"),
|
|
1019
|
+
method: "get",
|
|
1020
|
+
});
|
|
1021
|
+
return res === null || res === void 0 ? void 0 : res.tasks.map(({ id, name }) => ({
|
|
1022
|
+
value: {
|
|
1023
|
+
name,
|
|
1024
|
+
value: id,
|
|
1025
|
+
},
|
|
1026
|
+
name,
|
|
1027
|
+
}));
|
|
1028
|
+
});
|
|
1029
|
+
}
|
|
1030
|
+
/**
|
|
1031
|
+
* 获取禅道任务列表
|
|
1032
|
+
*/
|
|
1033
|
+
function getZenTaoBugListApi() {
|
|
1034
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
1035
|
+
const res = yield requestForZenTao({
|
|
1036
|
+
url: zenTaoURL("/my-work-bug.json?tid=mrrferp8"),
|
|
1037
|
+
method: "get",
|
|
1038
|
+
});
|
|
1039
|
+
return res === null || res === void 0 ? void 0 : res.bugs.map(({ id, title }) => ({
|
|
1040
|
+
name: title,
|
|
1041
|
+
value: {
|
|
1042
|
+
name: title,
|
|
1043
|
+
value: id,
|
|
1044
|
+
},
|
|
1045
|
+
}));
|
|
1046
|
+
});
|
|
1047
|
+
}
|
|
1048
|
+
|
|
1049
|
+
/**
|
|
1050
|
+
* 创建一个Jenkins job
|
|
1051
|
+
* @param data
|
|
1052
|
+
*/
|
|
1053
|
+
function createJenkinsJobApi(data) {
|
|
1054
|
+
return request({
|
|
1055
|
+
url: `${getConstantsConfig("jenkinsDomain")}/job/${data.jobName}/buildWithParameters`,
|
|
1056
|
+
method: "post",
|
|
1057
|
+
dataKey: "params",
|
|
1058
|
+
data: data.params,
|
|
1059
|
+
auth: {
|
|
1060
|
+
username: getConstantsConfig("jenkinsUsername"),
|
|
1061
|
+
password: getConstantsConfig("jenkinsPassword"),
|
|
1062
|
+
},
|
|
1063
|
+
});
|
|
1064
|
+
}
|
|
1065
|
+
|
|
1066
|
+
function getCheckbox(checked) {
|
|
1067
|
+
return checked ? chalk.green(figures.radioOn) : figures.radioOff;
|
|
1068
|
+
}
|
|
1069
|
+
function renderChoices(choices, pointer) {
|
|
1070
|
+
let output = "";
|
|
1071
|
+
choices.forEach((choice, i) => {
|
|
1072
|
+
if (choice.disabled) {
|
|
1073
|
+
output = `${output} - ${choice.name} (Disabled)`;
|
|
1074
|
+
}
|
|
1075
|
+
else {
|
|
1076
|
+
const isSelected = i === pointer;
|
|
1077
|
+
output += isSelected ? chalk.cyan(figures.pointer) + " " : " ";
|
|
1078
|
+
output += getCheckbox(choice.checked || false) + " " + choice.name;
|
|
1079
|
+
}
|
|
1080
|
+
output += "\n";
|
|
1081
|
+
});
|
|
1082
|
+
return output.replace(/\n$/, "");
|
|
1083
|
+
}
|
|
1084
|
+
class SearchCheckbox extends Base {
|
|
1085
|
+
constructor(questions, rl, answers) {
|
|
1086
|
+
super(questions, rl, answers);
|
|
1087
|
+
this.pointer = 0;
|
|
1088
|
+
// 检测选项参数是否传入
|
|
1089
|
+
if (!this.opt.choices) {
|
|
1090
|
+
this.throwParamError("choices");
|
|
1091
|
+
}
|
|
1092
|
+
// 初始化选项参数
|
|
1093
|
+
this.opt.choices.forEach((i, index) => {
|
|
1094
|
+
const item = i;
|
|
1095
|
+
item.checked = false;
|
|
1096
|
+
item.id = index;
|
|
1097
|
+
});
|
|
1098
|
+
// 选项中禁止分隔符
|
|
1099
|
+
const item = this.opt.choices.find((choice) => choice.type === "separator");
|
|
1100
|
+
if (item) {
|
|
1101
|
+
throw new Error("Separator is not allowed in choices.");
|
|
1102
|
+
}
|
|
1103
|
+
// 如果传了默认答案,则在选项中选中默认答案
|
|
1104
|
+
if (Array.isArray(this.opt.default)) {
|
|
1105
|
+
this.opt.choices.forEach((choice) => {
|
|
1106
|
+
// 排除分隔符选项
|
|
1107
|
+
if (choice.type === "separator") {
|
|
1108
|
+
return;
|
|
1109
|
+
}
|
|
1110
|
+
if (this.opt.default.includes(choice.value)) {
|
|
1111
|
+
choice.checked = true;
|
|
1112
|
+
}
|
|
1113
|
+
});
|
|
1114
|
+
}
|
|
1115
|
+
this.pointer = 0;
|
|
1116
|
+
this.selection = [];
|
|
1117
|
+
this.done = (opt) => {
|
|
1118
|
+
console.log(opt);
|
|
1119
|
+
};
|
|
1120
|
+
// Make sure no default is set (so it won't be printed)
|
|
1121
|
+
this.opt.default = null;
|
|
1122
|
+
this.paginator = new Paginator(this.screen, { isInfinite: true });
|
|
1123
|
+
this.filterList = this.allList = this.opt.choices.choices;
|
|
1124
|
+
}
|
|
1125
|
+
_run(cb) {
|
|
1126
|
+
this.done = cb;
|
|
1127
|
+
const events = observe(this.rl);
|
|
1128
|
+
const validation = this.handleSubmitEvents(events.line.pipe(map(this.getCurrentValue.bind(this))));
|
|
1129
|
+
validation.success.forEach(this.onEnd.bind(this));
|
|
1130
|
+
validation.error.forEach(this.onError.bind(this));
|
|
1131
|
+
events.normalizedUpKey.pipe(takeUntil(validation.success)).forEach(this.onUpKey.bind(this));
|
|
1132
|
+
events.normalizedDownKey.pipe(takeUntil(validation.success)).forEach(this.onDownKey.bind(this));
|
|
1133
|
+
events.spaceKey.pipe(takeUntil(validation.success)).forEach(this.onSpaceKey.bind(this));
|
|
1134
|
+
const ignoreKeys = ["up", "down", "space"];
|
|
1135
|
+
events.keypress
|
|
1136
|
+
.pipe(filter((value) => !value.key.ctrl && !ignoreKeys.includes(value.key.name || "")))
|
|
1137
|
+
.pipe(takeUntil(validation.success))
|
|
1138
|
+
.forEach(this.onKeyPress.bind(this));
|
|
1139
|
+
this.render();
|
|
1140
|
+
return this;
|
|
1141
|
+
}
|
|
1142
|
+
render(error) {
|
|
1143
|
+
// Render question
|
|
1144
|
+
let message = this.getQuestion();
|
|
1145
|
+
let bottomContent = "";
|
|
1146
|
+
this.getCurrentValue();
|
|
1147
|
+
// 根据状态展示不同内容
|
|
1148
|
+
if (this.status === "answered") {
|
|
1149
|
+
message += chalk.cyan(this.selection.join(","));
|
|
1150
|
+
}
|
|
1151
|
+
else {
|
|
1152
|
+
if (this.selection.length) {
|
|
1153
|
+
message += chalk.magenta(`[已选: ${this.selection.join(",")}]`);
|
|
1154
|
+
}
|
|
1155
|
+
else {
|
|
1156
|
+
message +=
|
|
1157
|
+
"(" +
|
|
1158
|
+
chalk.blue.bold("空格键") +
|
|
1159
|
+
"选择," +
|
|
1160
|
+
chalk.blue.bold("回车键") +
|
|
1161
|
+
"提交," +
|
|
1162
|
+
chalk.blue.bold("输入中文") +
|
|
1163
|
+
"搜索)";
|
|
1164
|
+
}
|
|
1165
|
+
message += chalk.bgYellowBright(chalk.black(this.rl.line));
|
|
1166
|
+
const choicesStr = renderChoices(this.filterList, this.pointer);
|
|
1167
|
+
bottomContent = this.paginator.paginate(choicesStr, this.pointer, this.opt.pageSize);
|
|
1168
|
+
}
|
|
1169
|
+
// 报错
|
|
1170
|
+
if (error) {
|
|
1171
|
+
bottomContent = chalk.red(">> ") + error;
|
|
1172
|
+
}
|
|
1173
|
+
// 渲染
|
|
1174
|
+
this.screen.render(message, bottomContent);
|
|
1175
|
+
}
|
|
1176
|
+
getCurrentValue() {
|
|
1177
|
+
const choices = this.allList.filter((choice) => choice.checked && !choice.disabled);
|
|
1178
|
+
this.selection = choices.map((choice) => choice.short);
|
|
1179
|
+
return choices.map((choice) => choice.value);
|
|
1180
|
+
}
|
|
1181
|
+
toggleChoice(index) {
|
|
1182
|
+
const item = this.filterList[index];
|
|
1183
|
+
if (item) {
|
|
1184
|
+
this.allList[item.id].checked = !item.checked;
|
|
1185
|
+
}
|
|
1186
|
+
}
|
|
1187
|
+
filterChoices() {
|
|
1188
|
+
this.filterList = this.allList.filter((i) => { var _a; return (_a = i.name) === null || _a === void 0 ? void 0 : _a.includes(this.rl.line.trim()); });
|
|
1189
|
+
}
|
|
1190
|
+
onSpaceKey() {
|
|
1191
|
+
this.toggleChoice(this.pointer);
|
|
1192
|
+
this.render();
|
|
1193
|
+
this.rl.resume();
|
|
1194
|
+
}
|
|
1195
|
+
onDownKey() {
|
|
1196
|
+
const len = this.filterList.length;
|
|
1197
|
+
this.pointer = this.pointer < len - 1 ? this.pointer + 1 : 0;
|
|
1198
|
+
this.render();
|
|
1199
|
+
}
|
|
1200
|
+
onUpKey() {
|
|
1201
|
+
const len = this.filterList.length;
|
|
1202
|
+
this.pointer = this.pointer > 0 ? this.pointer - 1 : len - 1;
|
|
1203
|
+
this.render();
|
|
1204
|
+
}
|
|
1205
|
+
onKeyPress() {
|
|
1206
|
+
this.pointer = 0;
|
|
1207
|
+
this.filterChoices();
|
|
1208
|
+
this.render();
|
|
1209
|
+
}
|
|
1210
|
+
onEnd(state) {
|
|
1211
|
+
this.status = "answered";
|
|
1212
|
+
this.render();
|
|
1213
|
+
this.screen.done();
|
|
1214
|
+
this.done(state.value);
|
|
1215
|
+
}
|
|
1216
|
+
onError(state) {
|
|
1217
|
+
this.render(state.isValid);
|
|
1218
|
+
}
|
|
1219
|
+
}
|
|
1220
|
+
|
|
1221
|
+
function initConfig$1() {
|
|
1222
|
+
process.on("unhandledRejection", (e) => {
|
|
1223
|
+
});
|
|
1224
|
+
process.on("uncaughtException", (e) => {
|
|
1225
|
+
});
|
|
1226
|
+
// 配置 shelljs
|
|
1227
|
+
// exec执行命令出错,将抛出js error
|
|
1228
|
+
shelljs.config.fatal = true;
|
|
1229
|
+
// 隐藏所有exec命令的输出内容
|
|
1230
|
+
shelljs.config.silent = true;
|
|
1231
|
+
// 调试模式下,打印命令
|
|
1232
|
+
shelljs.config.verbose = false;
|
|
1233
|
+
}
|
|
1234
|
+
function initStore() {
|
|
1235
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
1236
|
+
const argv = minimist(process.argv.slice(2));
|
|
1237
|
+
if (!["init", "i"].includes(argv._[0])) {
|
|
1238
|
+
// 读取配置
|
|
1239
|
+
if (isConfigFileExist()) {
|
|
1240
|
+
const config = fse.readJSONSync(getConfigFile());
|
|
1241
|
+
setProfileConfig(config.profile);
|
|
1242
|
+
setMainConfig(config.main);
|
|
1243
|
+
setConstantsConfig(config.constants);
|
|
1244
|
+
// 调试模式下,不需要自动更新
|
|
1245
|
+
{
|
|
1246
|
+
yield autoUpdate();
|
|
1247
|
+
}
|
|
1248
|
+
}
|
|
1249
|
+
else {
|
|
1250
|
+
if (!(argv.h || argv.help || argv.v || argv.version)) {
|
|
1251
|
+
error("请先初始化z-develop(执行 z i)。更多见https://zxf-fe.yuque.com/org-wiki-zxf-fe-ex4bve/qi89vg/us8lgdb5cq4kf3aa#PJK7q");
|
|
1252
|
+
}
|
|
1253
|
+
}
|
|
1254
|
+
}
|
|
1255
|
+
});
|
|
1256
|
+
}
|
|
1257
|
+
function autoUpdate() {
|
|
1258
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
1259
|
+
const latestCheckVersionTimestamp = getMainConfig("latestCheckVersionTimestamp");
|
|
1260
|
+
const versionCheckDuring = getMainConfig("versionCheckDuring");
|
|
1261
|
+
// 指定时间间隔后才检查是否需要更新
|
|
1262
|
+
if (Date.now() - Number(latestCheckVersionTimestamp) > Number(versionCheckDuring) * 24 * 3600 * 1000) {
|
|
1263
|
+
// 数据自动更新
|
|
1264
|
+
const constantsConfig = yield getConstantsConfigApi();
|
|
1265
|
+
setConstantsConfig(constantsConfig);
|
|
1266
|
+
// 版本自动升级
|
|
1267
|
+
const version = yield asyncExec(`npm view ${pkg.name} version`);
|
|
1268
|
+
if (version !== pkg.version) {
|
|
1269
|
+
console.log(`${chalk.blue(pkg.name)}本地版本为${pkg.version},低于线上版本${version},开始升级`);
|
|
1270
|
+
try {
|
|
1271
|
+
yield asyncExec(`npm i -g ${pkg.name}`);
|
|
1272
|
+
setMainConfig("latestCheckVersionTimestamp", Date.now());
|
|
1273
|
+
writeConfigFile();
|
|
1274
|
+
console.log(chalk.green("升级完成!"));
|
|
1275
|
+
process.exit(0);
|
|
1276
|
+
}
|
|
1277
|
+
catch (e) {
|
|
1278
|
+
console.log("升级出错!请重试,或者手动升级");
|
|
1279
|
+
error(e);
|
|
1280
|
+
}
|
|
1281
|
+
}
|
|
1282
|
+
}
|
|
1283
|
+
});
|
|
1284
|
+
}
|
|
1285
|
+
function initRegister() {
|
|
1286
|
+
// 注册inquirer组件
|
|
1287
|
+
inquirer.registerPrompt("search-checkbox", SearchCheckbox);
|
|
1288
|
+
}
|
|
1289
|
+
function preInit() {
|
|
1290
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
1291
|
+
initConfig$1();
|
|
1292
|
+
yield initStore();
|
|
1293
|
+
initRegister();
|
|
1294
|
+
});
|
|
1295
|
+
}
|
|
1296
|
+
|
|
1297
|
+
function initGlobal() {
|
|
1298
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
1299
|
+
try {
|
|
1300
|
+
if (isConfigFileExist()) {
|
|
1301
|
+
const { isRemove } = yield inquirer.prompt([
|
|
1302
|
+
{
|
|
1303
|
+
type: "confirm",
|
|
1304
|
+
name: "isRemove",
|
|
1305
|
+
message: "系统中已存在z的配置文件,确认重新配置?",
|
|
1306
|
+
},
|
|
1307
|
+
]);
|
|
1308
|
+
if (isRemove) {
|
|
1309
|
+
shelljs.rm("-rf", getConfigFile());
|
|
1310
|
+
}
|
|
1311
|
+
else {
|
|
1312
|
+
process.exit(0);
|
|
1313
|
+
}
|
|
1314
|
+
}
|
|
1315
|
+
const { jobType, account, password } = yield inquirer.prompt([
|
|
1316
|
+
{
|
|
1317
|
+
type: "list",
|
|
1318
|
+
name: "jobType",
|
|
1319
|
+
message: "请选择你要创建的类型",
|
|
1320
|
+
choices: [
|
|
1321
|
+
{
|
|
1322
|
+
name: "前端",
|
|
1323
|
+
value: "fe",
|
|
1324
|
+
},
|
|
1325
|
+
{
|
|
1326
|
+
name: "后端",
|
|
1327
|
+
value: "be",
|
|
1328
|
+
},
|
|
1329
|
+
],
|
|
1330
|
+
},
|
|
1331
|
+
{
|
|
1332
|
+
type: "input",
|
|
1333
|
+
name: "account",
|
|
1334
|
+
message: "请输入LDAP账号:",
|
|
1335
|
+
},
|
|
1336
|
+
{
|
|
1337
|
+
type: "input",
|
|
1338
|
+
name: "password",
|
|
1339
|
+
message: "请输入LDAP密码:",
|
|
1340
|
+
},
|
|
1341
|
+
]);
|
|
1342
|
+
// 设置基础信息
|
|
1343
|
+
setProfileConfig("jobType", jobType);
|
|
1344
|
+
setProfileConfig("ldapAccount", account);
|
|
1345
|
+
setProfileConfig("ldapPassword", password);
|
|
1346
|
+
// 获取静态配置信息
|
|
1347
|
+
const constantsConfig = yield getConstantsConfigApi();
|
|
1348
|
+
setConstantsConfig(constantsConfig);
|
|
1349
|
+
// 获取企微用户列表
|
|
1350
|
+
const choices = yield getSortedWeWorkUserList();
|
|
1351
|
+
const { weWork } = yield inquirer.prompt([
|
|
1352
|
+
{
|
|
1353
|
+
type: "search-checkbox",
|
|
1354
|
+
name: "weWork",
|
|
1355
|
+
message: "请选择你自己(用于企微通知):",
|
|
1356
|
+
choices,
|
|
1357
|
+
validate(input) {
|
|
1358
|
+
if (input.length > 1) {
|
|
1359
|
+
return "只能选一个";
|
|
1360
|
+
}
|
|
1361
|
+
if (input.length < 1) {
|
|
1362
|
+
return "请选一个";
|
|
1363
|
+
}
|
|
1364
|
+
return true;
|
|
1365
|
+
},
|
|
1366
|
+
},
|
|
1367
|
+
]);
|
|
1368
|
+
const { name: weWorkName, value: weWorkUserId } = choices.find((i) => i.value === weWork[0]);
|
|
1369
|
+
// 设置企微信息
|
|
1370
|
+
setProfileConfig({
|
|
1371
|
+
weWorkName,
|
|
1372
|
+
weWorkUserId,
|
|
1373
|
+
});
|
|
1374
|
+
}
|
|
1375
|
+
catch (e) {
|
|
1376
|
+
error(e);
|
|
1377
|
+
}
|
|
1378
|
+
const s1 = ora("配置信息初始化中").start();
|
|
1379
|
+
try {
|
|
1380
|
+
// 根据配置获取用户信息
|
|
1381
|
+
const userInfo = yield getUserInfoApi();
|
|
1382
|
+
// 设置用户信息
|
|
1383
|
+
setProfileConfig({
|
|
1384
|
+
gitUserId: userInfo.id,
|
|
1385
|
+
gitName: userInfo.name,
|
|
1386
|
+
gitEnglishName: userInfo.username,
|
|
1387
|
+
gitEmail: userInfo.email,
|
|
1388
|
+
});
|
|
1389
|
+
// 设置版本相关信息
|
|
1390
|
+
setMainConfig("latestCheckVersionTimestamp", Date.now());
|
|
1391
|
+
// 创建配置文件夹
|
|
1392
|
+
shelljs.mkdir("-p", getConfigDir());
|
|
1393
|
+
// 创建配置文件并写入配置
|
|
1394
|
+
writeConfigFile();
|
|
1395
|
+
s1.succeed("配置信息初始化完成");
|
|
1396
|
+
}
|
|
1397
|
+
catch (e) {
|
|
1398
|
+
s1.fail("配置信息初始化失败");
|
|
1399
|
+
if (axios.isAxiosError(e)) {
|
|
1400
|
+
error("请检查你的域名及令牌配置");
|
|
1401
|
+
}
|
|
1402
|
+
error(e);
|
|
1403
|
+
}
|
|
1404
|
+
});
|
|
1405
|
+
}
|
|
1406
|
+
|
|
1407
|
+
const dirname = ".z";
|
|
1408
|
+
const _ = (x = "") => path.join(dirname, x);
|
|
1409
|
+
let s1;
|
|
1410
|
+
function initProject() {
|
|
1411
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
1412
|
+
if (!fs.existsSync(".git")) {
|
|
1413
|
+
error("当前项目不是git项目,或不在根目录。请先执行git init,或切换到根目录");
|
|
1414
|
+
}
|
|
1415
|
+
if (fs.existsSync(dirname)) {
|
|
1416
|
+
const { isRemove } = yield inquirer.prompt([
|
|
1417
|
+
{
|
|
1418
|
+
type: "confirm",
|
|
1419
|
+
name: "isRemove",
|
|
1420
|
+
message: `当前项目中已存在配置文件夹${dirname},确认重新配置?`,
|
|
1421
|
+
},
|
|
1422
|
+
]);
|
|
1423
|
+
if (isRemove) {
|
|
1424
|
+
shelljs.rm("-rf", ".z");
|
|
1425
|
+
}
|
|
1426
|
+
else {
|
|
1427
|
+
process.exit(0);
|
|
1428
|
+
}
|
|
1429
|
+
}
|
|
1430
|
+
yield initConfig();
|
|
1431
|
+
yield initLint();
|
|
1432
|
+
s1 === null || s1 === void 0 ? void 0 : s1.succeed("初始化完成。");
|
|
1433
|
+
});
|
|
1434
|
+
}
|
|
1435
|
+
function initConfig() {
|
|
1436
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
1437
|
+
// 文件夹
|
|
1438
|
+
fs.mkdirSync(_(), { recursive: true });
|
|
1439
|
+
// 远程地址
|
|
1440
|
+
const repositoryURL = yield asyncExec("git remote get-url --push origin");
|
|
1441
|
+
if (!repositoryURL) {
|
|
1442
|
+
error("获取项目远程地址失败,请配置后重试");
|
|
1443
|
+
}
|
|
1444
|
+
// 选择项目语言
|
|
1445
|
+
const { language } = yield inquirer.prompt([
|
|
1446
|
+
{
|
|
1447
|
+
type: "list",
|
|
1448
|
+
name: "language",
|
|
1449
|
+
message: "请选择项目语言",
|
|
1450
|
+
choices: [
|
|
1451
|
+
{
|
|
1452
|
+
name: "JavaScript",
|
|
1453
|
+
value: "JavaScript",
|
|
1454
|
+
},
|
|
1455
|
+
{
|
|
1456
|
+
name: "Java",
|
|
1457
|
+
value: "Java",
|
|
1458
|
+
},
|
|
1459
|
+
],
|
|
1460
|
+
},
|
|
1461
|
+
]);
|
|
1462
|
+
s1 = ora("项目初始化中").start();
|
|
1463
|
+
setProjectJSON({
|
|
1464
|
+
language: language,
|
|
1465
|
+
"lint-staged": LINT_STAGED_CONFIG[language],
|
|
1466
|
+
repository: {
|
|
1467
|
+
url: repositoryURL,
|
|
1468
|
+
},
|
|
1469
|
+
});
|
|
1470
|
+
});
|
|
1471
|
+
}
|
|
1472
|
+
function initLint() {
|
|
1473
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
1474
|
+
// 文件夹
|
|
1475
|
+
fs.mkdirSync(_(), { recursive: true });
|
|
1476
|
+
// 配置git hooks指向文件夹
|
|
1477
|
+
child_process.spawnSync("git", ["config", "core.hooksPath", dirname]);
|
|
1478
|
+
// 生成空的模板文件
|
|
1479
|
+
fs.writeFileSync(_(".commit-msg-tpl"), "", { mode: 0o755 });
|
|
1480
|
+
// 配置git commit-msg模板指向模板文件
|
|
1481
|
+
child_process.spawnSync("git", ["config", "commit.template", path.join(dirname, ".commit-msg-tpl")]);
|
|
1482
|
+
// 生成脚本
|
|
1483
|
+
fs.writeFileSync(_("commit-msg"), `z lint commit-msg`, { mode: 0o755 });
|
|
1484
|
+
fs.writeFileSync(_("pre-commit"), `z lint commit-files`, { mode: 0o755 });
|
|
1485
|
+
// 生产prettier配置文件
|
|
1486
|
+
fs.writeFileSync(_(".prettierrc.json"), PRETTIER_RC, { mode: 0o755 });
|
|
1487
|
+
fs.writeFileSync(_(".prettierignore"), PRETTIER_IGNORE, { mode: 0o755 });
|
|
1488
|
+
// 生成.gitignore
|
|
1489
|
+
fs.writeFileSync(_(".gitignore"), ".commit-msg-tpl\n");
|
|
1490
|
+
});
|
|
1491
|
+
}
|
|
1492
|
+
|
|
1493
|
+
function initAction(_type) {
|
|
1494
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
1495
|
+
if (_type) {
|
|
1496
|
+
if (_type === ".") {
|
|
1497
|
+
yield initProject();
|
|
1498
|
+
}
|
|
1499
|
+
else {
|
|
1500
|
+
error("参数错误。执行 z init -h 查看帮助。");
|
|
1501
|
+
}
|
|
1502
|
+
}
|
|
1503
|
+
else {
|
|
1504
|
+
yield initGlobal();
|
|
1505
|
+
}
|
|
1506
|
+
});
|
|
1507
|
+
}
|
|
1508
|
+
|
|
1509
|
+
program
|
|
1510
|
+
.command("init")
|
|
1511
|
+
.alias("i")
|
|
1512
|
+
.argument("[type]", "非必填。可选值:.。不传为初始化工具配置;传 . 为初始化当前项目。")
|
|
1513
|
+
.description("初始化工具配置、项目配置")
|
|
1514
|
+
.action(initAction);
|
|
1515
|
+
|
|
1516
|
+
/**
|
|
1517
|
+
* 创建项目
|
|
1518
|
+
*/
|
|
1519
|
+
function createProject() {
|
|
1520
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
1521
|
+
try {
|
|
1522
|
+
const answers = {};
|
|
1523
|
+
const selectTemplate = () => __awaiter(this, void 0, void 0, function* () {
|
|
1524
|
+
const curDirList = listDirs();
|
|
1525
|
+
const { list } = yield getProjectListApi();
|
|
1526
|
+
const choices = list.map((i) => {
|
|
1527
|
+
const item = {
|
|
1528
|
+
name: `${i.name} [${i.desc}]`,
|
|
1529
|
+
value: i.name,
|
|
1530
|
+
short: i.name,
|
|
1531
|
+
disabled: false,
|
|
1532
|
+
};
|
|
1533
|
+
if (curDirList.includes(i.name)) {
|
|
1534
|
+
item.disabled = "目录下已存在同名文件夹";
|
|
1535
|
+
}
|
|
1536
|
+
return item;
|
|
1537
|
+
});
|
|
1538
|
+
const { project } = yield inquirer.prompt([
|
|
1539
|
+
{
|
|
1540
|
+
type: "list",
|
|
1541
|
+
name: "project",
|
|
1542
|
+
message: "请选择一个项目模板",
|
|
1543
|
+
choices,
|
|
1544
|
+
},
|
|
1545
|
+
]);
|
|
1546
|
+
const item = list.find((i) => i.name === project);
|
|
1547
|
+
answers.tplName = item.name;
|
|
1548
|
+
answers.tplUrl = item.url;
|
|
1549
|
+
});
|
|
1550
|
+
const selectGroup = () => __awaiter(this, void 0, void 0, function* () {
|
|
1551
|
+
const allGroups = yield getGroupListApi();
|
|
1552
|
+
const find = (input) => allGroups.find((i) => i.name === input);
|
|
1553
|
+
const { list } = yield getProjectGroupListApi();
|
|
1554
|
+
const { group } = yield inquirer.prompt([
|
|
1555
|
+
{
|
|
1556
|
+
type: "list",
|
|
1557
|
+
name: "group",
|
|
1558
|
+
message: "请选择一个分组",
|
|
1559
|
+
choices: list.map((i) => ({
|
|
1560
|
+
name: `${i.name} [${i.desc}]`,
|
|
1561
|
+
value: i.name,
|
|
1562
|
+
short: i.name,
|
|
1563
|
+
disabled: false,
|
|
1564
|
+
})),
|
|
1565
|
+
},
|
|
1566
|
+
]);
|
|
1567
|
+
const item = find(group);
|
|
1568
|
+
answers.group = group;
|
|
1569
|
+
answers.groupId = item.id;
|
|
1570
|
+
});
|
|
1571
|
+
const setProjectName = () => __awaiter(this, void 0, void 0, function* () {
|
|
1572
|
+
const { name, desc } = yield inquirer.prompt([
|
|
1573
|
+
{
|
|
1574
|
+
type: "input",
|
|
1575
|
+
name: "name",
|
|
1576
|
+
message: "请输入项目名称",
|
|
1577
|
+
validate: validateProjectName,
|
|
1578
|
+
},
|
|
1579
|
+
{
|
|
1580
|
+
type: "input",
|
|
1581
|
+
name: "desc",
|
|
1582
|
+
message: "请输入项目描述",
|
|
1583
|
+
validate: validateDesc,
|
|
1584
|
+
},
|
|
1585
|
+
]);
|
|
1586
|
+
answers.projectName = name;
|
|
1587
|
+
answers.projectDesc = desc;
|
|
1588
|
+
});
|
|
1589
|
+
// 选择模版
|
|
1590
|
+
yield selectTemplate();
|
|
1591
|
+
// 选择群组
|
|
1592
|
+
yield selectGroup();
|
|
1593
|
+
// 设置项目名称
|
|
1594
|
+
yield setProjectName();
|
|
1595
|
+
// 项目远程地址
|
|
1596
|
+
const gitRemote = `${GIT_DOMAIN}/${answers.group}/${answers.projectName}.git`;
|
|
1597
|
+
// 1.初始模版项目
|
|
1598
|
+
const s1 = ora("模版初始化中").start();
|
|
1599
|
+
// 拉取项目
|
|
1600
|
+
yield asyncExec(`git clone --depth=1 ${answers.tplUrl}`);
|
|
1601
|
+
// 修改目录名称,并进入
|
|
1602
|
+
shelljs.mv(answers.tplName, answers.projectName);
|
|
1603
|
+
shelljs.cd(answers.projectName);
|
|
1604
|
+
// 重置git
|
|
1605
|
+
shelljs.rm("-rf", ".git");
|
|
1606
|
+
yield asyncExec("git init --initial-branch=master");
|
|
1607
|
+
// 设置远程地址
|
|
1608
|
+
yield asyncExec(`git remote add origin ${gitRemote}`);
|
|
1609
|
+
// 设置用户信息
|
|
1610
|
+
yield asyncExec(`git config user.name ${getProfileConfig("gitName")}`);
|
|
1611
|
+
yield asyncExec(`git config user.email ${getProfileConfig("gitEmail")}`);
|
|
1612
|
+
// 设置package.json
|
|
1613
|
+
setProjectPackageJSON({
|
|
1614
|
+
name: answers.projectName,
|
|
1615
|
+
description: answers.projectDesc,
|
|
1616
|
+
});
|
|
1617
|
+
setProjectJSON({
|
|
1618
|
+
repository: {
|
|
1619
|
+
url: gitRemote,
|
|
1620
|
+
},
|
|
1621
|
+
"lint-staged": LINT_STAGED_CONFIG["JavaScript"],
|
|
1622
|
+
language: "JavaScript",
|
|
1623
|
+
});
|
|
1624
|
+
// 设置README.md
|
|
1625
|
+
yield fse.writeFile("README.md", generateReadMeTemplate(answers.projectName, answers.projectDesc));
|
|
1626
|
+
s1.succeed("模版初始化完成");
|
|
1627
|
+
// 2.安装依赖
|
|
1628
|
+
const s2 = ora("依赖安装中").start();
|
|
1629
|
+
yield asyncExec("npm install");
|
|
1630
|
+
s2.succeed("依赖安装完成");
|
|
1631
|
+
// 3.远程项目创建
|
|
1632
|
+
const s3 = ora("远程项目创建中").start();
|
|
1633
|
+
const projectDetail = yield createProjectApi({
|
|
1634
|
+
name: answers.projectName,
|
|
1635
|
+
description: answers.projectDesc,
|
|
1636
|
+
path: answers.projectName,
|
|
1637
|
+
visibility: "private",
|
|
1638
|
+
namespace_id: answers.groupId,
|
|
1639
|
+
});
|
|
1640
|
+
s3.succeed("远程项目创建成功");
|
|
1641
|
+
// 4.推送
|
|
1642
|
+
const s4 = ora("项目推送中").start();
|
|
1643
|
+
// commit
|
|
1644
|
+
yield asyncExec('git add . && git commit -m "chore: init"');
|
|
1645
|
+
// push master
|
|
1646
|
+
yield asyncExec("git push -u origin master");
|
|
1647
|
+
// 推送成功
|
|
1648
|
+
s4.succeed(`项目已推送到远程,地址: ${chalk.blue(gitRemote)}`);
|
|
1649
|
+
// 5. 初始化分支
|
|
1650
|
+
// create other main branches
|
|
1651
|
+
const s5 = ora("初始化分支中").start();
|
|
1652
|
+
const firstDevBranch = `feat_init_${getBranchNameDate()}}`;
|
|
1653
|
+
const initBranches = [...INFORM_MAIN_BRANCHES, firstDevBranch];
|
|
1654
|
+
const branchesCreateStatus = yield Promise.allSettled(initBranches.map((branchName) => createRemoteBranchApi({
|
|
1655
|
+
id: projectDetail.id,
|
|
1656
|
+
branch: branchName,
|
|
1657
|
+
ref: "master",
|
|
1658
|
+
})));
|
|
1659
|
+
const createdBranches = [];
|
|
1660
|
+
branchesCreateStatus.forEach(({ status }, index) => {
|
|
1661
|
+
if (status === "fulfilled") {
|
|
1662
|
+
createdBranches.push(initBranches[index]);
|
|
1663
|
+
}
|
|
1664
|
+
});
|
|
1665
|
+
// 拉取远程分支
|
|
1666
|
+
yield asyncExec(`git fetch origin`);
|
|
1667
|
+
if (createdBranches.includes(firstDevBranch)) {
|
|
1668
|
+
// 检出远程分支
|
|
1669
|
+
yield asyncExec(`git checkout -b ${firstDevBranch} origin/${firstDevBranch}`);
|
|
1670
|
+
s5.succeed(`项目已切换到初始分支: ${chalk.blue(firstDevBranch)}`);
|
|
1671
|
+
}
|
|
1672
|
+
else {
|
|
1673
|
+
s5.warn("开发分支检出失败!项目当前在主分支,请自行检出开发分支。");
|
|
1674
|
+
}
|
|
1675
|
+
// 6. 结束
|
|
1676
|
+
const command = `cd ${answers.projectName} && z start`;
|
|
1677
|
+
console.log(`输入 ${chalk.green(command)} 开始开发吧~`);
|
|
1678
|
+
}
|
|
1679
|
+
catch (e) {
|
|
1680
|
+
error(e);
|
|
1681
|
+
}
|
|
1682
|
+
});
|
|
1683
|
+
}
|
|
1684
|
+
|
|
1685
|
+
// https://git-scm.com/docs/partial-clone
|
|
1686
|
+
// https://stackoverflow.com/questions/36547904/git-clone-without-objects-to-do-git-log
|
|
1687
|
+
// https://git-scm.com/docs/git-clone
|
|
1688
|
+
// https://git-scm.com/docs/git-rev-list
|
|
1689
|
+
function createBranch() {
|
|
1690
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
1691
|
+
const gitStatus = yield asyncExec("git status -s");
|
|
1692
|
+
if (gitStatus !== "") {
|
|
1693
|
+
error("请先提交代码变动,再进行分支创建");
|
|
1694
|
+
}
|
|
1695
|
+
const { type, purpose } = yield inquirer.prompt([
|
|
1696
|
+
{
|
|
1697
|
+
name: "type",
|
|
1698
|
+
type: "list",
|
|
1699
|
+
message: "请选择创建分支的类型",
|
|
1700
|
+
choices: [
|
|
1701
|
+
{
|
|
1702
|
+
name: "开发新功能(feat)",
|
|
1703
|
+
value: "feat",
|
|
1704
|
+
},
|
|
1705
|
+
{
|
|
1706
|
+
name: "修复BUG(fix)",
|
|
1707
|
+
value: "fix",
|
|
1708
|
+
},
|
|
1709
|
+
{
|
|
1710
|
+
name: "重构/优化代码(refactor)",
|
|
1711
|
+
value: "refactor",
|
|
1712
|
+
},
|
|
1713
|
+
],
|
|
1714
|
+
},
|
|
1715
|
+
{
|
|
1716
|
+
name: "purpose",
|
|
1717
|
+
type: "input",
|
|
1718
|
+
message: "请输入创建分支的目的(大小写字母、数字,小驼峰式命名。如userInfo)",
|
|
1719
|
+
validate: validateBranchPurpose,
|
|
1720
|
+
},
|
|
1721
|
+
]);
|
|
1722
|
+
const branchName = `${type}_${purpose}_${getBranchNameDate()}`;
|
|
1723
|
+
const project = getProjectJSON();
|
|
1724
|
+
const projectInfo = yield asyncExec(`git remote show ${project.repository.url}`);
|
|
1725
|
+
const [, mainBranch] = projectInfo.match(/HEAD branch: (.+)\n/);
|
|
1726
|
+
yield asyncExec("git fetch origin");
|
|
1727
|
+
yield asyncExec(`git checkout -b ${branchName} origin/${mainBranch}`);
|
|
1728
|
+
});
|
|
1729
|
+
}
|
|
1730
|
+
|
|
1731
|
+
function notEmptyAnswer(input) {
|
|
1732
|
+
if (input.length === 0) {
|
|
1733
|
+
return "请选择项";
|
|
1734
|
+
}
|
|
1735
|
+
return true;
|
|
1736
|
+
}
|
|
1737
|
+
function createCommitMsg() {
|
|
1738
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
1739
|
+
const { type } = yield inquirer.prompt({
|
|
1740
|
+
type: "list",
|
|
1741
|
+
name: "type",
|
|
1742
|
+
message: "请选择你要创建的模板类型",
|
|
1743
|
+
choices: [
|
|
1744
|
+
{
|
|
1745
|
+
name: "feat - 业务需求开发",
|
|
1746
|
+
value: "feat",
|
|
1747
|
+
},
|
|
1748
|
+
{
|
|
1749
|
+
name: "fix - bug修复",
|
|
1750
|
+
value: "fix",
|
|
1751
|
+
},
|
|
1752
|
+
{
|
|
1753
|
+
name: "refactor - 技术内部需求",
|
|
1754
|
+
value: "refactor",
|
|
1755
|
+
},
|
|
1756
|
+
{
|
|
1757
|
+
name: "chore - 其他",
|
|
1758
|
+
value: "chore",
|
|
1759
|
+
},
|
|
1760
|
+
],
|
|
1761
|
+
});
|
|
1762
|
+
if (["feat", "refactor"].includes(type)) {
|
|
1763
|
+
const choices = yield getZenTaoTaskListApi();
|
|
1764
|
+
const { tasks } = yield inquirer.prompt({
|
|
1765
|
+
type: "checkbox",
|
|
1766
|
+
name: "tasks",
|
|
1767
|
+
message: "请关联开发任务(可多选):",
|
|
1768
|
+
validate: notEmptyAnswer,
|
|
1769
|
+
choices: choices.map((item) => (Object.assign(Object.assign({}, item), { name: `[${item.value.value}]: ${item.name}` }))),
|
|
1770
|
+
});
|
|
1771
|
+
makeCommitMsg("feat", tasks);
|
|
1772
|
+
}
|
|
1773
|
+
else if (type === "fix") {
|
|
1774
|
+
const choices = yield getZenTaoBugListApi();
|
|
1775
|
+
const { bugs } = yield inquirer.prompt({
|
|
1776
|
+
type: "checkbox",
|
|
1777
|
+
name: "bugs",
|
|
1778
|
+
message: "请关联Bug(可多选):",
|
|
1779
|
+
validate: notEmptyAnswer,
|
|
1780
|
+
choices: choices.map((item) => (Object.assign(Object.assign({}, item), { name: `[${item.value.value}]: ${item.name}` }))),
|
|
1781
|
+
});
|
|
1782
|
+
makeCommitMsg("fix", bugs);
|
|
1783
|
+
}
|
|
1784
|
+
else if (type === "chore") {
|
|
1785
|
+
const { msg } = yield inquirer.prompt({
|
|
1786
|
+
type: "input",
|
|
1787
|
+
name: "msg",
|
|
1788
|
+
message: "请输入commit msg:",
|
|
1789
|
+
validate: (input) => {
|
|
1790
|
+
if (input.length === 0) {
|
|
1791
|
+
return "请输入commit msg";
|
|
1792
|
+
}
|
|
1793
|
+
return true;
|
|
1794
|
+
},
|
|
1795
|
+
});
|
|
1796
|
+
makeCommitMsg("chore", [msg]);
|
|
1797
|
+
}
|
|
1798
|
+
});
|
|
1799
|
+
}
|
|
1800
|
+
function makeCommitMsg(type, items) {
|
|
1801
|
+
let msg = "";
|
|
1802
|
+
if (["feat", "refactor", "fix"].includes(type)) {
|
|
1803
|
+
const ids = items.map((item) => item.value).join(",");
|
|
1804
|
+
const names = items.map((item) => item.name).join(";");
|
|
1805
|
+
msg = `${type}(${ids}): ${names}`;
|
|
1806
|
+
}
|
|
1807
|
+
if (type === "chore") {
|
|
1808
|
+
msg = `${type}: ${items[0]}`;
|
|
1809
|
+
}
|
|
1810
|
+
fs.writeFileSync("./.z/.commit-msg-tpl", msg, "utf-8");
|
|
1811
|
+
console.log(chalk.yellow(msg));
|
|
1812
|
+
success("commit msg模板写入成功,可以进行提交了");
|
|
1813
|
+
}
|
|
1814
|
+
|
|
1815
|
+
function createAction(_type) {
|
|
1816
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
1817
|
+
let type;
|
|
1818
|
+
if (_type) {
|
|
1819
|
+
if (["project", "p"].includes(_type)) {
|
|
1820
|
+
type = "project";
|
|
1821
|
+
}
|
|
1822
|
+
else if (["branch", "b"].includes(_type)) {
|
|
1823
|
+
type = "branch";
|
|
1824
|
+
}
|
|
1825
|
+
else if (["commit-msg", "cm"].includes(_type)) {
|
|
1826
|
+
type = "commit-msg";
|
|
1827
|
+
}
|
|
1828
|
+
else {
|
|
1829
|
+
error("参数输入错误");
|
|
1830
|
+
}
|
|
1831
|
+
}
|
|
1832
|
+
else {
|
|
1833
|
+
const res = yield inquirer.prompt({
|
|
1834
|
+
type: "list",
|
|
1835
|
+
name: "type",
|
|
1836
|
+
message: "请选择你要创建的类型",
|
|
1837
|
+
choices: [
|
|
1838
|
+
{
|
|
1839
|
+
name: "项目",
|
|
1840
|
+
value: "project",
|
|
1841
|
+
disabled: false,
|
|
1842
|
+
},
|
|
1843
|
+
{
|
|
1844
|
+
name: "分支",
|
|
1845
|
+
value: "branch",
|
|
1846
|
+
disabled: false,
|
|
1847
|
+
},
|
|
1848
|
+
{
|
|
1849
|
+
name: "提交信息",
|
|
1850
|
+
value: "commit-msg",
|
|
1851
|
+
disabled: false,
|
|
1852
|
+
},
|
|
1853
|
+
],
|
|
1854
|
+
});
|
|
1855
|
+
type = res.type;
|
|
1856
|
+
}
|
|
1857
|
+
if (type === "project") {
|
|
1858
|
+
yield createProject();
|
|
1859
|
+
}
|
|
1860
|
+
else if (type === "branch") {
|
|
1861
|
+
yield createBranch();
|
|
1862
|
+
}
|
|
1863
|
+
else if (type === "commit-msg") {
|
|
1864
|
+
yield createCommitMsg();
|
|
1865
|
+
}
|
|
1866
|
+
});
|
|
1867
|
+
}
|
|
1868
|
+
|
|
1869
|
+
program
|
|
1870
|
+
.command("create")
|
|
1871
|
+
.alias("c")
|
|
1872
|
+
.argument("[type]", "可选值为project|p, branch|b, commit-msg|cm。传 p 为创建项目;传 b 为创建分支;传 cm 为创建提交信息")
|
|
1873
|
+
.description("创建项目/分支/提交信息")
|
|
1874
|
+
.action(createAction);
|
|
1875
|
+
|
|
1876
|
+
function createJenkinsJob(url, options) {
|
|
1877
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
1878
|
+
const jobName = getJenkinsJobNameFromGitLabURL(url);
|
|
1879
|
+
// const jobName = "test/job/test001";
|
|
1880
|
+
const { originBranch, environment, platform, notify, processId } = options;
|
|
1881
|
+
yield createJenkinsJobApi({
|
|
1882
|
+
params: {
|
|
1883
|
+
command: "build",
|
|
1884
|
+
params: JSON.stringify({
|
|
1885
|
+
originBranch,
|
|
1886
|
+
environment,
|
|
1887
|
+
platform,
|
|
1888
|
+
profile: {
|
|
1889
|
+
weWorkName: getProfileConfig("weWorkName"),
|
|
1890
|
+
weWorkUserId: getProfileConfig("weWorkUserId"),
|
|
1891
|
+
},
|
|
1892
|
+
}),
|
|
1893
|
+
options: JSON.stringify({
|
|
1894
|
+
processId,
|
|
1895
|
+
notify: notify || [],
|
|
1896
|
+
}),
|
|
1897
|
+
},
|
|
1898
|
+
jobName: jobName,
|
|
1899
|
+
});
|
|
1900
|
+
});
|
|
1901
|
+
}
|
|
1902
|
+
function deploy(options) {
|
|
1903
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
1904
|
+
try {
|
|
1905
|
+
const { originBranch, environment, platform, notify, processId } = options;
|
|
1906
|
+
// 检查当前项目是否满足部署条件
|
|
1907
|
+
const project = getProjectJSON();
|
|
1908
|
+
// 检测项目package.json文件
|
|
1909
|
+
checkPackageJSON(environment, platform);
|
|
1910
|
+
// 检测所有代码是否合规
|
|
1911
|
+
yield asyncExec("git fetch origin");
|
|
1912
|
+
yield checkVersionCodeValid(originBranch, environment);
|
|
1913
|
+
// 创建Jenkins任务
|
|
1914
|
+
yield createJenkinsJob(project.repository.url, {
|
|
1915
|
+
originBranch,
|
|
1916
|
+
environment,
|
|
1917
|
+
platform,
|
|
1918
|
+
notify,
|
|
1919
|
+
processId,
|
|
1920
|
+
});
|
|
1921
|
+
}
|
|
1922
|
+
catch (e) {
|
|
1923
|
+
error(e);
|
|
1924
|
+
}
|
|
1925
|
+
});
|
|
1926
|
+
}
|
|
1927
|
+
function deployAction() {
|
|
1928
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
1929
|
+
const notifyChoices = yield getSortedWeWorkUserList();
|
|
1930
|
+
const { environment, platform, notify } = yield inquirer.prompt([
|
|
1931
|
+
{
|
|
1932
|
+
type: "list",
|
|
1933
|
+
name: "environment",
|
|
1934
|
+
message: "请选择部署环境",
|
|
1935
|
+
choices: ENVIRONMENT_CHOICES,
|
|
1936
|
+
},
|
|
1937
|
+
{
|
|
1938
|
+
type: "list",
|
|
1939
|
+
name: "platform",
|
|
1940
|
+
message: "请选择平台",
|
|
1941
|
+
choices: PLATFORM_CHOICES,
|
|
1942
|
+
},
|
|
1943
|
+
{
|
|
1944
|
+
type: "search-checkbox",
|
|
1945
|
+
name: "notify",
|
|
1946
|
+
message: "请选择部署成功要通知的人员:",
|
|
1947
|
+
choices: notifyChoices,
|
|
1948
|
+
validate(input) {
|
|
1949
|
+
if (input.length < 1) {
|
|
1950
|
+
return "至少选一个";
|
|
1951
|
+
}
|
|
1952
|
+
return true;
|
|
1953
|
+
},
|
|
1954
|
+
},
|
|
1955
|
+
]);
|
|
1956
|
+
recordWeWorkUserUsage(notify, notifyChoices);
|
|
1957
|
+
let branchName = environment;
|
|
1958
|
+
let processId = "";
|
|
1959
|
+
// 如果是正式环境,需要输入要部署的分支及审批编号
|
|
1960
|
+
if (environment === "production" /* EEnv.PROD */) {
|
|
1961
|
+
const currentProject = yield getCurrentProject();
|
|
1962
|
+
const branchList = yield getRemoteBranchListApi(currentProject.id);
|
|
1963
|
+
const choices = branchList
|
|
1964
|
+
.filter((i) => ![...PROD_BRANCH, ...INFORM_MAIN_BRANCHES].includes(i.name))
|
|
1965
|
+
.map((i) => {
|
|
1966
|
+
return {
|
|
1967
|
+
name: i.name,
|
|
1968
|
+
value: i.name,
|
|
1969
|
+
};
|
|
1970
|
+
});
|
|
1971
|
+
const answer = yield inquirer.prompt([
|
|
1972
|
+
{
|
|
1973
|
+
type: "list",
|
|
1974
|
+
name: "branchName",
|
|
1975
|
+
message: "请输入要部署的分支:",
|
|
1976
|
+
choices,
|
|
1977
|
+
},
|
|
1978
|
+
{
|
|
1979
|
+
type: "input",
|
|
1980
|
+
name: "processId",
|
|
1981
|
+
message: "请输入本次部署的企微审批编号",
|
|
1982
|
+
},
|
|
1983
|
+
]);
|
|
1984
|
+
branchName = answer.branchName;
|
|
1985
|
+
processId = answer.processId;
|
|
1986
|
+
if (!validateBranchName(branchName)) {
|
|
1987
|
+
error("分支命名不符合规范,参见https://zhongshitech.yuque.com/nx4ugr/guide/mg8t44#bQhu4");
|
|
1988
|
+
}
|
|
1989
|
+
const { data: { state, content }, } = yield getProcessStateApi({ businessId: processId });
|
|
1990
|
+
// 校验审批编号
|
|
1991
|
+
if (state === 1 /* EProcessState.PROCESSING */) {
|
|
1992
|
+
error("当前审批流程状态为审批中,审批通过才能进行发布。");
|
|
1993
|
+
}
|
|
1994
|
+
if (state === 3 /* EProcessState.REJECT */) {
|
|
1995
|
+
error("当前审批流程状态为已驳回,请重新提交审批。");
|
|
1996
|
+
}
|
|
1997
|
+
if (state === 4 /* EProcessState.EXPIRED */) {
|
|
1998
|
+
error("当前审批流程状态为已发布,如需再次发布,请重新提交审批。");
|
|
1999
|
+
}
|
|
2000
|
+
// 校验审批编号对应的审批信息与实际发布的信息是否一致
|
|
2001
|
+
const { formComponentValues } = JSON.parse(content);
|
|
2002
|
+
const formData = {};
|
|
2003
|
+
formComponentValues.forEach((i) => {
|
|
2004
|
+
formData[i.name] = i.value;
|
|
2005
|
+
});
|
|
2006
|
+
if (formData["申请人"] !== getProfileConfig("weWorkName")) {
|
|
2007
|
+
error("发布审批申请人与当前发布人不一致!");
|
|
2008
|
+
}
|
|
2009
|
+
if (formData["项目名称"] !== getProjectPackageJSON().name) {
|
|
2010
|
+
error("发布审批项目名称与当前发布项目名称不一致!");
|
|
2011
|
+
}
|
|
2012
|
+
if (formData["发布分支"] !== branchName) {
|
|
2013
|
+
error("发布审批发布分支与当前发布分支不一致!");
|
|
2014
|
+
}
|
|
2015
|
+
if (formData["发布平台"] !== platform) {
|
|
2016
|
+
error("发布审批发布平台与当前发布平台不一致!");
|
|
2017
|
+
}
|
|
2018
|
+
}
|
|
2019
|
+
yield deploy({
|
|
2020
|
+
originBranch: branchName,
|
|
2021
|
+
environment,
|
|
2022
|
+
platform,
|
|
2023
|
+
notify,
|
|
2024
|
+
processId,
|
|
2025
|
+
});
|
|
2026
|
+
});
|
|
2027
|
+
}
|
|
2028
|
+
|
|
2029
|
+
function mergeAction() {
|
|
2030
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
2031
|
+
var _a, _b;
|
|
2032
|
+
const currentProject = yield getCurrentProject();
|
|
2033
|
+
const branchList = yield getRemoteBranchListApi(currentProject.id);
|
|
2034
|
+
const informMainBranches = [...INFORM_MAIN_BRANCHES];
|
|
2035
|
+
const choices = branchList
|
|
2036
|
+
.filter((i) => ![...PROD_BRANCH, currentProject.sourceBranch].includes(i.name))
|
|
2037
|
+
.map((i) => {
|
|
2038
|
+
let name = i.name;
|
|
2039
|
+
if (informMainBranches.includes(name)) {
|
|
2040
|
+
name = chalk.bold.blue(name);
|
|
2041
|
+
}
|
|
2042
|
+
return {
|
|
2043
|
+
name,
|
|
2044
|
+
value: i.name,
|
|
2045
|
+
};
|
|
2046
|
+
});
|
|
2047
|
+
const { target } = yield inquirer.prompt([
|
|
2048
|
+
{
|
|
2049
|
+
type: "list",
|
|
2050
|
+
name: "target",
|
|
2051
|
+
message: "请选择要合并到的目标分支:",
|
|
2052
|
+
choices,
|
|
2053
|
+
},
|
|
2054
|
+
]);
|
|
2055
|
+
// 如果目标分支是非正式环境的三个主要分支,则直接部署
|
|
2056
|
+
// 如果不是,则只合并,不部署
|
|
2057
|
+
const isDeploy = informMainBranches.includes(target);
|
|
2058
|
+
let platform = "h5" /* EPlatform.H5 */;
|
|
2059
|
+
let notify = [];
|
|
2060
|
+
if (isDeploy) {
|
|
2061
|
+
const notifyChoices = yield getSortedWeWorkUserList();
|
|
2062
|
+
const answers = yield inquirer.prompt([
|
|
2063
|
+
{
|
|
2064
|
+
type: "list",
|
|
2065
|
+
name: "platform",
|
|
2066
|
+
message: "请选择平台",
|
|
2067
|
+
choices: [
|
|
2068
|
+
{
|
|
2069
|
+
name: `网页 - ${"h5" /* EPlatform.H5 */}`,
|
|
2070
|
+
value: "h5" /* EPlatform.H5 */,
|
|
2071
|
+
},
|
|
2072
|
+
{
|
|
2073
|
+
name: `支付宝小程序 - ${"alipay" /* EPlatform.ALIPAY */}`,
|
|
2074
|
+
value: "alipay" /* EPlatform.ALIPAY */,
|
|
2075
|
+
},
|
|
2076
|
+
{
|
|
2077
|
+
name: `微信小程序 - ${"weapp" /* EPlatform.WEAPP */}`,
|
|
2078
|
+
value: "weapp" /* EPlatform.WEAPP */,
|
|
2079
|
+
},
|
|
2080
|
+
],
|
|
2081
|
+
},
|
|
2082
|
+
{
|
|
2083
|
+
type: "search-checkbox",
|
|
2084
|
+
name: "notify",
|
|
2085
|
+
message: "请选择部署成功要通知的人员:",
|
|
2086
|
+
choices: notifyChoices,
|
|
2087
|
+
validate(input) {
|
|
2088
|
+
if (input.length < 1) {
|
|
2089
|
+
return "至少选一个";
|
|
2090
|
+
}
|
|
2091
|
+
return true;
|
|
2092
|
+
},
|
|
2093
|
+
},
|
|
2094
|
+
]);
|
|
2095
|
+
recordWeWorkUserUsage(notify, notifyChoices);
|
|
2096
|
+
platform = answers.platform;
|
|
2097
|
+
notify = answers.notify;
|
|
2098
|
+
}
|
|
2099
|
+
// 开始分支合并
|
|
2100
|
+
// 保证工作区空状态
|
|
2101
|
+
const gitStatus = yield asyncExec("git status -s");
|
|
2102
|
+
if (gitStatus !== "") {
|
|
2103
|
+
error("请先提交代码变动,再进行分支合并。");
|
|
2104
|
+
}
|
|
2105
|
+
// 获取最新git info
|
|
2106
|
+
yield asyncExec("git fetch origin");
|
|
2107
|
+
// 提交当前分支到远程
|
|
2108
|
+
yield asyncExec(`git push -u origin ${currentProject.sourceBranch}`);
|
|
2109
|
+
// MR ID
|
|
2110
|
+
let mergeRequestIID = 0;
|
|
2111
|
+
// 创建MR
|
|
2112
|
+
try {
|
|
2113
|
+
const { iid } = yield createMRApi({
|
|
2114
|
+
title: `MR by z-develop`,
|
|
2115
|
+
id: currentProject.id,
|
|
2116
|
+
source_branch: currentProject.sourceBranch,
|
|
2117
|
+
target_branch: target,
|
|
2118
|
+
});
|
|
2119
|
+
mergeRequestIID = iid;
|
|
2120
|
+
}
|
|
2121
|
+
catch (e) {
|
|
2122
|
+
if (axios.isAxiosError(e)) {
|
|
2123
|
+
const status = (_a = e === null || e === void 0 ? void 0 : e.response) === null || _a === void 0 ? void 0 : _a.status;
|
|
2124
|
+
if (status === 409) {
|
|
2125
|
+
error(`存在重复的合并请求,前往查看${currentProject.mergeRequestUrl}`);
|
|
2126
|
+
}
|
|
2127
|
+
}
|
|
2128
|
+
error(e);
|
|
2129
|
+
}
|
|
2130
|
+
// 合并MR
|
|
2131
|
+
try {
|
|
2132
|
+
yield mergeMRApi({
|
|
2133
|
+
id: currentProject.id,
|
|
2134
|
+
iid: mergeRequestIID,
|
|
2135
|
+
});
|
|
2136
|
+
}
|
|
2137
|
+
catch (e) {
|
|
2138
|
+
if (axios.isAxiosError(e)) {
|
|
2139
|
+
const status = (_b = e === null || e === void 0 ? void 0 : e.response) === null || _b === void 0 ? void 0 : _b.status;
|
|
2140
|
+
if (status === 405) {
|
|
2141
|
+
yield updateMRApi({
|
|
2142
|
+
id: currentProject.id,
|
|
2143
|
+
iid: mergeRequestIID,
|
|
2144
|
+
state_event: "close",
|
|
2145
|
+
});
|
|
2146
|
+
error(`合并过程中出现冲突,MR已关闭。建议在本地完成合并。`);
|
|
2147
|
+
}
|
|
2148
|
+
}
|
|
2149
|
+
error(e);
|
|
2150
|
+
}
|
|
2151
|
+
// 开始部署
|
|
2152
|
+
// 如果是非正式环境主要分支,开始部署
|
|
2153
|
+
if (isDeploy) {
|
|
2154
|
+
yield deploy({
|
|
2155
|
+
originBranch: target,
|
|
2156
|
+
environment: target,
|
|
2157
|
+
platform,
|
|
2158
|
+
notify,
|
|
2159
|
+
processId: "",
|
|
2160
|
+
});
|
|
2161
|
+
}
|
|
2162
|
+
// 如果是其他分支,不部署,忽略通知
|
|
2163
|
+
});
|
|
2164
|
+
}
|
|
2165
|
+
|
|
2166
|
+
program.command("merge").alias("m").description("合并当前分支到指定远程分支,并部署。").action(mergeAction);
|
|
2167
|
+
|
|
2168
|
+
program.command("deploy").alias("d").description("发布指定远程分支").action(deployAction);
|
|
2169
|
+
|
|
2170
|
+
function startAction() {
|
|
2171
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
2172
|
+
// 确定当前是项目根目录
|
|
2173
|
+
const pkg = getProjectPackageJSON();
|
|
2174
|
+
const { environment, platform } = yield inquirer.prompt([
|
|
2175
|
+
{
|
|
2176
|
+
type: "list",
|
|
2177
|
+
name: "environment",
|
|
2178
|
+
message: "请选择环境",
|
|
2179
|
+
choices: ENVIRONMENT_CHOICES,
|
|
2180
|
+
},
|
|
2181
|
+
{
|
|
2182
|
+
type: "list",
|
|
2183
|
+
name: "platform",
|
|
2184
|
+
message: "请选择平台",
|
|
2185
|
+
choices: PLATFORM_CHOICES,
|
|
2186
|
+
},
|
|
2187
|
+
]);
|
|
2188
|
+
const scriptKey = `dev:${environment}:${platform}`;
|
|
2189
|
+
if (pkg.scripts[scriptKey]) {
|
|
2190
|
+
try {
|
|
2191
|
+
shelljs.exec(`npm run ${scriptKey}`, {
|
|
2192
|
+
silent: false,
|
|
2193
|
+
});
|
|
2194
|
+
}
|
|
2195
|
+
catch (e) {
|
|
2196
|
+
error(e);
|
|
2197
|
+
}
|
|
2198
|
+
}
|
|
2199
|
+
else {
|
|
2200
|
+
error(`项目中(package.josn > scripts)不存在命令${scriptKey},请先添加!`);
|
|
2201
|
+
}
|
|
2202
|
+
});
|
|
2203
|
+
}
|
|
2204
|
+
|
|
2205
|
+
program.command("start").alias("s").description("启动本地开发环境").action(startAction);
|
|
2206
|
+
|
|
2207
|
+
function lintCommitMsg() {
|
|
2208
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
2209
|
+
const msg = fs.readFileSync("./.git/COMMIT_EDITMSG", "utf-8");
|
|
2210
|
+
const result = validateMsg(msg);
|
|
2211
|
+
if (result === true) {
|
|
2212
|
+
success("commit msg validate success.");
|
|
2213
|
+
}
|
|
2214
|
+
else {
|
|
2215
|
+
error(`commit msg格式错误。${result}`);
|
|
2216
|
+
}
|
|
2217
|
+
});
|
|
2218
|
+
}
|
|
2219
|
+
function validateMsg(msg) {
|
|
2220
|
+
const msg2 = msg.split(": ");
|
|
2221
|
+
if (msg2.length === 1) {
|
|
2222
|
+
return "body前缺少「:」";
|
|
2223
|
+
}
|
|
2224
|
+
else {
|
|
2225
|
+
const header = msg2[0];
|
|
2226
|
+
if (/^(feat|fix|refactor)/.test(header)) {
|
|
2227
|
+
if (/\(\d+(?:,\d+)*\)/.test(header)) {
|
|
2228
|
+
return true;
|
|
2229
|
+
}
|
|
2230
|
+
else {
|
|
2231
|
+
return "ID缺少或者格式不正确。";
|
|
2232
|
+
}
|
|
2233
|
+
}
|
|
2234
|
+
else if (/^chore/.test(header)) {
|
|
2235
|
+
if (header === "chore") {
|
|
2236
|
+
return true;
|
|
2237
|
+
}
|
|
2238
|
+
else {
|
|
2239
|
+
return "chore类型无需填写ID。";
|
|
2240
|
+
}
|
|
2241
|
+
}
|
|
2242
|
+
else {
|
|
2243
|
+
return "不存在的提交类型。";
|
|
2244
|
+
}
|
|
2245
|
+
}
|
|
2246
|
+
}
|
|
2247
|
+
|
|
2248
|
+
function lintCommitFiles() {
|
|
2249
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
2250
|
+
const result = yield lintStaged({
|
|
2251
|
+
concurrent: false,
|
|
2252
|
+
debug: undefined,
|
|
2253
|
+
config: getConfig(),
|
|
2254
|
+
});
|
|
2255
|
+
if (result) {
|
|
2256
|
+
success("代码风格检测通过!");
|
|
2257
|
+
}
|
|
2258
|
+
else {
|
|
2259
|
+
error("代码风格检测未通过!");
|
|
2260
|
+
}
|
|
2261
|
+
});
|
|
2262
|
+
}
|
|
2263
|
+
function getConfig() {
|
|
2264
|
+
if (fs.existsSync(path.resolve(".z/project.json"))) {
|
|
2265
|
+
const project = fse.readJsonSync(path.resolve(".z/project.json"));
|
|
2266
|
+
if (project["lint-staged"]) {
|
|
2267
|
+
return project["lint-staged"];
|
|
2268
|
+
}
|
|
2269
|
+
else {
|
|
2270
|
+
throw new Error("未找到lint配置");
|
|
2271
|
+
}
|
|
2272
|
+
}
|
|
2273
|
+
else {
|
|
2274
|
+
throw new Error("请先初始化项目(z i .)。");
|
|
2275
|
+
}
|
|
2276
|
+
}
|
|
2277
|
+
|
|
2278
|
+
function lintAction(_type) {
|
|
2279
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
2280
|
+
let type;
|
|
2281
|
+
if (_type) {
|
|
2282
|
+
if (["commit-msg", "cm"].includes(_type)) {
|
|
2283
|
+
type = "commit-msg";
|
|
2284
|
+
}
|
|
2285
|
+
else if (["commit-files", "cf"].includes(_type)) {
|
|
2286
|
+
type = "commit-files";
|
|
2287
|
+
}
|
|
2288
|
+
else if (["prettier", "p"].includes(_type)) {
|
|
2289
|
+
type = "prettier";
|
|
2290
|
+
}
|
|
2291
|
+
else if (["eslint", "e"].includes(_type)) {
|
|
2292
|
+
type = "eslint";
|
|
2293
|
+
}
|
|
2294
|
+
else {
|
|
2295
|
+
error("参数输入错误");
|
|
2296
|
+
}
|
|
2297
|
+
}
|
|
2298
|
+
else {
|
|
2299
|
+
const res = yield inquirer.prompt({
|
|
2300
|
+
type: "list",
|
|
2301
|
+
name: "type",
|
|
2302
|
+
message: "请选择你要执行的操作",
|
|
2303
|
+
choices: [
|
|
2304
|
+
{
|
|
2305
|
+
name: "全局执行 prettier",
|
|
2306
|
+
value: "prettier",
|
|
2307
|
+
},
|
|
2308
|
+
{
|
|
2309
|
+
name: "全局执行 eslint",
|
|
2310
|
+
value: "eslint",
|
|
2311
|
+
},
|
|
2312
|
+
],
|
|
2313
|
+
});
|
|
2314
|
+
type = res.type;
|
|
2315
|
+
}
|
|
2316
|
+
if (type === "commit-msg") {
|
|
2317
|
+
yield lintCommitMsg();
|
|
2318
|
+
}
|
|
2319
|
+
else if (type === "commit-files") {
|
|
2320
|
+
yield lintCommitFiles();
|
|
2321
|
+
}
|
|
2322
|
+
else if (type === "prettier") {
|
|
2323
|
+
if (!fs.existsSync("node_modules/.bin/prettier")) {
|
|
2324
|
+
error("该项目未安装prettier,请安装后重试");
|
|
2325
|
+
}
|
|
2326
|
+
try {
|
|
2327
|
+
yield asyncExec("prettier --write . --config .z/.prettierrc.json --ignore-path .z/.prettierignore --ignore-unknown --no-error-on-unmatched-pattern", {
|
|
2328
|
+
silent: false,
|
|
2329
|
+
});
|
|
2330
|
+
success("prettier执行成功");
|
|
2331
|
+
}
|
|
2332
|
+
catch (e) {
|
|
2333
|
+
error("prettier校验出错");
|
|
2334
|
+
}
|
|
2335
|
+
}
|
|
2336
|
+
else if (type === "eslint") {
|
|
2337
|
+
if (!fs.existsSync("node_modules/.bin/eslint")) {
|
|
2338
|
+
error("该项目未安装eslint,请安装后重试");
|
|
2339
|
+
}
|
|
2340
|
+
try {
|
|
2341
|
+
yield asyncExec("eslint '**/*.{vue,js,jsx,cjs,mjs,ts,tsx,cts,mts}' --fix", {
|
|
2342
|
+
silent: false,
|
|
2343
|
+
});
|
|
2344
|
+
success("eslint执行成功");
|
|
2345
|
+
}
|
|
2346
|
+
catch (e) {
|
|
2347
|
+
error("eslint校验出错");
|
|
2348
|
+
}
|
|
2349
|
+
}
|
|
2350
|
+
});
|
|
2351
|
+
}
|
|
2352
|
+
|
|
2353
|
+
program
|
|
2354
|
+
.command("lint")
|
|
2355
|
+
.alias("l")
|
|
2356
|
+
.argument("[type]", "非必填。可选值为commit-msg|cm, commit-files|cf, prettier|p, eslint|e。" +
|
|
2357
|
+
"传 cm 为执行 lint commit-msg;传 cf 为执行 lint commit-files;传 p 为全部文件执行prettier;传 e 为全部文件执行eslint。" +
|
|
2358
|
+
"cm 和 cf属于内部命令,会在git hooks中自动执行。")
|
|
2359
|
+
.description("执行 lint 脚本。包含 lint staged 、 lint commit message、 prettier、 eslint。")
|
|
2360
|
+
.action(lintAction);
|
|
2361
|
+
|
|
2362
|
+
preInit();
|
|
2363
|
+
program
|
|
2364
|
+
.name("z-develop")
|
|
2365
|
+
.alias("z")
|
|
2366
|
+
.description(`z-develop, 开发流程管理工具。\n了解更多: ${chalk.blue("https://zxf-fe.yuque.com/org-wiki-zxf-fe-ex4bve/qi89vg/us8lgdb5cq4kf3aa")}`)
|
|
2367
|
+
.usage("<command> [options]")
|
|
2368
|
+
.version(pkg.version, "-v, --version", "当前版本号")
|
|
2369
|
+
.helpOption("-h, --help", "帮助")
|
|
2370
|
+
.showHelpAfterError("可以使用z -h查看帮助。");
|
|
2371
|
+
program.parse();
|