deploy.js 3.4 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798
  1. #!/usr/bin/env node
  2. import inquirer from 'inquirer'
  3. import { execSync } from 'child_process'
  4. import fs from 'fs'
  5. import { join } from 'path'
  6. import { tmpdir } from 'os'
  7. import archiver from 'archiver'
  8. import dayjs from 'dayjs'
  9. // ---------- 环境配置 ----------
  10. const ENV_CONFIG = {
  11. dev: { remoteUser: 'root', remoteIP: '192.168.2.140', remoteAppDir: '/work/web/dist' },
  12. test: { remoteUser: 'liujia', remoteIP: '43.137.10.199', remoteAppDir: '/work/web/dist' },
  13. prod: { remoteUser: 'root', remoteIP: '192.168.2.140', remoteAppDir: '/work/web/dist-prod' },
  14. }
  15. const SSH_PORT = 22
  16. const LOCAL_DIST = 'D:/project/ln-web/dist'
  17. // ---------- 处理环境参数 ----------
  18. let env = process.argv[2] // npm run deploy:dev 传入 dev/test/prod
  19. if (!env || !ENV_CONFIG[env]) {
  20. const answer = await inquirer.prompt([
  21. {
  22. type: 'list',
  23. name: 'env',
  24. message: '请选择要部署的环境:',
  25. choices: Object.keys(ENV_CONFIG),
  26. default: 'dev',
  27. },
  28. ])
  29. env = answer.env
  30. }
  31. const { remoteUser, remoteIP, remoteAppDir } = ENV_CONFIG[env]
  32. console.log(`\n你选择的环境是: ${env}, 远程目录: ${remoteAppDir}\n`)
  33. // ---------- 压缩 dist ----------
  34. const timestamp = dayjs().format('YYYYMMDDHHmmss')
  35. const zipFileName = `dist-${timestamp}.zip`
  36. const zipPath = join(tmpdir(), zipFileName)
  37. console.log('📦 正在压缩 dist...')
  38. if (!fs.existsSync(LOCAL_DIST)) {
  39. console.error(`❌ 本地 dist 目录不存在: ${LOCAL_DIST}`)
  40. process.exit(1)
  41. }
  42. await new Promise((resolve, reject) => {
  43. const output = fs.createWriteStream(zipPath)
  44. const archive = archiver('zip', { zlib: { level: 9 } })
  45. output.on('close', () => resolve())
  46. archive.on('error', (err) => reject(err))
  47. archive.pipe(output)
  48. archive.directory(LOCAL_DIST, false)
  49. archive.finalize()
  50. })
  51. const zipSizeMB = (fs.statSync(zipPath).size / 1024 / 1024).toFixed(2)
  52. console.log(`✅ 压缩完成: ${zipSizeMB} MB, 文件: ${zipPath}`)
  53. // ---------- 上传 & 部署脚本生成 ----------
  54. const remoteZip = `/tmp/dist-latest-${timestamp}.zip`
  55. execSync(`scp -P ${SSH_PORT} "${zipPath}" ${remoteUser}@${remoteIP}:${remoteZip}`, {
  56. stdio: 'inherit',
  57. })
  58. const remoteScriptContent = `#!/bin/bash
  59. set -e
  60. timestamp="${timestamp}"
  61. tmpdir="/tmp/dist-temp-$timestamp"
  62. target="${remoteAppDir}"
  63. zipfile="${remoteZip}"
  64. echo "📦 正在服务器上原子部署 ..."
  65. echo "📁 创建临时目录 $tmpdir"
  66. mkdir -p "$tmpdir"
  67. echo "📦 解压上传文件"
  68. unzip -qo "$zipfile" -d "$tmpdir"
  69. echo "🔒 设置权限"
  70. chown -R root:root "$tmpdir"
  71. echo "🚀 替换新目录"
  72. rm -rf "$target"
  73. mv "$tmpdir" "$target"
  74. echo "🧹 删除压缩包"
  75. rm -f "$zipfile"
  76. echo "✅ 远程部署完成: $target 替换成功"
  77. `
  78. const localScriptPath = join(tmpdir(), `deploy-temp-${timestamp}.sh`)
  79. fs.writeFileSync(localScriptPath, remoteScriptContent, { encoding: 'utf8' })
  80. const remoteScriptPath = `/tmp/deploy-temp-${timestamp}.sh`
  81. execSync(`scp -P ${SSH_PORT} "${localScriptPath}" ${remoteUser}@${remoteIP}:${remoteScriptPath}`, {
  82. stdio: 'inherit',
  83. })
  84. console.log('\n🚀 执行远程部署脚本...')
  85. execSync(`ssh -p ${SSH_PORT} ${remoteUser}@${remoteIP} "bash ${remoteScriptPath}"`, {
  86. stdio: 'inherit',
  87. })
  88. console.log('\n🧹 清理本地/远程临时文件...')
  89. fs.unlinkSync(zipPath)
  90. fs.unlinkSync(localScriptPath)
  91. execSync(`ssh -p ${SSH_PORT} ${remoteUser}@${remoteIP} "rm -f ${remoteScriptPath} ${remoteZip}"`, {
  92. stdio: 'inherit',
  93. })
  94. console.log(`\n✅ [${env}] 环境部署完成!`)