Files
mymelody/scripts/setup.js
2026-01-07 16:46:09 +08:00

236 lines
8.5 KiB
JavaScript

const exec = require('child_process').exec;
const https = require('https');
const path = require('path');
const fs = require('fs');
const isWin = require('os').platform().indexOf('win32') > -1;
const isLinux = require('os').platform().indexOf('linux') > -1;
const isDarwin = require('os').platform().indexOf('darwin') > -1;
const ROOT_DIR = `${__dirname}/../`;
const l = m => console.log(m);
const sleep = ms => new Promise(resolve => setTimeout(resolve, ms));
const runCmd = (cmd, shouldOutput = true, cwd = null) => {
const startTime = new Date();
const option = cwd ? {cwd} : {};
const currentCwd = cwd || process.cwd();
l(`[${startTime.toISOString()}] 开始执行命令: ${cmd}`);
l(`执行目录: ${currentCwd}`);
return new Promise(r => {
const childProcess = exec(cmd, option);
l(`进程ID: ${childProcess.pid}`);
let result = '';
let error = '';
childProcess.stdout.on('data', function(data) {
shouldOutput && console.log(data);
result += data.toString();
});
childProcess.stderr.on('data', (data) => {
shouldOutput && console.log(data);
error += data.toString();
})
childProcess.on('exit', (code, signal) => {
const endTime = new Date();
const duration = (endTime - startTime) / 1000;
l(`[${endTime.toISOString()}] 命令执行完成,耗时: ${duration}`);
if (signal) {
l(`进程被信号 ${signal} 终止`);
}
l(`退出码: ${code}`);
r({code, signal, result, error})
})
});
}
const runCmdAndExitWhenFailed = async (cmd, msg, shouldOutput = true, cwd = null) => {
l('----------------------------------------');
l(`执行命令: ${cmd}`);
l(`工作目录: ${cwd || process.cwd()}`);
const ret = await runCmd(cmd, shouldOutput, cwd);
if (ret.code !== 0 || ret.code === null) {
l('命令执行失败:');
l(msg);
l(`命令: ${cmd}`);
l(`工作目录: ${cwd || process.cwd()}`);
l('退出码: ' + ret.code);
l('错误输出: ' + ret.error);
l('标准输出: ' + ret.result);
l('系统信息:');
l(`- 平台: ${process.platform}`);
l(`- 架构: ${process.arch}`);
l(`- Node版本: ${process.version}`);
l(`- 内存使用: ${JSON.stringify(process.memoryUsage())}`);
process.exit(1);
}
l('----------------------------------------');
return ret;
}
function getMediaGetBinPath() {
return path.join(ROOT_DIR, 'backend', 'bin', `media-get${isWin ? '.exe' : ''}`);
}
async function checkAndUpdateMediaGet(currentMediaGetVersion) {
const MediaGetService = require('../backend/src/service/media_fetcher/media_get');
const latestVersion = await MediaGetService.getLatestMediaGetVersion();
if (latestVersion === false) {
return;
}
if (currentMediaGetVersion.localeCompare(latestVersion, undefined, { numeric: true, sensitivity: 'base' }) >= 0) {
l('当前 media-get 版本已经是最新版本');
return;
}
l(`当前 media-get(${currentMediaGetVersion})版本不是最新版本, 开始更新到${latestVersion}`);
await MediaGetService.downloadTheLatestMediaGet(latestVersion);
}
function copyDir(src, dest) {
fs.mkdirSync(dest);
fs.readdirSync(src).forEach(file => {
const srcPath = path.join(src, file);
const destPath = path.join(dest, file);
if (fs.statSync(srcPath).isDirectory()) {
copyDir(srcPath, destPath);
} else {
fs.copyFileSync(srcPath, destPath);
}
});
}
async function getPackageManager() {
let ret = await runCmd('pnpm --version', false);
if (ret.code === 0) {
return 'pnpm';
}
ret = await runCmd('yarn --version', false);
if (ret.code === 0) {
return 'yarn';
}
return 'npm';
}
async function downloadMediaGetWithRetry(latestVersion) {
const MediaGetService = require('../backend/src/service/media_fetcher/media_get');
const maxRetries = 3;
let retryCount = 0;
while (retryCount < maxRetries) {
l(`尝试下载 media-get (第 ${retryCount + 1} 次尝试)`);
const success = await MediaGetService.downloadTheLatestMediaGet(latestVersion);
if (success) {
return true;
}
retryCount++;
if (retryCount < maxRetries) {
l(`下载失败,等待 5 秒后重试...`);
await sleep(5000);
}
}
return false;
}
async function run() {
try {
l('开始执行...');
// 检查 FFmpeg 安装和位置
l('检查 FFmpeg 安装状态...');
if (!process.env.CROSS_COMPILING) {
const ffmpegRet = await runCmd('which ffmpeg && ls -l $(which ffmpeg)', true);
l('FFmpeg location and permissions:');
l(ffmpegRet.result);
await runCmdAndExitWhenFailed('ffmpeg -version', '请先安装 ffmpeg', false);
} else {
l('跳过 FFmpeg 检查 (CROSS_COMPILING=1)');
}
await runCmdAndExitWhenFailed('npm version', '请先安装 npm', false);
const pm = await getPackageManager();
l(`安装 node_module via ${pm}`)
l('开始安装后端依赖...');
l(`执行命令: ${pm} install --production --verbose in ${path.join(ROOT_DIR, 'backend')}`);
const backendInstallResult = await runCmdAndExitWhenFailed(`${pm} install --production --verbose`, '安装后端 node_module 失败', true, path.join(ROOT_DIR, 'backend'));
l('后端依赖安装结果:');
l('Exit code: ' + backendInstallResult.code);
l('Output: ' + backendInstallResult.result);
if (backendInstallResult.error) {
l('Error output: ' + backendInstallResult.error);
}
l('检查 media-get');
const MediaGetService = require('../backend/src/service/media_fetcher/media_get');
const mediaGetPath = getMediaGetBinPath();
l(`检查 media-get 权限: ${mediaGetPath}`);
await runCmd(`ls -l ${mediaGetPath}`, true);
if (!fs.existsSync(mediaGetPath)) {
const latestVersion = await MediaGetService.getLatestMediaGetVersion();
if (latestVersion === false) {
l('获取 media-get 最新版本失败,无法继续安装');
return false;
}
l('开始下载核心程序 media-get');
if (await downloadMediaGetWithRetry(latestVersion) === false) {
l('下载核心程序 media-get 失败');
return false;
}
} else {
const currentMediaGetVersion = await MediaGetService.getLatestMediaGetVersion();
await checkAndUpdateMediaGet(currentMediaGetVersion);
}
l('开始安装前端依赖...');
const frontendInstallResult = await runCmdAndExitWhenFailed(`${pm} install --verbose`, '安装前端 node_module 失败', true, path.join(ROOT_DIR, 'frontend'));
l('前端依赖安装结果:');
l('Exit code: ' + frontendInstallResult.code);
l('Output: ' + frontendInstallResult.result);
if (frontendInstallResult.error) {
l('Error output: ' + frontendInstallResult.error);
}
l('开始编译前端...');
const buildResult = await runCmdAndExitWhenFailed(`${pm} run build`, '前端编译失败', true, path.join(ROOT_DIR, 'frontend'));
l('前端编译结果:');
l('Exit code: ' + buildResult.code);
l('Output: ' + buildResult.result);
if (buildResult.error) {
l('Error output: ' + buildResult.error);
}
l('删除老目录');
try {
fs.rmdirSync(path.join(ROOT_DIR, 'backend', 'public'), { recursive: true });
} catch(e) {
l('删除老目录失败,但继续执行: ' + e.message);
}
l('拷贝前端目录');
try {
copyDir(path.join(ROOT_DIR, 'frontend', 'dist'), path.join(ROOT_DIR, 'backend', 'public'));
} catch(e) {
l('拷贝前端目录失败: ' + e.message);
return false;
}
return true;
} catch (error) {
l('执行过程中出现错误:');
l(error.stack || error.message || error);
return false;
}
}
run().then(isFine => {
l(isFine ? `执行完毕,执行以下命令启动服务:\r\n\r\nnpm run app` : '执行出错,请检查');
if (!isFine) {
process.exit(1);
}
});