Web 前端项目部署脚本前端
部署流程:(执行 zr-deploy
后)node
配置文件 zr-deploy-config.json
打包命令 buildCommand
打包项目local.distDir -> local.distZip
node-ssh
链接服务器server.distDir
)server.bakeup
true
: 备份旧的项目文件false
: 删除旧的项目文件👉预览图挂了的话点这里git
已发布 npm
,👉zr-deploygithub
源码 github
,👉zr-deployshell
md-note
在这里👉md-notenpm
注意 加
-g
/global
下载到全局,否则会提示找不到命令!json
这样也不用每一个项目加这个依赖,只要进到项目目录下,添加配置文件后,执行
zr-deploy
就能部署了windows
npm i -g zr-deploy
复制代码
或数组
yarn global add zr-deploy
复制代码
而后在 项目根目录 新建配置文件 zr-deploy-config.json
,bash
记住 加到
.gitignore
,不要把它上传到github
上面了
进入项目目录
zr-deploy
复制代码
local
buildCommand
: 打包命令distDir
: 本地打包输出的路径distZip
: 压缩打包文件的文件名server
name
: 选择的名字host
: 服务器 IPusername
: 服务器的登陆用户名password
: 对应用户名的密码distDir
: 项目路径distZipName
: 上传的压缩文件名bakeup
: 是否备份旧目录zr-deploy-config.json
格式以下
[
{
"local": {
"buildCommand": "yarn build",
"distDir": "./docs",
"distZip": "./dist.zip"
},
"server": {
"name": "服务器1",
"host": "1.1.1.1",
"username": "username",
"password": "password",
"distDir": "/var/www/xxx/xxx",
"distZipName": "dist",
"bakeup": false
}
},
{
"local": {
"buildCommand": "yarn build",
"distDir": "./docs",
"distZip": "./dist.zip"
},
"server": {
"name": "服务器2",
"host": "2.2.2.2",
"username": "username",
"password": "password",
"distDir": "/var/www/xxx/xxx",
"distZipName": "dist",
"bakeup": false
}
}
]
复制代码
.
├── CHANGE_LOG.md
├── Description.md
├── README.md
├── README_zh.md
├── __test__
│ ├── buildDist.t.js
│ ├── compressDist.t.js
│ ├── getConfig.t.js
│ ├── index.test.js
│ └── zr-deploy-config.json
├── bin
│ └── zr-deploy.js
├── package-lock.json
├── package.json
└── src
├── buildDist.js
├── compressDist.js
├── deploy.js
├── getConfig.js
├── index.js
├── selectEnv.js
└── utils
├── getTime.js
├── index.js
└── textConsole.js
复制代码
// src/buildDist.js
const { spawn } = require('child_process');
const build = spawn(cmd, params, {
shell: process.platform === 'win32', // 兼容windows系统
stdio: 'inherit', // 打印命令原始输出
});
复制代码
使用 inquirer,从配置文件中选择
// src\selectEnv.js
const inquirer = require('inquirer');
/** * 选择部署环境 * @param {*} CONFIG 配置文件内容 */
function selectEnv(CONFIG) {
return new Promise(async (resolve, reject) => {
const select = await inquirer.prompt({
type: 'list',
name: '选择部署的服务器',
choices: CONFIG.map((item, index) => ({
name: `${item.server.name}`,
value: index,
})),
});
const selectServer = CONFIG[Object.values(select)[0]];
if (selectServer) {
resolve(selectServer);
} else {
reject();
}
});
}
module.exports = selectEnv;
复制代码
yarn add zip-local
复制代码
yarn add ora
复制代码
调用 ora
返回值的 succeed
/fail
会替换原来的参数值(loading
)在终端上显示
const chalk = require('chalk');
const ora = require('ora');
const spinner = ora(chalk.cyan('正在打包... \n')).start();
spinner.succeed(chalk.green('打包完成!\n'));
spinner.fail(chalk.red('打包失败!\n'));
复制代码
将node.js
内置函数转化为 Promise
形式, promisify
包装一下,方便使用 async
/await
,记住要调用一下 next()
,至关于 Promise.resolve()
,否则是不会走到下一步的
注意:普通函数(非
node.js
内置)使用promisify
,调用next
,不传参数没问题,传参数给next(arg)
时,会走到catch
去,跟 手动new Promise()
对比一下,哪一个方便使用哪一个就是了
const { promisify } = require('util');
async function buildDist(cmd, params, next) {
// ...
if (next) next();
}
module.exports = promisify(buildDist);
复制代码
使用 node-ssh
链接服务器
yarn add node-ssh
复制代码
// src\deploy.js
const node_ssh = require('node-ssh');
const SSH = new node_ssh();
/* =================== 三、链接服务器 =================== */
/** * 链接服务器 * @param {*} params { host, username, password } */
async function connectServer(params) {
const spinner = ora(chalk.cyan('正在链接服务器...\n')).start();
await SSH.connect(params)
.then(() => {
spinner.succeed(chalk.green('服务器链接成功!\n'));
})
.catch((err) => {
spinner.fail(chalk.red('服务器链接失败!\n'));
textError(err);
process.exit(1);
});
}
/** * 经过 ssh 在服务器上命令 * @param {*} cmd shell 命令 * @param {*} cwd 路径 */
async function runCommand(cmd, cwd) {
await SSH.execCommand(cmd, {
cwd,
onStderr(chunk) {
textError(`${cmd}, stderrChunk, ${chunk.toString('utf8')}`);
},
});
}
复制代码
// src\index.js
'use strict';
/** * 前端自动部署项目脚本 */
const { textTitle, textInfo } = require('./utils/textConsole');
const getConfig = require('./getConfig');
const selectEnv = require('./selectEnv');
const buildDist = require('./buildDist');
const compressDist = require('./compressDist');
const deploy = require('./deploy');
/* =================== 0、获取配置 =================== */
/* =================== 一、选择部署环境 =================== */
/* =================== 二、项目打包 =================== */
/* =================== 三、项目压缩 =================== */
/* =================== 四、链接服务器 =================== */
/* =================== 五、部署项目 =================== */
async function start() {
const CONFIG = await selectEnv(getConfig());
if (!CONFIG) process.exit(1);
textTitle('======== 自动部署项目 ========');
textInfo('');
const [npm, ...script] = CONFIG.local.buildCommand.split(' ');
// await buildDist('yarn', ['build']);
await buildDist(npm, [...script]);
await compressDist(CONFIG.local);
await deploy(CONFIG.local, CONFIG.server);
process.exit();
}
module.exports = start;
复制代码
能够用 child_process.spawn
执行 shell
命令 npm/yarn build
spawn
的格式是child_process.spawn(command[, args][, options])
,以数组的形式传参
// src\buildDist.js
'use strict';
const { promisify } = require('util');
const { spawn } = require('child_process');
const { textError, textSuccess } = require('./utils/textConsole');
/** * 执行脚本 spawn 的封装 * @param {*} cmd * @param {*} params */
async function buildDist(cmd, params, next) {
const build = spawn(cmd, params, {
shell: process.platform === 'win32', // 兼容windows系统
stdio: 'inherit', // 打印命令原始输出
});
build.on('error', () => {
textError(`× [script: ${cmd} ${params}] 打包失败!\n`);
process.exit(1);
});
build.on('close', (code) => {
if (code === 0) {
textSuccess('√ 打包完成!\n');
} else {
textError(`× 打包失败![script: ${cmd} ${params}]\n`);
process.exit(1);
}
// 必传,promisify 回调继续执行后续函数
if (next) next();
});
}
module.exports = promisify(buildDist);
复制代码
// src\compressDist.js
'use strict';
const fs = require('fs');
const chalk = require('chalk');
const ora = require('ora');
const zipper = require('zip-local');
const { promisify } = require('util');
const { textError } = require('./utils/textConsole');
const { resolvePath } = require('./utils');
/** * 压缩打包好的项目 * @param {*} LOCAL_CONFIG 本地配置 * @param {*} next */
function compressDist(LOCAL_CONFIG, next) {
try {
const { distDir, distZip } = LOCAL_CONFIG;
const dist = resolvePath(process.cwd(), distDir);
if (!fs.existsSync(dist)) {
textError('× 压缩失败');
textError(`× 打包路径 [local.distDir] 配置错误,${dist} 不存在!\n`);
process.exit(1);
}
const spinner = ora(chalk.cyan('正在压缩...\n')).start();
zipper.sync.zip(dist).compress().save(resolvePath(process.cwd(), distZip));
spinner.succeed(chalk.green('压缩完成!\n'));
if (next) next();
} catch (err) {
textError('压缩失败!', err);
}
}
module.exports = promisify(compressDist);
复制代码
yarn add node-ssh
复制代码
// src\deploy.js
'use strict';
const { promisify } = require('util');
const ora = require('ora');
const chalk = require('chalk');
const node_ssh = require('node-ssh');
const getTime = require('./utils/getTime');
const { resolvePath } = require('./utils');
const { textError, textInfo } = require('./utils/textConsole');
const SSH = new node_ssh();
/* =================== 三、链接服务器 =================== */
/** * 链接服务器 * @param {*} params { host, username, password } */
async function connectServer(params) {
const spinner = ora(chalk.cyan('正在链接服务器...\n')).start();
await SSH.connect(params)
.then(() => {
spinner.succeed(chalk.green('服务器链接成功!\n'));
})
.catch((err) => {
spinner.fail(chalk.red('服务器链接失败!\n'));
textError(err);
process.exit(1);
});
}
/** * 经过 ssh 在服务器上命令 * @param {*} cmd shell 命令 * @param {*} cwd 路径 */
async function runCommand(cmd, cwd) {
await SSH.execCommand(cmd, {
cwd,
onStderr(chunk) {
textError(`${cmd}, stderrChunk, ${chunk.toString('utf8')}`);
},
});
}
/* =================== 四、部署项目 =================== */
async function deploy(LOCAL_CONFIG, SERVER_CONFIG, next) {
// ...
}
module.exports = promisify(deploy);
复制代码
server.bakeup
为 true
)server.bakeup
为 false
)// src\deploy.js
'use strict';
const { promisify } = require('util');
const ora = require('ora');
const chalk = require('chalk');
const node_ssh = require('node-ssh');
const getTime = require('./utils/getTime');
const { resolvePath } = require('./utils');
const { textError, textInfo } = require('./utils/textConsole');
const SSH = new node_ssh();
/* =================== 三、链接服务器 =================== */
/** * 链接服务器 * @param {*} params { host, username, password } */
async function connectServer(params) {
// ...
}
/** * 经过 ssh 在服务器上命令 * @param {*} cmd shell 命令 * @param {*} cwd 路径 */
async function runCommand(cmd, cwd) {
// ...
}
/* =================== 四、部署项目 =================== */
async function deploy(LOCAL_CONFIG, SERVER_CONFIG, next) {
const {
host,
username,
password,
distDir,
distZipName,
bakeup,
} = SERVER_CONFIG;
if (!distZipName || distDir === '/') {
textError('请正确配置zr-deploy-config.json!');
process.exit(1);
}
// 链接服务器
await connectServer({ host, username, password });
// privateKey: '/home/steel/.ssh/id_rsa'
const spinner = ora(chalk.cyan('正在部署项目...\n')).start();
try {
// 上传压缩的项目文件
await SSH.putFile(
resolvePath(process.cwd(), LOCAL_CONFIG.distZip),
`${distDir}/${distZipName}.zip`
);
if (bakeup) {
// 备份重命名原项目的文件
await runCommand(
`mv ${distZipName} ${distZipName}_${getTime()}`,
distDir
);
} else {
// 删除原项目的文件
await runCommand(`rm -rf ${distZipName}`, distDir);
}
// 修改文件权限
await runCommand(`chmod 777 ${distZipName}.zip`, distDir);
// 解压缩上传的项目文件
await runCommand(`unzip ./${distZipName}.zip -d ${distZipName}`, distDir);
// 删除服务器上的压缩的项目文件
await runCommand(`rm -rf ./${distZipName}.zip`, distDir);
spinner.succeed(chalk.green('部署完成!\n'));
textInfo(`项目路径: ${distDir}`);
textInfo(new Date());
textInfo('');
if (next) next();
} catch (err) {
spinner.fail(chalk.red('项目部署失败!\n'));
textError(`catch: ${err}`);
process.exit(1);
}
}
module.exports = promisify(deploy);
复制代码
没有意外的话,退出进程,而后就部署好了