前端工程化的一个基础能力就是自动部署前端打包后的代码到服务器.基于node和typecript写了一个自动部署的脚步.

脚本的逻辑是先将目录打包压缩包,再通过SSH上传到服务器,最后解压和备份.源码地址

图片

目录结构

  • deploy
    • index.ts — 项目启动目录
    • server.json — 配置项目
    • ssh.ts — 处理 ssh 脚本
    • zip — 处理 目录压缩脚本

index.ts

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
const sr = require("./server.json");
const { zipFile } = require("./zip.ts");
const { uploadFile } = require("./ssh.ts");
const d = new Date();

// 当前时间
const time = d.getFullYear() + "_" + (d.getMonth() + 1) + "_" + d.getDate() + "_" + d.getHours() + "_" + d.getMinutes();
// ssh2 配置项
const SSHConfig = {
host: sr.host,
username: sr.username,
password: sr.password,
port: sr.port
};
// 压缩包文件名
const zipFileName = "V" + time + ".zip";

function deploy() {
console.log("开始部署 🙊!");
zipFile(zipFileName, sr.localDir)
.then(() => {
console.log("压缩包📦 生成成功!");
uploadFile(SSHConfig, __dirname, sr.path, zipFileName);
})
.catch(() => {
console.error("压缩包📦 生成失败!");
});
}
deploy();

ssh.ts

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
import { SFTPStream, SSH2Stream } from "ssh2-streams";

const path = require("path");
const Client = require("ssh2").Client;
const fs = require("fs");
interface SSHConfig {
host: string;
port: number;
username: string;
password: string;
}

exports.uploadFile = function(config: SSHConfig, localPath: string, remotePath: string, fileName: string) {
const local = path.join(localPath, fileName);
const remote = remotePath+'/'+fileName;
var shellList = [
`cd ${remotePath}\n`, // 进入服务器远程目录
`unzip -ol ${fileName}\n`, // 解压
"rm -rf backup.zip\n", // 删除上个备份包
`mv ${fileName} backup.zip\n`, // 备份
"exit\n" // 退出
];
console.log("准备上传⏫文件!");
const conn = new Client();
conn
.on("ready", function() {
console.log("服务器连接成功!");
conn.sftp((err: any, sftp: SFTPStream) => {
console.log("开始上传文件!");
if (err) return;
sftp.fastPut(local, remote, {}, function(err: any) {
if (err) {
console.error(err);
return;
} else {
conn.shell(function(err: any, stream: SSH2Stream) {
stream
.on("close", function() {
console.log("文件上传成功!");
conn.end();
fs.unlink(local, () => {
console.log("已部署到测试服务器");
});
})
.on("data", function(data: any) {
console.log("OUTPUT: " + data);
});
stream.end(shellList.join(""));
});
}
});
});
})
.connect(config);
};

zip.ts

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
const pt = require("path");
const fs = require("fs");
const archiver = require("archiver");

exports.zipFile = function(fileName: string, localPath: string) {
console.log("zip", fileName, localPath);
return new Promise((resolve, reject) => {
console.log(`压缩包${fileName} 生成中……`);
const output = fs.createWriteStream(pt.join(__dirname, fileName));
var archive = archiver("zip", {
zlib: { level: 9 }
});
output.on("close", function() {
console.log(`压缩包${fileName} 生成成功!`);
resolve({ success: true });
});

output.on("end", function() {
console.log("Data has been drained");
});

archive.on("warning", function(err: any) {
console.error(err);
if (err.code === "ENOENT") {
// log warning
console.warn(err);
} else {
// throw error
reject(new Error(err));
}
});

archive.on("error", function(err: any) {
console.error(err);
reject(new Error(err));
});

archive.pipe(output);

archive.directory(localPath, false);
archive.finalize();
});
};

server.json

1
2
3
4
5
6
7
8
{
"host": "192.168.1.22",
"username": "front",
"password": "front",
"path": "/data/mmj/boss",
"port": 22,
"localDir": "dist/"
}