feat(initialization): 添加初始化页面和相关功能

- 新增初始化页面组件和路由
- 实现环境检查、Git下载、后端代码克隆等功能
- 添加下载服务和环境服务模块
- 更新类型定义,增加 Electron API 接口
This commit is contained in:
2025-08-07 00:11:29 +08:00
parent 18202045bf
commit ae151a9311
20 changed files with 2777 additions and 490 deletions

View File

@@ -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')

View File

@@ -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');
}
});

View File

@@ -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()
})
})

View File

@@ -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')
}
})

View File

@@ -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<void> {
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)
})
})
}

View File

@@ -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
}
}

View File

@@ -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<void>((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<void>((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<void>((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<void>((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 }
}
}

View File

@@ -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<void> {
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<void>((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<void>((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<void>((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<void>((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) }
}
}

View File

@@ -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"

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

View File

@@ -1,11 +1,16 @@
<script setup lang="ts">
import { onMounted } from 'vue'
import { onMounted, computed } from 'vue'
import { useRoute } from 'vue-router'
import { ConfigProvider } from 'ant-design-vue'
import { useTheme } from './composables/useTheme.ts'
import AppLayout from './components/AppLayout.vue'
const route = useRoute()
const { antdTheme, initTheme } = useTheme()
// 判断是否为初始化页面
const isInitializationPage = computed(() => route.name === 'Initialization')
onMounted(() => {
initTheme()
})
@@ -13,7 +18,10 @@ onMounted(() => {
<template>
<ConfigProvider :theme="antdTheme">
<AppLayout />
<!-- 初始化页面使用全屏布局 -->
<router-view v-if="isInitializationPage" />
<!-- 其他页面使用应用布局 -->
<AppLayout v-else />
</ConfigProvider>
</template>

View File

@@ -0,0 +1,354 @@
<template>
<div class="log-viewer">
<div class="log-header">
<div class="log-controls">
<a-select
v-model:value="selectedLevel"
style="width: 120px"
@change="filterLogs"
>
<a-select-option value="all">所有级别</a-select-option>
<a-select-option value="debug">Debug</a-select-option>
<a-select-option value="info">Info</a-select-option>
<a-select-option value="warn">Warn</a-select-option>
<a-select-option value="error">Error</a-select-option>
</a-select>
<a-input-search
v-model:value="searchText"
placeholder="搜索日志..."
style="width: 200px"
@search="filterLogs"
@change="filterLogs"
/>
<a-button @click="clearLogs" danger>
<template #icon>
<DeleteOutlined />
</template>
清空日志
</a-button>
<a-button @click="downloadLogs" type="primary">
<template #icon>
<DownloadOutlined />
</template>
导出日志
</a-button>
<a-button @click="toggleAutoScroll" :type="autoScroll ? 'primary' : 'default'">
<template #icon>
<VerticalAlignBottomOutlined />
</template>
自动滚动
</a-button>
</div>
<div class="log-stats">
总计: {{ filteredLogs.length }} 条日志
</div>
</div>
<div
ref="logContainer"
class="log-container"
@scroll="handleScroll"
>
<div
v-for="(log, index) in filteredLogs"
:key="index"
class="log-entry"
:class="[`log-${log.level}`, { 'log-highlight': highlightedIndex === index }]"
>
<div class="log-timestamp">{{ log.timestamp }}</div>
<div class="log-level">{{ log.level.toUpperCase() }}</div>
<div v-if="log.component" class="log-component">[{{ log.component }}]</div>
<div class="log-message">{{ log.message }}</div>
<div v-if="log.data" class="log-data">
<a-button
size="small"
type="link"
@click="toggleDataVisibility(index)"
>
{{ expandedData.has(index) ? '隐藏数据' : '显示数据' }}
</a-button>
<pre v-if="expandedData.has(index)" class="log-data-content">{{ JSON.stringify(log.data, null, 2) }}</pre>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, computed, nextTick, onMounted, onUnmounted } from 'vue'
import {
DeleteOutlined,
DownloadOutlined,
VerticalAlignBottomOutlined
} from '@ant-design/icons-vue'
import { logger, type LogEntry, type LogLevel } from '@/utils/logger'
const logContainer = ref<HTMLElement>()
const selectedLevel = ref<LogLevel | 'all'>('all')
const searchText = ref('')
const autoScroll = ref(true)
const expandedData = ref(new Set<number>())
const highlightedIndex = ref(-1)
const logs = logger.getLogs()
const filteredLogs = computed(() => {
let filtered = logs.value
// 按级别过滤
if (selectedLevel.value !== 'all') {
filtered = filtered.filter(log => log.level === selectedLevel.value)
}
// 按搜索文本过滤
if (searchText.value) {
const search = searchText.value.toLowerCase()
filtered = filtered.filter(log =>
log.message.toLowerCase().includes(search) ||
log.component?.toLowerCase().includes(search) ||
(log.data && JSON.stringify(log.data).toLowerCase().includes(search))
)
}
return filtered
})
function filterLogs() {
// 过滤逻辑已在computed中处理
nextTick(() => {
if (autoScroll.value) {
scrollToBottom()
}
})
}
function clearLogs() {
logger.clearLogs()
expandedData.value.clear()
}
function downloadLogs() {
logger.downloadLogs()
}
function toggleAutoScroll() {
autoScroll.value = !autoScroll.value
if (autoScroll.value) {
scrollToBottom()
}
}
function toggleDataVisibility(index: number) {
if (expandedData.value.has(index)) {
expandedData.value.delete(index)
} else {
expandedData.value.add(index)
}
}
function scrollToBottom() {
if (logContainer.value) {
logContainer.value.scrollTop = logContainer.value.scrollHeight
}
}
function handleScroll() {
if (!logContainer.value) return
const { scrollTop, scrollHeight, clientHeight } = logContainer.value
const isAtBottom = scrollTop + clientHeight >= scrollHeight - 10
if (!isAtBottom) {
autoScroll.value = false
}
}
// 监听新日志添加
let unwatchLogs: (() => void) | null = null
onMounted(() => {
// 初始滚动到底部
nextTick(() => {
scrollToBottom()
})
// 监听日志变化
unwatchLogs = logs.value && typeof logs.value === 'object' && 'length' in logs.value
? () => {} // 如果logs是响应式的Vue会自动处理
: null
})
onUnmounted(() => {
if (unwatchLogs) {
unwatchLogs()
}
})
// 监听日志变化,自动滚动
const prevLogsLength = ref(logs.value.length)
const checkForNewLogs = () => {
if (logs.value.length > prevLogsLength.value) {
prevLogsLength.value = logs.value.length
if (autoScroll.value) {
nextTick(() => {
scrollToBottom()
})
}
}
}
// 定期检查新日志
const logCheckInterval = setInterval(checkForNewLogs, 100)
onUnmounted(() => {
clearInterval(logCheckInterval)
})
</script>
<style scoped>
.log-viewer {
height: 100%;
display: flex;
flex-direction: column;
background: var(--ant-color-bg-container);
border-radius: 8px;
overflow: hidden;
}
.log-header {
padding: 16px;
border-bottom: 1px solid var(--ant-color-border);
display: flex;
justify-content: space-between;
align-items: center;
flex-wrap: wrap;
gap: 12px;
}
.log-controls {
display: flex;
align-items: center;
gap: 12px;
flex-wrap: wrap;
}
.log-stats {
font-size: 14px;
color: var(--ant-color-text-secondary);
}
.log-container {
flex: 1;
overflow-y: auto;
padding: 8px;
font-family: 'Consolas', 'Monaco', 'Courier New', monospace;
font-size: 12px;
line-height: 1.4;
}
.log-entry {
display: flex;
align-items: flex-start;
gap: 8px;
padding: 4px 8px;
border-radius: 4px;
margin-bottom: 2px;
word-break: break-all;
}
.log-entry:hover {
background: var(--ant-color-fill-quaternary);
}
.log-highlight {
background: var(--ant-color-primary-bg) !important;
}
.log-timestamp {
color: var(--ant-color-text-tertiary);
white-space: nowrap;
min-width: 140px;
}
.log-level {
font-weight: bold;
min-width: 50px;
text-align: center;
padding: 2px 6px;
border-radius: 3px;
font-size: 10px;
}
.log-debug .log-level {
background: var(--ant-color-fill-secondary);
color: var(--ant-color-text-secondary);
}
.log-info .log-level {
background: var(--ant-color-info-bg);
color: var(--ant-color-info);
}
.log-warn .log-level {
background: var(--ant-color-warning-bg);
color: var(--ant-color-warning);
}
.log-error .log-level {
background: var(--ant-color-error-bg);
color: var(--ant-color-error);
}
.log-component {
color: var(--ant-color-primary);
font-weight: 500;
white-space: nowrap;
}
.log-message {
flex: 1;
color: var(--ant-color-text);
}
.log-data {
margin-top: 4px;
width: 100%;
}
.log-data-content {
background: var(--ant-color-fill-quaternary);
padding: 8px;
border-radius: 4px;
margin-top: 4px;
font-size: 11px;
overflow-x: auto;
}
@media (max-width: 768px) {
.log-header {
flex-direction: column;
align-items: stretch;
}
.log-controls {
justify-content: center;
}
.log-entry {
flex-direction: column;
align-items: stretch;
gap: 4px;
}
.log-timestamp,
.log-level,
.log-component {
min-width: auto;
}
}
</style>

View File

@@ -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 })

View File

@@ -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({

View File

@@ -1,11 +1,30 @@
export {}
export interface ElectronAPI {
openDevTools: () => Promise<void>
selectFolder: () => Promise<string | null>
selectFile: (filters?: any[]) => Promise<string | null>
// 初始化相关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<string | null>
selectFile: (filters?: Array<{ name: string; extensions: string[] }>) => Promise<string | null>
}
electronAPI: ElectronAPI
}
}
}

View File

@@ -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
}

View File

@@ -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<LogEntry[]>([])
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)
}
}

View File

@@ -0,0 +1,967 @@
<template>
<div class="initialization-container">
<div class="initialization-content">
<div class="header">
<h1>AUTO MAA 初始化向导</h1>
<p>欢迎使用 AUTO MAA让我们来配置您的运行环境</p>
</div>
<a-steps
:current="currentStep"
:status="stepStatus"
class="init-steps"
>
<a-step title="主题设置" description="选择您喜欢的主题" />
<a-step title="Python 环境" description="安装 Python 运行环境" />
<a-step title="Git 工具" description="安装 Git 版本控制工具" />
<a-step title="源码获取" description="获取最新的后端代码" />
<a-step title="依赖安装" description="安装 Python 依赖包" />
<a-step title="启动服务" description="启动后端服务" />
</a-steps>
<div class="step-content">
<!-- 步骤 0: 主题设置 -->
<div v-if="currentStep === 0" class="step-panel">
<h3>选择您的主题偏好</h3>
<div class="theme-settings">
<div class="setting-group">
<label>主题模式</label>
<a-radio-group v-model:value="selectedThemeMode" @change="onThemeModeChange">
<a-radio-button value="light">浅色模式</a-radio-button>
<a-radio-button value="dark">深色模式</a-radio-button>
<a-radio-button value="system">跟随系统</a-radio-button>
</a-radio-group>
</div>
<div class="setting-group">
<label>主题色彩</label>
<div class="color-picker">
<div
v-for="(color, key) in themeColors"
:key="key"
class="color-option"
:class="{ active: selectedThemeColor === key }"
:style="{ backgroundColor: color }"
@click="onThemeColorChange(key)"
/>
</div>
</div>
</div>
</div>
<!-- 步骤 1: Python 环境 -->
<div v-if="currentStep === 1" class="step-panel">
<h3>Python 运行环境</h3>
<div v-if="!pythonInstalled" class="install-section">
<p>需要安装 Python 3.13.0 运行环境64位嵌入式版本</p>
<div class="mirror-grid">
<div
v-for="mirror in pythonMirrors"
:key="mirror.key"
class="mirror-card"
:class="{ active: selectedPythonMirror === mirror.key }"
@click="selectedPythonMirror = mirror.key"
>
<div class="mirror-header">
<h4>{{ mirror.name }}</h4>
<div class="speed-badge" :class="getSpeedClass(mirror.speed)">
<span v-if="mirror.speed === null && !testingSpeed">未测试</span>
<span v-else-if="testingSpeed">测试中...</span>
<span v-else-if="mirror.speed === 9999">超时</span>
<span v-else>{{ mirror.speed }}ms</span>
</div>
</div>
<div class="mirror-url">{{ mirror.url }}</div>
</div>
</div>
<div class="test-actions">
<a-button @click="testPythonMirrorSpeed" :loading="testingSpeed" type="primary">
{{ testingSpeed ? '测速中...' : '重新测速' }}
</a-button>
<span class="test-note">3秒无响应视为超时</span>
</div>
</div>
<div v-else class="already-installed">
<a-result status="success" title="Python 环境已安装" />
</div>
</div>
<!-- 步骤 2: Git 工具 -->
<div v-if="currentStep === 2" class="step-panel">
<h3>Git 版本控制工具</h3>
<div v-if="!gitInstalled" class="install-section">
<p>需要安装 Git 工具来获取源代码</p>
<div class="git-info">
<a-alert
message="Git 工具信息"
description="将安装便携版 Git 工具,包含完整的版本控制功能,无需系统安装。"
type="info"
show-icon
/>
</div>
</div>
<div v-else class="already-installed">
<a-result status="success" title="Git 工具已安装" />
</div>
</div>
<!-- 步骤 3: 源码获取 -->
<div v-if="currentStep === 3" class="step-panel">
<h3>获取后端源码</h3>
<div class="install-section">
<p>{{ backendExists ? '更新最新的后端代码' : '获取后端源代码' }}</p>
<div class="mirror-grid">
<div
v-for="mirror in gitMirrors"
:key="mirror.key"
class="mirror-card"
:class="{ active: selectedGitMirror === mirror.key }"
@click="selectedGitMirror = mirror.key"
>
<div class="mirror-header">
<h4>{{ mirror.name }}</h4>
<div class="speed-badge" :class="getSpeedClass(mirror.speed)">
<span v-if="mirror.speed === null && !testingGitSpeed">未测试</span>
<span v-else-if="testingGitSpeed">测试中...</span>
<span v-else-if="mirror.speed === 9999">超时</span>
<span v-else>{{ mirror.speed }}ms</span>
</div>
</div>
<div class="mirror-url">{{ mirror.url }}</div>
</div>
</div>
<div class="test-actions">
<a-button @click="testGitMirrorSpeed" :loading="testingGitSpeed" type="primary">
{{ testingGitSpeed ? '测速中...' : '开始测速' }}
</a-button>
<span class="test-note">3秒无响应视为超时</span>
</div>
</div>
</div>
<!-- 步骤 4: 依赖安装 -->
<div v-if="currentStep === 4" class="step-panel">
<h3>安装 Python 依赖包</h3>
<div class="install-section">
<p>通过 pip 安装项目所需的 Python 依赖包</p>
<div class="mirror-grid">
<div
v-for="mirror in pipMirrors"
:key="mirror.key"
class="mirror-card"
:class="{ active: selectedPipMirror === mirror.key }"
@click="selectedPipMirror = mirror.key"
>
<div class="mirror-header">
<h4>{{ mirror.name }}</h4>
<div class="speed-badge" :class="getSpeedClass(mirror.speed)">
<span v-if="mirror.speed === null && !testingPipSpeed">未测试</span>
<span v-else-if="testingPipSpeed">测试中...</span>
<span v-else-if="mirror.speed === 9999">超时</span>
<span v-else>{{ mirror.speed }}ms</span>
</div>
</div>
<div class="mirror-url">{{ mirror.url }}</div>
</div>
</div>
<div class="test-actions">
<a-button @click="testPipMirrorSpeed" :loading="testingPipSpeed" type="primary">
{{ testingPipSpeed ? '测速中...' : '重新测速' }}
</a-button>
<span class="test-note">3秒无响应视为超时</span>
</div>
</div>
</div>
<!-- 步骤 5: 启动服务 -->
<div v-if="currentStep === 5" class="step-panel">
<h3>启动后端服务</h3>
<div class="service-status">
<a-spin :spinning="startingService">
<div class="status-info">
<p>{{ serviceStatus }}</p>
<a-progress v-if="showServiceProgress" :percent="serviceProgress" />
</div>
</a-spin>
</div>
</div>
</div>
<div class="step-actions">
<a-button
v-if="currentStep > 0"
@click="prevStep"
:disabled="isProcessing"
>
上一步
</a-button>
<a-button
v-if="currentStep < 5"
type="primary"
@click="nextStep"
:loading="isProcessing"
>
{{ getNextButtonText() }}
</a-button>
<a-button
v-if="currentStep === 5 && allCompleted"
type="primary"
@click="enterApp"
>
进入应用
</a-button>
</div>
<div v-if="errorMessage" class="error-message">
<a-alert
:message="errorMessage"
type="error"
show-icon
closable
@close="errorMessage = ''"
/>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, computed, onMounted, onUnmounted } from 'vue'
import { useRouter } from 'vue-router'
import { useTheme } from '@/composables/useTheme'
import { createComponentLogger } from '@/utils/logger'
import type { InitializationStatus, DownloadProgress } from '@/types/initialization'
import type { ThemeMode, ThemeColor } from '@/composables/useTheme'
const router = useRouter()
const { themeColors, setThemeMode, setThemeColor } = useTheme()
const logger = createComponentLogger('Initialization')
// 基础状态
const currentStep = ref(0)
const stepStatus = ref<'wait' | 'process' | 'finish' | 'error'>('process')
const errorMessage = ref('')
const isProcessing = ref(false)
// 主题设置
const selectedThemeMode = ref<ThemeMode>('system')
const selectedThemeColor = ref<ThemeColor>('blue')
// 安装状态
const pythonInstalled = ref(false)
const gitInstalled = ref(false)
const backendExists = ref(false)
const dependenciesInstalled = ref(false)
// 镜像源配置
const pythonMirrors = ref([
{ key: 'official', name: 'Python 官方', url: 'https://www.python.org/ftp/python/3.13.0/', speed: null as number | null },
{ key: 'tsinghua', name: '清华 TUNA 镜像', url: 'https://mirrors.tuna.tsinghua.edu.cn/python/3.13.0/', speed: null as number | null },
{ key: 'ustc', name: '中科大镜像', url: 'https://mirrors.ustc.edu.cn/python/3.13.0/', speed: null as number | null },
{ key: 'huawei', name: '华为云镜像', url: 'https://mirrors.huaweicloud.com/repository/toolkit/python/3.13.0/', speed: null as number | null },
{ key: 'aliyun', name: '阿里云镜像', url: 'https://mirrors.aliyun.com/python-release/windows/', speed: null as number | null }
])
const pipMirrors = ref([
{ key: 'official', name: 'PyPI 官方', url: 'https://pypi.org/simple/', speed: null as number | null },
{ key: 'tsinghua', name: '清华大学', url: 'https://pypi.tuna.tsinghua.edu.cn/simple/', speed: null as number | null },
{ key: 'aliyun', name: '阿里云', url: 'https://mirrors.aliyun.com/pypi/simple/', speed: null as number | null },
{ key: 'douban', name: '豆瓣', url: 'https://pypi.douban.com/simple/', speed: null as number | null },
{ key: 'ustc', name: '中科大', url: 'https://pypi.mirrors.ustc.edu.cn/simple/', speed: null as number | null },
{ key: 'huawei', name: '华中科技大学', url: 'https://pypi.hustunique.com/simple/', speed: null as number | null }
])
const gitMirrors = ref([
{ key: 'github', name: 'GitHub 官方', url: 'https://github.com/DLmaster361/AUTO_MAA.git', speed: null as number | null },
{ key: 'fastgit', name: 'FastGit 镜像', url: 'https://ghfast.top/https://github.com/DLmaster361/AUTO_MAA.git', speed: null as number | null }
])
// 选中的镜像源
const selectedPythonMirror = ref('tsinghua')
const selectedPipMirror = ref('tsinghua')
const selectedGitMirror = ref('github')
// 测速状态
const testingSpeed = ref(false)
const testingPipSpeed = ref(false)
const testingGitSpeed = ref(false)
// 服务状态
const startingService = ref(false)
const showServiceProgress = ref(false)
const serviceProgress = ref(0)
const serviceStatus = ref('准备启动后端服务...')
const allCompleted = computed(() =>
pythonInstalled.value && gitInstalled.value && backendExists.value && dependenciesInstalled.value
)
// 主题设置相关
function onThemeModeChange() {
setThemeMode(selectedThemeMode.value)
saveSettings()
}
function onThemeColorChange(color: ThemeColor) {
selectedThemeColor.value = color
setThemeColor(color)
saveSettings()
}
// 保存设置到本地存储
function saveSettings() {
const settings = {
themeMode: selectedThemeMode.value,
themeColor: selectedThemeColor.value,
pythonMirror: selectedPythonMirror.value,
pipMirror: selectedPipMirror.value,
gitMirror: selectedGitMirror.value
}
localStorage.setItem('init-settings', JSON.stringify(settings))
}
// 加载设置
function loadSettings() {
const saved = localStorage.getItem('init-settings')
if (saved) {
try {
const settings = JSON.parse(saved)
selectedThemeMode.value = settings.themeMode || 'system'
selectedThemeColor.value = settings.themeColor || 'blue'
selectedPythonMirror.value = settings.pythonMirror || 'tsinghua'
selectedPipMirror.value = settings.pipMirror || 'tsinghua'
selectedGitMirror.value = settings.gitMirror || 'github'
} catch (error) {
console.warn('Failed to load settings:', error)
}
}
}
// 测速功能 - 带3秒超时
async function testMirrorWithTimeout(url: string, timeout = 3000): Promise<number> {
const startTime = Date.now()
try {
const controller = new AbortController()
const timeoutId = setTimeout(() => controller.abort(), timeout)
await fetch(url, {
method: 'HEAD',
mode: 'no-cors',
signal: controller.signal
})
clearTimeout(timeoutId)
return Date.now() - startTime
} catch (error) {
return 9999 // 超时或失败
}
}
async function testPythonMirrorSpeed() {
testingSpeed.value = true
try {
// 并发测试所有镜像源
const promises = pythonMirrors.value.map(async (mirror) => {
mirror.speed = await testMirrorWithTimeout(mirror.url)
return mirror
})
await Promise.all(promises)
// 按速度排序,最快的在前面
pythonMirrors.value.sort((a, b) => (a.speed || 9999) - (b.speed || 9999))
// 自动选择最快的镜像源
const fastest = pythonMirrors.value.find(m => m.speed !== 9999)
if (fastest) {
selectedPythonMirror.value = fastest.key
}
} finally {
testingSpeed.value = false
}
}
async function testPipMirrorSpeed() {
testingPipSpeed.value = true
try {
const promises = pipMirrors.value.map(async (mirror) => {
mirror.speed = await testMirrorWithTimeout(mirror.url)
return mirror
})
await Promise.all(promises)
pipMirrors.value.sort((a, b) => (a.speed || 9999) - (b.speed || 9999))
// 自动选择最快的镜像源
const fastest = pipMirrors.value.find(m => m.speed !== 9999)
if (fastest) {
selectedPipMirror.value = fastest.key
}
} finally {
testingPipSpeed.value = false
}
}
async function testGitMirrorSpeed() {
testingGitSpeed.value = true
try {
const promises = gitMirrors.value.map(async (mirror) => {
const url = mirror.url.replace('.git', '')
mirror.speed = await testMirrorWithTimeout(url)
return mirror
})
await Promise.all(promises)
gitMirrors.value.sort((a, b) => (a.speed || 9999) - (b.speed || 9999))
// 自动选择最快的镜像源
const fastest = gitMirrors.value.find(m => m.speed !== 9999)
if (fastest) {
selectedGitMirror.value = fastest.key
}
} finally {
testingGitSpeed.value = false
}
}
// 步骤控制
function prevStep() {
if (currentStep.value > 0) {
currentStep.value--
}
}
async function nextStep() {
isProcessing.value = true
errorMessage.value = ''
try {
switch (currentStep.value) {
case 0: // 主题设置
saveSettings()
break
case 1: // Python 环境
if (!pythonInstalled.value) {
await installPython()
}
break
case 2: // Git 工具
if (!gitInstalled.value) {
await installGit()
}
break
case 3: // 源码获取
if (!backendExists.value) {
await cloneBackend()
} else {
await updateBackend()
}
break
case 4: // 依赖安装
if (!dependenciesInstalled.value) {
await installDependencies()
}
break
case 5: // 启动服务
await startBackendService()
break
}
if (currentStep.value < 5) {
currentStep.value++
// 进入新步骤时自动开始测速
await autoStartSpeedTest()
}
} catch (error) {
errorMessage.value = error instanceof Error ? error.message : String(error)
stepStatus.value = 'error'
} finally {
isProcessing.value = false
}
}
// 自动开始测速
async function autoStartSpeedTest() {
switch (currentStep.value) {
case 1: // Python 环境
if (!pythonInstalled.value) {
await testPythonMirrorSpeed()
}
break
case 3: // 源码获取
await testGitMirrorSpeed()
break
case 4: // 依赖安装
if (!dependenciesInstalled.value) {
await testPipMirrorSpeed()
}
break
}
}
function getNextButtonText() {
switch (currentStep.value) {
case 0: return '下一步'
case 1: return pythonInstalled.value ? '下一步' : '安装 Python'
case 2: return gitInstalled.value ? '下一步' : '安装 Git'
case 3: return backendExists.value ? '更新代码' : '获取代码'
case 4: return '安装依赖'
case 5: return '启动服务'
default: return '下一步'
}
}
// 检查环境状态
async function checkEnvironment() {
try {
logger.info('开始检查环境状态')
const status = await window.electronAPI.checkEnvironment()
logger.info('环境检查结果', status)
pythonInstalled.value = status.pythonExists
gitInstalled.value = status.gitExists
backendExists.value = status.backendExists
dependenciesInstalled.value = status.dependenciesInstalled
// 如果所有环境都已准备好,跳到最后一步
if (status.isInitialized) {
logger.info('环境已初始化完成,跳转到启动服务步骤')
currentStep.value = 5
await startBackendService()
}
} catch (error) {
const errorMsg = `环境检查失败: ${error instanceof Error ? error.message : String(error)}`
logger.error('环境检查失败', error)
errorMessage.value = errorMsg
}
}
// 安装 Python
async function installPython() {
logger.info('开始安装Python', { mirror: selectedPythonMirror.value })
const result = await window.electronAPI.downloadPython(selectedPythonMirror.value)
if (result.success) {
logger.info('Python安装成功')
pythonInstalled.value = true
} else {
logger.error('Python安装失败', result.error)
throw new Error(result.error)
}
}
// 安装依赖
async function installDependencies() {
logger.info('开始安装Python依赖', { mirror: selectedPipMirror.value })
const result = await window.electronAPI.installDependencies(selectedPipMirror.value)
if (result.success) {
logger.info('Python依赖安装成功')
dependenciesInstalled.value = true
} else {
logger.error('Python依赖安装失败', result.error)
throw new Error(result.error)
}
}
// 安装 Git
async function installGit() {
logger.info('开始安装Git工具')
const result = await window.electronAPI.downloadGit()
if (result.success) {
logger.info('Git工具安装成功')
gitInstalled.value = true
} else {
logger.error('Git工具安装失败', result.error)
throw new Error(result.error)
}
}
// 克隆后端代码
async function cloneBackend() {
const selectedMirror = gitMirrors.value.find(m => m.key === selectedGitMirror.value)
logger.info('开始克隆后端代码', { mirror: selectedMirror?.name, url: selectedMirror?.url })
const result = await window.electronAPI.cloneBackend(selectedMirror?.url)
if (result.success) {
logger.info('后端代码克隆成功')
backendExists.value = true
} else {
logger.error('后端代码克隆失败', result.error)
throw new Error(result.error)
}
}
// 更新后端代码
async function updateBackend() {
const selectedMirror = gitMirrors.value.find(m => m.key === selectedGitMirror.value)
logger.info('开始更新后端代码', { mirror: selectedMirror?.name, url: selectedMirror?.url })
const result = await window.electronAPI.updateBackend(selectedMirror?.url)
if (!result.success) {
logger.error('后端代码更新失败', result.error)
throw new Error(result.error)
}
logger.info('后端代码更新成功')
}
// 启动后端服务
async function startBackendService() {
startingService.value = true
showServiceProgress.value = true
serviceStatus.value = '正在启动后端服务...'
logger.info('开始启动后端服务')
try {
const result = await window.electronAPI.startBackend()
if (result.success) {
serviceProgress.value = 100
serviceStatus.value = '后端服务启动成功'
stepStatus.value = 'finish'
logger.info('后端服务启动成功')
} else {
logger.error('后端服务启动失败', result.error)
throw new Error(result.error)
}
} catch (error) {
serviceStatus.value = '后端服务启动失败'
logger.error('后端服务启动异常', error)
throw error
} finally {
startingService.value = false
}
}
// 进入应用
function enterApp() {
router.push('/home')
}
// 获取速度样式类
function getSpeedClass(speed: number | null) {
if (speed === null) return 'speed-unknown'
if (speed === 9999) return 'speed-timeout'
if (speed < 500) return 'speed-fast'
if (speed < 1500) return 'speed-medium'
return 'speed-slow'
}
// 监听下载进度
function handleDownloadProgress(progress: DownloadProgress) {
if (progress.type === 'service') {
serviceProgress.value = progress.progress
serviceStatus.value = progress.message
}
}
onMounted(async () => {
loadSettings()
await checkEnvironment()
window.electronAPI.onDownloadProgress(handleDownloadProgress)
// 如果当前步骤需要测速,自动开始测速
await autoStartSpeedTest()
})
onUnmounted(() => {
window.electronAPI.removeDownloadProgressListener()
})
</script>
<style scoped>
.initialization-container {
min-height: 100vh;
background: var(--ant-color-bg-layout);
display: flex;
align-items: center;
justify-content: center;
padding: 20px;
}
.initialization-content {
background: var(--ant-color-bg-container);
border-radius: 16px;
padding: 40px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
max-width: 900px;
width: 100%;
}
.header {
text-align: center;
margin-bottom: 40px;
}
.header h1 {
font-size: 28px;
font-weight: 600;
color: var(--ant-color-text);
margin-bottom: 8px;
}
.header p {
font-size: 16px;
color: var(--ant-color-text-secondary);
margin: 0;
}
.init-steps {
margin-bottom: 40px;
}
.step-content {
min-height: 300px;
margin-bottom: 40px;
}
.step-panel {
padding: 20px;
background: var(--ant-color-bg-elevated);
border-radius: 8px;
border: 1px solid var(--ant-color-border);
}
.step-panel h3 {
font-size: 20px;
font-weight: 600;
color: var(--ant-color-text);
margin-bottom: 20px;
}
.theme-settings {
display: flex;
flex-direction: column;
gap: 24px;
}
.setting-group {
display: flex;
flex-direction: column;
gap: 12px;
}
.setting-group label {
font-weight: 500;
color: var(--ant-color-text);
}
.color-picker {
display: flex;
gap: 12px;
flex-wrap: wrap;
}
.color-option {
width: 40px;
height: 40px;
border-radius: 8px;
cursor: pointer;
border: 2px solid transparent;
transition: all 0.2s ease;
}
.color-option:hover {
transform: scale(1.1);
}
.color-option.active {
border-color: var(--ant-color-text);
transform: scale(1.1);
}
.install-section {
display: flex;
flex-direction: column;
gap: 20px;
}
.install-section p {
color: var(--ant-color-text-secondary);
margin: 0;
}
.mirror-selection {
display: flex;
align-items: center;
gap: 12px;
flex-wrap: wrap;
}
.mirror-selection label {
font-weight: 500;
color: var(--ant-color-text);
white-space: nowrap;
}
.speed-info {
color: var(--ant-color-text-tertiary);
font-size: 12px;
}
.already-installed {
display: flex;
justify-content: center;
align-items: center;
min-height: 200px;
}
.service-status {
display: flex;
justify-content: center;
align-items: center;
min-height: 200px;
}
.status-info {
text-align: center;
width: 100%;
}
.status-info p {
font-size: 16px;
color: var(--ant-color-text);
margin-bottom: 16px;
}
.step-actions {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
}
.error-message {
margin-top: 20px;
}
/* 镜像卡片样式 */
.mirror-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
gap: 16px;
margin-bottom: 20px;
}
.mirror-card {
padding: 16px;
border: 2px solid var(--ant-color-border);
border-radius: 8px;
cursor: pointer;
transition: all 0.2s ease;
background: var(--ant-color-bg-container);
}
.mirror-card:hover {
border-color: var(--ant-color-primary);
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.mirror-card.active {
border-color: var(--ant-color-primary);
background: var(--ant-color-primary-bg);
}
.mirror-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 8px;
}
.mirror-header h4 {
margin: 0;
font-size: 16px;
font-weight: 600;
color: var(--ant-color-text);
}
.speed-badge {
padding: 4px 8px;
border-radius: 4px;
font-size: 12px;
font-weight: 500;
}
.speed-badge.speed-unknown {
background: var(--ant-color-fill-tertiary);
color: var(--ant-color-text-tertiary);
}
.speed-badge.speed-fast {
background: var(--ant-color-success-bg);
color: var(--ant-color-success);
}
.speed-badge.speed-medium {
background: var(--ant-color-warning-bg);
color: var(--ant-color-warning);
}
.speed-badge.speed-slow {
background: var(--ant-color-error-bg);
color: var(--ant-color-error);
}
.speed-badge.speed-timeout {
background: var(--ant-color-error-bg);
color: var(--ant-color-error);
}
.mirror-url {
font-size: 12px;
color: var(--ant-color-text-tertiary);
word-break: break-all;
}
.test-actions {
display: flex;
align-items: center;
gap: 12px;
justify-content: center;
}
.test-note {
font-size: 12px;
color: var(--ant-color-text-tertiary);
}
.git-info {
margin-top: 16px;
}
@media (max-width: 768px) {
.initialization-content {
padding: 20px;
margin: 10px;
}
.mirror-grid {
grid-template-columns: 1fr;
}
.mirror-selection {
flex-direction: column;
align-items: stretch;
}
.mirror-selection label {
text-align: left;
}
.step-actions {
flex-direction: column;
gap: 12px;
}
.test-actions {
flex-direction: column;
gap: 8px;
}
}
</style>

View File

@@ -0,0 +1,55 @@
<template>
<div class="logs-page">
<div class="page-header">
<h2>系统日志</h2>
<p>查看应用运行日志支持搜索过滤和导出功能</p>
</div>
<div class="logs-content">
<LogViewer />
</div>
</div>
</template>
<script setup lang="ts">
import { onMounted } from 'vue'
import LogViewer from '@/components/LogViewer.vue'
import { createComponentLogger } from '@/utils/logger'
const logger = createComponentLogger('LogsPage')
onMounted(() => {
logger.info('进入日志查看页面')
})
</script>
<style scoped>
.logs-page {
height: 100%;
display: flex;
flex-direction: column;
padding: 24px;
}
.page-header {
margin-bottom: 24px;
}
.page-header h2 {
margin: 0 0 8px 0;
color: var(--ant-color-text);
font-size: 24px;
font-weight: 600;
}
.page-header p {
margin: 0;
color: var(--ant-color-text-secondary);
font-size: 14px;
}
.logs-content {
flex: 1;
min-height: 0;
}
</style>

View File

@@ -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"