自用
第一步, 把项目里 upload 文件夹复制到你项目根目录
第二步, 下载该js相关依赖
npm 或 cnpm i chalk ora shelljs node-ssh inquirer compressing -D
第三步, 打开 upload/config.js 配置文件, 配置ssh地址, 用户名, 验证方式,需要上传的目录
第四步, 在你项目中 package.json 文件中 加上 "deploy": "node ./upload/upload.js"
最后 在命令行输入 npm run deploy 选择发布环境
config.js
module.exports = Object.freeze({
development: {//测试
SERVER_PATH: '112.71.62.21', // ssh地址 服务器地址
SSH_USER: 'root', // ssh 用户名
//方式一 用秘钥登录服务器(推荐), private 本机私钥文件地址(需要在服务器用户目录 一般是 /root/.ssh/authorized_keys 配置公钥 并该文件权限为 600, (.ssh文件夹一般默认隐藏)
// PRIVATE_KEY: 'C:/Users/Html5/.ssh/id_rsa',
PASSWORD: '', //方式二 用密码连接服务器
PATH: '/usr/local/nginx/html/vue' // 需要上传的服务器目录地址 如 /usr/local/nginx/html
},
production: {//正式
SERVER_PATH: '',
SSH_USER: 'root',
PRIVATE_KEY: '',
PATH: '/test/html'
}
})
spinner_style.js
const style = {
"dots": {
"interval": 80,
"frames": [
"⠋",
"⠙",
"⠹",
"⠸",
"⠼",
"⠴",
"⠦",
"⠧",
"⠇",
"⠏"
]
},
"dots2": {
"interval": 80,
"frames": [
"⣾",
"⣽",
"⣻",
"⢿",
"⡿",
"⣟",
"⣯",
"⣷"
]
},
"dots3": {
"interval": 80,
"frames": [
"⠋",
"⠙",
"⠚",
"⠞",
"⠖",
"⠦",
"⠴",
"⠲",
"⠳",
"⠓"
]
},
"dots4": {
"interval": 80,
"frames": [
"⠄",
"⠆",
"⠇",
"⠋",
"⠙",
"⠸",
"⠰",
"⠠",
"⠰",
"⠸",
"⠙",
"⠋",
"⠇",
"⠆"
]
},
"dots5": {
"interval": 80,
"frames": [
"⠋",
"⠙",
"⠚",
"⠒",
"⠂",
"⠂",
"⠒",
"⠲",
"⠴",
"⠦",
"⠖",
"⠒",
"⠐",
"⠐",
"⠒",
"⠓",
"⠋"
]
},
"dots6": {
"interval": 80,
"frames": [
"⠁",
"⠉",
"⠙",
"⠚",
"⠒",
"⠂",
"⠂",
"⠒",
"⠲",
"⠴",
"⠤",
"⠄",
"⠄",
"⠤",
"⠴",
"⠲",
"⠒",
"⠂",
"⠂",
"⠒",
"⠚",
"⠙",
"⠉",
"⠁"
]
},
"dots7": {
"interval": 80,
"frames": [
"⠈",
"⠉",
"⠋",
"⠓",
"⠒",
"⠐",
"⠐",
"⠒",
"⠖",
"⠦",
"⠤",
"⠠",
"⠠",
"⠤",
"⠦",
"⠖",
"⠒",
"⠐",
"⠐",
"⠒",
"⠓",
"⠋",
"⠉",
"⠈"
]
},
"dots8": {
"interval": 80,
"frames": [
"⠁",
"⠁",
"⠉",
"⠙",
"⠚",
"⠒",
"⠂",
"⠂",
"⠒",
"⠲",
"⠴",
"⠤",
"⠄",
"⠄",
"⠤",
"⠠",
"⠠",
"⠤",
"⠦",
"⠖",
"⠒",
"⠐",
"⠐",
"⠒",
"⠓",
"⠋",
"⠉",
"⠈",
"⠈"
]
},
"dots9": {
"interval": 80,
"frames": [
"⢹",
"⢺",
"⢼",
"⣸",
"⣇",
"⡧",
"⡗",
"⡏"
]
},
"dots10": {
"interval": 80,
"frames": [
"⢄",
"⢂",
"⢁",
"⡁",
"⡈",
"⡐",
"⡠"
]
},
"dots11": {
"interval": 100,
"frames": [
"⠁",
"⠂",
"⠄",
"⡀",
"⢀",
"⠠",
"⠐",
"⠈"
]
},
"dots12": {
"interval": 80,
"frames": [
"⢀⠀",
"⡀⠀",
"⠄⠀",
"⢂⠀",
"⡂⠀",
"⠅⠀",
"⢃⠀",
"⡃⠀",
"⠍⠀",
"⢋⠀",
"⡋⠀",
"⠍⠁",
"⢋⠁",
"⡋⠁",
"⠍⠉",
"⠋⠉",
"⠋⠉",
"⠉⠙",
"⠉⠙",
"⠉⠩",
"⠈⢙",
"⠈⡙",
"⢈⠩",
"⡀⢙",
"⠄⡙",
"⢂⠩",
"⡂⢘",
"⠅⡘",
"⢃⠨",
"⡃⢐",
"⠍⡐",
"⢋⠠",
"⡋⢀",
"⠍⡁",
"⢋⠁",
"⡋⠁",
"⠍⠉",
"⠋⠉",
"⠋⠉",
"⠉⠙",
"⠉⠙",
"⠉⠩",
"⠈⢙",
"⠈⡙",
"⠈⠩",
"⠀⢙",
"⠀⡙",
"⠀⠩",
"⠀⢘",
"⠀⡘",
"⠀⠨",
"⠀⢐",
"⠀⡐",
"⠀⠠",
"⠀⢀",
"⠀⡀"
]
},
"line": {
"interval": 130,
"frames": [
"-",
"\\",
"|",
"/"
]
},
"line2": {
"interval": 100,
"frames": [
"⠂",
"-",
"–",
"—",
"–",
"-"
]
},
"pipe": {
"interval": 100,
"frames": [
"┤",
"┘",
"┴",
"└",
"├",
"┌",
"┬",
"┐"
]
},
"simpleDots": {
"interval": 400,
"frames": [
". ",
".. ",
"...",
" "
]
},
"simpleDotsScrolling": {
"interval": 200,
"frames": [
". ",
".. ",
"...",
" ..",
" .",
" "
]
},
"star": {
"interval": 70,
"frames": [
"✶",
"✸",
"✹",
"✺",
"✹",
"✷"
]
},
"star2": {
"interval": 80,
"frames": [
"+",
"x",
"*"
]
},
"flip": {
"interval": 70,
"frames": [
"_",
"_",
"_",
"-",
"`",
"`",
"'",
"´",
"-",
"_",
"_",
"_"
]
},
"hamburger": {
"interval": 100,
"frames": [
"☱",
"☲",
"☴"
]
},
"growVertical": {
"interval": 120,
"frames": [
"▁",
"▃",
"▄",
"▅",
"▆",
"▇",
"▆",
"▅",
"▄",
"▃"
]
},
"growHorizontal": {
"interval": 120,
"frames": [
"▏",
"▎",
"▍",
"▌",
"▋",
"▊",
"▉",
"▊",
"▋",
"▌",
"▍",
"▎"
]
},
"balloon": {
"interval": 140,
"frames": [
" ",
".",
"o",
"O",
"@",
"*",
" "
]
},
"balloon2": {
"interval": 120,
"frames": [
".",
"o",
"O",
"°",
"O",
"o",
"."
]
},
"noise": {
"interval": 100,
"frames": [
"▓",
"▒",
"░"
]
},
"bounce": {
"interval": 120,
"frames": [
"⠁",
"⠂",
"⠄",
"⠂"
]
},
"boxBounce": {
"interval": 120,
"frames": [
"▖",
"▘",
"▝",
"▗"
]
},
"boxBounce2": {
"interval": 100,
"frames": [
"▌",
"▀",
"▐",
"▄"
]
},
"triangle": {
"interval": 50,
"frames": [
"◢",
"◣",
"◤",
"◥"
]
},
"arc": {
"interval": 100,
"frames": [
"◜",
"◠",
"◝",
"◞",
"◡",
"◟"
]
},
"circle": {
"interval": 120,
"frames": [
"◡",
"⊙",
"◠"
]
},
"squareCorners": {
"interval": 180,
"frames": [
"◰",
"◳",
"◲",
"◱"
]
},
"circleQuarters": {
"interval": 120,
"frames": [
"◴",
"◷",
"◶",
"◵"
]
},
"circleHalves": {
"interval": 50,
"frames": [
"◐",
"◓",
"◑",
"◒"
]
},
"squish": {
"interval": 100,
"frames": [
"╫",
"╪"
]
},
"toggle": {
"interval": 250,
"frames": [
"⊶",
"⊷"
]
},
"toggle2": {
"interval": 80,
"frames": [
"▫",
"▪"
]
},
"toggle3": {
"interval": 120,
"frames": [
"□",
"■"
]
},
"toggle4": {
"interval": 100,
"frames": [
"■",
"□",
"▪",
"▫"
]
},
"toggle5": {
"interval": 100,
"frames": [
"▮",
"▯"
]
},
"toggle6": {
"interval": 300,
"frames": [
"ဝ",
"၀"
]
},
"toggle7": {
"interval": 80,
"frames": [
"⦾",
"⦿"
]
},
"toggle8": {
"interval": 100,
"frames": [
"◍",
"◌"
]
},
"toggle9": {
"interval": 100,
"frames": [
"◉",
"◎"
]
},
"toggle10": {
"interval": 100,
"frames": [
"㊂",
"㊀",
"㊁"
]
},
"toggle11": {
"interval": 50,
"frames": [
"⧇",
"⧆"
]
},
"toggle12": {
"interval": 120,
"frames": [
"☗",
"☖"
]
},
"toggle13": {
"interval": 80,
"frames": [
"=",
"*",
"-"
]
},
"arrow": {
"interval": 100,
"frames": [
"←",
"↖",
"↑",
"↗",
"→",
"↘",
"↓",
"↙"
]
},
"arrow2": {
"interval": 80,
"frames": [
"⬆️ ",
"↗️ ",
"➡️ ",
"↘️ ",
"⬇️ ",
"↙️ ",
"⬅️ ",
"↖️ "
]
},
"arrow3": {
"interval": 120,
"frames": [
"▹▹▹▹▹",
"▸▹▹▹▹",
"▹▸▹▹▹",
"▹▹▸▹▹",
"▹▹▹▸▹",
"▹▹▹▹▸"
]
},
"arrow4": {
"interval": 80,
"frames": [
"[> ]",
"[>>> ]",
"[>>>>> ]",
"[>>>>>>> ]",
"[>>>>>>>>> ]",
"[>>>>>>>>>>> ]",
"[>>>>>>>>>>>>>]"
]
},
"bouncingBar": {
"interval": 80,
"frames": [
"[ ]",
"[= ]",
"[== ]",
"[=== ]",
"[ ===]",
"[ ==]",
"[ =]",
"[ ]",
"[ =]",
"[ ==]",
"[ ===]",
"[====]",
"[=== ]",
"[== ]",
"[= ]"
]
},
"bouncingBall": {
"interval": 80,
"frames": [
"( ● )",
"( ● )",
"( ● )",
"( ● )",
"( ●)",
"( ● )",
"( ● )",
"( ● )",
"( ● )",
"(● )"
]
},
"smiley": {
"interval": 200,
"frames": [
"😄 ",
"😝 "
]
},
"monkey": {
"interval": 300,
"frames": [
"🙈 ",
"🙈 ",
"🙉 ",
"🙊 "
]
},
"hearts": {
"interval": 100,
"frames": [
"💛 ",
"💙 ",
"💜 ",
"💚 ",
"❤️ "
]
},
"clock": {
"interval": 100,
"frames": [
"🕛 ",
"🕐 ",
"🕑 ",
"🕒 ",
"🕓 ",
"🕔 ",
"🕕 ",
"🕖 ",
"🕗 ",
"🕘 ",
"🕙 ",
"🕚 "
]
},
"earth": {
"interval": 180,
"frames": [
"🌍 ",
"🌎 ",
"🌏 "
]
},
"moon": {
"interval": 80,
"frames": [
"🌑 ",
"🌒 ",
"🌓 ",
"🌔 ",
"🌕 ",
"🌖 ",
"🌗 ",
"🌘 "
]
},
"runner": {
"interval": 140,
"frames": [
"🚶 ",
"🏃 "
]
},
"pong": {
"interval": 80,
"frames": [
"▐⠂ ▌",
"▐⠈ ▌",
"▐ ⠂ ▌",
"▐ ⠠ ▌",
"▐ ⡀ ▌",
"▐ ⠠ ▌",
"▐ ⠂ ▌",
"▐ ⠈ ▌",
"▐ ⠂ ▌",
"▐ ⠠ ▌",
"▐ ⡀ ▌",
"▐ ⠠ ▌",
"▐ ⠂ ▌",
"▐ ⠈ ▌",
"▐ ⠂▌",
"▐ ⠠▌",
"▐ ⡀▌",
"▐ ⠠ ▌",
"▐ ⠂ ▌",
"▐ ⠈ ▌",
"▐ ⠂ ▌",
"▐ ⠠ ▌",
"▐ ⡀ ▌",
"▐ ⠠ ▌",
"▐ ⠂ ▌",
"▐ ⠈ ▌",
"▐ ⠂ ▌",
"▐ ⠠ ▌",
"▐ ⡀ ▌",
"▐⠠ ▌"
]
},
"shark": {
"interval": 120,
"frames": [
"▐|\\____________▌",
"▐_|\\___________▌",
"▐__|\\__________▌",
"▐___|\\_________▌",
"▐____|\\________▌",
"▐_____|\\_______▌",
"▐______|\\______▌",
"▐_______|\\_____▌",
"▐________|\\____▌",
"▐_________|\\___▌",
"▐__________|\\__▌",
"▐___________|\\_▌",
"▐____________|\\▌",
"▐____________/|▌",
"▐___________/|_▌",
"▐__________/|__▌",
"▐_________/|___▌",
"▐________/|____▌",
"▐_______/|_____▌",
"▐______/|______▌",
"▐_____/|_______▌",
"▐____/|________▌",
"▐___/|_________▌",
"▐__/|__________▌",
"▐_/|___________▌",
"▐/|____________▌"
]
},
"dqpb": {
"interval": 100,
"frames": [
"d",
"q",
"p",
"b"
]
},
"weather": {
"interval": 100,
"frames": [
"☀️ ",
"☀️ ",
"☀️ ",
"🌤 ",
"⛅️ ",
"🌥 ",
"☁️ ",
"🌧 ",
"🌨 ",
"🌧 ",
"🌨 ",
"🌧 ",
"🌨 ",
"⛈ ",
"🌨 ",
"🌧 ",
"🌨 ",
"☁️ ",
"🌥 ",
"⛅️ ",
"🌤 ",
"☀️ ",
"☀️ "
]
},
"christmas": {
"interval": 400,
"frames": [
"🌲",
"🎄"
]
},
"grenade": {
"interval": 80,
"frames": [
"، ",
"′ ",
" ´ ",
" ‾ ",
" ⸌",
" ⸊",
" |",
" ⁎",
" ⁕",
" ෴ ",
" ⁓",
" ",
" ",
" "
]
},
"point": {
"interval": 125,
"frames": [
"∙∙∙",
"●∙∙",
"∙●∙",
"∙∙●",
"∙∙∙"
]
},
"layer": {
"interval": 150,
"frames": [
"-",
"=",
"≡"
]
},
"betaWave": {
"interval": 80,
"frames": [
"ρββββββ",
"βρβββββ",
"ββρββββ",
"βββρβββ",
"ββββρββ",
"βββββρβ",
"ββββββρ"
]
}
}
module.exports = style;
upload.js
const chalk = require('chalk') //命令行颜色
const ora = require('ora') // 加载流程动画
const spinner_style = require('./spinner_style') //加载动画样式
const shell = require('shelljs') // 执行shell命令
const node_ssh = require('node-ssh') // ssh连接服务器
const inquirer = require('inquirer') //命令行交互
const zipFile = require('compressing')// 压缩zip
const fs = require('fs') // nodejs内置文件模块
const path = require('path') // nodejs内置路径模块
const CONFIG = require('./config') // 配置
const SSH = new node_ssh();
let config; // 用于保存 inquirer 命令行交互后选择正式|测试版的配置
//logs
const defaultLog = log => console.log(chalk.blue(`---------------- ${log} ----------------`));
const errorLog = log => console.log(chalk.red(`---------------- ${log} ----------------`));
const successLog = log => console.log(chalk.green(`---------------- ${log} ----------------`));
//文件夹目录
const distDir = path.resolve(__dirname, '../dist'); //待打包
const distZipPath = path.resolve(__dirname, `../dist.zip`); //打包后地址(dist.zip是文件名,不需要更改, 主要在config中配置 PATH 即可)
//项目打包代码 npm run build
const compileDist = async () => {
const loading = ora( defaultLog('项目开始打包') ).start();
loading.spinner = spinner_style.arrow4;
shell.cd(path.resolve(__dirname, '../'));
const res = await shell.exec('npm run build'); //执行shell 打包命令
loading.stop();
if(res.code === 0) {
successLog('项目打包成功!');
} else {
errorLog('项目打包失败, 请重试!');
process.exit(); //退出流程
}
}
//压缩代码
const zipDist = async ()=>{
defaultLog('项目开始压缩');
try {
await zipFile.zip.compressDir(distDir, distZipPath)
successLog('压缩成功!');
} catch (error) {
errorLog(error);
errorLog('压缩失败, 退出程序!');
process.exit(); //退出流程
}
}
//连接服务器
const connectSSH = async ()=>{
const loading = ora( defaultLog('正在连接服务器') ).start();
loading.spinner = spinner_style.arrow4;
try {
await SSH.connect({
host: config.SERVER_PATH,
username: config.SSH_USER,
// privateKey: config.PRIVATE_KEY, //秘钥登录(推荐) 方式一
password: config.PASSWORD // 密码登录 方式二
});
successLog('SSH连接成功!');
} catch (error) {
errorLog(error);
errorLog('SSH连接失败!');
process.exit(); //退出流程
}
loading.stop();
}
//线上执行命令
/**
*
* @param {String} command 命令操作 如 ls
*/
const runCommand = async (command)=> {
const result = await SSH.exec(command, [], { cwd: config.PATH})
// defaultLog(result);
}
//清空线上目标目录里的旧文件
const clearOldFile = async () =>{
const commands = ['ls', 'rm -rf *'];
await Promise.all(commands.map(async (it)=>{
return await runCommand(it);
}));
}
//传送zip文件到服务器
const uploadZipBySSH = async () =>{
//连接ssh
await connectSSH();
//线上目标文件清空
await clearOldFile();
const loading = ora( defaultLog('准备上传文件') ).start();
loading.spinner = spinner_style.arrow4;
try {
await SSH.putFiles([{ local: distZipPath, remote: config.PATH + '/dist.zip' }]); //local 本地 ; remote 服务器 ;
successLog('上传成功!');
loading.text = '正在解压文件';
await runCommand('unzip ./dist.zip'); //解压
await runCommand(`rm -rf ${config.PATH}/dist.zip`); //解压完删除线上压缩包
//将目标目录的dist里面文件移出到目标文件
//举个例子 假如我们部署在 /test/html 这个目录下 只有一个网站, 那么上传解压后的文件在 /test/html/dist 里
//需要将 dist 目录下的文件 移出到 /test/html ; 多网站情况, 如 /test/html/h5 或者 /test/html/admin 都和上面同样道理
await runCommand(`mv -f ${config.PATH}/dist/* ${config.PATH}`);
await runCommand(`rm -rf ${config.PATH}/dist`); //移出后删除 dist 文件夹
SSH.dispose(); //断开连接
} catch (error) {
errorLog(error);
errorLog('上传失败!');
process.exit(); //退出流程
}
loading.stop();
}
//------------发布程序---------------
const runUploadTask = async () => {
console.log(chalk.yellow(`---------> 欢迎使用 2020年自动部署工具 <---------`));
//打包
await compileDist();
//压缩
await zipDist();
//连接服务器上传文件
await uploadZipBySSH();
successLog('大吉大利, 部署成功!');
process.exit();
}
// 开始前的配置检查
/**
*
* @param {Object} conf 配置对象
*/
const checkConfig = (conf) =>{
const checkArr = Object.entries(conf);
checkArr.map(it=>{
const key = it[0];
if(key === 'PATH' && conf[key] === '/') { //上传zip前会清空目标目录内所有文件
errorLog('PATH 不能是服务器根目录!');
process.exit(); //退出流程
}
if(!conf[key]) {
errorLog(`配置项 ${key} 不能为空`);
process.exit(); //退出流程
}
})
}
// 执行交互后 启动发布程序
inquirer
.prompt([{
type: 'list',
message: '请选择发布环境',
name: 'env',
choices: [{
name: '测试环境',
value: 'development'
},{
name: '正式环境',
value: 'production'
}]
}])
.then(answers => {
config = CONFIG[answers.env];
checkConfig(config); // 检查
runUploadTask(); // 发布
});
另 在部署过程中出现 bash: unzip: command not found 进入远端服务器下载 运行 yum install -y unzip zip 下载解压插件再次运行就行