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);
複製代碼
沒有意外的話,退出進程,而後就部署好了