自从遇到了google/zx,很快我就将原先使用shell脚本的代码,全部转换到了google/zx
相当于使用前端js来写脚本了,对前端来说实在太友好了
使用zx编写脚本,可以随心所欲的使用nodejs模块类库,我的demo使用mysql类库去将日志写入到mysql数据库, 当然你可以调用fs-extra,写入到本地文件当中,选择性很多了
使用mjs模块,可以像使用前端js代码一样轻松使用 import和 export
引用zx中的$符号,可以随意使用shell脚本中的所有命令
同时使用typescript来写zx的脚本,很是不错哟,不过在import时需要写成 ’./xxx.mjs‘,其实调用的就是mts
可以在代码中随意调用 npm、 yarn、 pnpm等,以及git pull 、git push 等等。
可以在代码中更好的编写脚本的逻辑控制,虽然在shell脚本中页可以实现,但对于前端工程师来说,用js这简直就是so easy的事情。
完美的使用await async来控制 同步和异步代码的执行顺序
1、之前通过shell脚本写的打包脚本
脚本入口文件地址 :github.com/aehyok/2022…克隆下来,有兴趣的可以下载下来看看,cd到本目录下就可以进行测试,将十五个应用编译后的文件自动复制到测试环境
sh build-all.sh -v 2.5.1.006 -p awpqc |tee build-log.txt -v 2.5.1.006 版本号设置 -p awpqc 分别代表五个项目
2、我通过zx进行了重写
如今我用google/zx(TypeScript)写的代码入口地址:github.com/aehyok/2022…
先说一下背景:
- PC:主应用 + 八个子应用(全部是vue3)
- APP: 主应用(vue2) + 两个子应用(vue3)
- Other: P、Q、W 其他三个都是一个应用就完事的
- 算一下,9+3+3=15个应用 我这脚本就是为了方便打包这15个应用
3、怎么做的呢
- 十五个应用每次都到对应的目录去yarn build,或者pnpm build,会相当麻烦,而且会有遗漏的可能。
- 项目在我本地都是可以运行的,所以这里我省去了, npm install 或者 yarn 或者pnpm i 。
- 通过我的小demo,便可以通过一个命令 pnpm build:all,将上面说的15个应用全部打包完毕,放到指定服务器上。
- 然后可以查看log日志,确认报错信息,或者直接线上测试查看发布情况。如果有个别的子应用出现问题,可以单独去打包子应用即可。
- 对每个子应用的打包目录,做了调整,共同打包到一个release目录,将这个目录作为一个代码仓库,方便回退和记录的。
- 过程:(这里面暂时使用的命令工具为yarn)
- 1、到指定的git仓库目录,拉取代码 cd path、git pull
- 2、安装依赖 (此步骤省略) yarn
- 3、代码仓库只有一个应用的直接编译应用 yarn build
- 4、代码仓库包含多个应用的 需要先到指定的子应用目录 cd path, 再进行yarn build
- 5、编译结束,根据版本号version 给代码仓库打上tag标签 git tag git push
- 6、将编译后的文件拷贝到服务器指定的目录 scp -r
- 7、所有的代码仓库都编译上传完毕以后,要将release代码仓库进行 git add . git commit git push,然后再打上对应的tag标签,推送到服务器
4、实际操作(根据上面的过程进行一步一步的代码实现)
- 1、拉取代码
// 精简版代码(不包括写日志) export const gitPullBy = async(name: string, path: string) => { try { const gitPullInfo = await $`cd ${path}; git pull;`; if (gitPullInfo.exitCode === 0) { console.log('git pull 拉取代码成功') } } catch { } };
- 2、安装依赖
这一步按照道理,拉取代码后,要进行pnpm i,安装依赖的过程。暂时通过手动挡处理:保证了项目在本地开发环境能够运行,也就是说依赖肯定安装好了,要不然都跑不起来,自动化任务或者脚本里,难道能判断依赖是否安装过,或者说有新增依赖,可以通过什么方式来监测吗?
- 3、直接编译子应用
// 精简版代码 export const yarnBuildBy = async (path) => { try { const buildInfo = await $` cd ${path};yarn build;`; if (buildInfo.exitCode === 0) { console.log(`yarn build end success`); } } catch(error) { } }
- 4、多个子应用批量编译
通过promise.all并发一起执行多个字应用的编译
// 精简版代码 export const yarnBuildChildList = async(list) => { try { const result =await Promise.all( list.map((item) => { return $`cd ${item}; yarn build`; }) ) if(result) { console.log('all', 'result') } } catch { } }
- 5、编译结束,根据版本号打tag标签
- 首先根据版本号判断是否已经存在相对应的version tag
- 如果存在的话,先删除标签;如果不存在,直接打tag标签
- 最后git push 推送到代码仓库即可
// 全过程代码 export const gitTag = async () => { const { name } = global.project const path = baseUrl + name // 判断是否已经存在tag标签 const isExist = await isExistTag(path) console.log(isExist, 'isExist') if (isExist) { // 如果存在泽进行删除tag标签的操作 let isSuccess = await deleteTag(path); if (isSuccess) { await addTag(path, isExist); } else { oneLogger(`delete tag [${global.version}] error`); } } else { await addTag(path,isExist); } }; const isExistTag = async (path) => { const result = await $` cd ${path};git tag;`; console.log("判断是否存在tag", result); if (result && result.exitCode === 0) { let array = result.stdout.split("\n"); if (array.length > 0 && array.includes(global.version)) { return true; } return false; } }; const deleteTag = async (path) => { oneLogger(`delete tag [${global.version}] start`); const result = await $` cd ${path}; git tag -d ${global.version}; git push origin :refs/tags/${global.version}`; if (result.exitCode === 0) { oneLogger(`delete tag [${global.version}] end success`); return true; } return false; }; /** * * @param {*} path 路径 * @param {*} isExist 0为不存在,直接创建的;1为已存在删除的,重新创建 */ const addTag = async (path, isExist) => { const result = await $`cd ${path}; git tag -a ${global.version} -m 'chore:version ${global.version}版本号'; git push origin ${global.version};`; if (result && result.exitCode === 0) { if (isExist) { oneLogger(`re create tag [${global.version}] success`); } else { oneLogger(`create tag [${global.version}] success`); } } }; //调用写入mysql日志的 const oneLogger = (info) => { console.log(info); const { name } = global.project writerLog(name, info, global.version); };
- 6、拷贝文件到指定服务器
这里如果想更方便一些,可以把本地的ssh生成的公钥拷贝到相应的linux服务器的ssh目录
export const copyFile = async(path: string) => { try { const result = await $`scp -r /e/work/git/dvs-2.x/release/cms/${path}/* root@139.9.184.171:/usr/local/sunlight/dvs/dvs-uis/${path}/` if(result.exitCode === 0) { oneLogger(`copy file [${global.version}] end success`) } else { console.log("fail", $`$?`); } } catch { oneLogger(`copy file [${global.version}] end error`) } } const oneLogger = (info) => { console.log(info); const { name } = global.project writerLog(name, info, global.version); };
- 7、整理所有项目编译后的文件
对于打包生成的文件进行release目录整理,当然你可以全部编译打包完,再一起copy到指定的服务器,根据需要都可以去调整。
// 这一步有打包的代码需要进行git push export const gitPushBy = async(name: string, path: string) => { try { await writerLog(name, `git push start`, global.version); // const message=`build:前端${name} -- commit-version:${global.version}` const message=`build:前端app、qrocde、wechat、park、console(child)commit-version:${global.version}` const result = await $`cd ${path}; git add . ; sleep 2; git commit -m ${message}; git push origin;` if(result && result.exitCode === 0 ) { await writerLog(name, `git push end success`, global.version); } else { await writerLog(name, `git push error: ${result.stderr}`, global.version); } } catch (error){ console.log(error, 'error') if(error.stdout.includes('nothing to commit, working tree clean')) { await writerLog(name, `git push nothing to commit`, global.version); } await writerLog(name, `git push error`, global.version); } }
5、在使用过程中的问题
- 1、 pre...和post命令的使用
"prebuild:all": "ts-node-esm pull.mts -v 4.5.3.007", "build:all": "concurrently \"pnpm build:app\" \"pnpm build:pc\" \"pnpm build:wechat\" \"pnpm build:park\" \"pnpm build:qrcode\" ", "postbuild:all": "ts-node-esm push.mts -v 4.5.3.007",
在执行命令时执行 yarn build:all,那么会先执行命令 yarn prebuild:all, 执行完以后,才会真正执行yarn build:all, 执行完yarn:build:all以后,会执行yarn: postbuild:all。
也就是pre为提前执行的钩子,post为结束后执行的钩子。但是如果使用pnpm执行 就不会执行pre和post钩子。(这里还没找到问题!!!!!)
- 2、concurrently,并行运行脚本,使用前先安装依赖 pnpm i concurrently
"build:all": "concurrently \"pnpm build:app\" \"pnpm build:pc\" \"pnpm build:wechat\" \"pnpm build:park\" \"pnpm build:qrcode\" ",
- 3、在命令行中 & 为并行, && 串行,在pnpm安装的依赖中行不行,所以使用了concurrently
"build": "ts-node-esm index.mts -v 4.5.3.007 -p app & ts-node-esm index.mts -v 4.5.3.007 -p pc & ts-node-esm index.mts -v 4.5.3.007 -p wechat & ts-node-esm index.mts -v 4.5.3.007 -p park & ts-node-esm index.mts -v 4.5.3.007 -p qrcode",
- 这样使用还是不能并行,pnpm build,或者yarn build,都一样(这里也没找到问题!!!!!)
6、执行日志管理
我这里暂时是通过mysql库,写入的数据库日志
- 1、实例化数据库链接对象
let _conn = mysql.createConnection({ // 创建mysql实例,这是我的个人数据库可以访问,尽量只去查看 host: "139.159.245.209", port: "3306", user: "meta", password: "meta", database: "meta", });
- 2、通过实例链接数据库
_conn.connect(function (err) { if (err) { console.log("fail to connect db", err.stack); throw err; } // 这里正真连接上数据库了。 console.log( "链接成功--db state %s and threadID %s", _conn.state, _conn.threadId ); logDbStat(); //此为记录数据库链接的线程和状态 }); const logDbState = function () { console.log( "db state %s and threadID %s", _conn.state, _conn.threadId ); };
- 3、用完后关闭链接
const close = (conn) => { conn.end((err) => { if (err) { console.log("error ", err); } else { console.log("关闭成功", conn.state, conn.threadId); } }); };
- 4、最后返回一个Promise 方便处理和调用
return new Promise((resolve, reject) => { try { let sqlParamsList = [sql]; if (params) { sqlParamsList.push(params); } _conn.query(...sqlParamsList, (err, res) => { if (err) { reject(err); } else { resolve(res); close(_conn); } }); } catch (error) { reject(error); } });
- 5、封装方法进行调用
export const writerLog = async (project, content, version) => { let id = shortid.generate() await executeSql("INSERT INTO CiCdLog values(?,?,?,?,null,null)", [id, project, content, version, ]); };
- 6、在使用的地方进行import导入,然后调用即可
import { writerLog } from "./sql-helper.mjs" // 传递三个参数即可 await writerLog(name, `git push error`, global.version);
7、测试命令行编译
一个pc代码仓库,一个主应用和两个子应用,脚本所在代码仓库链接地址github.com/aehyok/2022…
yarn build
执行yarn build的时候,会先调用 prebuild命令
然后执行完yarn build之后,会调用postbuild命令
通过代码产生的写入日志,如下截图所示
8、总结
zx可以处理很多的事情,这里我就不一一列举了,有兴趣的可以继续深入学习了解探索一下,或许也会发现新的大陆。