feat(deploy): implement new deployment script using ssh2-sftp-client
- Replace PowerShell deploy script with TypeScript-based deploy script - Add SSH2 SFTP client for secure file uploads to remote server - Include error handling and logging for deployment process - Update package.json to reflect changes in deploy command and add new dependencies
This commit is contained in:
4
.env
4
.env
@@ -40,4 +40,6 @@ PROXY_PORT=3211
|
|||||||
LOG_LEVEL=info
|
LOG_LEVEL=info
|
||||||
|
|
||||||
# OpenAI API Key (用于 AI 推荐)
|
# OpenAI API Key (用于 AI 推荐)
|
||||||
ARK_API_KEY=a63d58b6-cf56-434b-8a42-5c781ba0822a
|
ARK_API_KEY=a63d58b6-cf56-434b-8a42-5c781ba0822a
|
||||||
|
|
||||||
|
SSH_PASSPHRASE=x
|
||||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -12,4 +12,5 @@ build
|
|||||||
*-lock.json
|
*-lock.json
|
||||||
*.woff2
|
*.woff2
|
||||||
widget/looker/frontend/src/assets/fonts/OFL.txt
|
widget/looker/frontend/src/assets/fonts/OFL.txt
|
||||||
dist-electron
|
dist-electron
|
||||||
|
unpackage
|
||||||
@@ -23,7 +23,7 @@
|
|||||||
"update-source": "ts-node -r tsconfig-paths/register src/scripts/update-source.ts",
|
"update-source": "ts-node -r tsconfig-paths/register src/scripts/update-source.ts",
|
||||||
"ai-recommendations": "ts-node -r tsconfig-paths/register src/scripts/ai-recommendations.ts",
|
"ai-recommendations": "ts-node -r tsconfig-paths/register src/scripts/ai-recommendations.ts",
|
||||||
"sync": "ts-node -r tsconfig-paths/register src/scripts/sync.ts",
|
"sync": "ts-node -r tsconfig-paths/register src/scripts/sync.ts",
|
||||||
"deploy": "powershell -ExecutionPolicy Bypass -File src/scripts/deploy.ps1",
|
"deploy": "ts-node src/scripts/deploy.ts",
|
||||||
"electron:dev": "chcp 65001 >nul 2>&1 & npm run -prefix frontend build && npm run build && set NODE_ENV=development && electron ./app",
|
"electron:dev": "chcp 65001 >nul 2>&1 & npm run -prefix frontend build && npm run build && set NODE_ENV=development && electron ./app",
|
||||||
"electron:build": "npm run -prefix frontend build && npm run build && electron-builder --config ./app/electron-builder.json"
|
"electron:build": "npm run -prefix frontend build && npm run build && electron-builder --config ./app/electron-builder.json"
|
||||||
},
|
},
|
||||||
@@ -46,6 +46,7 @@
|
|||||||
"puppeteer-extra-plugin-stealth": "^2.11.2",
|
"puppeteer-extra-plugin-stealth": "^2.11.2",
|
||||||
"reflect-metadata": "^0.2.2",
|
"reflect-metadata": "^0.2.2",
|
||||||
"rxjs": "^7.8.1",
|
"rxjs": "^7.8.1",
|
||||||
|
"ssh2": "^1.17.0",
|
||||||
"typeorm": "^0.3.28",
|
"typeorm": "^0.3.28",
|
||||||
"winston": "^3.19.0",
|
"winston": "^3.19.0",
|
||||||
"winston-daily-rotate-file": "^5.0.0"
|
"winston-daily-rotate-file": "^5.0.0"
|
||||||
@@ -63,6 +64,8 @@
|
|||||||
"@types/jest": "^30.0.0",
|
"@types/jest": "^30.0.0",
|
||||||
"@types/node": "^22.10.7",
|
"@types/node": "^22.10.7",
|
||||||
"@types/responselike": "^1.0.3",
|
"@types/responselike": "^1.0.3",
|
||||||
|
"@types/ssh2": "^1.15.5",
|
||||||
|
"@types/ssh2-sftp-client": "^9.0.6",
|
||||||
"@types/supertest": "^6.0.2",
|
"@types/supertest": "^6.0.2",
|
||||||
"concurrently": "^9.2.1",
|
"concurrently": "^9.2.1",
|
||||||
"electron": "^39.2.7",
|
"electron": "^39.2.7",
|
||||||
@@ -74,6 +77,7 @@
|
|||||||
"jest": "^30.0.0",
|
"jest": "^30.0.0",
|
||||||
"prettier": "^3.4.2",
|
"prettier": "^3.4.2",
|
||||||
"source-map-support": "^0.5.21",
|
"source-map-support": "^0.5.21",
|
||||||
|
"ssh2-sftp-client": "^12.0.1",
|
||||||
"supertest": "^7.0.0",
|
"supertest": "^7.0.0",
|
||||||
"ts-jest": "^29.2.5",
|
"ts-jest": "^29.2.5",
|
||||||
"ts-loader": "^9.5.2",
|
"ts-loader": "^9.5.2",
|
||||||
|
|||||||
149
src/scripts/deploy.ts
Normal file
149
src/scripts/deploy.ts
Normal file
@@ -0,0 +1,149 @@
|
|||||||
|
/**
|
||||||
|
* Deploy script - Upload files to remote server using SSH2
|
||||||
|
* 使用 ssh2-sftp-client 避免每次输入密钥密码
|
||||||
|
*/
|
||||||
|
|
||||||
|
import * as fs from 'fs';
|
||||||
|
import * as path from 'path';
|
||||||
|
import SftpClient from 'ssh2-sftp-client';
|
||||||
|
import * as dotenv from 'dotenv';
|
||||||
|
|
||||||
|
// 加载 .env 文件
|
||||||
|
dotenv.config();
|
||||||
|
|
||||||
|
// Configuration
|
||||||
|
const config = {
|
||||||
|
host: '127.0.0.1',
|
||||||
|
port: 1122,
|
||||||
|
username: 'cubie',
|
||||||
|
privateKey: fs.readFileSync('d:\\163'),
|
||||||
|
passphrase: process.env.SSH_PASSPHRASE || '',
|
||||||
|
};
|
||||||
|
|
||||||
|
const destinations = {
|
||||||
|
server: '/home/cubie/down/document/bidding/publish/server',
|
||||||
|
frontend: '/home/cubie/down/document/bidding/publish/frontend',
|
||||||
|
src: '/home/cubie/down/document/bidding/',
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
async function uploadDirectory(
|
||||||
|
sftp: SftpClient,
|
||||||
|
localPath: string,
|
||||||
|
remotePath: string,
|
||||||
|
): Promise<void> {
|
||||||
|
const startTime = Date.now();
|
||||||
|
console.log(`\n[上传] ${localPath} -> ${remotePath}`);
|
||||||
|
|
||||||
|
// 检查本地目录
|
||||||
|
if (!fs.existsSync(localPath)) {
|
||||||
|
throw new Error(`本地目录不存在: ${localPath}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 统计文件数量
|
||||||
|
const countFiles = (dir: string): number => {
|
||||||
|
let count = 0;
|
||||||
|
const items = fs.readdirSync(dir);
|
||||||
|
for (const item of items) {
|
||||||
|
const fullPath = path.join(dir, item);
|
||||||
|
if (fs.statSync(fullPath).isDirectory()) {
|
||||||
|
count += countFiles(fullPath);
|
||||||
|
} else {
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return count;
|
||||||
|
};
|
||||||
|
|
||||||
|
const fileCount = countFiles(localPath);
|
||||||
|
console.log(` 文件数量: ${fileCount}`);
|
||||||
|
|
||||||
|
await sftp.uploadDir(localPath, remotePath);
|
||||||
|
|
||||||
|
const duration = ((Date.now() - startTime) / 1000).toFixed(2);
|
||||||
|
console.log(` 完成! 耗时: ${duration}s`);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function deploy(): Promise<void> {
|
||||||
|
// 检查必要目录
|
||||||
|
const requiredDirs = ['dist', 'frontend', 'src'];
|
||||||
|
for (const dir of requiredDirs) {
|
||||||
|
if (!fs.existsSync(dir)) {
|
||||||
|
console.error(`${dir} 目录不存在`);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查私钥文件
|
||||||
|
if (!fs.existsSync('d:\\163')) {
|
||||||
|
console.error('私钥文件不存在: d:\\163');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!config.passphrase) {
|
||||||
|
console.error('请在 .env 文件中设置 SSH_PASSPHRASE');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const sftp = new SftpClient();
|
||||||
|
|
||||||
|
// 添加详细日志
|
||||||
|
sftp.on('upload', (info) => {
|
||||||
|
console.log(` 已上传: ${info.source}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
console.log('开始部署...');
|
||||||
|
console.log(`远程服务器: ${config.host}:${config.port}`);
|
||||||
|
console.log(`用户名: ${config.username}`);
|
||||||
|
console.log(`私钥文件: d:\\163`);
|
||||||
|
console.log('正在连接...');
|
||||||
|
|
||||||
|
await sftp.connect({
|
||||||
|
...config,
|
||||||
|
keepaliveInterval: 5000, // 每5秒发送keepalive
|
||||||
|
keepaliveCountMax: 10,
|
||||||
|
readyTimeout: 60000, // 60秒连接超时
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('连接成功!');
|
||||||
|
|
||||||
|
// 上传 dist 目录内容到 server 目录
|
||||||
|
await uploadDirectory(sftp, 'dist', destinations.server);
|
||||||
|
|
||||||
|
// 上传 frontend/dist 到 frontend 目录
|
||||||
|
await uploadDirectory(
|
||||||
|
sftp,
|
||||||
|
path.join('frontend', 'dist'),
|
||||||
|
destinations.frontend,
|
||||||
|
);
|
||||||
|
|
||||||
|
// 上传 src 目录
|
||||||
|
await uploadDirectory(sftp, 'src', destinations.src + 'src');
|
||||||
|
|
||||||
|
// 上传 package.json
|
||||||
|
console.log('\n[上传] package.json -> ' + destinations.src + 'package.json');
|
||||||
|
await sftp.put('package.json', destinations.src + 'package.json');
|
||||||
|
console.log(' 已上传: package.json');
|
||||||
|
|
||||||
|
console.log('\n========================================');
|
||||||
|
console.log('部署完成!');
|
||||||
|
console.log('========================================');
|
||||||
|
} catch (err) {
|
||||||
|
console.error('\n========================================');
|
||||||
|
console.error('部署失败!');
|
||||||
|
console.error('========================================');
|
||||||
|
console.error('错误信息:', err instanceof Error ? err.message : err);
|
||||||
|
if (err instanceof Error && err.stack) {
|
||||||
|
console.error('\n堆栈信息:');
|
||||||
|
console.error(err.stack);
|
||||||
|
}
|
||||||
|
process.exit(1);
|
||||||
|
} finally {
|
||||||
|
console.log('\n断开连接...');
|
||||||
|
await sftp.end();
|
||||||
|
console.log('连接已关闭');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
deploy();
|
||||||
Reference in New Issue
Block a user