diff --git a/frontend/dist-electron/main.js b/frontend/dist-electron/main.js index ced6ec1..7798390 100644 --- a/frontend/dist-electron/main.js +++ b/frontend/dist-electron/main.js @@ -35,6 +35,10 @@ var __importStar = (this && this.__importStar) || (function () { Object.defineProperty(exports, "__esModule", { value: true }); const electron_1 = require("electron"); const path = __importStar(require("path")); +const environmentService_1 = require("./services/environmentService"); +const downloadService_1 = require("./services/downloadService"); +const pythonService_1 = require("./services/pythonService"); +const gitService_1 = require("./services/gitService"); let mainWindow = null; function createWindow() { mainWindow = new electron_1.BrowserWindow({ @@ -42,18 +46,14 @@ function createWindow() { height: 900, minWidth: 800, minHeight: 600, - icon: path.join(__dirname, '../src/assets/AUTO_MAA.ico'), // 设置应用图标 + icon: path.join(__dirname, '../src/assets/AUTO_MAA.ico'), webPreferences: { preload: path.join(__dirname, 'preload.js'), nodeIntegration: false, contextIsolation: true, }, - // 隐藏菜单栏 autoHideMenuBar: true, - // 或者完全移除菜单栏(推荐) - // menuBarVisible: false }); - // 完全移除菜单栏 mainWindow.setMenuBarVisibility(false); const devServer = process.env.VITE_DEV_SERVER_URL; if (devServer) { @@ -66,15 +66,19 @@ function createWindow() { mainWindow.on('closed', () => { mainWindow = null; }); + // 设置各个服务的主窗口引用 + if (mainWindow) { + (0, downloadService_1.setMainWindow)(mainWindow); + (0, pythonService_1.setMainWindow)(mainWindow); + (0, gitService_1.setMainWindow)(mainWindow); + } } -// 处理开发者工具请求 +// IPC处理函数 electron_1.ipcMain.handle('open-dev-tools', () => { if (mainWindow) { - // 在新窗口中打开开发者工具 mainWindow.webContents.openDevTools({ mode: 'undocked' }); } }); -// 处理文件夹选择请求 electron_1.ipcMain.handle('select-folder', async () => { if (!mainWindow) return null; @@ -82,12 +86,8 @@ electron_1.ipcMain.handle('select-folder', async () => { properties: ['openDirectory'], title: '选择文件夹', }); - if (result.canceled) { - return null; - } - return result.filePaths[0]; + return result.canceled ? null : result.filePaths[0]; }); -// 处理文件选择请求 electron_1.ipcMain.handle('select-file', async (event, filters = []) => { if (!mainWindow) return null; @@ -96,11 +96,40 @@ electron_1.ipcMain.handle('select-file', async (event, filters = []) => { title: '选择文件', filters: filters.length > 0 ? filters : [{ name: '所有文件', extensions: ['*'] }], }); - if (result.canceled) { - return null; - } - return result.filePaths[0]; + return result.canceled ? null : result.filePaths[0]; }); +// 环境检查 +electron_1.ipcMain.handle('check-environment', async () => { + const appRoot = (0, environmentService_1.getAppRoot)(); + return (0, environmentService_1.checkEnvironment)(appRoot); +}); +// Python相关 +electron_1.ipcMain.handle('download-python', async (event, mirror = 'tsinghua') => { + const appRoot = (0, environmentService_1.getAppRoot)(); + return (0, pythonService_1.downloadPython)(appRoot, mirror); +}); +electron_1.ipcMain.handle('install-dependencies', async (event, mirror = 'tsinghua') => { + const appRoot = (0, environmentService_1.getAppRoot)(); + return (0, pythonService_1.installDependencies)(appRoot, mirror); +}); +electron_1.ipcMain.handle('start-backend', async () => { + const appRoot = (0, environmentService_1.getAppRoot)(); + return (0, pythonService_1.startBackend)(appRoot); +}); +// Git相关 +electron_1.ipcMain.handle('download-git', async () => { + const appRoot = (0, environmentService_1.getAppRoot)(); + return (0, gitService_1.downloadGit)(appRoot); +}); +electron_1.ipcMain.handle('clone-backend', async (event, repoUrl = 'https://github.com/DLmaster361/AUTO_MAA.git') => { + const appRoot = (0, environmentService_1.getAppRoot)(); + return (0, gitService_1.cloneBackend)(appRoot, repoUrl); +}); +electron_1.ipcMain.handle('update-backend', async (event, repoUrl = 'https://github.com/DLmaster361/AUTO_MAA.git') => { + const appRoot = (0, environmentService_1.getAppRoot)(); + return (0, gitService_1.cloneBackend)(appRoot, repoUrl); // 使用相同的逻辑,会自动判断是pull还是clone +}); +// 应用生命周期 electron_1.app.whenReady().then(createWindow); electron_1.app.on('window-all-closed', () => { if (process.platform !== 'darwin') diff --git a/frontend/dist-electron/preload.js b/frontend/dist-electron/preload.js index 59fe621..79704da 100644 --- a/frontend/dist-electron/preload.js +++ b/frontend/dist-electron/preload.js @@ -8,5 +8,20 @@ window.addEventListener('DOMContentLoaded', () => { electron_1.contextBridge.exposeInMainWorld('electronAPI', { openDevTools: () => electron_1.ipcRenderer.invoke('open-dev-tools'), selectFolder: () => electron_1.ipcRenderer.invoke('select-folder'), - selectFile: (filters) => electron_1.ipcRenderer.invoke('select-file', filters) + selectFile: (filters) => electron_1.ipcRenderer.invoke('select-file', filters), + // 初始化相关API + checkEnvironment: () => electron_1.ipcRenderer.invoke('check-environment'), + downloadPython: (mirror) => electron_1.ipcRenderer.invoke('download-python', mirror), + downloadGit: () => electron_1.ipcRenderer.invoke('download-git'), + installDependencies: (mirror) => electron_1.ipcRenderer.invoke('install-dependencies', mirror), + cloneBackend: (repoUrl) => electron_1.ipcRenderer.invoke('clone-backend', repoUrl), + updateBackend: (repoUrl) => electron_1.ipcRenderer.invoke('update-backend', repoUrl), + startBackend: () => electron_1.ipcRenderer.invoke('start-backend'), + // 监听下载进度 + onDownloadProgress: (callback) => { + electron_1.ipcRenderer.on('download-progress', (_, progress) => callback(progress)); + }, + removeDownloadProgressListener: () => { + electron_1.ipcRenderer.removeAllListeners('download-progress'); + } }); diff --git a/frontend/electron/main.ts b/frontend/electron/main.ts index 12aa23a..cd80eb9 100644 --- a/frontend/electron/main.ts +++ b/frontend/electron/main.ts @@ -1,5 +1,9 @@ import { app, BrowserWindow, ipcMain, dialog } from 'electron' import * as path from 'path' +import { getAppRoot, checkEnvironment } from './services/environmentService' +import { setMainWindow as setDownloadMainWindow } from './services/downloadService' +import { setMainWindow as setPythonMainWindow, downloadPython, installDependencies, startBackend } from './services/pythonService' +import { setMainWindow as setGitMainWindow, downloadGit, cloneBackend } from './services/gitService' let mainWindow: BrowserWindow | null = null @@ -9,19 +13,15 @@ function createWindow() { height: 900, minWidth: 800, minHeight: 600, - icon: path.join(__dirname, '../src/assets/AUTO_MAA.ico'), // 设置应用图标 + icon: path.join(__dirname, '../src/assets/AUTO_MAA.ico'), webPreferences: { preload: path.join(__dirname, 'preload.js'), nodeIntegration: false, contextIsolation: true, }, - // 隐藏菜单栏 autoHideMenuBar: true, - // 或者完全移除菜单栏(推荐) - // menuBarVisible: false }) - // 完全移除菜单栏 mainWindow.setMenuBarVisibility(false) const devServer = process.env.VITE_DEV_SERVER_URL @@ -35,49 +35,80 @@ function createWindow() { mainWindow.on('closed', () => { mainWindow = null }) + + // 设置各个服务的主窗口引用 + if (mainWindow) { + setDownloadMainWindow(mainWindow) + setPythonMainWindow(mainWindow) + setGitMainWindow(mainWindow) + } } -// 处理开发者工具请求 +// IPC处理函数 ipcMain.handle('open-dev-tools', () => { if (mainWindow) { - // 在新窗口中打开开发者工具 mainWindow.webContents.openDevTools({ mode: 'undocked' }) } }) -// 处理文件夹选择请求 ipcMain.handle('select-folder', async () => { if (!mainWindow) return null - const result = await dialog.showOpenDialog(mainWindow, { properties: ['openDirectory'], title: '选择文件夹', }) - - if (result.canceled) { - return null - } - - return result.filePaths[0] + return result.canceled ? null : result.filePaths[0] }) -// 处理文件选择请求 ipcMain.handle('select-file', async (event, filters = []) => { if (!mainWindow) return null - const result = await dialog.showOpenDialog(mainWindow, { properties: ['openFile'], title: '选择文件', filters: filters.length > 0 ? filters : [{ name: '所有文件', extensions: ['*'] }], }) - - if (result.canceled) { - return null - } - - return result.filePaths[0] + return result.canceled ? null : result.filePaths[0] }) +// 环境检查 +ipcMain.handle('check-environment', async () => { + const appRoot = getAppRoot() + return checkEnvironment(appRoot) +}) + +// Python相关 +ipcMain.handle('download-python', async (event, mirror = 'tsinghua') => { + const appRoot = getAppRoot() + return downloadPython(appRoot, mirror) +}) + +ipcMain.handle('install-dependencies', async (event, mirror = 'tsinghua') => { + const appRoot = getAppRoot() + return installDependencies(appRoot, mirror) +}) + +ipcMain.handle('start-backend', async () => { + const appRoot = getAppRoot() + return startBackend(appRoot) +}) + +// Git相关 +ipcMain.handle('download-git', async () => { + const appRoot = getAppRoot() + return downloadGit(appRoot) +}) + +ipcMain.handle('clone-backend', async (event, repoUrl = 'https://github.com/DLmaster361/AUTO_MAA.git') => { + const appRoot = getAppRoot() + return cloneBackend(appRoot, repoUrl) +}) + +ipcMain.handle('update-backend', async (event, repoUrl = 'https://github.com/DLmaster361/AUTO_MAA.git') => { + const appRoot = getAppRoot() + return cloneBackend(appRoot, repoUrl) // 使用相同的逻辑,会自动判断是pull还是clone +}) + +// 应用生命周期 app.whenReady().then(createWindow) app.on('window-all-closed', () => { @@ -86,4 +117,4 @@ app.on('window-all-closed', () => { app.on('activate', () => { if (mainWindow === null) createWindow() -}) +}) \ No newline at end of file diff --git a/frontend/electron/preload.ts b/frontend/electron/preload.ts index 38bcaae..71492e0 100644 --- a/frontend/electron/preload.ts +++ b/frontend/electron/preload.ts @@ -8,5 +8,22 @@ window.addEventListener('DOMContentLoaded', () => { contextBridge.exposeInMainWorld('electronAPI', { openDevTools: () => ipcRenderer.invoke('open-dev-tools'), selectFolder: () => ipcRenderer.invoke('select-folder'), - selectFile: (filters?: any[]) => ipcRenderer.invoke('select-file', filters) + selectFile: (filters?: any[]) => ipcRenderer.invoke('select-file', filters), + + // 初始化相关API + checkEnvironment: () => ipcRenderer.invoke('check-environment'), + downloadPython: (mirror?: string) => ipcRenderer.invoke('download-python', mirror), + downloadGit: () => ipcRenderer.invoke('download-git'), + installDependencies: (mirror?: string) => ipcRenderer.invoke('install-dependencies', mirror), + cloneBackend: (repoUrl?: string) => ipcRenderer.invoke('clone-backend', repoUrl), + updateBackend: (repoUrl?: string) => ipcRenderer.invoke('update-backend', repoUrl), + startBackend: () => ipcRenderer.invoke('start-backend'), + + // 监听下载进度 + onDownloadProgress: (callback: (progress: any) => void) => { + ipcRenderer.on('download-progress', (_, progress) => callback(progress)) + }, + removeDownloadProgressListener: () => { + ipcRenderer.removeAllListeners('download-progress') + } }) diff --git a/frontend/electron/services/downloadService.ts b/frontend/electron/services/downloadService.ts new file mode 100644 index 0000000..7fca276 --- /dev/null +++ b/frontend/electron/services/downloadService.ts @@ -0,0 +1,58 @@ +import * as https from 'https' +import * as fs from 'fs' +import { BrowserWindow } from 'electron' + +let mainWindow: BrowserWindow | null = null + +export function setMainWindow(window: BrowserWindow) { + mainWindow = window +} + +// 下载文件的通用函数 +export function downloadFile(url: string, outputPath: string): Promise { + return new Promise((resolve, reject) => { + console.log(`开始下载文件: ${url}`) + console.log(`保存路径: ${outputPath}`) + + const file = fs.createWriteStream(outputPath) + + https.get(url, (response) => { + const totalSize = parseInt(response.headers['content-length'] || '0', 10) + let downloadedSize = 0 + + console.log(`文件大小: ${totalSize} bytes`) + + response.pipe(file) + + response.on('data', (chunk) => { + downloadedSize += chunk.length + const progress = Math.round((downloadedSize / totalSize) * 100) + + console.log(`下载进度: ${progress}% (${downloadedSize}/${totalSize})`) + + if (mainWindow) { + mainWindow.webContents.send('download-progress', { + progress, + status: 'downloading', + message: `下载中... ${progress}%` + }) + } + }) + + file.on('finish', () => { + file.close() + console.log(`文件下载完成: ${outputPath}`) + resolve() + }) + + file.on('error', (err) => { + console.error(`文件写入错误: ${err.message}`) + fs.unlink(outputPath, () => {}) // 删除不完整的文件 + reject(err) + }) + }).on('error', (err) => { + console.error(`下载错误: ${err.message}`) + reject(err) + }) + }) +} \ No newline at end of file diff --git a/frontend/electron/services/environmentService.ts b/frontend/electron/services/environmentService.ts new file mode 100644 index 0000000..a1f3600 --- /dev/null +++ b/frontend/electron/services/environmentService.ts @@ -0,0 +1,35 @@ +import * as path from 'path' +import * as fs from 'fs' +import { app } from 'electron' + +// 获取应用根目录 +export function getAppRoot(): string { + return process.env.NODE_ENV === 'development' + ? process.cwd() + : path.dirname(app.getPath('exe')) +} + +// 检查环境 +export function checkEnvironment(appRoot: string) { + const environmentPath = path.join(appRoot, 'environment') + const pythonPath = path.join(environmentPath, 'python') + const gitPath = path.join(environmentPath, 'git') + const backendPath = path.join(appRoot, 'backend') + const requirementsPath = path.join(backendPath, 'requirements.txt') + + const pythonExists = fs.existsSync(pythonPath) + const gitExists = fs.existsSync(gitPath) + const backendExists = fs.existsSync(backendPath) + + // 检查依赖是否已安装(简单检查是否存在site-packages目录) + const sitePackagesPath = path.join(pythonPath, 'Lib', 'site-packages') + const dependenciesInstalled = fs.existsSync(sitePackagesPath) && fs.readdirSync(sitePackagesPath).length > 10 + + return { + pythonExists, + gitExists, + backendExists, + dependenciesInstalled, + isInitialized: pythonExists && gitExists && backendExists && dependenciesInstalled + } +} \ No newline at end of file diff --git a/frontend/electron/services/gitService.ts b/frontend/electron/services/gitService.ts new file mode 100644 index 0000000..85f5baf --- /dev/null +++ b/frontend/electron/services/gitService.ts @@ -0,0 +1,425 @@ +import * as path from 'path' +import * as fs from 'fs' +import { spawn } from 'child_process' +import { BrowserWindow } from 'electron' +import AdmZip from 'adm-zip' +import { downloadFile } from './downloadService' + +let mainWindow: BrowserWindow | null = null + +export function setMainWindow(window: BrowserWindow) { + mainWindow = window +} + +const gitDownloadUrl = 'https://alist-automaa.fearr.xyz/d/AUTO_MAA/git.zip' + +// 获取Git环境变量配置 +function getGitEnvironment(appRoot: string) { + const gitDir = path.join(appRoot, 'environment', 'git') + const binPath = path.join(gitDir, 'bin') + const mingw64BinPath = path.join(gitDir, 'mingw64', 'bin') + const gitCorePath = path.join(gitDir, 'mingw64', 'libexec', 'git-core') + + return { + ...process.env, + // 修复remote-https问题的关键:确保所有Git相关路径都在PATH中 + PATH: `${binPath};${mingw64BinPath};${gitCorePath};${process.env.PATH}`, + GIT_EXEC_PATH: gitCorePath, + GIT_TEMPLATE_DIR: path.join(gitDir, 'mingw64', 'share', 'git-core', 'templates'), + HOME: process.env.USERPROFILE || process.env.HOME, + // // SSL证书路径 + // GIT_SSL_CAINFO: path.join(gitDir, 'mingw64', 'ssl', 'certs', 'ca-bundle.crt'), + // 禁用系统Git配置 + GIT_CONFIG_NOSYSTEM: '1', + // 禁用交互式认证 + GIT_TERMINAL_PROMPT: '0', + GIT_ASKPASS: '', + // // 修复remote-https问题的关键环境变量 + // CURL_CA_BUNDLE: path.join(gitDir, 'mingw64', 'ssl', 'certs', 'ca-bundle.crt'), + // 确保Git能找到所有必要的程序 + GIT_HTTP_LOW_SPEED_LIMIT: '0', + GIT_HTTP_LOW_SPEED_TIME: '0' + } +} + +// 检查是否为Git仓库 +function isGitRepository(dirPath: string): boolean { + const gitDir = path.join(dirPath, '.git') + return fs.existsSync(gitDir) +} + +// 下载Git +export async function downloadGit(appRoot: string): Promise<{ success: boolean; error?: string }> { + try { + const environmentPath = path.join(appRoot, 'environment') + const gitPath = path.join(environmentPath, 'git') + + if (!fs.existsSync(environmentPath)) { + fs.mkdirSync(environmentPath, { recursive: true }) + } + + if (mainWindow) { + mainWindow.webContents.send('download-progress', { + type: 'git', + progress: 0, + status: 'downloading', + message: '开始下载Git...' + }) + } + + // 使用自定义Git压缩包 + const zipPath = path.join(environmentPath, 'git.zip') + await downloadFile(gitDownloadUrl, zipPath) + + if (mainWindow) { + mainWindow.webContents.send('download-progress', { + type: 'git', + progress: 100, + status: 'extracting', + message: '正在解压Git...' + }) + } + + // 解压Git到临时目录,然后移动到正确位置 + console.log(`开始解压Git到: ${gitPath}`) + + // 创建临时解压目录 + const tempExtractPath = path.join(environmentPath, 'git_temp') + if (!fs.existsSync(tempExtractPath)) { + fs.mkdirSync(tempExtractPath, { recursive: true }) + console.log(`创建临时解压目录: ${tempExtractPath}`) + } + + // 解压到临时目录 + const zip = new AdmZip(zipPath) + zip.extractAllTo(tempExtractPath, true) + console.log(`Git解压到临时目录: ${tempExtractPath}`) + + // 检查解压后的目录结构 + const tempContents = fs.readdirSync(tempExtractPath) + console.log(`临时目录内容:`, tempContents) + + // 如果解压后有git子目录,则从git子目录移动内容 + let sourceDir = tempExtractPath + if (tempContents.length === 1 && tempContents[0] === 'git') { + sourceDir = path.join(tempExtractPath, 'git') + console.log(`检测到git子目录,使用源目录: ${sourceDir}`) + } + + // 确保目标Git目录存在 + if (!fs.existsSync(gitPath)) { + fs.mkdirSync(gitPath, { recursive: true }) + console.log(`创建Git目录: ${gitPath}`) + } + + // 移动文件到最终目录 + const sourceContents = fs.readdirSync(sourceDir) + for (const item of sourceContents) { + const sourcePath = path.join(sourceDir, item) + const targetPath = path.join(gitPath, item) + + // 如果目标已存在,先删除 + if (fs.existsSync(targetPath)) { + if (fs.statSync(targetPath).isDirectory()) { + fs.rmSync(targetPath, { recursive: true, force: true }) + } else { + fs.unlinkSync(targetPath) + } + } + + // 移动文件或目录 + fs.renameSync(sourcePath, targetPath) + console.log(`移动: ${sourcePath} -> ${targetPath}`) + } + + // 清理临时目录 + fs.rmSync(tempExtractPath, { recursive: true, force: true }) + console.log(`清理临时目录: ${tempExtractPath}`) + + console.log(`Git解压完成到: ${gitPath}`) + + // 删除zip文件 + fs.unlinkSync(zipPath) + console.log(`删除临时文件: ${zipPath}`) + + if (mainWindow) { + mainWindow.webContents.send('download-progress', { + type: 'git', + progress: 100, + status: 'completed', + message: 'Git安装完成' + }) + } + + return { success: true } + } catch (error) { + const errorMessage = error instanceof Error ? error.message : String(error) + if (mainWindow) { + mainWindow.webContents.send('download-progress', { + type: 'git', + progress: 0, + status: 'error', + message: `Git下载失败: ${errorMessage}` + }) + } + return { success: false, error: errorMessage } + } +} + +// 克隆后端代码 +export async function cloneBackend(appRoot: string, repoUrl = 'https://github.com/DLmaster361/AUTO_MAA.git'): Promise<{ success: boolean; error?: string }> { + try { + const backendPath = path.join(appRoot) + const backendCheckPath = path.join(appRoot,'app') + const gitPath = path.join(appRoot, 'environment', 'git', 'bin', 'git.exe') + + console.log(`开始获取后端代码`) + console.log(`Git路径: ${gitPath}`) + console.log(`仓库URL: ${repoUrl}`) + console.log(`目标路径: ${backendPath}`) + + // 检查Git可执行文件是否存在 + if (!fs.existsSync(gitPath)) { + throw new Error(`Git可执行文件不存在: ${gitPath}`) + } + + // 获取Git环境变量 + const gitEnv = getGitEnvironment(appRoot) + + // 先测试Git是否能正常运行 + console.log('测试Git版本...') + try { + await new Promise((resolve, reject) => { + const testProcess = spawn(gitPath, ['--version'], { + stdio: 'pipe', + env: gitEnv + }) + + testProcess.stdout?.on('data', (data) => { + console.log('Git版本信息:', data.toString()) + }) + + testProcess.stderr?.on('data', (data) => { + console.log('Git版本错误:', data.toString()) + }) + + testProcess.on('close', (code) => { + if (code === 0) { + console.log('Git版本检查成功') + resolve() + } else { + reject(new Error(`Git版本检查失败,退出码: ${code}`)) + } + }) + + testProcess.on('error', (error) => { + reject(error) + }) + }) + } catch (error) { + console.error('Git版本检查失败:', error) + throw new Error(`Git无法正常运行: ${error}`) + } + + console.log('Git环境变量:', gitEnv) + + // 检查backend目录是否存在且是否为Git仓库 + if (fs.existsSync(backendCheckPath)) { + if (isGitRepository(backendPath)) { + // 如果是Git仓库,执行pull更新 + console.log('检测到Git仓库,执行pull更新...') + + if (mainWindow) { + mainWindow.webContents.send('download-progress', { + type: 'backend', + progress: 0, + status: 'downloading', + message: '正在更新后端代码...' + }) + } + + await new Promise((resolve, reject) => { + const process = spawn(gitPath, [ + 'clone', + '--progress', + '--verbose', + '-b', 'feature/refactor-backend', + repoUrl, + backendPath + ], { + stdio: 'pipe', + env: gitEnv, + cwd: appRoot + }) + + process.stdout?.on('data', (data) => { + const output = data.toString() + console.log('Git pull output:', output) + }) + + process.stderr?.on('data', (data) => { + const errorOutput = data.toString() + console.log('Git pull stderr:', errorOutput) + }) + + process.on('close', (code) => { + console.log(`git pull完成,退出码: ${code}`) + if (code === 0) { + resolve() + } else { + reject(new Error(`代码更新失败,退出码: ${code}`)) + } + }) + + process.on('error', (error) => { + console.error('git pull进程错误:', error) + reject(error) + }) + }) + + if (mainWindow) { + mainWindow.webContents.send('download-progress', { + type: 'backend', + progress: 100, + status: 'completed', + message: '后端代码更新完成' + }) + } + } else { + // 如果目录存在但不是Git仓库,删除后重新克隆 + console.log('目录存在但不是Git仓库,删除后重新克隆...') + fs.rmSync(backendPath, { recursive: true, force: true }) + + if (mainWindow) { + mainWindow.webContents.send('download-progress', { + type: 'backend', + progress: 0, + status: 'downloading', + message: '正在克隆后端代码...' + }) + } + + await new Promise((resolve, reject) => { + const process = spawn(gitPath, [ + 'clone', + '--progress', + '--verbose', + repoUrl, + backendPath + ], { + stdio: 'pipe', + env: gitEnv, + cwd: appRoot + }) + + process.stdout?.on('data', (data) => { + const output = data.toString() + console.log('Git clone output:', output) + }) + + process.stderr?.on('data', (data) => { + const errorOutput = data.toString() + console.log('Git clone stderr:', errorOutput) + }) + + process.on('close', (code) => { + console.log(`git clone完成,退出码: ${code}`) + if (code === 0) { + resolve() + } else { + reject(new Error(`代码克隆失败,退出码: ${code}`)) + } + }) + + process.on('error', (error) => { + console.error('git clone进程错误:', error) + reject(error) + }) + }) + + if (mainWindow) { + mainWindow.webContents.send('download-progress', { + type: 'backend', + progress: 100, + status: 'completed', + message: '后端代码克隆完成' + }) + } + } + } else { + // 如果目录不存在,直接克隆 + console.log('目录不存在,开始克隆...') + + if (mainWindow) { + mainWindow.webContents.send('download-progress', { + type: 'backend', + progress: 0, + status: 'downloading', + message: '正在克隆后端代码...' + }) + } + + await new Promise((resolve, reject) => { + const process = spawn(gitPath, [ + 'clone', + '--progress', + '--verbose', + '-b', 'feature/refactor-backend', + repoUrl, + backendPath + ], { + stdio: 'pipe', + env: gitEnv, + cwd: appRoot + }) + + process.stdout?.on('data', (data) => { + const output = data.toString() + console.log('Git clone output:', output) + }) + + process.stderr?.on('data', (data) => { + const errorOutput = data.toString() + console.log('Git clone stderr:', errorOutput) + }) + + process.on('close', (code) => { + console.log(`git clone完成,退出码: ${code}`) + if (code === 0) { + resolve() + } else { + reject(new Error(`代码克隆失败,退出码: ${code}`)) + } + }) + + process.on('error', (error) => { + console.error('git clone进程错误:', error) + reject(error) + }) + }) + + if (mainWindow) { + mainWindow.webContents.send('download-progress', { + type: 'backend', + progress: 100, + status: 'completed', + message: '后端代码克隆完成' + }) + } + } + + return { success: true } + } catch (error) { + const errorMessage = error instanceof Error ? error.message : String(error) + console.error('获取后端代码失败:', errorMessage) + if (mainWindow) { + mainWindow.webContents.send('download-progress', { + type: 'backend', + progress: 0, + status: 'error', + message: `后端代码获取失败: ${errorMessage}` + }) + } + return { success: false, error: errorMessage } + } +} \ No newline at end of file diff --git a/frontend/electron/services/pythonService.ts b/frontend/electron/services/pythonService.ts new file mode 100644 index 0000000..74dbb55 --- /dev/null +++ b/frontend/electron/services/pythonService.ts @@ -0,0 +1,456 @@ +import * as path from 'path' +import * as fs from 'fs' +import { spawn } from 'child_process' +import { BrowserWindow } from 'electron' +import AdmZip from 'adm-zip' +import { downloadFile } from './downloadService' + +let mainWindow: BrowserWindow | null = null + +export function setMainWindow(window: BrowserWindow) { + mainWindow = window +} + +// Python镜像源URL映射 +const pythonMirrorUrls = { + official: 'https://www.python.org/ftp/python/3.13.0/python-3.13.0-embed-amd64.zip', + tsinghua: 'https://mirrors.tuna.tsinghua.edu.cn/python/3.13.0/python-3.13.0-embed-amd64.zip', + ustc: 'https://mirrors.ustc.edu.cn/python/3.13.0/python-3.13.0-embed-amd64.zip', + huawei: 'https://mirrors.huaweicloud.com/repository/toolkit/python/3.13.0/python-3.13.0-embed-amd64.zip', + aliyun: 'https://mirrors.aliyun.com/python-release/windows/python-3.13.0-embed-amd64.zip' +} + +// 检查pip是否已安装 +function isPipInstalled(pythonPath: string): boolean { + const scriptsPath = path.join(pythonPath, 'Scripts') + const pipExePath = path.join(scriptsPath, 'pip.exe') + const pip3ExePath = path.join(scriptsPath, 'pip3.exe') + + console.log(`检查pip安装状态:`) + console.log(`Scripts目录: ${scriptsPath}`) + console.log(`pip.exe路径: ${pipExePath}`) + console.log(`pip3.exe路径: ${pip3ExePath}`) + + const scriptsExists = fs.existsSync(scriptsPath) + const pipExists = fs.existsSync(pipExePath) + const pip3Exists = fs.existsSync(pip3ExePath) + + console.log(`Scripts目录存在: ${scriptsExists}`) + console.log(`pip.exe存在: ${pipExists}`) + console.log(`pip3.exe存在: ${pip3Exists}`) + + return scriptsExists && (pipExists || pip3Exists) +} + +// 安装pip +async function installPip(pythonPath: string, appRoot: string): Promise { + console.log('开始检查pip安装状态...') + + const pythonExe = path.join(pythonPath, 'python.exe') + + // 检查Python可执行文件是否存在 + if (!fs.existsSync(pythonExe)) { + throw new Error(`Python可执行文件不存在: ${pythonExe}`) + } + + // 检查pip是否已安装 + if (isPipInstalled(pythonPath)) { + console.log('pip已经安装,跳过安装步骤') + console.log('检测到pip.exe文件存在,认为pip安装成功') + console.log('pip检查完成') + return + } + + console.log('pip未安装,开始安装...') + + const getPipPath = path.join(pythonPath, 'get-pip.py') + const getPipUrl = 'https://alist-automaa.fearr.xyz/d/AUTO_MAA/get-pip.py' + + console.log(`Python可执行文件路径: ${pythonExe}`) + console.log(`get-pip.py下载URL: ${getPipUrl}`) + console.log(`get-pip.py保存路径: ${getPipPath}`) + + // 下载get-pip.py + console.log('开始下载get-pip.py...') + try { + await downloadFile(getPipUrl, getPipPath) + console.log('get-pip.py下载完成') + + // 检查下载的文件大小 + const stats = fs.statSync(getPipPath) + console.log(`get-pip.py文件大小: ${stats.size} bytes`) + + if (stats.size < 10000) { // 如果文件小于10KB,可能是无效文件 + throw new Error(`get-pip.py文件大小异常: ${stats.size} bytes,可能下载失败`) + } + } catch (error) { + console.error('下载get-pip.py失败:', error) + throw new Error(`下载get-pip.py失败: ${error}`) + } + + // 执行pip安装 + await new Promise((resolve, reject) => { + console.log('执行pip安装命令...') + + const process = spawn(pythonExe, [getPipPath], { + cwd: pythonPath, + stdio: 'pipe' + }) + + process.stdout?.on('data', (data) => { + const output = data.toString() + console.log('pip安装输出:', output) + }) + + process.stderr?.on('data', (data) => { + const errorOutput = data.toString() + console.log('pip安装错误输出:', errorOutput) + }) + + process.on('close', (code) => { + console.log(`pip安装完成,退出码: ${code}`) + if (code === 0) { + console.log('pip安装成功') + resolve() + } else { + reject(new Error(`pip安装失败,退出码: ${code}`)) + } + }) + + process.on('error', (error) => { + console.error('pip安装进程错误:', error) + reject(error) + }) + }) + + // 验证pip是否安装成功 + console.log('验证pip安装...') + await new Promise((resolve, reject) => { + const verifyProcess = spawn(pythonExe, ['-m', 'pip', '--version'], { + cwd: pythonPath, + stdio: 'pipe' + }) + + verifyProcess.stdout?.on('data', (data) => { + const output = data.toString() + console.log('pip版本信息:', output) + }) + + verifyProcess.stderr?.on('data', (data) => { + const errorOutput = data.toString() + console.log('pip版本检查错误:', errorOutput) + }) + + verifyProcess.on('close', (code) => { + if (code === 0) { + console.log('pip验证成功') + resolve() + } else { + reject(new Error(`pip验证失败,退出码: ${code}`)) + } + }) + + verifyProcess.on('error', (error) => { + console.error('pip验证进程错误:', error) + reject(error) + }) + }) + + // 清理临时文件 + console.log('清理临时文件...') + try { + if (fs.existsSync(getPipPath)) { + fs.unlinkSync(getPipPath) + console.log('get-pip.py临时文件已删除') + } + } catch (error) { + console.warn('清理get-pip.py文件时出错:', error) + } + + console.log('pip安装和验证完成') +} + +// 下载Python +export async function downloadPython(appRoot: string, mirror = 'ustc'): Promise<{ success: boolean; error?: string }> { + try { + const environmentPath = path.join(appRoot, 'environment') + const pythonPath = path.join(environmentPath, 'python') + + // 确保environment目录存在 + if (!fs.existsSync(environmentPath)) { + fs.mkdirSync(environmentPath, { recursive: true }) + } + + if (mainWindow) { + mainWindow.webContents.send('download-progress', { + type: 'python', + progress: 0, + status: 'downloading', + message: '开始下载Python...' + }) + } + + // 根据选择的镜像源获取下载链接 + const pythonUrl = pythonMirrorUrls[mirror as keyof typeof pythonMirrorUrls] || pythonMirrorUrls.ustc + const zipPath = path.join(environmentPath, 'python.zip') + + await downloadFile(pythonUrl, zipPath) + + // 检查下载的Python文件大小 + const stats = fs.statSync(zipPath) + console.log(`Python压缩包大小: ${stats.size} bytes (${(stats.size / 1024 / 1024).toFixed(2)} MB)`) + + // Python 3.13.0嵌入式版本应该大约30MB,如果小于5MB可能是无效文件 + if (stats.size < 5 * 1024 * 1024) { // 5MB + fs.unlinkSync(zipPath) // 删除无效文件 + throw new Error(`Python下载文件大小异常: ${stats.size} bytes (${(stats.size / 1024).toFixed(2)} KB),可能是镜像站返回的错误页面或无效文件`) + } + + if (mainWindow) { + mainWindow.webContents.send('download-progress', { + type: 'python', + progress: 100, + status: 'extracting', + message: '正在解压Python...' + }) + } + + // 解压Python到指定目录 + console.log(`开始解压Python到: ${pythonPath}`) + + // 确保Python目录存在 + if (!fs.existsSync(pythonPath)) { + fs.mkdirSync(pythonPath, { recursive: true }) + console.log(`创建Python目录: ${pythonPath}`) + } + + const zip = new AdmZip(zipPath) + zip.extractAllTo(pythonPath, true) + console.log(`Python解压完成到: ${pythonPath}`) + + // 删除zip文件 + fs.unlinkSync(zipPath) + console.log(`删除临时文件: ${zipPath}`) + + // 启用 site-packages 支持 + const pthFile = path.join(pythonPath, 'python313._pth') + if (fs.existsSync(pthFile)) { + let content = fs.readFileSync(pthFile, 'utf-8') + content = content.replace(/^#import site/m, 'import site') + fs.writeFileSync(pthFile, content, 'utf-8') + console.log('已启用 site-packages 支持') + } + + + // 安装pip + if (mainWindow) { + mainWindow.webContents.send('download-progress', { + type: 'python', + progress: 80, + status: 'installing', + message: '正在安装pip...' + }) + } + + await installPip(pythonPath, appRoot) + + if (mainWindow) { + mainWindow.webContents.send('download-progress', { + type: 'python', + progress: 100, + status: 'completed', + message: 'Python和pip安装完成' + }) + } + + return { success: true } + } catch (error) { + const errorMessage = error instanceof Error ? error.message : String(error) + if (mainWindow) { + mainWindow.webContents.send('download-progress', { + type: 'python', + progress: 0, + status: 'error', + message: `Python下载失败: ${errorMessage}` + }) + } + return { success: false, error: errorMessage } + } +} + +// pip镜像源URL映射 +const pipMirrorUrls = { + official: 'https://pypi.org/simple/', + tsinghua: 'https://pypi.tuna.tsinghua.edu.cn/simple/', + ustc: 'https://pypi.mirrors.ustc.edu.cn/simple/', + aliyun: 'https://mirrors.aliyun.com/pypi/simple/', + douban: 'https://pypi.douban.com/simple/' +} + +// 安装Python依赖 +export async function installDependencies(appRoot: string, mirror = 'tsinghua'): Promise<{ success: boolean; error?: string }> { + try { + const pythonPath = path.join(appRoot, 'environment', 'python', 'python.exe') + const backendPath = path.join(appRoot, 'backend') + const requirementsPath = path.join(backendPath, 'requirements.txt') + + // 检查文件是否存在 + if (!fs.existsSync(pythonPath)) { + throw new Error('Python可执行文件不存在') + } + if (!fs.existsSync(requirementsPath)) { + throw new Error('requirements.txt文件不存在') + } + + if (mainWindow) { + mainWindow.webContents.send('download-progress', { + type: 'dependencies', + progress: 0, + status: 'downloading', + message: '正在安装Python依赖包...' + }) + } + + // 获取pip镜像源URL + const pipMirrorUrl = pipMirrorUrls[mirror as keyof typeof pipMirrorUrls] || pipMirrorUrls.tsinghua + + // 使用Scripts文件夹中的pip.exe + const pythonDir = path.join(appRoot, 'environment', 'python') + const pipExePath = path.join(pythonDir, 'Scripts', 'pip.exe') + + console.log(`开始安装Python依赖`) + console.log(`Python目录: ${pythonDir}`) + console.log(`pip.exe路径: ${pipExePath}`) + console.log(`requirements.txt路径: ${requirementsPath}`) + console.log(`pip镜像源: ${pipMirrorUrl}`) + + // 检查pip.exe是否存在 + if (!fs.existsSync(pipExePath)) { + throw new Error(`pip.exe不存在: ${pipExePath}`) + } + + // 安装依赖 - 直接使用pip.exe而不是python -m pip + await new Promise((resolve, reject) => { + const process = spawn(pipExePath, [ + 'install', + '-r', requirementsPath, + '-i', pipMirrorUrl, + '--trusted-host', new URL(pipMirrorUrl).hostname + ], { + cwd: backendPath, + stdio: 'pipe' + }) + + process.stdout?.on('data', (data) => { + const output = data.toString() + console.log('Pip output:', output) + + if (mainWindow) { + mainWindow.webContents.send('download-progress', { + type: 'dependencies', + progress: 50, + status: 'downloading', + message: '正在安装依赖包...' + }) + } + }) + + process.stderr?.on('data', (data) => { + const errorOutput = data.toString() + console.error('Pip error:', errorOutput) + }) + + process.on('close', (code) => { + console.log(`pip安装完成,退出码: ${code}`) + if (code === 0) { + resolve() + } else { + reject(new Error(`依赖安装失败,退出码: ${code}`)) + } + }) + + process.on('error', (error) => { + console.error('pip进程错误:', error) + reject(error) + }) + }) + + if (mainWindow) { + mainWindow.webContents.send('download-progress', { + type: 'dependencies', + progress: 100, + status: 'completed', + message: 'Python依赖安装完成' + }) + } + + return { success: true } + } catch (error) { + const errorMessage = error instanceof Error ? error.message : String(error) + if (mainWindow) { + mainWindow.webContents.send('download-progress', { + type: 'dependencies', + progress: 0, + status: 'error', + message: `依赖安装失败: ${errorMessage}` + }) + } + return { success: false, error: errorMessage } + } +} + +// 启动后端 +export async function startBackend(appRoot: string): Promise<{ success: boolean; error?: string }> { + try { + const pythonPath = path.join(appRoot, 'environment', 'python', 'python.exe') + const backendPath = path.join(appRoot, 'backend') + const mainPyPath = path.join(backendPath, 'app','main.py') + + // 检查文件是否存在 + if (!fs.existsSync(pythonPath)) { + throw new Error('Python可执行文件不存在') + } + if (!fs.existsSync(mainPyPath)) { + throw new Error('后端主文件不存在') + } + + // 启动后端进程 + const backendProcess = spawn(pythonPath, [mainPyPath], { + cwd: backendPath, + stdio: 'pipe' + }) + + + + // 等待后端启动 + await new Promise((resolve, reject) => { + const timeout = setTimeout(() => { + reject(new Error('后端启动超时')) + }, 30000) // 30秒超时 + + backendProcess.stdout?.on('data', (data) => { + const output = data.toString() + console.log('Backend output:', output) + + // 检查是否包含启动成功的标志 + if (output.includes('uvicorn') || output.includes('8000')) { + clearTimeout(timeout) + resolve() + } + }) + + backendProcess.stderr?.on('data', (data) => { + console.error('Backend error:', data.toString()) + }) + + backendProcess.on('error', (error) => { + clearTimeout(timeout) + reject(error) + }) + }) + + return { success: true } + } catch (error) { + return { success: false, error: error instanceof Error ? error.message : String(error) } + } +} \ No newline at end of file diff --git a/frontend/package.json b/frontend/package.json index 77e11ca..3221657 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -34,6 +34,8 @@ }, "dependencies": { "@ant-design/icons-vue": "^7.0.1", + "@types/adm-zip": "^0.5.7", + "adm-zip": "^0.5.16", "ant-design-vue": "4.x", "vue": "^3.5.17", "vue-router": "4" diff --git a/frontend/public/AUTO_MAA.ico b/frontend/public/AUTO_MAA.ico new file mode 100644 index 0000000..6f9c7ae Binary files /dev/null and b/frontend/public/AUTO_MAA.ico differ diff --git a/frontend/src/App.vue b/frontend/src/App.vue index 3508855..c4441d6 100644 --- a/frontend/src/App.vue +++ b/frontend/src/App.vue @@ -1,11 +1,16 @@ + + \ No newline at end of file diff --git a/frontend/src/main.ts b/frontend/src/main.ts index 81ed311..667ad70 100644 --- a/frontend/src/main.ts +++ b/frontend/src/main.ts @@ -2,6 +2,7 @@ import { createApp } from 'vue' import App from './App.vue' import router from './router/index.ts' import { OpenAPI } from '@/api' +import LoggerPlugin, { logger } from '@/utils/logger' import Antd from 'ant-design-vue' import 'ant-design-vue/dist/reset.css' @@ -9,4 +10,16 @@ import 'ant-design-vue/dist/reset.css' // 配置API基础URL OpenAPI.BASE = 'http://localhost:8000' -createApp(App).use(Antd).use(router).mount('#app') +// 创建应用实例 +const app = createApp(App) + +// 注册插件 +app.use(Antd) +app.use(router) +app.use(LoggerPlugin) + +// 挂载应用 +app.mount('#app') + +// 记录应用启动日志 +logger.info('应用启动', { version: '1.0.0', environment: process.env.NODE_ENV }) diff --git a/frontend/src/router/index.ts b/frontend/src/router/index.ts index ed2aedc..d2a7869 100644 --- a/frontend/src/router/index.ts +++ b/frontend/src/router/index.ts @@ -4,7 +4,13 @@ import type { RouteRecordRaw } from 'vue-router' const routes: RouteRecordRaw[] = [ { path: '/', - redirect: '/home', + redirect: '/initialization', + }, + { + path: '/initialization', + name: 'Initialization', + component: () => import('../views/Initialization.vue'), + meta: { title: '初始化' }, }, { path: '/home', @@ -66,6 +72,12 @@ const routes: RouteRecordRaw[] = [ component: () => import('../views/Settings.vue'), meta: { title: '设置' }, }, + { + path: '/logs', + name: 'Logs', + component: () => import('../views/Logs.vue'), + meta: { title: '系统日志' }, + }, ] const router = createRouter({ diff --git a/frontend/src/types/electron.d.ts b/frontend/src/types/electron.d.ts index 1e3aa85..a844a0c 100644 --- a/frontend/src/types/electron.d.ts +++ b/frontend/src/types/electron.d.ts @@ -1,11 +1,30 @@ -export {} +export interface ElectronAPI { + openDevTools: () => Promise + selectFolder: () => Promise + selectFile: (filters?: any[]) => Promise + + // 初始化相关API + checkEnvironment: () => Promise<{ + pythonExists: boolean + gitExists: boolean + backendExists: boolean + dependenciesInstalled: boolean + isInitialized: boolean + }> + downloadPython: (mirror?: string) => Promise<{ success: boolean; error?: string }> + downloadGit: () => Promise<{ success: boolean; error?: string }> + installDependencies: (mirror?: string) => Promise<{ success: boolean; error?: string }> + cloneBackend: (repoUrl?: string) => Promise<{ success: boolean; error?: string }> + updateBackend: (repoUrl?: string) => Promise<{ success: boolean; error?: string }> + startBackend: () => Promise<{ success: boolean; error?: string }> + + // 监听下载进度 + onDownloadProgress: (callback: (progress: any) => void) => void + removeDownloadProgressListener: () => void +} declare global { interface Window { - electronAPI: { - openDevTools: () => void, - selectFolder: () => Promise - selectFile: (filters?: Array<{ name: string; extensions: string[] }>) => Promise - } + electronAPI: ElectronAPI } -} +} \ No newline at end of file diff --git a/frontend/src/types/initialization.ts b/frontend/src/types/initialization.ts new file mode 100644 index 0000000..407dab7 --- /dev/null +++ b/frontend/src/types/initialization.ts @@ -0,0 +1,21 @@ +export interface InitializationStatus { + pythonExists: boolean + gitExists: boolean + backendExists: boolean + dependenciesInstalled: boolean + isInitialized: boolean +} + +export interface DownloadProgress { + type: 'python' | 'git' | 'backend' | 'dependencies' | 'service' + progress: number + status: 'downloading' | 'extracting' | 'installing' | 'completed' | 'error' + message: string +} + +export interface MirrorSource { + key: string + name: string + url: string + speed: number | null +} \ No newline at end of file diff --git a/frontend/src/utils/logger.ts b/frontend/src/utils/logger.ts new file mode 100644 index 0000000..cfc87a1 --- /dev/null +++ b/frontend/src/utils/logger.ts @@ -0,0 +1,185 @@ +import { ref } from 'vue' + +export type LogLevel = 'debug' | 'info' | 'warn' | 'error' + +export interface LogEntry { + timestamp: string + level: LogLevel + message: string + data?: any + component?: string +} + +class Logger { + private logs = ref([]) + private maxLogs = 1000 // 最大日志条数 + private logToConsole = true + private logToStorage = true + + constructor() { + this.loadLogsFromStorage() + } + + private formatTimestamp(): string { + const now = new Date() + return now.toISOString().replace('T', ' ').substring(0, 19) + } + + private addLog(level: LogLevel, message: string, data?: any, component?: string) { + const logEntry: LogEntry = { + timestamp: this.formatTimestamp(), + level, + message, + data, + component + } + + // 添加到内存日志 + this.logs.value.push(logEntry) + + // 限制日志数量 + if (this.logs.value.length > this.maxLogs) { + this.logs.value.shift() + } + + // 输出到控制台 + if (this.logToConsole) { + const consoleMessage = `[${logEntry.timestamp}] [${level.toUpperCase()}] ${component ? `[${component}] ` : ''}${message}` + + switch (level) { + case 'debug': + console.debug(consoleMessage, data) + break + case 'info': + console.info(consoleMessage, data) + break + case 'warn': + console.warn(consoleMessage, data) + break + case 'error': + console.error(consoleMessage, data) + break + } + } + + // 保存到本地存储 + if (this.logToStorage) { + this.saveLogsToStorage() + } + } + + private saveLogsToStorage() { + try { + const logsToSave = this.logs.value.slice(-500) // 只保存最近500条日志 + localStorage.setItem('app-logs', JSON.stringify(logsToSave)) + } catch (error) { + console.error('保存日志到本地存储失败:', error) + } + } + + private loadLogsFromStorage() { + try { + const savedLogs = localStorage.getItem('app-logs') + if (savedLogs) { + const parsedLogs = JSON.parse(savedLogs) as LogEntry[] + this.logs.value = parsedLogs + } + } catch (error) { + console.error('从本地存储加载日志失败:', error) + } + } + + // 公共方法 + debug(message: string, data?: any, component?: string) { + this.addLog('debug', message, data, component) + } + + info(message: string, data?: any, component?: string) { + this.addLog('info', message, data, component) + } + + warn(message: string, data?: any, component?: string) { + this.addLog('warn', message, data, component) + } + + error(message: string, data?: any, component?: string) { + this.addLog('error', message, data, component) + } + + // 获取日志 + getLogs() { + return this.logs + } + + // 清空日志 + clearLogs() { + this.logs.value = [] + localStorage.removeItem('app-logs') + } + + // 导出日志到文件 + exportLogs(): string { + const logText = this.logs.value + .map(log => { + const dataStr = log.data ? ` | Data: ${JSON.stringify(log.data)}` : '' + const componentStr = log.component ? ` | Component: ${log.component}` : '' + return `[${log.timestamp}] [${log.level.toUpperCase()}]${componentStr} ${log.message}${dataStr}` + }) + .join('\n') + + return logText + } + + // 下载日志文件 + downloadLogs() { + const logText = this.exportLogs() + const blob = new Blob([logText], { type: 'text/plain;charset=utf-8' }) + const url = URL.createObjectURL(blob) + + const link = document.createElement('a') + link.href = url + link.download = `auto-maa-logs-${new Date().toISOString().split('T')[0]}.txt` + document.body.appendChild(link) + link.click() + document.body.removeChild(link) + + URL.revokeObjectURL(url) + } + + // 配置选项 + setLogToConsole(enabled: boolean) { + this.logToConsole = enabled + } + + setLogToStorage(enabled: boolean) { + this.logToStorage = enabled + } + + setMaxLogs(max: number) { + this.maxLogs = max + if (this.logs.value.length > max) { + this.logs.value = this.logs.value.slice(-max) + } + } +} + +// 创建全局日志实例 +export const logger = new Logger() + +// 创建组件专用的日志器 +export function createComponentLogger(componentName: string) { + return { + debug: (message: string, data?: any) => logger.debug(message, data, componentName), + info: (message: string, data?: any) => logger.info(message, data, componentName), + warn: (message: string, data?: any) => logger.warn(message, data, componentName), + error: (message: string, data?: any) => logger.error(message, data, componentName), + } +} + +// Vue插件 +export default { + install(app: any) { + app.config.globalProperties.$logger = logger + app.provide('logger', logger) + } +} \ No newline at end of file diff --git a/frontend/src/views/Initialization.vue b/frontend/src/views/Initialization.vue new file mode 100644 index 0000000..17fedc7 --- /dev/null +++ b/frontend/src/views/Initialization.vue @@ -0,0 +1,967 @@ + + + + + \ No newline at end of file diff --git a/frontend/src/views/Logs.vue b/frontend/src/views/Logs.vue new file mode 100644 index 0000000..91d7dc0 --- /dev/null +++ b/frontend/src/views/Logs.vue @@ -0,0 +1,55 @@ + + + + + \ No newline at end of file diff --git a/frontend/yarn.lock b/frontend/yarn.lock index d8d66d5..ceb6fca 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -31,15 +31,6 @@ "@types/json-schema" "^7.0.15" js-yaml "^4.1.0" -"@babel/code-frame@^7.0.0": - version "7.27.1" - resolved "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz" - integrity sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg== - dependencies: - "@babel/helper-validator-identifier" "^7.27.1" - js-tokens "^4.0.0" - picocolors "^1.1.1" - "@babel/helper-string-parser@^7.27.1": version "7.27.1" resolved "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz" @@ -281,11 +272,6 @@ "@eslint/core" "^0.15.1" levn "^0.4.1" -"@exodus/schemasafe@^1.0.0-rc.2": - version "1.3.0" - resolved "https://registry.npmjs.org/@exodus/schemasafe/-/schemasafe-1.3.0.tgz" - integrity sha512-5Aap/GaRupgNx/feGBwLLTVv8OQFfv3pq2lPRzPg9R+IOBnDgghTGW7l7EuVXOvg5cc/xSAlRW8rBrjIC3Nvqw== - "@gar/promisify@^1.1.3": version "1.1.3" resolved "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz" @@ -489,6 +475,13 @@ resolved "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz" integrity sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A== +"@types/adm-zip@^0.5.7": + version "0.5.7" + resolved "https://registry.npmjs.org/@types/adm-zip/-/adm-zip-0.5.7.tgz" + integrity sha512-DNEs/QvmyRLurdQPChqq0Md4zGvPwHerAJYWk9l2jCbD1VPpnzRJorOdiq4zsw09NFbYnhfsoEhWtxIzXpn2yw== + dependencies: + "@types/node" "*" + "@types/cacheable-request@^6.0.1": version "6.0.3" resolved "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.3.tgz" @@ -674,29 +667,6 @@ "@typescript-eslint/types" "8.38.0" eslint-visitor-keys "^4.2.1" -"@umijs/openapi@^1.13.15": - version "1.13.15" - resolved "https://registry.npmjs.org/@umijs/openapi/-/openapi-1.13.15.tgz" - integrity sha512-+oJBEXV9Liu7tZzkYANs72hXiwqEngVhpUQN+XLVsAr49+D6thr+Fyb0cezcrydulOSsxa+VPaPMXPmXbAVuYA== - dependencies: - chalk "^4.1.2" - cosmiconfig "^9.0.0" - dayjs "^1.10.3" - glob "^7.1.6" - lodash "^4.17.21" - memoizee "^0.4.15" - mock.js "^0.2.0" - mockjs "^1.1.0" - node-fetch "^2.6.1" - number-to-words "^1.2.4" - nunjucks "^3.2.2" - openapi3-ts "^2.0.1" - prettier "^2.2.1" - reserved-words "^0.1.2" - rimraf "^3.0.2" - swagger2openapi "^7.0.4" - tiny-pinyin "^1.3.2" - "@vitejs/plugin-vue@^6.0.1": version "6.0.1" resolved "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-6.0.1.tgz" @@ -847,11 +817,6 @@ resolved "https://registry.npmjs.org/7zip-bin/-/7zip-bin-5.2.0.tgz" integrity sha512-ukTPVhqG4jNzMro2qA9HSCSSVJN3aN7tlb+hfqYCt3ER0yWroeA2VR38MNrOHLQ/cVj+DaIMad0kFCtWWowh/A== -a-sync-waterfall@^1.0.0: - version "1.0.1" - resolved "https://registry.npmjs.org/a-sync-waterfall/-/a-sync-waterfall-1.0.1.tgz" - integrity sha512-RYTOHHdWipFUliRFMCS4X2Yn2X8M87V/OpSqWzKKOGhzqyUxzyVmhHDH9sAvG+ZuQf/TAOFsLCpMw09I1ufUnA== - abbrev@^1.0.0: version "1.1.1" resolved "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz" @@ -867,6 +832,11 @@ acorn@^8.15.0: resolved "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz" integrity sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg== +adm-zip@^0.5.16: + version "0.5.16" + resolved "https://registry.npmjs.org/adm-zip/-/adm-zip-0.5.16.tgz" + integrity sha512-TGw5yVi4saajsSEgz25grObGHEUaDrniwvA2qwSC060KfqGPdglhvPMA2lPIoxs3PQIItj2iag35fONcQqgUaQ== + agent-base@^6.0.2, agent-base@6: version "6.0.2" resolved "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz" @@ -1023,11 +993,6 @@ array-tree-filter@^2.1.0: resolved "https://registry.npmjs.org/array-tree-filter/-/array-tree-filter-2.1.0.tgz" integrity sha512-4ROwICNlNw/Hqa9v+rk5h22KjmzB1JGTMVKP2AKJBOCgb0yL0ASf0+YvCcLNNwquOHNX48jkeZIJ3a+oOQqKcw== -asap@^2.0.3: - version "2.0.6" - resolved "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz" - integrity sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA== - async-exit-hook@^2.0.1: version "2.0.1" resolved "https://registry.npmjs.org/async-exit-hook/-/async-exit-hook-2.0.1.tgz" @@ -1212,11 +1177,6 @@ call-bind-apply-helpers@^1.0.1, call-bind-apply-helpers@^1.0.2: es-errors "^1.3.0" function-bind "^1.1.2" -call-me-maybe@^1.0.1: - version "1.0.2" - resolved "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.2.tgz" - integrity sha512-HpX65o1Hnr9HH25ojC1YGs7HCQLq0GCOibSaWER0eNpgJ/Z1MZv2mTc7+xh6WOPxbRVcmgbv4hGU+uSQ/2xFZQ== - callsites@^3.0.0: version "3.1.0" resolved "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz" @@ -1307,16 +1267,16 @@ combined-stream@^1.0.8: dependencies: delayed-stream "~1.0.0" -commander@*, commander@^5.0.0, commander@^5.1.0: - version "5.1.0" - resolved "https://registry.npmjs.org/commander/-/commander-5.1.0.tgz" - integrity sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg== - commander@^12.0.0: version "12.1.0" resolved "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz" integrity sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA== +commander@^5.0.0: + version "5.1.0" + resolved "https://registry.npmjs.org/commander/-/commander-5.1.0.tgz" + integrity sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg== + compare-version@^0.1.2: version "0.1.2" resolved "https://registry.npmjs.org/compare-version/-/compare-version-0.1.2.tgz" @@ -1358,16 +1318,6 @@ core-js@^3.15.1: resolved "https://registry.npmjs.org/core-js/-/core-js-3.44.0.tgz" integrity sha512-aFCtd4l6GvAXwVEh3XbbVqJGHDJt0OZRa+5ePGx3LLwi12WfexqQxcsohb2wgsa/92xtl19Hd66G/L+TaAxDMw== -cosmiconfig@^9.0.0: - version "9.0.0" - resolved "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.0.tgz" - integrity sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg== - dependencies: - env-paths "^2.2.1" - import-fresh "^3.3.0" - js-yaml "^4.1.0" - parse-json "^5.2.0" - cross-env@^10.0.0: version "10.0.0" resolved "https://registry.npmjs.org/cross-env/-/cross-env-10.0.0.tgz" @@ -1395,15 +1345,7 @@ csstype@^3.1.1, csstype@^3.1.3: resolved "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz" integrity sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw== -d@^1.0.1, d@^1.0.2, d@1: - version "1.0.2" - resolved "https://registry.npmjs.org/d/-/d-1.0.2.tgz" - integrity sha512-MOqHvMWF9/9MX6nza0KgvFH4HpMU0EF5uUDXqX/BtxtU8NfB0QzRtJ8Oe/6SuS4kbhyzVJwjd97EA4PKrzJ8bw== - dependencies: - es5-ext "^0.10.64" - type "^2.7.2" - -dayjs@^1.10.3, dayjs@^1.10.5: +dayjs@^1.10.5: version "1.11.13" resolved "https://registry.npmjs.org/dayjs/-/dayjs-1.11.13.tgz" integrity sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg== @@ -1610,7 +1552,7 @@ entities@^4.5.0: resolved "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz" integrity sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw== -env-paths@^2.2.0, env-paths@^2.2.1: +env-paths@^2.2.0: version "2.2.1" resolved "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz" integrity sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A== @@ -1620,13 +1562,6 @@ err-code@^2.0.2: resolved "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz" integrity sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA== -error-ex@^1.3.1: - version "1.3.2" - resolved "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz" - integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g== - dependencies: - is-arrayish "^0.2.1" - es-define-property@^1.0.0, es-define-property@^1.0.1: version "1.0.1" resolved "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz" @@ -1654,53 +1589,11 @@ es-set-tostringtag@^2.1.0: has-tostringtag "^1.0.2" hasown "^2.0.2" -es5-ext@^0.10.35, es5-ext@^0.10.46, es5-ext@^0.10.62, es5-ext@^0.10.64, es5-ext@~0.10.14, es5-ext@~0.10.2: - version "0.10.64" - resolved "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.64.tgz" - integrity sha512-p2snDhiLaXe6dahss1LddxqEm+SkuDvV8dnIQG0MWjyHpcMNfXKPE+/Cc0y+PhxJX3A4xGNeFCj5oc0BUh6deg== - dependencies: - es6-iterator "^2.0.3" - es6-symbol "^3.1.3" - esniff "^2.0.1" - next-tick "^1.1.0" - es6-error@^4.1.1: version "4.1.1" resolved "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz" integrity sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg== -es6-iterator@^2.0.3: - version "2.0.3" - resolved "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz" - integrity sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g== - dependencies: - d "1" - es5-ext "^0.10.35" - es6-symbol "^3.1.1" - -es6-promise@^3.2.1: - version "3.3.1" - resolved "https://registry.npmjs.org/es6-promise/-/es6-promise-3.3.1.tgz" - integrity sha512-SOp9Phqvqn7jtEUxPWdWfWoLmyt2VaJ6MpvP9Comy1MceMXqE6bxvaTu4iaxpYYPzhny28Lc+M87/c2cPK6lDg== - -es6-symbol@^3.1.1, es6-symbol@^3.1.3: - version "3.1.4" - resolved "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.4.tgz" - integrity sha512-U9bFFjX8tFiATgtkJ1zg25+KviIXpgRvRHS8sau3GfhVzThRQrOeksPeT0BWW2MNZs1OEWJ1DPXOQMn0KKRkvg== - dependencies: - d "^1.0.2" - ext "^1.7.0" - -es6-weak-map@^2.0.3: - version "2.0.3" - resolved "https://registry.npmjs.org/es6-weak-map/-/es6-weak-map-2.0.3.tgz" - integrity sha512-p5um32HOTO1kP+w7PRnB+5lQ43Z6muuMuIMffvDN8ZB4GcnjLBV6zGStpbASIMk4DCAvEaamhe2zhyCb/QXXsA== - dependencies: - d "1" - es5-ext "^0.10.46" - es6-iterator "^2.0.3" - es6-symbol "^3.1.1" - esbuild@^0.25.0: version "0.25.8" resolved "https://registry.npmjs.org/esbuild/-/esbuild-0.25.8.tgz" @@ -1827,16 +1720,6 @@ eslint@^9.32.0: natural-compare "^1.4.0" optionator "^0.9.3" -esniff@^2.0.1: - version "2.0.1" - resolved "https://registry.npmjs.org/esniff/-/esniff-2.0.1.tgz" - integrity sha512-kTUIGKQ/mDPFoJ0oVfcmyJn4iBDRptjNVIzwIFR7tqWXdVI9xfA2RMwY/gbSpJG3lkdWNEjLap/NqVHZiJsdfg== - dependencies: - d "^1.0.1" - es5-ext "^0.10.62" - event-emitter "^0.3.5" - type "^2.7.2" - espree@^10.0.1, espree@^10.3.0, espree@^10.4.0: version "10.4.0" resolved "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz" @@ -1875,26 +1758,11 @@ esutils@^2.0.2: resolved "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz" integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== -event-emitter@^0.3.5: - version "0.3.5" - resolved "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz" - integrity sha512-D9rRn9y7kLPnJ+hMq7S/nhvoKwwvVJahBi2BPmx3bvbsEdK3W9ii8cBSGjP+72/LnM4n6fo3+dkCX5FeTQruXA== - dependencies: - d "1" - es5-ext "~0.10.14" - exponential-backoff@^3.1.1: version "3.1.2" resolved "https://registry.npmjs.org/exponential-backoff/-/exponential-backoff-3.1.2.tgz" integrity sha512-8QxYTVXUkuy7fIIoitQkPwGonB8F3Zj8eEO8Sqg9Zv/bkI7RJAzowee4gr81Hak/dUTpA2Z7VfQgoijjPNlUZA== -ext@^1.7.0: - version "1.7.0" - resolved "https://registry.npmjs.org/ext/-/ext-1.7.0.tgz" - integrity sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw== - dependencies: - type "^2.7.2" - extract-zip@^2.0.1: version "2.0.1" resolved "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz" @@ -1937,11 +1805,6 @@ fast-levenshtein@^2.0.6: resolved "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz" integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw== -fast-safe-stringify@^2.0.7: - version "2.1.1" - resolved "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz" - integrity sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA== - fastq@^1.6.0: version "1.19.1" resolved "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz" @@ -2330,11 +2193,6 @@ http-proxy-agent@^7.0.0: agent-base "^7.1.0" debug "^4.3.4" -http2-client@^1.2.5: - version "1.3.5" - resolved "https://registry.npmjs.org/http2-client/-/http2-client-1.3.5.tgz" - integrity sha512-EC2utToWl4RKfs5zd36Mxq7nzHHBuomZboI0yYL6Y0RmBgT7Sgkq4rQ0ezFTYoIsSs7Tm9SJe+o2FcAg6GBhGA== - http2-wrapper@^1.0.0-beta.5.2: version "1.0.3" resolved "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-1.0.3.tgz" @@ -2388,7 +2246,7 @@ ignore@^7.0.0: resolved "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz" integrity sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg== -import-fresh@^3.2.1, import-fresh@^3.3.0: +import-fresh@^3.2.1: version "3.3.1" resolved "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz" integrity sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ== @@ -2432,11 +2290,6 @@ ip-address@^9.0.5: jsbn "1.1.0" sprintf-js "^1.1.3" -is-arrayish@^0.2.1: - version "0.2.1" - resolved "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz" - integrity sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg== - is-ci@^3.0.0: version "3.0.1" resolved "https://registry.npmjs.org/is-ci/-/is-ci-3.0.1.tgz" @@ -2481,11 +2334,6 @@ is-plain-object@3.0.1: resolved "https://registry.npmjs.org/is-plain-object/-/is-plain-object-3.0.1.tgz" integrity sha512-Xnpx182SBMrr/aBik8y+GuR4U1L9FqMSojwDQwPMmxyC6bvEqly9UBCxhauBF5vNh2gwWJNX6oDV7O+OM4z34g== -is-promise@^2.2.2: - version "2.2.2" - resolved "https://registry.npmjs.org/is-promise/-/is-promise-2.2.2.tgz" - integrity sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ== - is-unicode-supported@^0.1.0: version "0.1.0" resolved "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz" @@ -2536,7 +2384,7 @@ joi@^17.13.3: "@sideway/formula" "^3.0.1" "@sideway/pinpoint" "^2.0.0" -"js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0: +"js-tokens@^3.0.0 || ^4.0.0": version "4.0.0" resolved "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz" integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== @@ -2558,11 +2406,6 @@ json-buffer@3.0.1: resolved "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz" integrity sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ== -json-parse-even-better-errors@^2.3.0: - version "2.3.1" - resolved "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz" - integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== - json-schema-traverse@^0.4.1: version "0.4.1" resolved "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz" @@ -2619,11 +2462,6 @@ levn@^0.4.1: prelude-ls "^1.2.1" type-check "~0.4.0" -lines-and-columns@^1.1.6: - version "1.2.4" - resolved "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz" - integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg== - locate-path@^6.0.0: version "6.0.0" resolved "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz" @@ -2683,13 +2521,6 @@ lru-cache@^7.7.1: resolved "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz" integrity sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA== -lru-queue@^0.1.0: - version "0.1.0" - resolved "https://registry.npmjs.org/lru-queue/-/lru-queue-0.1.0.tgz" - integrity sha512-BpdYkt9EvGl8OfWHDQPISVpcl5xZthb+XPsbELj5AQXxIC8IriDZIQYjBJPEm5rS420sjZ0TLEzRcq5KdBhYrQ== - dependencies: - es5-ext "~0.10.2" - magic-string@^0.30.17: version "0.30.17" resolved "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz" @@ -2731,20 +2562,6 @@ math-intrinsics@^1.1.0: resolved "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz" integrity sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g== -memoizee@^0.4.15: - version "0.4.17" - resolved "https://registry.npmjs.org/memoizee/-/memoizee-0.4.17.tgz" - integrity sha512-DGqD7Hjpi/1or4F/aYAspXKNm5Yili0QDAFAY4QYvpqpgiY6+1jOfqpmByzjxbWd/T9mChbCArXAbDAsTm5oXA== - dependencies: - d "^1.0.2" - es5-ext "^0.10.64" - es6-weak-map "^2.0.3" - event-emitter "^0.3.5" - is-promise "^2.2.2" - lru-queue "^0.1.0" - next-tick "^1.1.0" - timers-ext "^0.1.7" - merge2@^1.3.0: version "1.4.1" resolved "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz" @@ -2904,18 +2721,6 @@ mkdirp@^1.0.3, mkdirp@^1.0.4: resolved "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz" integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== -mock.js@^0.2.0: - version "0.2.0" - resolved "https://registry.npmjs.org/mock.js/-/mock.js-0.2.0.tgz" - integrity sha512-DKI8Rh/h7Mma+fg+6aD0uUvwn0QXAjKG6q3s+lTaCboCQ/kvQMBN9IXRBzgEaz4aPiYoRnKU9jVsfZp0mHpWrQ== - -mockjs@^1.1.0: - version "1.1.0" - resolved "https://registry.npmjs.org/mockjs/-/mockjs-1.1.0.tgz" - integrity sha512-eQsKcWzIaZzEZ07NuEyO4Nw65g0hdWAyurVol1IPl1gahRwY+svqzfgfey8U8dahLwG44d6/RwEzuK52rSa/JQ== - dependencies: - commander "*" - ms@^2.0.0, ms@^2.1.3: version "2.1.3" resolved "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz" @@ -2951,11 +2756,6 @@ neo-async@^2.6.2: resolved "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz" integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw== -next-tick@^1.1.0: - version "1.1.0" - resolved "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz" - integrity sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ== - node-abi@^3.45.0: version "3.75.0" resolved "https://registry.npmjs.org/node-abi/-/node-abi-3.75.0.tgz" @@ -2970,27 +2770,6 @@ node-api-version@^0.2.0: dependencies: semver "^7.3.5" -node-fetch-h2@^2.3.0: - version "2.3.0" - resolved "https://registry.npmjs.org/node-fetch-h2/-/node-fetch-h2-2.3.0.tgz" - integrity sha512-ofRW94Ab0T4AOh5Fk8t0h8OBWrmjb0SSB20xh1H8YnPV9EJ+f5AMoYSUQ2zgJ4Iq2HAK0I2l5/Nequ8YzFS3Hg== - dependencies: - http2-client "^1.2.5" - -node-fetch@^2.6.1: - version "2.7.0" - resolved "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz" - integrity sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A== - dependencies: - whatwg-url "^5.0.0" - -node-readfiles@^0.2.0: - version "0.2.0" - resolved "https://registry.npmjs.org/node-readfiles/-/node-readfiles-0.2.0.tgz" - integrity sha512-SU00ZarexNlE4Rjdm83vglt5Y9yiQ+XI1XpflWlb7q7UTN1JUItm69xMeiQCTxtTfnzt+83T8Cx+vI2ED++VDA== - dependencies: - es6-promise "^3.2.1" - nopt@^6.0.0: version "6.0.0" resolved "https://registry.npmjs.org/nopt/-/nopt-6.0.0.tgz" @@ -3010,66 +2789,6 @@ nth-check@^2.1.1: dependencies: boolbase "^1.0.0" -number-to-words@^1.2.4: - version "1.2.4" - resolved "https://registry.npmjs.org/number-to-words/-/number-to-words-1.2.4.tgz" - integrity sha512-/fYevVkXRcyBiZDg6yzZbm0RuaD6i0qRfn8yr+6D0KgBMOndFPxuW10qCHpzs50nN8qKuv78k8MuotZhcVX6Pw== - -nunjucks@^3.2.2: - version "3.2.4" - resolved "https://registry.npmjs.org/nunjucks/-/nunjucks-3.2.4.tgz" - integrity sha512-26XRV6BhkgK0VOxfbU5cQI+ICFUtMLixv1noZn1tGU38kQH5A5nmmbk/O45xdyBhD1esk47nKrY0mvQpZIhRjQ== - dependencies: - a-sync-waterfall "^1.0.0" - asap "^2.0.3" - commander "^5.1.0" - -oas-kit-common@^1.0.8: - version "1.0.8" - resolved "https://registry.npmjs.org/oas-kit-common/-/oas-kit-common-1.0.8.tgz" - integrity sha512-pJTS2+T0oGIwgjGpw7sIRU8RQMcUoKCDWFLdBqKB2BNmGpbBMH2sdqAaOXUg8OzonZHU0L7vfJu1mJFEiYDWOQ== - dependencies: - fast-safe-stringify "^2.0.7" - -oas-linter@^3.2.2: - version "3.2.2" - resolved "https://registry.npmjs.org/oas-linter/-/oas-linter-3.2.2.tgz" - integrity sha512-KEGjPDVoU5K6swgo9hJVA/qYGlwfbFx+Kg2QB/kd7rzV5N8N5Mg6PlsoCMohVnQmo+pzJap/F610qTodKzecGQ== - dependencies: - "@exodus/schemasafe" "^1.0.0-rc.2" - should "^13.2.1" - yaml "^1.10.0" - -oas-resolver@^2.5.6: - version "2.5.6" - resolved "https://registry.npmjs.org/oas-resolver/-/oas-resolver-2.5.6.tgz" - integrity sha512-Yx5PWQNZomfEhPPOphFbZKi9W93CocQj18NlD2Pa4GWZzdZpSJvYwoiuurRI7m3SpcChrnO08hkuQDL3FGsVFQ== - dependencies: - node-fetch-h2 "^2.3.0" - oas-kit-common "^1.0.8" - reftools "^1.1.9" - yaml "^1.10.0" - yargs "^17.0.1" - -oas-schema-walker@^1.1.5: - version "1.1.5" - resolved "https://registry.npmjs.org/oas-schema-walker/-/oas-schema-walker-1.1.5.tgz" - integrity sha512-2yucenq1a9YPmeNExoUa9Qwrt9RFkjqaMAA1X+U7sbb0AqBeTIdMHky9SQQ6iN94bO5NW0W4TRYXerG+BdAvAQ== - -oas-validator@^5.0.8: - version "5.0.8" - resolved "https://registry.npmjs.org/oas-validator/-/oas-validator-5.0.8.tgz" - integrity sha512-cu20/HE5N5HKqVygs3dt94eYJfBi0TsZvPVXDhbXQHiEityDN+RROTleefoKRKKJ9dFAF2JBkDHgvWj0sjKGmw== - dependencies: - call-me-maybe "^1.0.1" - oas-kit-common "^1.0.8" - oas-linter "^3.2.2" - oas-resolver "^2.5.6" - oas-schema-walker "^1.1.5" - reftools "^1.1.9" - should "^13.2.1" - yaml "^1.10.0" - object-keys@^1.1.1: version "1.1.1" resolved "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz" @@ -3100,13 +2819,6 @@ openapi-typescript-codegen@^0.29.0: fs-extra "^11.2.0" handlebars "^4.7.8" -openapi3-ts@^2.0.1: - version "2.0.2" - resolved "https://registry.npmjs.org/openapi3-ts/-/openapi3-ts-2.0.2.tgz" - integrity sha512-TxhYBMoqx9frXyOgnRHufjQfPXomTIHYKhSKJ6jHfj13kS8OEIhvmE8CTuQyKtjjWttAjX5DPxM1vmalEpo8Qw== - dependencies: - yaml "^1.10.2" - optionator@^0.9.3: version "0.9.4" resolved "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz" @@ -3172,16 +2884,6 @@ parent-module@^1.0.0: dependencies: callsites "^3.0.0" -parse-json@^5.2.0: - version "5.2.0" - resolved "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz" - integrity sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg== - dependencies: - "@babel/code-frame" "^7.0.0" - error-ex "^1.3.1" - json-parse-even-better-errors "^2.3.0" - lines-and-columns "^1.1.6" - path-browserify@^1.0.1: version "1.0.1" resolved "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz" @@ -3278,11 +2980,6 @@ prettier-linter-helpers@^1.0.0: dependencies: fast-diff "^1.1.2" -prettier@^2.2.1: - version "2.8.8" - resolved "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz" - integrity sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q== - prettier@^3.6.2: version "3.6.2" resolved "https://registry.npmjs.org/prettier/-/prettier-3.6.2.tgz" @@ -3355,11 +3052,6 @@ readable-stream@^3.4.0: string_decoder "^1.1.1" util-deprecate "^1.0.1" -reftools@^1.1.9: - version "1.1.9" - resolved "https://registry.npmjs.org/reftools/-/reftools-1.1.9.tgz" - integrity sha512-OVede/NQE13xBQ+ob5CKd5KyeJYU2YInb1bmV4nRoOfquZPkAkxuOXicSe1PvqIuZZ4kD13sPKBbR7UFDmli6w== - require-directory@^2.1.1: version "2.1.1" resolved "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz" @@ -3372,11 +3064,6 @@ resedit@^1.7.0: dependencies: pe-library "^0.4.1" -reserved-words@^0.1.2: - version "0.1.2" - resolved "https://registry.npmjs.org/reserved-words/-/reserved-words-0.1.2.tgz" - integrity sha512-0S5SrIUJ9LfpbVl4Yzij6VipUdafHrOTzvmfazSw/jeZrZtQK303OPZW+obtkaw7jQlTQppy0UvZWm9872PbRw== - resize-observer-polyfill@^1.5.1: version "1.5.1" resolved "https://registry.npmjs.org/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz" @@ -3564,50 +3251,6 @@ shell-quote@^1.8.1: resolved "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.3.tgz" integrity sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw== -should-equal@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/should-equal/-/should-equal-2.0.0.tgz" - integrity sha512-ZP36TMrK9euEuWQYBig9W55WPC7uo37qzAEmbjHz4gfyuXrEUgF8cUvQVO+w+d3OMfPvSRQJ22lSm8MQJ43LTA== - dependencies: - should-type "^1.4.0" - -should-format@^3.0.3: - version "3.0.3" - resolved "https://registry.npmjs.org/should-format/-/should-format-3.0.3.tgz" - integrity sha512-hZ58adtulAk0gKtua7QxevgUaXTTXxIi8t41L3zo9AHvjXO1/7sdLECuHeIN2SRtYXpNkmhoUP2pdeWgricQ+Q== - dependencies: - should-type "^1.3.0" - should-type-adaptors "^1.0.1" - -should-type-adaptors@^1.0.1: - version "1.1.0" - resolved "https://registry.npmjs.org/should-type-adaptors/-/should-type-adaptors-1.1.0.tgz" - integrity sha512-JA4hdoLnN+kebEp2Vs8eBe9g7uy0zbRo+RMcU0EsNy+R+k049Ki+N5tT5Jagst2g7EAja+euFuoXFCa8vIklfA== - dependencies: - should-type "^1.3.0" - should-util "^1.0.0" - -should-type@^1.3.0, should-type@^1.4.0: - version "1.4.0" - resolved "https://registry.npmjs.org/should-type/-/should-type-1.4.0.tgz" - integrity sha512-MdAsTu3n25yDbIe1NeN69G4n6mUnJGtSJHygX3+oN0ZbO3DTiATnf7XnYJdGT42JCXurTb1JI0qOBR65shvhPQ== - -should-util@^1.0.0: - version "1.0.1" - resolved "https://registry.npmjs.org/should-util/-/should-util-1.0.1.tgz" - integrity sha512-oXF8tfxx5cDk8r2kYqlkUJzZpDBqVY/II2WhvU0n9Y3XYvAYRmeaf1PvvIvTgPnv4KJ+ES5M0PyDq5Jp+Ygy2g== - -should@^13.2.1: - version "13.2.3" - resolved "https://registry.npmjs.org/should/-/should-13.2.3.tgz" - integrity sha512-ggLesLtu2xp+ZxI+ysJTmNjh2U0TsC+rQ/pfED9bUZZ4DKefP27D+7YJVVTvKsmjLpIi9jAa7itwDGkDDmt1GQ== - dependencies: - should-equal "^2.0.0" - should-format "^3.0.3" - should-type "^1.4.0" - should-type-adaptors "^1.0.1" - should-util "^1.0.0" - signal-exit@^3.0.2: version "3.0.7" resolved "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz" @@ -3768,23 +3411,6 @@ supports-color@^8.1.1: dependencies: has-flag "^4.0.0" -swagger2openapi@^7.0.4: - version "7.0.8" - resolved "https://registry.npmjs.org/swagger2openapi/-/swagger2openapi-7.0.8.tgz" - integrity sha512-upi/0ZGkYgEcLeGieoz8gT74oWHA0E7JivX7aN9mAf+Tc7BQoRBvnIGHoPDw+f9TXTW4s6kGYCZJtauP6OYp7g== - dependencies: - call-me-maybe "^1.0.1" - node-fetch "^2.6.1" - node-fetch-h2 "^2.3.0" - node-readfiles "^0.2.0" - oas-kit-common "^1.0.8" - oas-resolver "^2.5.6" - oas-schema-walker "^1.1.5" - oas-validator "^5.0.8" - reftools "^1.1.9" - yaml "^1.10.0" - yargs "^17.0.1" - synckit@^0.11.7: version "0.11.11" resolved "https://registry.npmjs.org/synckit/-/synckit-0.11.11.tgz" @@ -3817,14 +3443,6 @@ throttle-debounce@^5.0.0: resolved "https://registry.npmjs.org/throttle-debounce/-/throttle-debounce-5.0.2.tgz" integrity sha512-B71/4oyj61iNH0KeCamLuE2rmKuTO5byTOSVwECM5FA7TiAiAW+UqTKZ9ERueC4qvgSttUhdmq1mXC3kJqGX7A== -timers-ext@^0.1.7: - version "0.1.8" - resolved "https://registry.npmjs.org/timers-ext/-/timers-ext-0.1.8.tgz" - integrity sha512-wFH7+SEAcKfJpfLPkrgMPvvwnEtj8W4IurvEyrKsDleXnKLCDw71w8jltvfLa8Rm4qQxxT4jmDBYbJG/z7qoww== - dependencies: - es5-ext "^0.10.64" - next-tick "^1.1.0" - tiny-async-pool@1.3.0: version "1.3.0" resolved "https://registry.npmjs.org/tiny-async-pool/-/tiny-async-pool-1.3.0.tgz" @@ -3832,11 +3450,6 @@ tiny-async-pool@1.3.0: dependencies: semver "^5.5.0" -tiny-pinyin@^1.3.2: - version "1.3.2" - resolved "https://registry.npmjs.org/tiny-pinyin/-/tiny-pinyin-1.3.2.tgz" - integrity sha512-uHNGu4evFt/8eNLldazeAM1M8JrMc1jshhJJfVRARTN3yT8HEEibofeQ7QETWQ5ISBjd6fKtTVBCC/+mGS6FpA== - tinyglobby@^0.2.14: version "0.2.14" resolved "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.14.tgz" @@ -3864,11 +3477,6 @@ to-regex-range@^5.0.1: dependencies: is-number "^7.0.0" -tr46@~0.0.3: - version "0.0.3" - resolved "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz" - integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw== - tree-kill@^1.2.2: version "1.2.2" resolved "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz" @@ -3903,11 +3511,6 @@ type-fest@^0.13.1: resolved "https://registry.npmjs.org/type-fest/-/type-fest-0.13.1.tgz" integrity sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg== -type@^2.7.2: - version "2.7.3" - resolved "https://registry.npmjs.org/type/-/type-2.7.3.tgz" - integrity sha512-8j+1QmAbPvLZow5Qpi6NCaN8FB60p/6x8/vfNqOk/hC+HuvFZhL4+WfekuhQLiqFZXOgQdrs3B+XxEmCc6b3FQ== - typescript@^5.4.3, typescript@^5.9.2: version "5.9.2" resolved "https://registry.npmjs.org/typescript/-/typescript-5.9.2.tgz" @@ -4067,19 +3670,6 @@ wcwidth@^1.0.1: dependencies: defaults "^1.0.3" -webidl-conversions@^3.0.0: - version "3.0.1" - resolved "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz" - integrity sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ== - -whatwg-url@^5.0.0: - version "5.0.0" - resolved "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz" - integrity sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw== - dependencies: - tr46 "~0.0.3" - webidl-conversions "^3.0.0" - which@^2.0.1, which@^2.0.2: version "2.0.2" resolved "https://registry.npmjs.org/which/-/which-2.0.2.tgz" @@ -4149,11 +3739,6 @@ yallist@^4.0.0: resolved "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz" integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== -yaml@^1.10.0, yaml@^1.10.2: - version "1.10.2" - resolved "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz" - integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg== - yargs-parser@^21.1.1: version "21.1.1" resolved "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz"