diff --git a/.env b/.env index f832357..2224d19 100644 --- a/.env +++ b/.env @@ -40,4 +40,6 @@ PROXY_PORT=3211 LOG_LEVEL=info # OpenAI API Key (用于 AI 推荐) -ARK_API_KEY=a63d58b6-cf56-434b-8a42-5c781ba0822a \ No newline at end of file +ARK_API_KEY=a63d58b6-cf56-434b-8a42-5c781ba0822a + +SSH_PASSPHRASE=x \ No newline at end of file diff --git a/.gitignore b/.gitignore index f18448b..76e6b03 100644 --- a/.gitignore +++ b/.gitignore @@ -12,4 +12,5 @@ build *-lock.json *.woff2 widget/looker/frontend/src/assets/fonts/OFL.txt -dist-electron \ No newline at end of file +dist-electron +unpackage \ No newline at end of file diff --git a/package.json b/package.json index 2641976..cd24172 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,7 @@ "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", "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: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", "reflect-metadata": "^0.2.2", "rxjs": "^7.8.1", + "ssh2": "^1.17.0", "typeorm": "^0.3.28", "winston": "^3.19.0", "winston-daily-rotate-file": "^5.0.0" @@ -63,6 +64,8 @@ "@types/jest": "^30.0.0", "@types/node": "^22.10.7", "@types/responselike": "^1.0.3", + "@types/ssh2": "^1.15.5", + "@types/ssh2-sftp-client": "^9.0.6", "@types/supertest": "^6.0.2", "concurrently": "^9.2.1", "electron": "^39.2.7", @@ -74,6 +77,7 @@ "jest": "^30.0.0", "prettier": "^3.4.2", "source-map-support": "^0.5.21", + "ssh2-sftp-client": "^12.0.1", "supertest": "^7.0.0", "ts-jest": "^29.2.5", "ts-loader": "^9.5.2", diff --git a/src/scripts/deploy.ts b/src/scripts/deploy.ts new file mode 100644 index 0000000..c7ef399 --- /dev/null +++ b/src/scripts/deploy.ts @@ -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 { + 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 { + // 检查必要目录 + 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();