@zhengyizhao/deploy-helper 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +120 -0
- package/package.json +24 -0
- package/src/commands/backup.js +313 -0
- package/src/commands/env.js +264 -0
- package/src/commands/init.js +261 -0
- package/src/commands/rollback.js +188 -0
- package/src/commands/status.js +105 -0
- package/src/commands/update.js +284 -0
- package/src/index.js +66 -0
- package/src/utils/config.js +23 -0
- package/src/utils/detect.js +48 -0
- package/src/utils/setup.js +126 -0
- package/src/utils/ssh.js +63 -0
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
|
|
4
|
+
export function detectProjectType(projectPath = process.cwd()) {
|
|
5
|
+
const files = fs.readdirSync(projectPath);
|
|
6
|
+
|
|
7
|
+
if (files.includes('package.json')) {
|
|
8
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(projectPath, 'package.json'), 'utf-8'));
|
|
9
|
+
// 判断是否是纯前端项目
|
|
10
|
+
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
11
|
+
if (deps['next'] || deps['nuxt']) return 'nodejs'; // SSR 框架当后端处理
|
|
12
|
+
if (pkg.scripts?.build && !pkg.scripts?.start) return 'static'; // 只有 build 没有 start
|
|
13
|
+
return 'nodejs';
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
if (files.includes('requirements.txt') || files.includes('pyproject.toml') || files.includes('setup.py')) {
|
|
17
|
+
return 'python';
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
if (files.includes('Dockerfile')) {
|
|
21
|
+
return 'docker';
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if (files.includes('index.html') || files.includes('index.htm')) {
|
|
25
|
+
return 'static';
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return 'unknown';
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export const PROJECT_TYPE_LABELS = {
|
|
32
|
+
nodejs: 'Node.js 应用(Express / Koa / Next.js 等)',
|
|
33
|
+
python: 'Python 应用(Flask / FastAPI / Django 等)',
|
|
34
|
+
static: '静态网站(纯 HTML/CSS/JS)',
|
|
35
|
+
docker: 'Docker 容器',
|
|
36
|
+
unknown: '其他 / 我来手动指定',
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
// 根据项目类型返回启动命令建议
|
|
40
|
+
export function getStartCommand(type, pkg) {
|
|
41
|
+
if (type === 'nodejs') {
|
|
42
|
+
if (pkg?.scripts?.start) return pkg.scripts.start;
|
|
43
|
+
return 'node index.js';
|
|
44
|
+
}
|
|
45
|
+
if (type === 'python') return 'python app.py';
|
|
46
|
+
if (type === 'docker') return 'docker-compose up -d';
|
|
47
|
+
return '';
|
|
48
|
+
}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
// 返回在服务器上执行的 shell 命令数组
|
|
2
|
+
export function getSetupCommands(config) {
|
|
3
|
+
const { projectType, nodeVersion = '20', pythonVersion = '3.11' } = config;
|
|
4
|
+
const steps = [];
|
|
5
|
+
|
|
6
|
+
// 通用:更新系统 & 安装 nginx
|
|
7
|
+
steps.push({
|
|
8
|
+
label: '更新系统包',
|
|
9
|
+
cmd: 'apt-get update -qq',
|
|
10
|
+
});
|
|
11
|
+
steps.push({
|
|
12
|
+
label: '安装 Nginx',
|
|
13
|
+
cmd: 'apt-get install -y -qq nginx',
|
|
14
|
+
});
|
|
15
|
+
steps.push({
|
|
16
|
+
label: '安装 Certbot(用于 HTTPS)',
|
|
17
|
+
cmd: 'apt-get install -y -qq certbot python3-certbot-nginx',
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
if (projectType === 'nodejs') {
|
|
21
|
+
steps.push({
|
|
22
|
+
label: `安装 Node.js ${nodeVersion}`,
|
|
23
|
+
cmd: `curl -fsSL https://deb.nodesource.com/setup_${nodeVersion}.x | bash - && apt-get install -y nodejs`,
|
|
24
|
+
});
|
|
25
|
+
steps.push({
|
|
26
|
+
label: '安装 PM2(进程管理器)',
|
|
27
|
+
cmd: 'npm install -g pm2',
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (projectType === 'python') {
|
|
32
|
+
steps.push({
|
|
33
|
+
label: '安装 Python & pip',
|
|
34
|
+
cmd: `apt-get install -y -qq python3 python3-pip python3-venv`,
|
|
35
|
+
});
|
|
36
|
+
steps.push({
|
|
37
|
+
label: '安装 supervisor(进程管理)',
|
|
38
|
+
cmd: 'apt-get install -y -qq supervisor',
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (projectType === 'docker') {
|
|
43
|
+
steps.push({
|
|
44
|
+
label: '安装 Docker',
|
|
45
|
+
cmd: `curl -fsSL https://get.docker.com | sh`,
|
|
46
|
+
});
|
|
47
|
+
steps.push({
|
|
48
|
+
label: '安装 docker-compose',
|
|
49
|
+
cmd: `apt-get install -y -qq docker-compose-plugin`,
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return steps;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// 启动/重启应用的命令
|
|
57
|
+
export function getStartCommands(config) {
|
|
58
|
+
const { projectType, remotePath, startCmd, appName, port } = config;
|
|
59
|
+
|
|
60
|
+
if (projectType === 'nodejs') {
|
|
61
|
+
return [
|
|
62
|
+
{ label: '安装依赖', cmd: `cd ${remotePath} && npm install --production` },
|
|
63
|
+
{ label: '启动应用(PM2)', cmd: `cd ${remotePath} && pm2 delete ${appName} 2>/dev/null; pm2 start ${startCmd} --name ${appName}` },
|
|
64
|
+
{ label: '设置 PM2 开机自启', cmd: `pm2 save && pm2 startup | tail -1 | bash || true` },
|
|
65
|
+
];
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (projectType === 'python') {
|
|
69
|
+
return [
|
|
70
|
+
{ label: '安装 Python 依赖', cmd: `cd ${remotePath} && pip3 install -r requirements.txt -q` },
|
|
71
|
+
{ label: '启动应用(supervisor)', cmd: `cd ${remotePath} && supervisorctl reread && supervisorctl update && supervisorctl restart ${appName}` },
|
|
72
|
+
];
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (projectType === 'docker') {
|
|
76
|
+
return [
|
|
77
|
+
{ label: '启动容器', cmd: `cd ${remotePath} && docker compose up -d --build` },
|
|
78
|
+
];
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (projectType === 'static') {
|
|
82
|
+
return [
|
|
83
|
+
{ label: '设置 Nginx 文件权限', cmd: `chown -R www-data:www-data ${remotePath}` },
|
|
84
|
+
];
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return [];
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// 生成 Nginx 配置
|
|
91
|
+
export function getNginxConfig(config) {
|
|
92
|
+
const { domain, port, projectType, remotePath } = config;
|
|
93
|
+
|
|
94
|
+
if (projectType === 'static') {
|
|
95
|
+
return `server {
|
|
96
|
+
listen 80;
|
|
97
|
+
server_name ${domain};
|
|
98
|
+
root ${remotePath};
|
|
99
|
+
index index.html;
|
|
100
|
+
|
|
101
|
+
location / {
|
|
102
|
+
try_files $uri $uri/ /index.html;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
gzip on;
|
|
106
|
+
gzip_types text/plain text/css application/json application/javascript;
|
|
107
|
+
}`;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// 反向代理(Node.js / Python / Docker)
|
|
111
|
+
return `server {
|
|
112
|
+
listen 80;
|
|
113
|
+
server_name ${domain};
|
|
114
|
+
|
|
115
|
+
location / {
|
|
116
|
+
proxy_pass http://127.0.0.1:${port};
|
|
117
|
+
proxy_http_version 1.1;
|
|
118
|
+
proxy_set_header Upgrade $http_upgrade;
|
|
119
|
+
proxy_set_header Connection 'upgrade';
|
|
120
|
+
proxy_set_header Host $host;
|
|
121
|
+
proxy_set_header X-Real-IP $remote_addr;
|
|
122
|
+
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
123
|
+
proxy_cache_bypass $http_upgrade;
|
|
124
|
+
}
|
|
125
|
+
}`;
|
|
126
|
+
}
|
package/src/utils/ssh.js
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { NodeSSH } from 'node-ssh';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import fs from 'fs';
|
|
4
|
+
|
|
5
|
+
export async function connectSSH(config) {
|
|
6
|
+
const ssh = new NodeSSH();
|
|
7
|
+
|
|
8
|
+
const connectOptions = {
|
|
9
|
+
host: config.host,
|
|
10
|
+
port: config.port || 22,
|
|
11
|
+
username: config.user,
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
if (config.authType === 'key') {
|
|
15
|
+
connectOptions.privateKeyPath = config.keyPath;
|
|
16
|
+
} else {
|
|
17
|
+
connectOptions.password = config.password;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
await ssh.connect(connectOptions);
|
|
21
|
+
return ssh;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// 在服务器上执行命令,并实时打印输出
|
|
25
|
+
export async function runRemote(ssh, command, label) {
|
|
26
|
+
if (label) console.log(chalk.gray(` → ${label}`));
|
|
27
|
+
|
|
28
|
+
const result = await ssh.execCommand(command, {
|
|
29
|
+
onStdout: (chunk) => process.stdout.write(chalk.gray(' ' + chunk.toString())),
|
|
30
|
+
onStderr: (chunk) => process.stdout.write(chalk.yellow(' ' + chunk.toString())),
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
if (result.code !== 0 && result.code !== null) {
|
|
34
|
+
throw new Error(`命令失败 (exit ${result.code}): ${command}\n${result.stderr}`);
|
|
35
|
+
}
|
|
36
|
+
return result.stdout.trim();
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// 静默执行(不打印输出)
|
|
40
|
+
export async function runRemoteSilent(ssh, command) {
|
|
41
|
+
const result = await ssh.execCommand(command);
|
|
42
|
+
return { stdout: result.stdout.trim(), code: result.code };
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// 上传本地目录到服务器
|
|
46
|
+
export async function uploadDirectory(ssh, localPath, remotePath, ora) {
|
|
47
|
+
const failed = [];
|
|
48
|
+
await ssh.putDirectory(localPath, remotePath, {
|
|
49
|
+
recursive: true,
|
|
50
|
+
concurrency: 5,
|
|
51
|
+
validate: (itemPath) => {
|
|
52
|
+
const base = itemPath.split('/').pop();
|
|
53
|
+
// 跳过不必要的目录
|
|
54
|
+
return !['node_modules', '.git', '.env', 'dist', '__pycache__', '.DS_Store'].includes(base);
|
|
55
|
+
},
|
|
56
|
+
tick: (localFile, remoteFile, error) => {
|
|
57
|
+
if (error) failed.push(localFile);
|
|
58
|
+
},
|
|
59
|
+
});
|
|
60
|
+
if (failed.length > 0) {
|
|
61
|
+
console.log(chalk.yellow(` ⚠ 以下文件上传失败:${failed.join(', ')}`));
|
|
62
|
+
}
|
|
63
|
+
}
|