feat(initialization): 添加初始化页面和相关功能
- 新增初始化页面组件和路由 - 实现环境检查、Git下载、后端代码克隆等功能 - 添加下载服务和环境服务模块 - 更新类型定义,增加 Electron API 接口
This commit is contained in:
@@ -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')
|
||||
|
||||
@@ -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');
|
||||
}
|
||||
});
|
||||
|
||||
@@ -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()
|
||||
})
|
||||
})
|
||||
@@ -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')
|
||||
}
|
||||
})
|
||||
|
||||
58
frontend/electron/services/downloadService.ts
Normal file
58
frontend/electron/services/downloadService.ts
Normal 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)
|
||||
})
|
||||
})
|
||||
}
|
||||
35
frontend/electron/services/environmentService.ts
Normal file
35
frontend/electron/services/environmentService.ts
Normal 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
|
||||
}
|
||||
}
|
||||
425
frontend/electron/services/gitService.ts
Normal file
425
frontend/electron/services/gitService.ts
Normal 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 }
|
||||
}
|
||||
}
|
||||
456
frontend/electron/services/pythonService.ts
Normal file
456
frontend/electron/services/pythonService.ts
Normal 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) }
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
|
||||
BIN
frontend/public/AUTO_MAA.ico
Normal file
BIN
frontend/public/AUTO_MAA.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 51 KiB |
@@ -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>
|
||||
|
||||
|
||||
354
frontend/src/components/LogViewer.vue
Normal file
354
frontend/src/components/LogViewer.vue
Normal 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>
|
||||
@@ -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 })
|
||||
|
||||
@@ -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({
|
||||
|
||||
33
frontend/src/types/electron.d.ts
vendored
33
frontend/src/types/electron.d.ts
vendored
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
21
frontend/src/types/initialization.ts
Normal file
21
frontend/src/types/initialization.ts
Normal 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
|
||||
}
|
||||
185
frontend/src/utils/logger.ts
Normal file
185
frontend/src/utils/logger.ts
Normal 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)
|
||||
}
|
||||
}
|
||||
967
frontend/src/views/Initialization.vue
Normal file
967
frontend/src/views/Initialization.vue
Normal 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>
|
||||
55
frontend/src/views/Logs.vue
Normal file
55
frontend/src/views/Logs.vue
Normal 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>
|
||||
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user