mirror of
https://github.com/whyour/qinglong.git
synced 2025-10-28 15:16:07 +08:00
478 lines
14 KiB
JavaScript
Executable File
478 lines
14 KiB
JavaScript
Executable File
#!/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(); |