upload.js 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166
  1. /* eslint-disable new-cap */
  2. const path = require('path')
  3. const chalk = require('chalk') // 命令行颜色
  4. const ora = require('ora') // 加载流程动画
  5. const shell = require('shelljs') // 执行shell命令
  6. const { NodeSSH } = require('node-ssh') // ssh连接服务器
  7. const inquirer = require('inquirer') // 命令行交互
  8. const zipFile = require('compressing')// 压缩zip
  9. const spinner_style = require('./spinner_style') // 加载动画样式
  10. const CONFIG = require('./config') // 配置
  11. const SSH = new NodeSSH()
  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')
  20. // 项目打包代码 yarn build
  21. const compileDist = async () => {
  22. const loading = ora(defaultLog('project build start')).start()
  23. loading.spinner = spinner_style.arrow4
  24. shell.cd(path.resolve(__dirname, '../'))
  25. const res = await shell.exec('yarn build') // 执行shell 打包命令
  26. loading.stop()
  27. if (res.code === 0) {
  28. successLog('project build success!')
  29. } else {
  30. errorLog('project build error, please try again!')
  31. process.exit() // 退出流程
  32. }
  33. }
  34. // 压缩代码
  35. const zipDist = async () => {
  36. defaultLog('project zip start')
  37. try {
  38. await zipFile.zip.compressDir(distDir, distZipPath)
  39. successLog('project zip success!')
  40. } catch (error) {
  41. errorLog(error)
  42. errorLog('project zip error, exit process!')
  43. process.exit() // 退出流程
  44. }
  45. }
  46. // 连接服务器
  47. const connectSSH = async () => {
  48. const loading = ora(defaultLog('connecting server')).start()
  49. loading.spinner = spinner_style.arrow4
  50. try {
  51. await SSH.connect({
  52. host: config.SERVER_PATH,
  53. username: config.SSH_USER,
  54. password: config.PASSWORD,
  55. })
  56. successLog('SSH connect success!')
  57. } catch (error) {
  58. errorLog(error)
  59. errorLog('SSH connect error!')
  60. process.exit() // 退出流程
  61. }
  62. loading.stop()
  63. }
  64. // 线上执行命令
  65. /**
  66. *
  67. * @param {String} command 命令操作 如 ls
  68. */
  69. const runCommand = async (command) => {
  70. const result = await SSH.exec(command, [], { cwd: config.PATH })
  71. defaultLog(result)
  72. }
  73. // 清空线上目标目录里的旧文件
  74. const clearOldFile = async () => {
  75. const commands = ['ls', 'rm -rf *']
  76. await Promise.all(commands.map(async (it) => {
  77. return await runCommand(it)
  78. }))
  79. }
  80. // 传送zip文件到服务器
  81. const uploadZipBySSH = async () => {
  82. // 连接ssh
  83. await connectSSH()
  84. // 线上目标文件清空
  85. await clearOldFile()
  86. const loading = ora(defaultLog('prepare to upload files')).start()
  87. loading.spinner = spinner_style.arrow4
  88. try {
  89. await SSH.putFiles([{ local: distZipPath, remote: config.PATH + '/dist.zip' }]) // local 本地 ; remote 服务器 ;
  90. successLog('upload files success!')
  91. loading.text = 'unzip file'
  92. await runCommand('unzip ./dist.zip') // 解压
  93. await runCommand(`rm -rf ${config.PATH}/dist.zip`) // 解压完删除线上压缩包
  94. // 将目标目录的dist里面文件移出到目标文件
  95. await runCommand(`mv -f ${config.PATH}/dist/* ${config.PATH}`)
  96. await runCommand(`rm -rf ${config.PATH}/dist`) // 移出后删除 dist 文件夹
  97. SSH.dispose() // 断开连接
  98. } catch (error) {
  99. errorLog(error)
  100. errorLog('upload failed!')
  101. process.exit() // 退出流程
  102. }
  103. loading.stop()
  104. }
  105. // ------------发布程序---------------
  106. const runUploadTask = async () => {
  107. console.log(chalk.yellow('---------> Welcome to MJ 2020 automatic deployment tool <---------'))
  108. // 打包
  109. await compileDist()
  110. // 压缩
  111. await zipDist()
  112. // 连接服务器上传文件
  113. await uploadZipBySSH()
  114. successLog('Good luck, successful deployment!')
  115. process.exit()
  116. }
  117. // 开始前的配置检查
  118. /**
  119. *
  120. * @param {Object} conf 配置对象
  121. */
  122. const checkConfig = (conf) => {
  123. const checkArr = Object.entries(conf)
  124. checkArr.map(it => {
  125. const key = it[0]
  126. if (key === 'PATH' && conf[key] === '/') { // 上传zip前会清空目标目录内所有文件
  127. errorLog('PATH cannot be the server root directory!')
  128. process.exit() // 退出流程
  129. }
  130. if (!conf[key]) {
  131. errorLog(`Configuration ${key} cannot be empty`)
  132. process.exit() // 退出流程
  133. }
  134. })
  135. }
  136. // 执行交互后 启动发布程序
  137. inquirer
  138. .prompt([{
  139. type: 'list',
  140. message: 'Please select publishing environment',
  141. name: 'env',
  142. choices: [{
  143. name: 'development',
  144. value: 'development',
  145. }, {
  146. name: 'production',
  147. value: 'production',
  148. }],
  149. }])
  150. .then(answers => {
  151. config = CONFIG[answers.env]
  152. checkConfig(config) // 检查
  153. runUploadTask() // 发布
  154. })