mirror of
https://github.com/whyour/qinglong.git
synced 2025-10-28 15:16:07 +08:00
支持二进制文件部署
This commit is contained in:
parent
07951964a1
commit
c1e982f1d3
52
.github/workflows/build-release.yml
vendored
Normal file
52
.github/workflows/build-release.yml
vendored
Normal file
|
|
@ -0,0 +1,52 @@
|
||||||
|
name: Build Release Packages
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
paths-ignore:
|
||||||
|
- "*.md"
|
||||||
|
tags:
|
||||||
|
- "v*"
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build-release:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- uses: pnpm/action-setup@v3
|
||||||
|
with:
|
||||||
|
version: "8.3.1"
|
||||||
|
|
||||||
|
- uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: "18"
|
||||||
|
cache: "pnpm"
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: |
|
||||||
|
pnpm install
|
||||||
|
pnpm install -g pkg
|
||||||
|
|
||||||
|
- name: Install zip tool
|
||||||
|
run: sudo apt-get install -y zip
|
||||||
|
|
||||||
|
- name: Build release packages
|
||||||
|
run: node build-release.js
|
||||||
|
|
||||||
|
- name: Upload release artifacts
|
||||||
|
uses: actions/upload-artifact@v5
|
||||||
|
with:
|
||||||
|
name: release-packages
|
||||||
|
path: dist/*.zip
|
||||||
|
retention-days: 7
|
||||||
|
|
||||||
|
- name: Create GitHub Release
|
||||||
|
if: startsWith(github.ref, 'refs/tags/v')
|
||||||
|
uses: ncipollo/release-action@v1
|
||||||
|
with:
|
||||||
|
artifacts: "dist/*.zip"
|
||||||
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
generateReleaseNotes: true
|
||||||
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -28,3 +28,5 @@ __pycache__
|
||||||
/shell/preload/notify.*
|
/shell/preload/notify.*
|
||||||
/shell/preload/*-notify.json
|
/shell/preload/*-notify.json
|
||||||
/shell/preload/__ql_notify__.*
|
/shell/preload/__ql_notify__.*
|
||||||
|
/dist
|
||||||
|
/.pkg-temp
|
||||||
|
|
|
||||||
|
|
@ -96,7 +96,9 @@ class Application {
|
||||||
}
|
}
|
||||||
|
|
||||||
private setupMiddlewares() {
|
private setupMiddlewares() {
|
||||||
this.app.use(helmet());
|
this.app.use(helmet({
|
||||||
|
contentSecurityPolicy: false,
|
||||||
|
}));
|
||||||
this.app.use(cors(config.cors));
|
this.app.use(cors(config.cors));
|
||||||
this.app.use(compression());
|
this.app.use(compression());
|
||||||
this.app.use(monitoringMiddleware);
|
this.app.use(monitoringMiddleware);
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ import rewrite from 'express-urlrewrite';
|
||||||
import { errors } from 'celebrate';
|
import { errors } from 'celebrate';
|
||||||
import { serveEnv } from '../config/serverEnv';
|
import { serveEnv } from '../config/serverEnv';
|
||||||
import { IKeyvStore, shareStore } from '../shared/store';
|
import { IKeyvStore, shareStore } from '../shared/store';
|
||||||
|
import path from 'path';
|
||||||
|
|
||||||
export default ({ app }: { app: Application }) => {
|
export default ({ app }: { app: Application }) => {
|
||||||
app.set('trust proxy', 'loopback');
|
app.set('trust proxy', 'loopback');
|
||||||
|
|
@ -19,12 +20,16 @@ export default ({ app }: { app: Application }) => {
|
||||||
app.use(bodyParser.json({ limit: '50mb' }));
|
app.use(bodyParser.json({ limit: '50mb' }));
|
||||||
app.use(bodyParser.urlencoded({ limit: '50mb', extended: true }));
|
app.use(bodyParser.urlencoded({ limit: '50mb', extended: true }));
|
||||||
|
|
||||||
|
const frontendPath = path.join(config.rootPath, 'static/dist');
|
||||||
|
app.use(express.static(frontendPath));
|
||||||
|
|
||||||
app.use(
|
app.use(
|
||||||
expressjwt({
|
expressjwt({
|
||||||
secret: config.jwt.secret,
|
secret: config.jwt.secret,
|
||||||
algorithms: ['HS384'],
|
algorithms: ['HS384'],
|
||||||
}).unless({
|
}).unless({
|
||||||
path: [...config.apiWhiteList, /^\/open\//],
|
// 使用正则表达式排除非API路径,只对/api/和/open/路径应用JWT验证
|
||||||
|
path: [...config.apiWhiteList, /^\/$/, /^\/(?!api\/)(?!open\/).*/]
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -39,6 +44,10 @@ export default ({ app }: { app: Application }) => {
|
||||||
});
|
});
|
||||||
|
|
||||||
app.use(async (req: Request, res, next) => {
|
app.use(async (req: Request, res, next) => {
|
||||||
|
if (!['/open/', '/api/'].some((x) => req.path.startsWith(x))) {
|
||||||
|
return next();
|
||||||
|
}
|
||||||
|
|
||||||
const headerToken = getToken(req);
|
const headerToken = getToken(req);
|
||||||
if (req.path.startsWith('/open/')) {
|
if (req.path.startsWith('/open/')) {
|
||||||
const apps = await shareStore.getApps();
|
const apps = await shareStore.getApps();
|
||||||
|
|
@ -110,10 +119,15 @@ export default ({ app }: { app: Application }) => {
|
||||||
app.use(rewrite('/open/*', '/api/$1'));
|
app.use(rewrite('/open/*', '/api/$1'));
|
||||||
app.use(config.api.prefix, routes());
|
app.use(config.api.prefix, routes());
|
||||||
|
|
||||||
app.use((req, res, next) => {
|
app.get('*', (req, res, next) => {
|
||||||
const err: any = new Error('Not Found');
|
const indexPath = path.join(frontendPath, 'index.html');
|
||||||
err['status'] = 404;
|
res.sendFile(indexPath, (err) => {
|
||||||
next(err);
|
if (err) {
|
||||||
|
const err: any = new Error('Not Found');
|
||||||
|
err['status'] = 404;
|
||||||
|
next(err);
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
app.use(errors());
|
app.use(errors());
|
||||||
|
|
|
||||||
478
build-release.js
Executable file
478
build-release.js
Executable file
|
|
@ -0,0 +1,478 @@
|
||||||
|
#!/usr/bin/env node
|
||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
const { execSync } = require('child_process');
|
||||||
|
|
||||||
|
// 获取当前平台信息
|
||||||
|
const getCurrentPlatform = () => {
|
||||||
|
const platform = process.platform === 'darwin' ? 'macos' : process.platform === 'win32' ? 'win' : 'linux';
|
||||||
|
const arch = process.arch === 'x64' ? 'x64' : process.arch === 'arm64' || process.arch === 'aarch64' ? 'arm64' : 'x64';
|
||||||
|
return { platform, arch };
|
||||||
|
};
|
||||||
|
|
||||||
|
// 配置
|
||||||
|
const CONFIG = {
|
||||||
|
outputDir: path.join(__dirname, 'dist'),
|
||||||
|
platforms: [
|
||||||
|
{ nodeVersion: '18', platform: 'linux', arch: 'x64' },
|
||||||
|
{ nodeVersion: '18', platform: 'linux', arch: 'arm64' },
|
||||||
|
{ nodeVersion: '18', platform: 'win', arch: 'x64' },
|
||||||
|
{ nodeVersion: '18', platform: 'macos', arch: 'x64' },
|
||||||
|
{ nodeVersion: '18', platform: 'macos', arch: 'arm64' },
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
// 解析命令行参数
|
||||||
|
const args = process.argv.slice(2);
|
||||||
|
const options = {
|
||||||
|
localOnly: args.includes('--local-only'),
|
||||||
|
platform: args.find(arg => arg.startsWith('--platform='))?.split('=')[1],
|
||||||
|
arch: args.find(arg => arg.startsWith('--arch='))?.split('=')[1],
|
||||||
|
};
|
||||||
|
|
||||||
|
// 根据参数过滤平台
|
||||||
|
function getTargetPlatforms() {
|
||||||
|
if (options.localOnly) {
|
||||||
|
const current = getCurrentPlatform();
|
||||||
|
console.log(`只构建当前平台: ${current.platform}-${current.arch}`);
|
||||||
|
return [{
|
||||||
|
nodeVersion: '18',
|
||||||
|
platform: current.platform,
|
||||||
|
arch: current.arch
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.platform && options.arch) {
|
||||||
|
console.log(`只构建指定平台: ${options.platform}-${options.arch}`);
|
||||||
|
return [{
|
||||||
|
nodeVersion: '18',
|
||||||
|
platform: options.platform,
|
||||||
|
arch: options.arch
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
|
||||||
|
return CONFIG.platforms;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 确保输出目录存在
|
||||||
|
function ensureDir(dir) {
|
||||||
|
if (!fs.existsSync(dir)) {
|
||||||
|
fs.mkdirSync(dir, { recursive: true });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 执行命令
|
||||||
|
function runCommand(command, cwd = __dirname) {
|
||||||
|
console.log(`执行命令: ${command}`);
|
||||||
|
execSync(command, { cwd, stdio: 'inherit' });
|
||||||
|
}
|
||||||
|
|
||||||
|
// 清理之前的构建
|
||||||
|
function cleanBuild() {
|
||||||
|
console.log('清理之前的构建...');
|
||||||
|
if (fs.existsSync(CONFIG.outputDir)) {
|
||||||
|
fs.rmSync(CONFIG.outputDir, { recursive: true, force: true });
|
||||||
|
}
|
||||||
|
if (fs.existsSync(path.join(__dirname, 'back/dist'))) {
|
||||||
|
fs.rmSync(path.join(__dirname, 'back/dist'), { recursive: true, force: true });
|
||||||
|
}
|
||||||
|
if (fs.existsSync(path.join(__dirname, 'static/build'))) {
|
||||||
|
fs.rmSync(path.join(__dirname, 'static/build'), { recursive: true, force: true });
|
||||||
|
}
|
||||||
|
ensureDir(CONFIG.outputDir);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 安装依赖
|
||||||
|
function installDependencies() {
|
||||||
|
console.log('安装依赖...');
|
||||||
|
runCommand('pnpm install');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 构建前端
|
||||||
|
function buildFrontend() {
|
||||||
|
console.log('构建前端...');
|
||||||
|
runCommand('npm run build:front');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 构建后端
|
||||||
|
function buildBackend() {
|
||||||
|
console.log('构建后端...');
|
||||||
|
runCommand('npm run build:back');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 准备打包所需文件
|
||||||
|
function preparePackageFiles() {
|
||||||
|
console.log('准备打包所需文件...');
|
||||||
|
|
||||||
|
// 创建临时目录用于打包
|
||||||
|
const tempDir = path.join(__dirname, '.pkg-temp');
|
||||||
|
if (fs.existsSync(tempDir)) {
|
||||||
|
fs.rmSync(tempDir, { recursive: true, force: true });
|
||||||
|
}
|
||||||
|
ensureDir(tempDir);
|
||||||
|
|
||||||
|
// 复制必要的文件和目录
|
||||||
|
const copyFiles = [
|
||||||
|
// 直接使用编译后的JavaScript文件,不再依赖TypeScript源文件
|
||||||
|
{ from: path.join(__dirname, 'static/build'), to: path.join(tempDir, 'static/build') },
|
||||||
|
{ from: path.join(__dirname, 'shell'), to: path.join(tempDir, 'shell') },
|
||||||
|
{ from: path.join(__dirname, 'sample'), to: path.join(tempDir, 'sample') },
|
||||||
|
{ from: path.join(__dirname, '.env.example'), to: path.join(tempDir, '.env.example') },
|
||||||
|
// 复制整个static目录以确保前端文件完整(包含dist目录中的前端文件)
|
||||||
|
{ from: path.join(__dirname, 'static'), to: path.join(tempDir, 'static') },
|
||||||
|
];
|
||||||
|
|
||||||
|
// 额外复制前端构建产物到更明确的位置
|
||||||
|
if (fs.existsSync(path.join(__dirname, 'static/dist'))) {
|
||||||
|
copyFiles.push({
|
||||||
|
from: path.join(__dirname, 'static/dist'),
|
||||||
|
to: path.join(tempDir, 'static/dist')
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
copyFiles.forEach(({ from, to }) => {
|
||||||
|
if (fs.existsSync(from)) {
|
||||||
|
console.log(`复制文件: ${from} -> ${to}`);
|
||||||
|
fs.cpSync(from, to, { recursive: true });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 创建入口文件
|
||||||
|
const entryContent = `
|
||||||
|
require('dotenv').config();
|
||||||
|
const path = require('path');
|
||||||
|
const fs = require('fs');
|
||||||
|
|
||||||
|
// 设置QL_DIR环境变量
|
||||||
|
process.env.QL_DIR = process.env.QL_DIR || path.dirname(process.execPath);
|
||||||
|
const qlDir = process.env.QL_DIR;
|
||||||
|
console.log('🚀 QL_DIR is set to: ' + qlDir);
|
||||||
|
|
||||||
|
// 设置NODE_PATH以支持模块解析
|
||||||
|
process.env.NODE_PATH = path.resolve(qlDir, 'node_modules');
|
||||||
|
require('module').Module._initPaths();
|
||||||
|
|
||||||
|
// 配置静态文件服务路径,确保前端可以正确加载
|
||||||
|
process.env.STATIC_PATH = path.resolve(qlDir, 'static');
|
||||||
|
console.log('📁 Static files path: ' + process.env.STATIC_PATH);
|
||||||
|
|
||||||
|
// 只使用编译后的JavaScript文件作为入口,不再依赖TypeScript和ts-node
|
||||||
|
let appPath;
|
||||||
|
const possiblePaths = [
|
||||||
|
// 主要使用编译后的app.js
|
||||||
|
path.join(qlDir, 'static', 'build', 'app.js'),
|
||||||
|
// 备用路径
|
||||||
|
path.join(process.cwd(), 'static', 'build', 'app.js')
|
||||||
|
];
|
||||||
|
|
||||||
|
console.log('🔍 Looking for app entry in:');
|
||||||
|
for (const p of possiblePaths) {
|
||||||
|
console.log(' - ' + p);
|
||||||
|
if (fs.existsSync(p)) {
|
||||||
|
appPath = p;
|
||||||
|
console.log('✅ Found app entry file: ' + appPath);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!appPath) {
|
||||||
|
console.error('❌ Error: Cannot find compiled app.js file');
|
||||||
|
console.error('Please make sure you have properly built the project with: npm run build:back');
|
||||||
|
console.error('Expected path: static/build/app.js');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证前端文件是否存在 - 检查多个可能的位置
|
||||||
|
const frontendIndexPaths = [
|
||||||
|
path.join(qlDir, 'static', 'dist', 'index.html'),
|
||||||
|
path.join(qlDir, 'static', 'index.html')
|
||||||
|
];
|
||||||
|
let frontendFound = false;
|
||||||
|
|
||||||
|
for (const indexPath of frontendIndexPaths) {
|
||||||
|
if (fs.existsSync(indexPath)) {
|
||||||
|
console.log('✅ Frontend files found at: ' + path.dirname(indexPath));
|
||||||
|
// 如果在dist子目录找到,设置正确的静态文件路径
|
||||||
|
if (indexPath.includes('dist')) {
|
||||||
|
process.env.STATIC_PATH = path.dirname(path.dirname(indexPath));
|
||||||
|
console.log('📁 Updated static path to: ' + process.env.STATIC_PATH);
|
||||||
|
}
|
||||||
|
frontendFound = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!frontendFound) {
|
||||||
|
console.warn('⚠️ Frontend index.html not found in expected locations:');
|
||||||
|
frontendIndexPaths.forEach(path => console.log(' - ' + path));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 加载应用
|
||||||
|
try {
|
||||||
|
console.log('🎯 Loading application from:', appPath);
|
||||||
|
require(appPath);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ Error loading app:', error.message);
|
||||||
|
console.error('Error stack:', error.stack);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
fs.writeFileSync(path.join(tempDir, 'entry.js'), entryContent);
|
||||||
|
|
||||||
|
return tempDir;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用pkg打包
|
||||||
|
function packageWithPkg(tempDir, platformConfig) {
|
||||||
|
const { nodeVersion, platform, arch } = platformConfig;
|
||||||
|
const outputName = `qinglong-${platform}-${arch}`;
|
||||||
|
const outputPath = path.join(CONFIG.outputDir, outputName);
|
||||||
|
|
||||||
|
console.log(`打包 ${platform}-${arch} 版本...`);
|
||||||
|
|
||||||
|
try {
|
||||||
|
runCommand(
|
||||||
|
`npx pkg@5.8.1 --targets node${nodeVersion}-${platform}-${arch} --output ${outputPath} ${tempDir}/entry.js`,
|
||||||
|
__dirname
|
||||||
|
);
|
||||||
|
|
||||||
|
// 创建发布包
|
||||||
|
const releaseDir = path.join(CONFIG.outputDir, `${outputName}-release`);
|
||||||
|
ensureDir(releaseDir);
|
||||||
|
|
||||||
|
// 复制二进制文件到发布目录
|
||||||
|
if (platform === 'win') {
|
||||||
|
if (fs.existsSync(`${outputPath}.exe`)) {
|
||||||
|
fs.cpSync(`${outputPath}.exe`, path.join(releaseDir, `${outputName}.exe`));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (fs.existsSync(outputPath)) {
|
||||||
|
fs.cpSync(outputPath, path.join(releaseDir, outputName));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 复制其他必要文件
|
||||||
|
const copyFiles = [
|
||||||
|
{ from: path.join(tempDir, 'static'), to: path.join(releaseDir, 'static') },
|
||||||
|
{ from: path.join(tempDir, 'shell'), to: path.join(releaseDir, 'shell') },
|
||||||
|
{ from: path.join(tempDir, 'sample'), to: path.join(releaseDir, 'sample') },
|
||||||
|
{ from: path.join(tempDir, '.env.example'), to: path.join(releaseDir, '.env.example') },
|
||||||
|
];
|
||||||
|
|
||||||
|
copyFiles.forEach(({ from, to }) => {
|
||||||
|
if (fs.existsSync(from)) {
|
||||||
|
fs.cpSync(from, to, { recursive: true });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 创建启动脚本
|
||||||
|
if (platform === 'win') {
|
||||||
|
const startScript = `@echo off
|
||||||
|
set QL_DIR=%~dp0
|
||||||
|
${outputName}.exe
|
||||||
|
`;
|
||||||
|
fs.writeFileSync(path.join(releaseDir, 'start.bat'), startScript);
|
||||||
|
} else {
|
||||||
|
const startScript = `#!/bin/bash
|
||||||
|
|
||||||
|
# 设置QL_DIR环境变量
|
||||||
|
QL_DIR=$(cd "$(dirname "$0")" && pwd)
|
||||||
|
export QL_DIR
|
||||||
|
|
||||||
|
echo "🚀 QL_DIR is set to: $QL_DIR"
|
||||||
|
echo "📁 Static files path: $QL_DIR/static"
|
||||||
|
|
||||||
|
# 检查是否存在.env文件,如果不存在则从.env.example复制
|
||||||
|
if [ ! -f ".env" ]; then
|
||||||
|
if [ -f ".env.example" ]; then
|
||||||
|
cp .env.example .env
|
||||||
|
echo "✅ Created .env file from .env.example"
|
||||||
|
else
|
||||||
|
echo "❌ .env.example not found, please create .env file manually"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 更新.env文件设置正确的端口
|
||||||
|
echo "✅ .env file updated with correct ports"
|
||||||
|
cat > .env << 'EOF2'
|
||||||
|
# 数据库配置
|
||||||
|
DB_TYPE=sqlite
|
||||||
|
DB_HOST=
|
||||||
|
DB_PORT=
|
||||||
|
DB_USERNAME=
|
||||||
|
DB_PASSWORD=
|
||||||
|
DB_NAME=go.db
|
||||||
|
|
||||||
|
# 服务器配置
|
||||||
|
PORT=5700
|
||||||
|
BACK_PORT=5700
|
||||||
|
GRPC_PORT=5500
|
||||||
|
GOTTY_PORT=5600
|
||||||
|
|
||||||
|
# 环境配置
|
||||||
|
NODE_ENV=production
|
||||||
|
QL_DIR=$QL_DIR
|
||||||
|
EOF2
|
||||||
|
|
||||||
|
# 设置执行权限
|
||||||
|
chmod +x ./${outputName}
|
||||||
|
|
||||||
|
# 添加信号处理,支持Ctrl+C正常退出
|
||||||
|
cleanup() {
|
||||||
|
echo "\n🛑 正在停止服务..."
|
||||||
|
# 找到并终止所有相关进程
|
||||||
|
if [ -n "$PID" ]; then
|
||||||
|
kill -INT $PID 2>/dev/null
|
||||||
|
wait $PID 2>/dev/null
|
||||||
|
fi
|
||||||
|
echo "✅ 服务已停止"
|
||||||
|
exit 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# 捕获SIGINT信号(Ctrl+C)
|
||||||
|
trap cleanup SIGINT SIGTERM
|
||||||
|
|
||||||
|
# 启动二进制文件并保存PID
|
||||||
|
echo "🚀 正在启动服务..."
|
||||||
|
./${outputName} &
|
||||||
|
PID=$!
|
||||||
|
|
||||||
|
# 等待进程结束或信号
|
||||||
|
echo "🎯 服务启动成功,PID: $PID"
|
||||||
|
echo "💡 按 Ctrl+C 停止服务"
|
||||||
|
wait $PID
|
||||||
|
`;
|
||||||
|
fs.writeFileSync(path.join(releaseDir, 'start.sh'), startScript);
|
||||||
|
try {
|
||||||
|
execSync(`chmod +x ${path.join(releaseDir, 'start.sh')}`);
|
||||||
|
} catch (e) {
|
||||||
|
console.log('警告: 设置执行权限失败,但不影响打包');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 复制version.yaml文件到发布目录
|
||||||
|
const versionYamlPath = path.join(__dirname, 'version.yaml');
|
||||||
|
if (fs.existsSync(versionYamlPath)) {
|
||||||
|
try {
|
||||||
|
fs.cpSync(versionYamlPath, path.join(releaseDir, 'version.yaml'));
|
||||||
|
console.log(`✅ version.yaml已复制到发布目录`);
|
||||||
|
} catch (e) {
|
||||||
|
console.log('警告: 复制version.yaml失败,但不影响打包');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建zip包
|
||||||
|
console.log(`创建 ${outputName}.zip...`);
|
||||||
|
const zipCommand = platform === 'win'
|
||||||
|
? `powershell Compress-Archive -Path "${releaseDir}\*" -DestinationPath "${CONFIG.outputDir}\${outputName}.zip" -Force`
|
||||||
|
: `cd "${CONFIG.outputDir}" && zip -r "${outputName}.zip" "${outputName}-release"`;
|
||||||
|
|
||||||
|
try {
|
||||||
|
runCommand(zipCommand);
|
||||||
|
console.log(`${platform}-${arch} 版本打包完成: ${CONFIG.outputDir}/${outputName}.zip`);
|
||||||
|
} catch (e) {
|
||||||
|
console.error(`创建zip包失败,可能需要安装zip工具:`, e.message);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`打包 ${platform}-${arch} 版本失败:`, error.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 生成GitHub Release脚本
|
||||||
|
function generateGithubReleaseScript() {
|
||||||
|
const releaseScriptPath = path.join(__dirname, 'github-release.js');
|
||||||
|
const releaseScript = `#!/usr/bin/env node
|
||||||
|
const { execSync } = require('child_process');
|
||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
// 读取版本信息
|
||||||
|
const version = require('./package.json').version;
|
||||||
|
const releaseTag = \`v\${version}\`;
|
||||||
|
const releaseTitle = \`Release v\${version}\`;
|
||||||
|
const releaseBody = \`## v\${version}\n\n自动生成的发布版本。\`;
|
||||||
|
const assetsDir = path.join(__dirname, 'dist');
|
||||||
|
|
||||||
|
// 获取所有zip文件
|
||||||
|
const assets = fs.readdirSync(assetsDir)
|
||||||
|
.filter(file => file.endsWith('.zip'))
|
||||||
|
.map(file => path.join(assetsDir, file));
|
||||||
|
|
||||||
|
console.log(\`创建GitHub Release: \${releaseTag}\`);
|
||||||
|
|
||||||
|
// 构建命令
|
||||||
|
let command = \`gh release create \${releaseTag} --title "\${releaseTitle}" --body "\${releaseBody}"\`;
|
||||||
|
|
||||||
|
// 添加资产文件
|
||||||
|
assets.forEach(asset => {
|
||||||
|
command += \` "\${asset}"\`;
|
||||||
|
});
|
||||||
|
|
||||||
|
// 执行命令
|
||||||
|
console.log('执行GitHub Release创建命令...');
|
||||||
|
try {
|
||||||
|
execSync(command, { stdio: 'inherit' });
|
||||||
|
console.log('GitHub Release创建完成!');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('创建GitHub Release失败,请确保已安装GitHub CLI并登录。');
|
||||||
|
console.error('错误信息:', error.message);
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
fs.writeFileSync(releaseScriptPath, releaseScript);
|
||||||
|
try {
|
||||||
|
fs.chmodSync(releaseScriptPath, '755');
|
||||||
|
} catch (e) {
|
||||||
|
console.log('警告: 设置GitHub Release脚本执行权限失败');
|
||||||
|
}
|
||||||
|
console.log(`GitHub Release脚本已生成: ${releaseScriptPath}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 主函数
|
||||||
|
async function main() {
|
||||||
|
try {
|
||||||
|
// 清理构建
|
||||||
|
cleanBuild();
|
||||||
|
|
||||||
|
// 安装依赖
|
||||||
|
installDependencies();
|
||||||
|
|
||||||
|
// 构建前端和后端
|
||||||
|
buildFrontend();
|
||||||
|
buildBackend();
|
||||||
|
|
||||||
|
// 准备打包文件
|
||||||
|
const tempDir = preparePackageFiles();
|
||||||
|
|
||||||
|
// 获取目标平台
|
||||||
|
const targetPlatforms = getTargetPlatforms();
|
||||||
|
|
||||||
|
// 为每个平台打包
|
||||||
|
for (const platform of targetPlatforms) {
|
||||||
|
packageWithPkg(tempDir, platform);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 清理临时目录
|
||||||
|
if (fs.existsSync(tempDir)) {
|
||||||
|
try {
|
||||||
|
fs.rmSync(tempDir, { recursive: true, force: true });
|
||||||
|
} catch (e) {
|
||||||
|
console.log('警告: 清理临时目录失败');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 生成GitHub Release脚本
|
||||||
|
generateGithubReleaseScript();
|
||||||
|
|
||||||
|
console.log('\n🎉 所有平台的二进制包打包完成!');
|
||||||
|
console.log('📁 输出目录:', CONFIG.outputDir);
|
||||||
|
console.log('\n要创建GitHub Release,请运行:');
|
||||||
|
console.log(' node github-release.js');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('\n❌ 构建过程中出现错误:', error.message);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 执行主函数
|
||||||
|
main();
|
||||||
38
github-release.js
Executable file
38
github-release.js
Executable file
|
|
@ -0,0 +1,38 @@
|
||||||
|
#!/usr/bin/env node
|
||||||
|
const { execSync } = require('child_process');
|
||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
// 读取版本信息
|
||||||
|
const version = require('./package.json').version;
|
||||||
|
const releaseTag = `v${version}`;
|
||||||
|
const releaseTitle = `Release v${version}`;
|
||||||
|
const releaseBody = `## v${version}
|
||||||
|
|
||||||
|
自动生成的发布版本。`;
|
||||||
|
const assetsDir = path.join(__dirname, 'dist');
|
||||||
|
|
||||||
|
// 获取所有zip文件
|
||||||
|
const assets = fs.readdirSync(assetsDir)
|
||||||
|
.filter(file => file.endsWith('.zip'))
|
||||||
|
.map(file => path.join(assetsDir, file));
|
||||||
|
|
||||||
|
console.log(`创建GitHub Release: ${releaseTag}`);
|
||||||
|
|
||||||
|
// 构建命令
|
||||||
|
let command = `gh release create ${releaseTag} --title "${releaseTitle}" --body "${releaseBody}"`;
|
||||||
|
|
||||||
|
// 添加资产文件
|
||||||
|
assets.forEach(asset => {
|
||||||
|
command += ` "${asset}"`;
|
||||||
|
});
|
||||||
|
|
||||||
|
// 执行命令
|
||||||
|
console.log('执行GitHub Release创建命令...');
|
||||||
|
try {
|
||||||
|
execSync(command, { stdio: 'inherit' });
|
||||||
|
console.log('GitHub Release创建完成!');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('创建GitHub Release失败,请确保已安装GitHub CLI并登录。');
|
||||||
|
console.error('错误信息:', error.message);
|
||||||
|
}
|
||||||
|
|
@ -11,7 +11,8 @@
|
||||||
"prettier": "prettier --write '**/*.{js,jsx,tsx,ts,less,md,json}'",
|
"prettier": "prettier --write '**/*.{js,jsx,tsx,ts,less,md,json}'",
|
||||||
"postinstall": "max setup 2>/dev/null || true",
|
"postinstall": "max setup 2>/dev/null || true",
|
||||||
"test": "umi-test",
|
"test": "umi-test",
|
||||||
"test:coverage": "umi-test --coverage"
|
"test:coverage": "umi-test --coverage",
|
||||||
|
"build-release": "node build-release.js"
|
||||||
},
|
},
|
||||||
"gitHooks": {
|
"gitHooks": {
|
||||||
"pre-commit": "lint-staged"
|
"pre-commit": "lint-staged"
|
||||||
|
|
@ -97,7 +98,7 @@
|
||||||
"@keyv/sqlite": "^4.0.1",
|
"@keyv/sqlite": "^4.0.1",
|
||||||
"proper-lockfile": "^4.1.2",
|
"proper-lockfile": "^4.1.2",
|
||||||
"compression": "^1.7.4",
|
"compression": "^1.7.4",
|
||||||
"helmet": "^6.0.1"
|
"helmet": "^8.1.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"moment": "2.30.1",
|
"moment": "2.30.1",
|
||||||
|
|
|
||||||
|
|
@ -63,8 +63,8 @@ dependencies:
|
||||||
specifier: ^2.0.3
|
specifier: ^2.0.3
|
||||||
version: 2.0.3
|
version: 2.0.3
|
||||||
helmet:
|
helmet:
|
||||||
specifier: ^6.0.1
|
specifier: ^8.1.0
|
||||||
version: 6.2.0
|
version: 8.1.0
|
||||||
hpagent:
|
hpagent:
|
||||||
specifier: ^1.2.0
|
specifier: ^1.2.0
|
||||||
version: 1.2.0
|
version: 1.2.0
|
||||||
|
|
@ -3873,7 +3873,7 @@ packages:
|
||||||
resolution: {integrity: sha512-ONIn/nSNQA57yRge3oaMQESef/6QhoeX7llWeDli0UZIfz8TQMkfNPTXA8VnnyeA1WUjG2pGqdjEIueYonMdfQ==}
|
resolution: {integrity: sha512-ONIn/nSNQA57yRge3oaMQESef/6QhoeX7llWeDli0UZIfz8TQMkfNPTXA8VnnyeA1WUjG2pGqdjEIueYonMdfQ==}
|
||||||
deprecated: This is a stub types definition. helmet provides its own type definitions, so you do not need this installed.
|
deprecated: This is a stub types definition. helmet provides its own type definitions, so you do not need this installed.
|
||||||
dependencies:
|
dependencies:
|
||||||
helmet: 6.2.0
|
helmet: 8.1.0
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/@types/hoist-non-react-statics@3.3.5:
|
/@types/hoist-non-react-statics@3.3.5:
|
||||||
|
|
@ -8467,9 +8467,9 @@ packages:
|
||||||
hasBin: true
|
hasBin: true
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/helmet@6.2.0:
|
/helmet@8.1.0:
|
||||||
resolution: {integrity: sha512-DWlwuXLLqbrIOltR6tFQXShj/+7Cyp0gLi6uAb8qMdFh/YBBFbKSgQ6nbXmScYd8emMctuthmgIa7tUfo9Rtyg==}
|
resolution: {integrity: sha512-jOiHyAZsmnr8LqoPGmCjYAaiuWwjAPLgY8ZX2XrmHawt99/u1y6RgrZMTeoPfpUbV96HOalYgz1qzkRbw54Pmg==}
|
||||||
engines: {node: '>=14.0.0'}
|
engines: {node: '>=18.0.0'}
|
||||||
|
|
||||||
/history@5.3.0:
|
/history@5.3.0:
|
||||||
resolution: {integrity: sha512-ZqaKwjjrAYUYfLG+htGaIIZ4nioX2L70ZUMIFysS3xvBsSG4x/n1V6TXV3N8ZYNuFGlDirFg32T7B6WOUPDYcQ==}
|
resolution: {integrity: sha512-ZqaKwjjrAYUYfLG+htGaIIZ4nioX2L70ZUMIFysS3xvBsSG4x/n1V6TXV3N8ZYNuFGlDirFg32T7B6WOUPDYcQ==}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user