From ae151a9311bb59d07873a8e2d8d9e82580971dda Mon Sep 17 00:00:00 2001 From: AoXuan Date: Thu, 7 Aug 2025 00:11:29 +0800 Subject: [PATCH] =?UTF-8?q?feat(initialization):=20=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E5=88=9D=E5=A7=8B=E5=8C=96=E9=A1=B5=E9=9D=A2=E5=92=8C=E7=9B=B8?= =?UTF-8?q?=E5=85=B3=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增初始化页面组件和路由 - 实现环境检查、Git下载、后端代码克隆等功能 - 添加下载服务和环境服务模块 - 更新类型定义,增加 Electron API 接口 --- frontend/dist-electron/main.js | 63 +- frontend/dist-electron/preload.js | 17 +- frontend/electron/main.ts | 79 +- frontend/electron/preload.ts | 19 +- frontend/electron/services/downloadService.ts | 58 ++ .../electron/services/environmentService.ts | 35 + frontend/electron/services/gitService.ts | 425 ++++++++ frontend/electron/services/pythonService.ts | 456 +++++++++ frontend/package.json | 2 + frontend/public/AUTO_MAA.ico | Bin 0 -> 52504 bytes frontend/src/App.vue | 12 +- frontend/src/components/LogViewer.vue | 354 +++++++ frontend/src/main.ts | 15 +- frontend/src/router/index.ts | 14 +- frontend/src/types/electron.d.ts | 33 +- frontend/src/types/initialization.ts | 21 + frontend/src/utils/logger.ts | 185 ++++ frontend/src/views/Initialization.vue | 967 ++++++++++++++++++ frontend/src/views/Logs.vue | 55 + frontend/yarn.lock | 457 +-------- 20 files changed, 2777 insertions(+), 490 deletions(-) create mode 100644 frontend/electron/services/downloadService.ts create mode 100644 frontend/electron/services/environmentService.ts create mode 100644 frontend/electron/services/gitService.ts create mode 100644 frontend/electron/services/pythonService.ts create mode 100644 frontend/public/AUTO_MAA.ico create mode 100644 frontend/src/components/LogViewer.vue create mode 100644 frontend/src/types/initialization.ts create mode 100644 frontend/src/utils/logger.ts create mode 100644 frontend/src/views/Initialization.vue create mode 100644 frontend/src/views/Logs.vue 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 0000000000000000000000000000000000000000..6f9c7ae4fa557c3ba71b45c69ad2c6926c4cd627 GIT binary patch literal 52504 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 XmCSc;6~lV3?Mb!4r1<|yx%vM9ynh1m literal 0 HcmV?d00001 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"