diff --git a/frontend/electron/main.ts b/frontend/electron/main.ts index e50c348..78ebef3 100644 --- a/frontend/electron/main.ts +++ b/frontend/electron/main.ts @@ -10,8 +10,10 @@ import { installPipPackage, installDependencies, startBackend, + stopBackend, } from './services/pythonService' import { setMainWindow as setGitMainWindow, downloadGit, cloneBackend } from './services/gitService' +import { setupLogger, log, getLogPath, cleanOldLogs } from './services/logService' // 检查是否以管理员权限运行 function isRunningAsAdmin(): boolean { @@ -59,6 +61,8 @@ function restartAsAdmin(): void { let mainWindow: BrowserWindow | null = null function createWindow() { + log.info('开始创建主窗口') + mainWindow = new BrowserWindow({ width: 1600, height: 1000, @@ -76,13 +80,16 @@ function createWindow() { mainWindow.setMenuBarVisibility(false) const devServer = process.env.VITE_DEV_SERVER_URL if (devServer) { + log.info(`加载开发服务器: ${devServer}`) mainWindow.loadURL(devServer) } else { const indexHtmlPath = path.join(app.getAppPath(), 'dist', 'index.html') + log.info(`加载生产环境页面: ${indexHtmlPath}`) mainWindow.loadFile(indexHtmlPath) } mainWindow.on('closed', () => { + log.info('主窗口已关闭') mainWindow = null }) @@ -91,6 +98,7 @@ function createWindow() { setDownloadMainWindow(mainWindow) setPythonMainWindow(mainWindow) setGitMainWindow(mainWindow) + log.info('主窗口创建完成,服务引用已设置') } } @@ -239,6 +247,62 @@ ipcMain.handle('reset-config', async () => { }) // 日志文件操作 +ipcMain.handle('get-log-path', async () => { + try { + return getLogPath() + } catch (error) { + log.error('获取日志路径失败:', error) + throw error + } +}) + +ipcMain.handle('get-logs', async (event, lines?: number) => { + try { + const logFilePath = getLogPath() + + if (!fs.existsSync(logFilePath)) { + return '' + } + + const logs = fs.readFileSync(logFilePath, 'utf8') + + if (lines && lines > 0) { + const logLines = logs.split('\n') + return logLines.slice(-lines).join('\n') + } + + return logs + } catch (error) { + log.error('读取日志文件失败:', error) + throw error + } +}) + +ipcMain.handle('clear-logs', async () => { + try { + const logFilePath = getLogPath() + + if (fs.existsSync(logFilePath)) { + fs.writeFileSync(logFilePath, '', 'utf8') + log.info('日志文件已清空') + } + } catch (error) { + log.error('清空日志文件失败:', error) + throw error + } +}) + +ipcMain.handle('clean-old-logs', async (event, daysToKeep = 7) => { + try { + cleanOldLogs(daysToKeep) + log.info(`已清理${daysToKeep}天前的旧日志文件`) + } catch (error) { + log.error('清理旧日志文件失败:', error) + throw error + } +}) + +// 保留原有的日志操作方法以兼容现有代码 ipcMain.handle('save-logs-to-file', async (event, logs: string) => { try { const appRoot = getAppRoot() @@ -251,9 +315,9 @@ ipcMain.handle('save-logs-to-file', async (event, logs: string) => { const logFilePath = path.join(logsDir, 'app.log') fs.writeFileSync(logFilePath, logs, 'utf8') - console.log(`日志已保存到: ${logFilePath}`) + log.info(`日志已保存到: ${logFilePath}`) } catch (error) { - console.error('保存日志文件失败:', error) + log.error('保存日志文件失败:', error) throw error } }) @@ -265,13 +329,13 @@ ipcMain.handle('load-logs-from-file', async () => { if (fs.existsSync(logFilePath)) { const logs = fs.readFileSync(logFilePath, 'utf8') - console.log(`从文件加载日志: ${logFilePath}`) + log.info(`从文件加载日志: ${logFilePath}`) return logs } return null } catch (error) { - console.error('加载日志文件失败:', error) + log.error('加载日志文件失败:', error) return null } }) @@ -301,13 +365,45 @@ app.on('second-instance', () => { } }) +app.on('before-quit', async event => { + // 只处理一次,避免多重触发 + event.preventDefault() + log.info('应用准备退出') + try { + await stopBackend() + log.info('后端服务已停止') + } catch (e) { + log.error('停止后端时出错:', e) + console.error('停止后端时出错:', e) + } finally { + log.info('应用退出') + app.exit(0) + } +}) + app.whenReady().then(() => { + // 初始化日志系统 + setupLogger() + + // 清理7天前的旧日志 + cleanOldLogs(7) + + log.info('应用启动') + log.info(`应用版本: ${app.getVersion()}`) + log.info(`Electron版本: ${process.versions.electron}`) + log.info(`Node版本: ${process.versions.node}`) + log.info(`平台: ${process.platform}`) + // 检查管理员权限 if (!isRunningAsAdmin()) { + log.warn('应用未以管理员权限运行') console.log('应用未以管理员权限运行') // 在生产环境中,可以选择是否强制要求管理员权限 // 这里先创建窗口,让用户选择是否重新启动 + } else { + log.info('应用以管理员权限运行') } + createWindow() }) diff --git a/frontend/electron/preload.ts b/frontend/electron/preload.ts index 4db6e31..2134795 100644 --- a/frontend/electron/preload.ts +++ b/frontend/electron/preload.ts @@ -1,7 +1,7 @@ import { contextBridge, ipcRenderer } from 'electron' window.addEventListener('DOMContentLoaded', () => { - console.log('Preload loaded') + console.log('预加载脚本已加载') }) // 暴露安全的 API 给渲染进程 @@ -31,6 +31,12 @@ contextBridge.exposeInMainWorld('electronAPI', { resetConfig: () => ipcRenderer.invoke('reset-config'), // 日志文件操作 + getLogPath: () => ipcRenderer.invoke('get-log-path'), + getLogs: (lines?: number) => ipcRenderer.invoke('get-logs', lines), + clearLogs: () => ipcRenderer.invoke('clear-logs'), + cleanOldLogs: (daysToKeep?: number) => ipcRenderer.invoke('clean-old-logs', daysToKeep), + + // 保留原有方法以兼容现有代码 saveLogsToFile: (logs: string) => ipcRenderer.invoke('save-logs-to-file', logs), loadLogsFromFile: () => ipcRenderer.invoke('load-logs-from-file'), diff --git a/frontend/electron/services/logService.ts b/frontend/electron/services/logService.ts new file mode 100644 index 0000000..c2196b3 --- /dev/null +++ b/frontend/electron/services/logService.ts @@ -0,0 +1,140 @@ +import log from 'electron-log' +import * as path from 'path' +import { getAppRoot } from './environmentService' + +// 移除ANSI颜色转义字符的函数 +function stripAnsiColors(text: string): string { + // 匹配ANSI转义序列的正则表达式 - 更完整的模式 + const ansiRegex = /\x1b\[[0-9;]*[mGKHF]|\x1b\[[\d;]*[A-Za-z]/g + return text.replace(ansiRegex, '') +} + +// 获取应用安装目录下的日志路径 +function getLogDirectory(): string { + const appRoot = getAppRoot() + return path.join(appRoot, 'logs') +} + +// 配置日志系统 +export function setupLogger() { + // 设置日志文件路径到软件安装目录 + const logPath = getLogDirectory() + + // 确保日志目录存在 + const fs = require('fs') + if (!fs.existsSync(logPath)) { + fs.mkdirSync(logPath, { recursive: true }) + } + + // 配置日志格式 + log.transports.file.format = '[{y}-{m}-{d} {h}:{i}:{s}.{ms}] [{level}] {text}' + log.transports.console.format = '[{y}-{m}-{d} {h}:{i}:{s}.{ms}] [{level}] {text}' + + // 设置主进程日志文件路径和名称 + log.transports.file.resolvePathFn = () => path.join(logPath, 'app.log') + + // 设置日志级别 + log.transports.file.level = 'debug' + log.transports.console.level = 'debug' + + // 设置文件大小限制 (10MB) + log.transports.file.maxSize = 10 * 1024 * 1024 + + // 保留最近的5个日志文件 + log.transports.file.archiveLog = (file: any) => { + const filePath = file.toString() + const info = path.parse(filePath) + + try { + return path.join(info.dir, `${info.name}.old${info.ext}`) + } catch (e) { + console.warn('Could not archive log file', e) + return null + } + } + + // 捕获未处理的异常和Promise拒绝 + log.catchErrors({ + showDialog: false, + onError: (options: any) => { + log.error('未处理的错误:', options.error) + log.error('版本信息:', options.versions) + log.error('进程类型:', options.processType) + }, + }) + + // 重写console方法,将所有控制台输出重定向到日志 + const originalConsole = { + log: console.log, + error: console.error, + warn: console.warn, + info: console.info, + debug: console.debug, + } + + console.log = (...args) => { + log.info(...args) + originalConsole.log(...args) + } + + console.error = (...args) => { + log.error(...args) + originalConsole.error(...args) + } + + console.warn = (...args) => { + log.warn(...args) + originalConsole.warn(...args) + } + + console.info = (...args) => { + log.info(...args) + originalConsole.info(...args) + } + + console.debug = (...args) => { + log.debug(...args) + originalConsole.debug(...args) + } + + log.info('日志系统初始化完成') + log.info(`日志文件路径: ${path.join(logPath, 'main.log')}`) + + return log +} + +// 导出日志实例和工具函数 +export { log, stripAnsiColors } + +// 获取日志文件路径 +export function getLogPath(): string { + return path.join(getLogDirectory(), 'main.log') +} + +// 清理旧日志文件 +export function cleanOldLogs(daysToKeep: number = 7) { + const fs = require('fs') + const logDir = getLogDirectory() + + if (!fs.existsSync(logDir)) { + return + } + + const files = fs.readdirSync(logDir) + const now = Date.now() + const maxAge = daysToKeep * 24 * 60 * 60 * 1000 // 转换为毫秒 + + files.forEach((file: string) => { + const filePath = path.join(logDir, file) + const stats = fs.statSync(filePath) + + if (now - stats.mtime.getTime() > maxAge) { + try { + fs.unlinkSync(filePath) + log.info(`已删除旧日志文件: ${file}`) + } catch (error) { + log.error(`删除旧日志文件失败: ${file}`, error) + } + } + }) +} \ No newline at end of file diff --git a/frontend/electron/services/pythonService.ts b/frontend/electron/services/pythonService.ts index e700959..24da491 100644 --- a/frontend/electron/services/pythonService.ts +++ b/frontend/electron/services/pythonService.ts @@ -4,6 +4,9 @@ import { spawn } from 'child_process' import { BrowserWindow } from 'electron' import AdmZip from 'adm-zip' import { downloadFile } from './downloadService' +import { ChildProcessWithoutNullStreams } from 'node:child_process' +import { log, stripAnsiColors } from './logService' + let mainWindow: BrowserWindow | null = null @@ -100,13 +103,13 @@ async function installPip(pythonPath: string, appRoot: string): Promise { }) process.stdout?.on('data', data => { - const output = data.toString() - console.log('pip安装输出:', output) + const output = stripAnsiColors(data.toString()) + log.info('pip安装输出:', output) }) process.stderr?.on('data', data => { - const errorOutput = data.toString() - console.log('pip安装错误输出:', errorOutput) + const errorOutput = stripAnsiColors(data.toString()) + log.warn('pip安装错误输出:', errorOutput) }) process.on('close', code => { @@ -134,13 +137,13 @@ async function installPip(pythonPath: string, appRoot: string): Promise { }) verifyProcess.stdout?.on('data', data => { - const output = data.toString() - console.log('pip版本信息:', output) + const output = stripAnsiColors(data.toString()) + log.info('pip版本信息:', output) }) verifyProcess.stderr?.on('data', data => { - const errorOutput = data.toString() - console.log('pip版本检查错误:', errorOutput) + const errorOutput = stripAnsiColors(data.toString()) + log.warn('pip版本检查错误:', errorOutput) }) verifyProcess.on('close', code => { @@ -366,8 +369,8 @@ export async function installDependencies( ) process.stdout?.on('data', data => { - const output = data.toString() - console.log('Pip output:', output) + const output = stripAnsiColors(data.toString()) + log.info('Pip output:', output) if (mainWindow) { mainWindow.webContents.send('download-progress', { @@ -380,8 +383,8 @@ export async function installDependencies( }) process.stderr?.on('data', data => { - const errorOutput = data.toString() - console.error('Pip error:', errorOutput) + const errorOutput = stripAnsiColors(data.toString()) + log.error('Pip error:', errorOutput) }) process.on('close', code => { @@ -470,73 +473,139 @@ export async function installPipPackage( } // 启动后端 -export async function startBackend(appRoot: string): Promise<{ success: boolean; error?: string }> { +let backendProc: ChildProcessWithoutNullStreams | null = null + +/** + * 启动后端 + * @param appRoot 项目根目录 + * @param timeoutMs 等待启动超时(默认 30 秒) + */ +export async function startBackend(appRoot: string, timeoutMs = 30_000) { try { - const pythonPath = path.join(appRoot, 'environment', 'python', 'python.exe') - const backendPath = path.join(appRoot) - const mainPyPath = path.join(backendPath, 'main.py') - - // 检查文件是否存在 - if (!fs.existsSync(pythonPath)) { - throw new Error('Python可执行文件不存在') - } - if (!fs.existsSync(mainPyPath)) { - throw new Error('后端主文件不存在') + // 如果已经在运行,直接返回 + if (backendProc && !backendProc.killed && backendProc.exitCode == null) { + console.log('[Backend] 已在运行, PID =', backendProc.pid) + return { success: true } } - console.log(`启动后端指令: "${pythonPath}" "${mainPyPath}"(cwd: ${appRoot})`) + const pythonExe = path.join(appRoot, 'environment', 'python', 'python.exe') + const mainPy = path.join(appRoot, 'main.py') - // 启动后端进程 - const backendProcess = spawn(pythonPath, [mainPyPath], { + if (!fs.existsSync(pythonExe)) { + throw new Error(`Python可执行文件不存在: ${pythonExe}`) + } + if (!fs.existsSync(mainPy)) { + throw new Error(`后端主文件不存在: ${mainPy}`) + } + + console.log(`[Backend] spawn "${pythonExe}" "${mainPy}" (cwd=${appRoot})`) + + backendProc = spawn(pythonExe, [mainPy], { cwd: appRoot, - stdio: 'pipe', - env: { - ...process.env, - PYTHONIOENCODING: 'utf-8', // 设置Python输出编码为UTF-8 - }, + stdio: ['pipe', 'pipe', 'pipe'], + env: { ...process.env, PYTHONIOENCODING: 'utf-8' }, }) - // 等待后端启动 + backendProc.stdout.setEncoding('utf8') + backendProc.stderr.setEncoding('utf8') + + backendProc.stdout.on('data', d => { + const line = stripAnsiColors(d.toString().trim()) + if (line) log.info('[Backend]', line) + }) + backendProc.stderr.on('data', d => { + const line = stripAnsiColors(d.toString().trim()) + if (line) log.info('[Backend]', line) + }) + + backendProc.once('exit', (code, signal) => { + console.log('[Backend] 退出', { code, signal }) + backendProc = null + }) + backendProc.once('error', e => { + console.error('[Backend] 进程错误:', e) + }) + + // 等待启动成功(匹配 Uvicorn 的输出) await new Promise((resolve, reject) => { - const timeout = setTimeout(() => { - reject(new Error('后端启动超时')) - }, 30000) // 30秒超时 + let settled = false + const timer = setTimeout(() => { + if (!settled) { + settled = true + reject(new Error('后端启动超时')) + } + }, timeoutMs) - backendProcess.stdout?.on('data', data => { - const output = data.toString() - console.log('Backend output:', output) - - // 检查是否包含启动成功的标志 - if (output.includes('Uvicorn running') || output.includes('36163')) { - clearTimeout(timeout) + const checkReady = (buf: Buffer | string) => { + if (settled) return + const s = buf.toString() + if (/Uvicorn running|http:\/\/0\.0\.0\.0:\d+/.test(s)) { + settled = true + clearTimeout(timer) resolve() } - }) + } - // ✅ 重要:也要监听 stderr - backendProcess.stderr?.on('data', data => { - const output = data.toString() - console.error('Backend error:', output) // 保留原有日志 + backendProc!.stdout.on('data', checkReady) + backendProc!.stderr.on('data', checkReady) - // ✅ 在 stderr 中也检查启动标志 - if (output.includes('Uvicorn running') || output.includes('36163')) { - clearTimeout(timeout) - resolve() + backendProc!.once('exit', (code, sig) => { + if (!settled) { + settled = true + clearTimeout(timer) + reject(new Error(`后端提前退出: code=${code}, signal=${sig ?? ''}`)) } }) - - backendProcess.stderr?.on('data', data => { - console.error('Backend error:', data.toString()) - }) - - backendProcess.on('error', error => { - clearTimeout(timeout) - reject(error) + backendProc!.once('error', err => { + if (!settled) { + settled = true + clearTimeout(timer) + reject(err) + } }) }) + console.log('[Backend] 启动成功, PID =', backendProc.pid) return { success: true } - } catch (error) { - return { success: false, error: error instanceof Error ? error.message : String(error) } + } catch (e) { + console.error('[Backend] 启动失败:', e) + return { success: false, error: e instanceof Error ? e.message : String(e) } } } + +/** 停止后端进程(如果没启动就直接返回成功) */ +export async function stopBackend() { + if (!backendProc || backendProc.killed) { + console.log('[Backend] 未运行,无需停止') + return { success: true } + } + + const pid = backendProc.pid + console.log('[Backend] 正在停止后端服务, PID =', pid) + + return new Promise<{ success: boolean; error?: string }>(resolve => { + // 清监听,避免重复日志 + backendProc?.stdout?.removeAllListeners('data') + backendProc?.stderr?.removeAllListeners('data') + + backendProc!.once('exit', (code, signal) => { + console.log('[Backend] 已退出', { code, signal }) + backendProc = null + resolve({ success: true }) + }) + + backendProc!.once('error', err => { + console.error('[Backend] 停止时出错:', err) + backendProc = null + resolve({ success: false, error: err instanceof Error ? err.message : String(err) }) + }) + + try { + backendProc!.kill() // 默认 SIGTERM,Windows 下等价于结束进程 + } catch (e) { + console.error('[Backend] kill 调用失败:', e) + backendProc = null + resolve({ success: false, error: e instanceof Error ? e.message : String(e) }) + } + }) +} diff --git a/frontend/package.json b/frontend/package.json index 6d98394..f23d804 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -53,6 +53,7 @@ "ant-design-vue": "4.x", "axios": "^1.11.0", "dayjs": "^1.11.13", + "electron-log": "^5.4.3", "form-data": "^4.0.4", "markdown-it": "^14.1.0", "vue": "^3.5.17", diff --git a/frontend/src/App.vue b/frontend/src/App.vue index c11ee23..36e57aa 100644 --- a/frontend/src/App.vue +++ b/frontend/src/App.vue @@ -5,6 +5,7 @@ import { ConfigProvider } from 'ant-design-vue' import { useTheme } from './composables/useTheme.ts' import AppLayout from './components/AppLayout.vue' import zhCN from 'ant-design-vue/es/locale/zh_CN' +import { logger } from '@/utils/logger' const route = useRoute() const { antdTheme, initTheme } = useTheme() @@ -13,7 +14,9 @@ const { antdTheme, initTheme } = useTheme() const isInitializationPage = computed(() => route.name === 'Initialization') onMounted(() => { + logger.info('App组件已挂载') initTheme() + logger.info('主题初始化完成') }) diff --git a/frontend/src/api/models/GeneralConfig_Script.ts b/frontend/src/api/models/GeneralConfig_Script.ts index 9d1f4d4..8367c45 100644 --- a/frontend/src/api/models/GeneralConfig_Script.ts +++ b/frontend/src/api/models/GeneralConfig_Script.ts @@ -3,57 +3,56 @@ /* tslint:disable */ /* eslint-disable */ export type GeneralConfig_Script = { - /** - * 脚本可执行文件路径 - */ - ScriptPath?: (string | null); - /** - * 脚本启动附加命令参数 - */ - Arguments?: (string | null); - /** - * 是否追踪脚本子进程 - */ - IfTrackProcess?: (boolean | null); - /** - * 配置文件路径 - */ - ConfigPath?: (string | null); - /** - * 配置文件类型: 单个文件, 文件夹 - */ - ConfigPathMode?: ('File' | 'Folder' | null); - /** - * 更新配置时机, 从不, 仅成功时, 仅失败时, 任务结束时 - */ - UpdateConfigMode?: ('Never' | 'Success' | 'Failure' | 'Always' | null); - /** - * 日志文件路径 - */ - LogPath?: (string | null); - /** - * 日志文件名格式 - */ - LogPathFormat?: (string | null); - /** - * 日志时间戳开始位置 - */ - LogTimeStart?: (number | null); - /** - * 日志时间戳结束位置 - */ - LogTimeEnd?: (number | null); - /** - * 日志时间戳格式 - */ - LogTimeFormat?: (string | null); - /** - * 成功时日志 - */ - SuccessLog?: (string | null); - /** - * 错误时日志 - */ - ErrorLog?: (string | null); -}; - + /** + * 脚本可执行文件路径 + */ + ScriptPath?: string | null + /** + * 脚本启动附加命令参数 + */ + Arguments?: string | null + /** + * 是否追踪脚本子进程 + */ + IfTrackProcess?: boolean | null + /** + * 配置文件路径 + */ + ConfigPath?: string | null + /** + * 配置文件类型: 单个文件, 文件夹 + */ + ConfigPathMode?: 'File' | 'Folder' | null + /** + * 更新配置时机, 从不, 仅成功时, 仅失败时, 任务结束时 + */ + UpdateConfigMode?: 'Never' | 'Success' | 'Failure' | 'Always' | null + /** + * 日志文件路径 + */ + LogPath?: string | null + /** + * 日志文件名格式 + */ + LogPathFormat?: string | null + /** + * 日志时间戳开始位置 + */ + LogTimeStart?: number | null + /** + * 日志时间戳结束位置 + */ + LogTimeEnd?: number | null + /** + * 日志时间戳格式 + */ + LogTimeFormat?: string | null + /** + * 成功时日志 + */ + SuccessLog?: string | null + /** + * 错误时日志 + */ + ErrorLog?: string | null +} diff --git a/frontend/src/components/LogViewer.vue b/frontend/src/components/LogViewer.vue index c890ad5..94cdc05 100644 --- a/frontend/src/components/LogViewer.vue +++ b/frontend/src/components/LogViewer.vue @@ -1,344 +1,189 @@ \ No newline at end of file diff --git a/frontend/src/components/initialization/AdminCheck.vue b/frontend/src/components/initialization/AdminCheck.vue index 9865b60..46492b7 100644 --- a/frontend/src/components/initialization/AdminCheck.vue +++ b/frontend/src/components/initialization/AdminCheck.vue @@ -13,15 +13,13 @@ diff --git a/frontend/src/components/initialization/AutoMode.vue b/frontend/src/components/initialization/AutoMode.vue index 152b590..7339565 100644 --- a/frontend/src/components/initialization/AutoMode.vue +++ b/frontend/src/components/initialization/AutoMode.vue @@ -39,12 +39,11 @@ - - diff --git a/frontend/src/views/Settings.vue b/frontend/src/views/Settings.vue index 10d8ced..e2ca012 100644 --- a/frontend/src/views/Settings.vue +++ b/frontend/src/views/Settings.vue @@ -687,11 +687,7 @@ onMounted(() => { -
-

系统日志

-

查看应用运行日志,用于问题排查和调试

- -
+ diff --git a/frontend/yarn.lock b/frontend/yarn.lock index 07f49e6..1185c32 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -1775,21 +1775,33 @@ electron-builder@^26.0.12: resolved "https://registry.npmmirror.com/electron-builder/-/electron-builder-26.0.12.tgz#797af2e70efdd96c9ea5d8a8164b8728c90d65ff" integrity sha512-cD1kz5g2sgPTMFHjLxfMjUK5JABq3//J4jPswi93tOPFz6btzXYtK5NrDt717NRbukCUDOrrvmYVOWERlqoiXA== dependencies: - app-builder-lib "26.0.12" - builder-util "26.0.11" - builder-util-runtime "9.3.1" - chalk "^4.1.2" - dmg-builder "26.0.12" - fs-extra "^10.1.0" - is-ci "^3.0.0" - lazy-val "^1.0.5" - simple-update-notifier "2.0.0" - yargs "^17.6.2" + app-builder-lib: "npm:26.0.12" + builder-util: "npm:26.0.11" + builder-util-runtime: "npm:9.3.1" + chalk: "npm:^4.1.2" + dmg-builder: "npm:26.0.12" + fs-extra: "npm:^10.1.0" + is-ci: "npm:^3.0.0" + lazy-val: "npm:^1.0.5" + simple-update-notifier: "npm:2.0.0" + yargs: "npm:^17.6.2" + bin: + electron-builder: cli.js + install-app-deps: install-app-deps.js + checksum: 10c0/88911a812c37bc8c154aed876af26f07206ee3734b6b0b011beffd2c5788fbbdaf41e99ac1663c23627add5963fbc31678de859f946462fca05eb0e24027625b + languageName: node + linkType: hard -electron-publish@26.0.11: - version "26.0.11" - resolved "https://registry.npmmirror.com/electron-publish/-/electron-publish-26.0.11.tgz#92c9329a101af2836d9d228c82966eca1eee9a7b" - integrity sha512-a8QRH0rAPIWH9WyyS5LbNvW9Ark6qe63/LqDB7vu2JXYpi0Gma5Q60Dh4tmTqhOBQt0xsrzD8qE7C+D7j+B24A== +"electron-log@npm:^5.4.3": + version: 5.4.3 + resolution: "electron-log@npm:5.4.3" + checksum: 10c0/84d398d2a53cd73fdd1118f7b376b08d74938c79cb4aa3454aa176b61ebf1522f5cc92179355e69e0053301df7a19fbe6cf302319e5fbe5fc6a1600f282a2646 + languageName: node + linkType: hard + +"electron-publish@npm:26.0.11": + version: 26.0.11 + resolution: "electron-publish@npm:26.0.11" dependencies: "@types/fs-extra" "^9.0.11" builder-util "26.0.11" @@ -2186,14 +2198,45 @@ fs-extra@^10.0.0, fs-extra@^10.1.0: resolved "https://registry.npmmirror.com/fs-extra/-/fs-extra-10.1.0.tgz#02873cfbc4084dde127eaa5f9905eef2325d1abf" integrity sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ== dependencies: - graceful-fs "^4.2.0" - jsonfile "^6.0.1" - universalify "^2.0.0" + "@ant-design/icons-vue": "npm:^7.0.1" + "@types/adm-zip": "npm:^0.5.7" + "@types/markdown-it": "npm:^14.1.2" + "@types/node": "npm:22.17.1" + "@typescript-eslint/eslint-plugin": "npm:^8.38.0" + "@typescript-eslint/parser": "npm:^8.38.0" + "@vitejs/plugin-vue": "npm:^6.0.1" + "@vue/tsconfig": "npm:^0.7.0" + adm-zip: "npm:^0.5.16" + ant-design-vue: "npm:4.x" + axios: "npm:^1.11.0" + concurrently: "npm:^9.2.0" + cross-env: "npm:^10.0.0" + dayjs: "npm:^1.11.13" + electron: "npm:^37.2.5" + electron-builder: "npm:^26.0.12" + electron-log: "npm:^5.4.3" + eslint: "npm:^9.32.0" + eslint-config-prettier: "npm:^10.1.8" + eslint-plugin-prettier: "npm:^5.5.3" + eslint-plugin-vue: "npm:^10.4.0" + form-data: "npm:^4.0.4" + markdown-it: "npm:^14.1.0" + openapi-typescript-codegen: "npm:^0.29.0" + prettier: "npm:^3.6.2" + typescript: "npm:^5.9.2" + vite: "npm:^7.0.4" + vite-plugin-eslint: "npm:^1.8.1" + vue: "npm:^3.5.17" + vue-eslint-parser: "npm:^10.2.0" + vue-router: "npm:4" + vue-tsc: "npm:^2.2.12" + wait-on: "npm:^8.0.4" + languageName: unknown + linkType: soft -fs-extra@^11.1.1, fs-extra@^11.2.0: - version "11.3.1" - resolved "https://registry.npmmirror.com/fs-extra/-/fs-extra-11.3.1.tgz#ba7a1f97a85f94c6db2e52ff69570db3671d5a74" - integrity sha512-eXvGGwZ5CL17ZSwHWd3bbgk7UUpF6IFHtP57NYYakPvHOs8GDgDe5KJI36jIJzDkJ6eJjuzRA8eBQb6SkKue0g== +"fs-extra@npm:^10.0.0, fs-extra@npm:^10.1.0": + version: 10.1.0 + resolution: "fs-extra@npm:10.1.0" dependencies: graceful-fs "^4.2.0" jsonfile "^6.0.1"