upload.js 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172
  1. const chalk = require('chalk') //命令行颜色
  2. const ora = require('ora') // 加载流程动画
  3. const spinner_style = require('./spinner_style') //加载动画样式
  4. const shell = require('shelljs') // 执行shell命令
  5. const node_ssh = require('node-ssh') // ssh连接服务器
  6. const inquirer = require('inquirer') //命令行交互
  7. const zipFile = require('compressing')// 压缩zip
  8. const fs = require('fs') // nodejs内置文件模块
  9. const path = require('path') // nodejs内置路径模块
  10. const CONFIG = require('./config') // 配置
  11. const SSH = new node_ssh();
  12. let config; // 用于保存 inquirer 命令行交互后选择正式|测试版的配置
  13. //logs
  14. const defaultLog = log => console.log(chalk.blue(`---------------- ${log} ----------------`));
  15. const errorLog = log => console.log(chalk.red(`---------------- ${log} ----------------`));
  16. const successLog = log => console.log(chalk.green(`---------------- ${log} ----------------`));
  17. //文件夹目录
  18. const distDir = path.resolve(__dirname, '../dist'); //待打包
  19. const distZipPath = path.resolve(__dirname, `../dist.zip`); //打包后地址(dist.zip是文件名,不需要更改, 主要在config中配置 PATH 即可)
  20. //项目打包代码 npm run build
  21. const compileDist = async () => {
  22. const loading = ora( defaultLog('项目开始打包') ).start();
  23. loading.spinner = spinner_style.arrow4;
  24. shell.cd(path.resolve(__dirname, '../'));
  25. const res = await shell.exec('npm run build'); //执行shell 打包命令
  26. loading.stop();
  27. if(res.code === 0) {
  28. successLog('项目打包成功!');
  29. } else {
  30. errorLog('项目打包失败, 请重试!');
  31. process.exit(); //退出流程
  32. }
  33. }
  34. //压缩代码
  35. const zipDist = async ()=>{
  36. defaultLog('项目开始压缩');
  37. try {
  38. await zipFile.zip.compressDir(distDir, distZipPath)
  39. successLog('压缩成功!');
  40. } catch (error) {
  41. errorLog(error);
  42. errorLog('压缩失败, 退出程序!');
  43. process.exit(); //退出流程
  44. }
  45. }
  46. //连接服务器
  47. const connectSSH = async ()=>{
  48. const loading = ora( defaultLog('正在连接服务器') ).start();
  49. loading.spinner = spinner_style.arrow4;
  50. try {
  51. await SSH.connect({
  52. host: config.SERVER_PATH,
  53. username: config.SSH_USER,
  54. // privateKey: config.PRIVATE_KEY, //秘钥登录(推荐) 方式一
  55. password: config.PASSWORD // 密码登录 方式二
  56. });
  57. successLog('SSH连接成功!');
  58. } catch (error) {
  59. errorLog(error);
  60. errorLog('SSH连接失败!');
  61. process.exit(); //退出流程
  62. }
  63. loading.stop();
  64. }
  65. //线上执行命令
  66. /**
  67. *
  68. * @param {String} command 命令操作 如 ls
  69. */
  70. const runCommand = async (command)=> {
  71. const result = await SSH.exec(command, [], { cwd: config.PATH})
  72. // defaultLog(result);
  73. }
  74. //清空线上目标目录里的旧文件
  75. const clearOldFile = async () =>{
  76. const commands = ['ls', 'rm -rf *'];
  77. await Promise.all(commands.map(async (it)=>{
  78. return await runCommand(it);
  79. }));
  80. }
  81. //传送zip文件到服务器
  82. const uploadZipBySSH = async () =>{
  83. //连接ssh
  84. await connectSSH();
  85. //线上目标文件清空
  86. await clearOldFile();
  87. const loading = ora( defaultLog('准备上传文件') ).start();
  88. loading.spinner = spinner_style.arrow4;
  89. try {
  90. await SSH.putFiles([{ local: distZipPath, remote: config.PATH + '/dist.zip' }]); //local 本地 ; remote 服务器 ;
  91. successLog('上传成功!');
  92. loading.text = '正在解压文件';
  93. await runCommand('unzip ./dist.zip'); //解压
  94. await runCommand(`rm -rf ${config.PATH}/dist.zip`); //解压完删除线上压缩包
  95. //将目标目录的dist里面文件移出到目标文件
  96. //举个例子 假如我们部署在 /test/html 这个目录下 只有一个网站, 那么上传解压后的文件在 /test/html/dist 里
  97. //需要将 dist 目录下的文件 移出到 /test/html ; 多网站情况, 如 /test/html/h5 或者 /test/html/admin 都和上面同样道理
  98. await runCommand(`mv -f ${config.PATH}/dist/* ${config.PATH}`);
  99. await runCommand(`rm -rf ${config.PATH}/dist`); //移出后删除 dist 文件夹
  100. SSH.dispose(); //断开连接
  101. } catch (error) {
  102. errorLog(error);
  103. errorLog('上传失败!');
  104. process.exit(); //退出流程
  105. }
  106. loading.stop();
  107. }
  108. //------------发布程序---------------
  109. const runUploadTask = async () => {
  110. console.log(chalk.yellow(`---------> 欢迎使用 OnePsycho.com 2020年自动部署工具 <---------`));
  111. //打包
  112. await compileDist();
  113. //压缩
  114. await zipDist();
  115. //连接服务器上传文件
  116. await uploadZipBySSH();
  117. successLog('大吉大利, 部署成功!');
  118. process.exit();
  119. }
  120. // 开始前的配置检查
  121. /**
  122. *
  123. * @param {Object} conf 配置对象
  124. */
  125. const checkConfig = (conf) =>{
  126. const checkArr = Object.entries(conf);
  127. checkArr.map(it=>{
  128. const key = it[0];
  129. if(key === 'PATH' && conf[key] === '/') { //上传zip前会清空目标目录内所有文件
  130. errorLog('PATH 不能是服务器根目录!');
  131. process.exit(); //退出流程
  132. }
  133. if(!conf[key]) {
  134. errorLog(`配置项 ${key} 不能为空`);
  135. process.exit(); //退出流程
  136. }
  137. })
  138. }
  139. // 执行交互后 启动发布程序
  140. inquirer
  141. .prompt([{
  142. type: 'list',
  143. message: '请选择发布环境',
  144. name: 'env',
  145. choices: [{
  146. name: '测试环境',
  147. value: 'development'
  148. },{
  149. name: '正式环境',
  150. value: 'production'
  151. }]
  152. }])
  153. .then(answers => {
  154. config = CONFIG[answers.env];
  155. checkConfig(config); // 检查
  156. runUploadTask(); // 发布
  157. });