From 24dc4c739bb1e50b3621fcf8e016ca800e97e2d9 Mon Sep 17 00:00:00 2001 From: ethanfly Date: Fri, 26 Dec 2025 03:35:54 +0800 Subject: [PATCH] Sure! Pl --- .gitignore | 35 + README.md | 283 ++ build/installer.nsh | 13 + docs/.gitkeep | 5 + electron/main.ts | 196 + electron/preload.ts | 150 + electron/services/ConfigStore.ts | 212 + electron/services/HostsManager.ts | 224 + electron/services/MysqlManager.ts | 1096 +++++ electron/services/NginxManager.ts | 848 ++++ electron/services/NodeManager.ts | 460 ++ electron/services/PhpManager.ts | 2063 ++++++++ electron/services/RedisManager.ts | 666 +++ electron/services/ServiceManager.ts | 356 ++ index.html | 14 + package-lock.json | 6807 +++++++++++++++++++++++++++ package.json | 83 + pecl_redis_page.html | 1762 +++++++ pecl_search.html | 992 ++++ pecl_test.html | 388 ++ public/favicon.svg | 12 + redis_detail.html | 1762 +++++++ src/App.vue | 373 ++ src/main.ts | 23 + src/router/index.ts | 64 + src/styles/main.scss | 549 +++ src/views/Dashboard.vue | 597 +++ src/views/HostsManager.vue | 205 + src/views/MysqlManager.vue | 555 +++ src/views/NginxManager.vue | 517 ++ src/views/NodeManager.vue | 397 ++ src/views/PhpManager.vue | 835 ++++ src/views/RedisManager.vue | 529 +++ src/views/Settings.vue | 244 + src/views/SitesManager.vue | 697 +++ src/vite-env.d.ts | 110 + tsconfig.json | 26 + tsconfig.node.json | 11 + vite.config.ts | 70 + 39 files changed, 24229 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 build/installer.nsh create mode 100644 docs/.gitkeep create mode 100644 electron/main.ts create mode 100644 electron/preload.ts create mode 100644 electron/services/ConfigStore.ts create mode 100644 electron/services/HostsManager.ts create mode 100644 electron/services/MysqlManager.ts create mode 100644 electron/services/NginxManager.ts create mode 100644 electron/services/NodeManager.ts create mode 100644 electron/services/PhpManager.ts create mode 100644 electron/services/RedisManager.ts create mode 100644 electron/services/ServiceManager.ts create mode 100644 index.html create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 pecl_redis_page.html create mode 100644 pecl_search.html create mode 100644 pecl_test.html create mode 100644 public/favicon.svg create mode 100644 redis_detail.html create mode 100644 src/App.vue create mode 100644 src/main.ts create mode 100644 src/router/index.ts create mode 100644 src/styles/main.scss create mode 100644 src/views/Dashboard.vue create mode 100644 src/views/HostsManager.vue create mode 100644 src/views/MysqlManager.vue create mode 100644 src/views/NginxManager.vue create mode 100644 src/views/NodeManager.vue create mode 100644 src/views/PhpManager.vue create mode 100644 src/views/RedisManager.vue create mode 100644 src/views/Settings.vue create mode 100644 src/views/SitesManager.vue create mode 100644 src/vite-env.d.ts create mode 100644 tsconfig.json create mode 100644 tsconfig.node.json create mode 100644 vite.config.ts diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c0beecb --- /dev/null +++ b/.gitignore @@ -0,0 +1,35 @@ +# Dependencies +node_modules/ + +# Build output +dist/ +dist-electron/ +release/ + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? + +# Local env files +.env.local +.env.*.local + +# Log files +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* + +# OS files +.DS_Store +Thumbs.db + +# Electron +*.asar + diff --git a/README.md b/README.md new file mode 100644 index 0000000..3ce5fc4 --- /dev/null +++ b/README.md @@ -0,0 +1,283 @@ +# PHPer 开发环境管理器 + +

+ PHPer Logo +

+ +

+ 一款功能强大的 Windows PHP 开发环境管理工具 +

+ +

+ 轻松管理 PHP、MySQL、Nginx、Redis 等服务,告别繁琐的手动配置 +

+ +

+ 功能特性 • + 安装使用 • + 使用指南 • + 常见问题 +

+ +--- + +## 📸 界面预览 + + + + + + + + + + +
仪表盘PHP管理
仪表盘PHP 版本管理
+ +## ✨ 功能特性 + +### 🐘 PHP 版本管理 + +| 功能 | 说明 | +| ---------- | ---------------------------------------------------------- | +| 多版本管理 | 支持同时安装 PHP 8.1、8.2、8.3、8.4、8.5 等多个版本 | +| 一键切换 | 点击即可切换 PHP 版本,自动配置系统环境变量 | +| 扩展管理 | 可视化管理 PHP 扩展,一键启用/禁用 | +| 配置编辑 | 在线编辑 php.ini,无需手动查找配置文件 | +| 自动配置 | 安装时自动启用常用扩展(curl、gd、mbstring、pdo_mysql 等) | + +### 🐬 MySQL 管理 + +| 功能 | 说明 | +| ---------- | -------------------------------- | +| 版本支持 | 支持 MySQL 5.7.x 和 8.0.x 系列 | +| 服务控制 | 启动、停止、重启 MySQL 服务 | +| 密码管理 | 一键修改 root 密码 | +| 配置编辑 | 在线编辑 my.ini 配置文件 | +| 自动初始化 | 安装时自动初始化数据库,开箱即用 | + +### 🌐 Nginx 管理 + +| 功能 | 说明 | +| ------------ | ------------------------------------ | +| 版本管理 | 支持多个 Nginx 版本,可随时切换 | +| 服务控制 | 启动、停止、重启、热重载配置 | +| 站点管理 | 可视化添加、删除、启用、禁用虚拟主机 | +| Laravel 支持 | 自动生成 Laravel 项目的伪静态配置 | +| SSL 证书 | 支持申请 Let's Encrypt 免费 SSL 证书 | +| 配置编辑 | 在线编辑 nginx.conf 主配置文件 | + +### 🔴 Redis 管理 + +| 功能 | 说明 | +| ------------ | -------------------------------- | +| Windows 版本 | 使用 Windows 原生编译版 Redis | +| 服务控制 | 启动、停止、重启 Redis 服务 | +| 状态监控 | 实时查看运行状态、内存使用情况 | +| 配置编辑 | 在线编辑 redis.windows.conf 配置 | + +### 🌍 站点管理 + +- ➕ **快速创建站点** - 填写域名和路径即可创建虚拟主机 +- 🎯 **Laravel 一键配置** - 自动配置 public 目录和伪静态规则 +- 🔒 **SSL 证书申请** - 集成 Let's Encrypt 自动申请 +- 📝 **Hosts 自动配置** - 自动添加域名到系统 hosts 文件 + +### ⚙️ 其他功能 + +- 🚀 **开机自启动** - 可配置各服务开机自动启动 +- 📋 **Hosts 管理** - 可视化管理系统 hosts 文件 +- 🌙 **深色/浅色主题** - 支持主题切换 +- 📊 **服务状态监控** - 实时显示各服务运行状态 + +## 🛠️ 技术栈 + +| 技术 | 说明 | +| --------------------------------------------- | ------------ | +| [Vue 3](https://vuejs.org/) | 前端框架 | +| [TypeScript](https://www.typescriptlang.org/) | 类型安全 | +| [Electron](https://www.electronjs.org/) | 桌面应用框架 | +| [Element Plus](https://element-plus.org/) | UI 组件库 | +| [Vite](https://vitejs.dev/) | 构建工具 | +| [Pinia](https://pinia.vuejs.org/) | 状态管理 | + +## 📦 安装使用 + +### 系统要求 + +- ✅ Windows 10/11 (64 位) +- ✅ Node.js 18.0 或更高版本 +- ✅ 管理员权限(用于管理服务和修改 hosts 文件) +- ✅ [Visual C++ Redistributable 2015-2022](https://aka.ms/vs/17/release/vc_redist.x64.exe) + +### 开发环境 + +```bash +# 克隆项目 +git clone https://github.com/your-username/phper.git +cd phper + +# 安装依赖 +npm install + +# 启动开发服务器 +npm run electron:dev +``` + +### 构建生产版本 + +```bash +# 构建 Windows 安装包 +npm run electron:build +``` + +构建完成后,安装包将生成在 `release` 目录中。 + +## 📁 项目结构 + +``` +phper/ +├── electron/ # Electron 主进程 +│ ├── main.ts # 主进程入口 +│ ├── preload.ts # 预加载脚本(IPC 通信) +│ └── services/ # 服务管理模块 +│ ├── ConfigStore.ts # 配置存储(使用 electron-store) +│ ├── PhpManager.ts # PHP 版本管理器 +│ ├── MysqlManager.ts # MySQL 服务管理器 +│ ├── NginxManager.ts # Nginx 服务管理器 +│ ├── RedisManager.ts # Redis 服务管理器 +│ ├── ServiceManager.ts # 开机自启服务管理器 +│ └── HostsManager.ts # Hosts 文件管理器 +│ +├── src/ # Vue 前端源码 +│ ├── App.vue # 根组件 +│ ├── main.ts # 入口文件 +│ ├── vite-env.d.ts # 类型声明 +│ ├── router/ # 路由配置 +│ │ └── index.ts +│ ├── styles/ # 样式文件 +│ │ └── main.scss # 全局样式(含主题变量) +│ └── views/ # 页面视图 +│ ├── Dashboard.vue # 仪表盘 +│ ├── PhpManager.vue # PHP 管理 +│ ├── MysqlManager.vue # MySQL 管理 +│ ├── NginxManager.vue # Nginx 管理 +│ ├── RedisManager.vue # Redis 管理 +│ ├── SitesManager.vue # 站点管理 +│ ├── HostsManager.vue # Hosts 管理 +│ └── Settings.vue # 设置 +│ +├── public/ # 静态资源 +│ └── favicon.svg # 应用图标 +│ +├── index.html # HTML 模板 +├── package.json # 项目配置 +├── vite.config.ts # Vite 配置 +├── tsconfig.json # TypeScript 配置 +└── README.md # 项目说明 +``` + +## 📖 使用指南 + +### 首次使用 + +1. **安装运行时依赖** + + - 确保已安装 [Visual C++ Redistributable 2015-2022](https://aka.ms/vs/17/release/vc_redist.x64.exe) + +2. **以管理员身份运行** + + - 右键点击应用图标,选择"以管理员身份运行" + - 这是管理服务和修改 hosts 文件所必需的 + +3. **安装服务** + - 首次使用需要安装 PHP、MySQL、Nginx、Redis + - 进入对应管理页面,点击"安装"按钮 + +### 创建第一个站点 + +1. 安装并启动 Nginx 和 PHP +2. 进入"站点管理"页面 +3. 点击"添加站点" +4. 填写站点信息: + - 站点名称:如 `myproject` + - 域名:如 `myproject.test` + - 根目录:如 `C:\Projects\myproject`(Laravel 项目无需指定 public) + - 选择 PHP 版本 + - 如果是 Laravel 项目,开启"Laravel 项目"选项 +5. 点击确认,站点即创建完成 +6. 在浏览器访问 http://myproject.test + +### 配置开机自启动 + +1. 进入"设置"页面 +2. 在"开机自启动"部分,开启需要自启的服务 +3. 应用会在 Windows 启动目录创建启动脚本 + +## ❓ 常见问题 + +### Q: 为什么需要管理员权限? + +A: 应用需要管理员权限来: + +- 启动/停止 Windows 服务 +- 修改系统 hosts 文件 +- 配置系统环境变量 + +### Q: PHP 下载很慢怎么办? + +A: PHP 从 windows.php.net 官方下载,如果速度较慢: + +- 可以手动从官网下载 ZIP 文件 +- 解压到 `[安装目录]/php/php-版本号` 目录 +- 重新打开应用即可识别 + +### Q: MySQL 启动失败? + +A: 常见原因: + +- 3306 端口被占用,检查是否有其他 MySQL 实例 +- 防火墙阻止,添加防火墙规则 +- 数据目录权限问题,确保目录可写 + +### Q: 如何卸载服务? + +A: 进入对应服务管理页面,先停止服务,然后点击"卸载"按钮。 + +## 🔗 相关资源 + +- [PHP for Windows](https://windows.php.net/download/) - PHP Windows 官方下载 +- [MySQL Downloads](https://dev.mysql.com/downloads/) - MySQL 官方下载 +- [Nginx](https://nginx.org/) - Nginx 官方网站 +- [Redis for Windows](https://github.com/redis-windows/redis-windows) - Windows 版 Redis + +## 📄 开源协议 + +本项目基于 [MIT License](LICENSE) 开源。 + +## 🤝 贡献指南 + +欢迎提交 Issue 和 Pull Request! + +1. Fork 本仓库 +2. 创建功能分支 (`git checkout -b feature/AmazingFeature`) +3. 提交更改 (`git commit -m 'Add some AmazingFeature'`) +4. 推送到分支 (`git push origin feature/AmazingFeature`) +5. 提交 Pull Request + +## 📮 反馈建议 + +如果您在使用过程中遇到问题或有任何建议,欢迎: + +- 提交 [Issue](https://github.com/your-username/phper/issues) +- 发送邮件至 your-email@example.com + +--- + +

+ Made with ❤️ for PHP Developers +

+ +

+ ⭐ 如果这个项目对您有帮助,请给我们一个 Star! +

diff --git a/build/installer.nsh b/build/installer.nsh new file mode 100644 index 0000000..8aa8c7d --- /dev/null +++ b/build/installer.nsh @@ -0,0 +1,13 @@ +!macro customInit + ; 设置默认安装目录为 D 盘 + StrCpy $INSTDIR "D:\PHPer" +!macroend + +!macro customInstall + ; 安装完成后的自定义操作 +!macroend + +!macro customUnInstall + ; 卸载时的自定义操作 +!macroend + diff --git a/docs/.gitkeep b/docs/.gitkeep new file mode 100644 index 0000000..077cdec --- /dev/null +++ b/docs/.gitkeep @@ -0,0 +1,5 @@ +# 此目录用于存放文档截图 +# 请将以下截图放入此目录: +# - dashboard.png 仪表盘截图 +# - php-manager.png PHP管理页面截图 + diff --git a/electron/main.ts b/electron/main.ts new file mode 100644 index 0000000..274af3f --- /dev/null +++ b/electron/main.ts @@ -0,0 +1,196 @@ +import { app, BrowserWindow, ipcMain, shell } from 'electron' +import { join } from 'path' +import { PhpManager } from './services/PhpManager' +import { MysqlManager } from './services/MysqlManager' +import { NginxManager } from './services/NginxManager' +import { RedisManager } from './services/RedisManager' +import { NodeManager } from './services/NodeManager' +import { ServiceManager } from './services/ServiceManager' +import { HostsManager } from './services/HostsManager' +import { ConfigStore } from './services/ConfigStore' + +let mainWindow: BrowserWindow | null = null + +// 发送下载进度到渲染进程 +export function sendDownloadProgress(type: string, progress: number, downloaded: number, total: number) { + if (mainWindow && !mainWindow.isDestroyed()) { + mainWindow.webContents.send('download-progress', { type, progress, downloaded, total }) + } +} + +// 初始化各服务管理器 +const configStore = new ConfigStore() +const phpManager = new PhpManager(configStore) +const mysqlManager = new MysqlManager(configStore) +const nginxManager = new NginxManager(configStore) +const redisManager = new RedisManager(configStore) +const nodeManager = new NodeManager(configStore) +const serviceManager = new ServiceManager(configStore) +const hostsManager = new HostsManager() + +function createWindow() { + mainWindow = new BrowserWindow({ + width: 1400, + height: 900, + minWidth: 1200, + minHeight: 700, + webPreferences: { + preload: join(__dirname, 'preload.js'), + contextIsolation: true, + nodeIntegration: false + }, + titleBarStyle: 'hidden', + titleBarOverlay: { + color: '#1a1a2e', + symbolColor: '#ffffff', + height: 40 + }, + frame: false, + icon: join(__dirname, '../public/icon.ico') + }) + + // 开发环境加载 Vite 开发服务器 + const VITE_DEV_SERVER_URL = process.env.VITE_DEV_SERVER_URL + if (VITE_DEV_SERVER_URL) { + mainWindow.loadURL(VITE_DEV_SERVER_URL) + mainWindow.webContents.openDevTools() + } else { + mainWindow.loadFile(join(__dirname, '../dist/index.html')) + } + + mainWindow.on('closed', () => { + mainWindow = null + }) +} + +app.whenReady().then(() => { + createWindow() + + app.on('activate', () => { + if (BrowserWindow.getAllWindows().length === 0) { + createWindow() + } + }) +}) + +app.on('window-all-closed', () => { + if (process.platform !== 'darwin') { + app.quit() + } +}) + +// ==================== IPC 处理程序 ==================== + +// 窗口控制 +ipcMain.handle('window:minimize', () => mainWindow?.minimize()) +ipcMain.handle('window:maximize', () => { + if (mainWindow?.isMaximized()) { + mainWindow.unmaximize() + } else { + mainWindow?.maximize() + } +}) +ipcMain.handle('window:close', () => mainWindow?.close()) + +// 打开外部链接 +ipcMain.handle('shell:openExternal', (_, url: string) => shell.openExternal(url)) +ipcMain.handle('shell:openPath', (_, path: string) => shell.openPath(path)) + +// 选择文件夹对话框 +ipcMain.handle('dialog:selectDirectory', async () => { + const { dialog } = await import('electron') + const result = await dialog.showOpenDialog(mainWindow!, { + properties: ['openDirectory'], + title: '选择目录' + }) + return result.canceled ? null : result.filePaths[0] +}) + +// ==================== PHP 管理 ==================== +ipcMain.handle('php:getVersions', () => phpManager.getInstalledVersions()) +ipcMain.handle('php:getAvailableVersions', () => phpManager.getAvailableVersions()) +ipcMain.handle('php:install', (_, version: string) => phpManager.install(version)) +ipcMain.handle('php:uninstall', (_, version: string) => phpManager.uninstall(version)) +ipcMain.handle('php:setActive', (_, version: string) => phpManager.setActive(version)) +ipcMain.handle('php:getExtensions', (_, version: string) => phpManager.getExtensions(version)) +ipcMain.handle('php:openExtensionDir', (_, version: string) => phpManager.openExtensionDir(version)) +ipcMain.handle('php:getAvailableExtensions', (_, version: string, searchKeyword?: string) => phpManager.getAvailableExtensions(version, searchKeyword)) +ipcMain.handle('php:enableExtension', (_, version: string, ext: string) => phpManager.enableExtension(version, ext)) +ipcMain.handle('php:disableExtension', (_, version: string, ext: string) => phpManager.disableExtension(version, ext)) +ipcMain.handle('php:installExtension', (_, version: string, ext: string, downloadUrl?: string, packageName?: string) => phpManager.installExtension(version, ext, downloadUrl, packageName)) +ipcMain.handle('php:getConfig', (_, version: string) => phpManager.getConfig(version)) +ipcMain.handle('php:saveConfig', (_, version: string, config: string) => phpManager.saveConfig(version, config)) + +// ==================== MySQL 管理 ==================== +ipcMain.handle('mysql:getVersions', () => mysqlManager.getInstalledVersions()) +ipcMain.handle('mysql:getAvailableVersions', () => mysqlManager.getAvailableVersions()) +ipcMain.handle('mysql:install', (_, version: string) => mysqlManager.install(version)) +ipcMain.handle('mysql:uninstall', (_, version: string) => mysqlManager.uninstall(version)) +ipcMain.handle('mysql:start', (_, version: string) => mysqlManager.start(version)) +ipcMain.handle('mysql:stop', (_, version: string) => mysqlManager.stop(version)) +ipcMain.handle('mysql:restart', (_, version: string) => mysqlManager.restart(version)) +ipcMain.handle('mysql:getStatus', (_, version: string) => mysqlManager.getStatus(version)) +ipcMain.handle('mysql:changePassword', (_, version: string, newPassword: string, currentPassword?: string) => mysqlManager.changeRootPassword(version, newPassword, currentPassword)) +ipcMain.handle('mysql:getConfig', (_, version: string) => mysqlManager.getConfig(version)) +ipcMain.handle('mysql:saveConfig', (_, version: string, config: string) => mysqlManager.saveConfig(version, config)) +ipcMain.handle('mysql:reinitialize', (_, version: string) => mysqlManager.reinitialize(version)) + +// ==================== Nginx 管理 ==================== +ipcMain.handle('nginx:getVersions', () => nginxManager.getInstalledVersions()) +ipcMain.handle('nginx:getAvailableVersions', () => nginxManager.getAvailableVersions()) +ipcMain.handle('nginx:install', (_, version: string) => nginxManager.install(version)) +ipcMain.handle('nginx:uninstall', (_, version: string) => nginxManager.uninstall(version)) +ipcMain.handle('nginx:start', () => nginxManager.start()) +ipcMain.handle('nginx:stop', () => nginxManager.stop()) +ipcMain.handle('nginx:restart', () => nginxManager.restart()) +ipcMain.handle('nginx:reload', () => nginxManager.reload()) +ipcMain.handle('nginx:getStatus', () => nginxManager.getStatus()) +ipcMain.handle('nginx:getConfig', () => nginxManager.getConfig()) +ipcMain.handle('nginx:saveConfig', (_, config: string) => nginxManager.saveConfig(config)) +ipcMain.handle('nginx:getSites', () => nginxManager.getSites()) +ipcMain.handle('nginx:addSite', (_, site: any) => nginxManager.addSite(site)) +ipcMain.handle('nginx:removeSite', (_, name: string) => nginxManager.removeSite(name)) +ipcMain.handle('nginx:updateSite', (_, originalName: string, site: any) => nginxManager.updateSite(originalName, site)) +ipcMain.handle('nginx:enableSite', (_, name: string) => nginxManager.enableSite(name)) +ipcMain.handle('nginx:disableSite', (_, name: string) => nginxManager.disableSite(name)) +ipcMain.handle('nginx:generateLaravelConfig', (_, site: any) => nginxManager.generateLaravelConfig(site)) +ipcMain.handle('nginx:requestSSL', (_, domain: string, email: string) => nginxManager.requestSSLCertificate(domain, email)) + +// ==================== Redis 管理 ==================== +ipcMain.handle('redis:getVersions', () => redisManager.getInstalledVersions()) +ipcMain.handle('redis:getAvailableVersions', () => redisManager.getAvailableVersions()) +ipcMain.handle('redis:install', (_, version: string) => redisManager.install(version)) +ipcMain.handle('redis:uninstall', (_, version: string) => redisManager.uninstall(version)) +ipcMain.handle('redis:start', () => redisManager.start()) +ipcMain.handle('redis:stop', () => redisManager.stop()) +ipcMain.handle('redis:restart', () => redisManager.restart()) +ipcMain.handle('redis:getStatus', () => redisManager.getStatus()) +ipcMain.handle('redis:getConfig', () => redisManager.getConfig()) +ipcMain.handle('redis:saveConfig', (_, config: string) => redisManager.saveConfig(config)) + +// ==================== Node.js 管理 ==================== +ipcMain.handle('node:getVersions', () => nodeManager.getInstalledVersions()) +ipcMain.handle('node:getAvailableVersions', () => nodeManager.getAvailableVersions()) +ipcMain.handle('node:install', (_, version: string, downloadUrl: string) => nodeManager.install(version, downloadUrl)) +ipcMain.handle('node:uninstall', (_, version: string) => nodeManager.uninstall(version)) +ipcMain.handle('node:setActive', (_, version: string) => nodeManager.setActive(version)) +ipcMain.handle('node:getInfo', (_, version: string) => nodeManager.getNodeInfo(version)) + +// ==================== 服务管理 ==================== +ipcMain.handle('service:getAll', () => serviceManager.getAllServices()) +ipcMain.handle('service:setAutoStart', (_, service: string, enabled: boolean) => serviceManager.setAutoStart(service, enabled)) +ipcMain.handle('service:getAutoStart', (_, service: string) => serviceManager.getAutoStart(service)) +ipcMain.handle('service:startAll', () => serviceManager.startAll()) +ipcMain.handle('service:stopAll', () => serviceManager.stopAll()) + +// ==================== Hosts 管理 ==================== +ipcMain.handle('hosts:get', () => hostsManager.getHosts()) +ipcMain.handle('hosts:add', (_, domain: string, ip: string) => hostsManager.addHost(domain, ip)) +ipcMain.handle('hosts:remove', (_, domain: string) => hostsManager.removeHost(domain)) + +// ==================== 配置管理 ==================== +ipcMain.handle('config:get', (_, key: string) => configStore.get(key)) +ipcMain.handle('config:set', (_, key: string, value: any) => configStore.set(key, value)) +ipcMain.handle('config:getBasePath', () => configStore.getBasePath()) +ipcMain.handle('config:setBasePath', (_, path: string) => configStore.setBasePath(path)) + diff --git a/electron/preload.ts b/electron/preload.ts new file mode 100644 index 0000000..90e2ca4 --- /dev/null +++ b/electron/preload.ts @@ -0,0 +1,150 @@ +import { contextBridge, ipcRenderer } from 'electron' + +// 暴露安全的 API 到渲染进程 +contextBridge.exposeInMainWorld('electronAPI', { + // 窗口控制 + minimize: () => ipcRenderer.invoke('window:minimize'), + maximize: () => ipcRenderer.invoke('window:maximize'), + close: () => ipcRenderer.invoke('window:close'), + + // Shell + openExternal: (url: string) => ipcRenderer.invoke('shell:openExternal', url), + openPath: (path: string) => ipcRenderer.invoke('shell:openPath', path), + + // Dialog + selectDirectory: () => ipcRenderer.invoke('dialog:selectDirectory'), + + // 下载进度监听 + onDownloadProgress: (callback: (data: { type: string; progress: number; downloaded: number; total: number }) => void) => { + ipcRenderer.on('download-progress', (_, data) => callback(data)) + }, + removeDownloadProgressListener: () => { + ipcRenderer.removeAllListeners('download-progress') + }, + + // PHP 管理 + php: { + getVersions: () => ipcRenderer.invoke('php:getVersions'), + getAvailableVersions: () => ipcRenderer.invoke('php:getAvailableVersions'), + install: (version: string) => ipcRenderer.invoke('php:install', version), + uninstall: (version: string) => ipcRenderer.invoke('php:uninstall', version), + setActive: (version: string) => ipcRenderer.invoke('php:setActive', version), + getExtensions: (version: string) => ipcRenderer.invoke('php:getExtensions', version), + openExtensionDir: (version: string) => ipcRenderer.invoke('php:openExtensionDir', version), + getAvailableExtensions: (version: string, searchKeyword?: string) => ipcRenderer.invoke('php:getAvailableExtensions', version, searchKeyword), + enableExtension: (version: string, ext: string) => ipcRenderer.invoke('php:enableExtension', version, ext), + disableExtension: (version: string, ext: string) => ipcRenderer.invoke('php:disableExtension', version, ext), + installExtension: (version: string, ext: string, downloadUrl?: string, packageName?: string) => ipcRenderer.invoke('php:installExtension', version, ext, downloadUrl, packageName), + getConfig: (version: string) => ipcRenderer.invoke('php:getConfig', version), + saveConfig: (version: string, config: string) => ipcRenderer.invoke('php:saveConfig', version, config) + }, + + // MySQL 管理 + mysql: { + getVersions: () => ipcRenderer.invoke('mysql:getVersions'), + getAvailableVersions: () => ipcRenderer.invoke('mysql:getAvailableVersions'), + install: (version: string) => ipcRenderer.invoke('mysql:install', version), + uninstall: (version: string) => ipcRenderer.invoke('mysql:uninstall', version), + start: (version: string) => ipcRenderer.invoke('mysql:start', version), + stop: (version: string) => ipcRenderer.invoke('mysql:stop', version), + restart: (version: string) => ipcRenderer.invoke('mysql:restart', version), + getStatus: (version: string) => ipcRenderer.invoke('mysql:getStatus', version), + changePassword: (version: string, newPassword: string, currentPassword?: string) => ipcRenderer.invoke('mysql:changePassword', version, newPassword, currentPassword), + getConfig: (version: string) => ipcRenderer.invoke('mysql:getConfig', version), + saveConfig: (version: string, config: string) => ipcRenderer.invoke('mysql:saveConfig', version, config) + }, + + // Nginx 管理 + nginx: { + getVersions: () => ipcRenderer.invoke('nginx:getVersions'), + getAvailableVersions: () => ipcRenderer.invoke('nginx:getAvailableVersions'), + install: (version: string) => ipcRenderer.invoke('nginx:install', version), + uninstall: (version: string) => ipcRenderer.invoke('nginx:uninstall', version), + start: () => ipcRenderer.invoke('nginx:start'), + stop: () => ipcRenderer.invoke('nginx:stop'), + restart: () => ipcRenderer.invoke('nginx:restart'), + reload: () => ipcRenderer.invoke('nginx:reload'), + getStatus: () => ipcRenderer.invoke('nginx:getStatus'), + getConfig: () => ipcRenderer.invoke('nginx:getConfig'), + saveConfig: (config: string) => ipcRenderer.invoke('nginx:saveConfig', config), + getSites: () => ipcRenderer.invoke('nginx:getSites'), + addSite: (site: any) => ipcRenderer.invoke('nginx:addSite', site), + removeSite: (name: string) => ipcRenderer.invoke('nginx:removeSite', name), + updateSite: (originalName: string, site: any) => ipcRenderer.invoke('nginx:updateSite', originalName, site), + enableSite: (name: string) => ipcRenderer.invoke('nginx:enableSite', name), + disableSite: (name: string) => ipcRenderer.invoke('nginx:disableSite', name), + generateLaravelConfig: (site: any) => ipcRenderer.invoke('nginx:generateLaravelConfig', site), + requestSSL: (domain: string, email: string) => ipcRenderer.invoke('nginx:requestSSL', domain, email) + }, + + // Redis 管理 + redis: { + getVersions: () => ipcRenderer.invoke('redis:getVersions'), + getAvailableVersions: () => ipcRenderer.invoke('redis:getAvailableVersions'), + install: (version: string) => ipcRenderer.invoke('redis:install', version), + uninstall: (version: string) => ipcRenderer.invoke('redis:uninstall', version), + start: () => ipcRenderer.invoke('redis:start'), + stop: () => ipcRenderer.invoke('redis:stop'), + restart: () => ipcRenderer.invoke('redis:restart'), + getStatus: () => ipcRenderer.invoke('redis:getStatus'), + getConfig: () => ipcRenderer.invoke('redis:getConfig'), + saveConfig: (config: string) => ipcRenderer.invoke('redis:saveConfig', config) + }, + + // Node.js 管理 + node: { + getVersions: () => ipcRenderer.invoke('node:getVersions'), + getAvailableVersions: () => ipcRenderer.invoke('node:getAvailableVersions'), + install: (version: string, downloadUrl: string) => ipcRenderer.invoke('node:install', version, downloadUrl), + uninstall: (version: string) => ipcRenderer.invoke('node:uninstall', version), + setActive: (version: string) => ipcRenderer.invoke('node:setActive', version), + getInfo: (version: string) => ipcRenderer.invoke('node:getInfo', version) + }, + + // 服务管理 + service: { + getAll: () => ipcRenderer.invoke('service:getAll'), + setAutoStart: (service: string, enabled: boolean) => ipcRenderer.invoke('service:setAutoStart', service, enabled), + getAutoStart: (service: string) => ipcRenderer.invoke('service:getAutoStart', service), + startAll: () => ipcRenderer.invoke('service:startAll'), + stopAll: () => ipcRenderer.invoke('service:stopAll') + }, + + // Hosts 管理 + hosts: { + get: () => ipcRenderer.invoke('hosts:get'), + add: (domain: string, ip: string) => ipcRenderer.invoke('hosts:add', domain, ip), + remove: (domain: string) => ipcRenderer.invoke('hosts:remove', domain) + }, + + // 配置管理 + config: { + get: (key: string) => ipcRenderer.invoke('config:get', key), + set: (key: string, value: any) => ipcRenderer.invoke('config:set', key, value), + getBasePath: () => ipcRenderer.invoke('config:getBasePath'), + setBasePath: (path: string) => ipcRenderer.invoke('config:setBasePath', path) + } +}) + +// 声明 Window 接口扩展 +declare global { + interface Window { + electronAPI: typeof api + } +} + +const api = { + minimize: () => ipcRenderer.invoke('window:minimize'), + maximize: () => ipcRenderer.invoke('window:maximize'), + close: () => ipcRenderer.invoke('window:close'), + openExternal: (url: string) => ipcRenderer.invoke('shell:openExternal', url), + openPath: (path: string) => ipcRenderer.invoke('shell:openPath', path), + php: {} as any, + mysql: {} as any, + nginx: {} as any, + redis: {} as any, + service: {} as any, + hosts: {} as any, + config: {} as any +} + diff --git a/electron/services/ConfigStore.ts b/electron/services/ConfigStore.ts new file mode 100644 index 0000000..acd90a8 --- /dev/null +++ b/electron/services/ConfigStore.ts @@ -0,0 +1,212 @@ +import Store from 'electron-store' +import { join } from 'path' +import { existsSync, mkdirSync } from 'fs' +import { app } from 'electron' + +interface ConfigSchema { + basePath: string + phpVersions: string[] + mysqlVersions: string[] + nginxVersions: string[] + redisVersions: string[] + activePhpVersion: string + autoStart: { + nginx: boolean + mysql: boolean + redis: boolean + } + sites: SiteConfig[] +} + +export interface SiteConfig { + name: string + domain: string + rootPath: string + phpVersion: string + isLaravel: boolean + ssl: boolean + enabled: boolean +} + +export class ConfigStore { + private store: Store + private basePath: string + + constructor() { + this.store = new Store({ + defaults: { + basePath: join(app.getPath('userData'), 'PHPer'), + phpVersions: [], + mysqlVersions: [], + nginxVersions: [], + redisVersions: [], + activePhpVersion: '', + autoStart: { + nginx: false, + mysql: false, + redis: false + }, + sites: [] + } + }) + + this.basePath = this.store.get('basePath') + this.ensureDirectories() + } + + private ensureDirectories(): void { + const dirs = [ + this.basePath, + join(this.basePath, 'php'), + join(this.basePath, 'mysql'), + join(this.basePath, 'nginx'), + join(this.basePath, 'nginx', 'sites-available'), + join(this.basePath, 'nginx', 'sites-enabled'), + join(this.basePath, 'nginx', 'ssl'), + join(this.basePath, 'redis'), + join(this.basePath, 'logs'), + join(this.basePath, 'temp'), + join(this.basePath, 'www') + ] + + for (const dir of dirs) { + if (!existsSync(dir)) { + mkdirSync(dir, { recursive: true }) + } + } + } + + get(key: K): ConfigSchema[K] { + return this.store.get(key) + } + + set(key: K, value: ConfigSchema[K]): void { + this.store.set(key, value) + } + + getBasePath(): string { + return this.basePath + } + + setBasePath(path: string): void { + this.basePath = path + this.store.set('basePath', path) + this.ensureDirectories() + } + + getPhpPath(version: string): string { + return join(this.basePath, 'php', `php-${version}`) + } + + getMysqlPath(version: string): string { + return join(this.basePath, 'mysql', `mysql-${version}`) + } + + getNginxPath(): string { + return join(this.basePath, 'nginx') + } + + getRedisPath(): string { + return join(this.basePath, 'redis') + } + + getNodePath(): string { + return join(this.basePath, 'nodejs') + } + + getLogsPath(): string { + return join(this.basePath, 'logs') + } + + getTempPath(): string { + return join(this.basePath, 'temp') + } + + getWwwPath(): string { + return join(this.basePath, 'www') + } + + getSitesAvailablePath(): string { + return join(this.basePath, 'nginx', 'sites-available') + } + + getSitesEnabledPath(): string { + return join(this.basePath, 'nginx', 'sites-enabled') + } + + getSSLPath(): string { + return join(this.basePath, 'nginx', 'ssl') + } + + addPhpVersion(version: string): void { + const versions = this.store.get('phpVersions') + if (!versions.includes(version)) { + versions.push(version) + this.store.set('phpVersions', versions) + } + } + + removePhpVersion(version: string): void { + const versions = this.store.get('phpVersions') + const index = versions.indexOf(version) + if (index > -1) { + versions.splice(index, 1) + this.store.set('phpVersions', versions) + } + } + + addMysqlVersion(version: string): void { + const versions = this.store.get('mysqlVersions') + if (!versions.includes(version)) { + versions.push(version) + this.store.set('mysqlVersions', versions) + } + } + + removeMysqlVersion(version: string): void { + const versions = this.store.get('mysqlVersions') + const index = versions.indexOf(version) + if (index > -1) { + versions.splice(index, 1) + this.store.set('mysqlVersions', versions) + } + } + + addSite(site: SiteConfig): void { + const sites = this.store.get('sites') + sites.push(site) + this.store.set('sites', sites) + } + + removeSite(name: string): void { + const sites = this.store.get('sites') + const index = sites.findIndex(s => s.name === name) + if (index > -1) { + sites.splice(index, 1) + this.store.set('sites', sites) + } + } + + updateSite(originalName: string, site: any): void { + const sites = this.store.get('sites') + const index = sites.findIndex(s => s.name === originalName) + if (index > -1) { + sites[index] = site + this.store.set('sites', sites) + } + } + + updateSite(name: string, site: Partial): void { + const sites = this.store.get('sites') + const index = sites.findIndex(s => s.name === name) + if (index > -1) { + sites[index] = { ...sites[index], ...site } + this.store.set('sites', sites) + } + } + + getSites(): SiteConfig[] { + return this.store.get('sites') + } +} + diff --git a/electron/services/HostsManager.ts b/electron/services/HostsManager.ts new file mode 100644 index 0000000..52f812a --- /dev/null +++ b/electron/services/HostsManager.ts @@ -0,0 +1,224 @@ +import { exec } from 'child_process' +import { promisify } from 'util' +import { existsSync, readFileSync, writeFileSync, unlinkSync } from 'fs' +import { join } from 'path' +import sudo from 'sudo-prompt' + +const execAsync = promisify(exec) + +const sudoExec = (command: string, name: string): Promise<{ stdout: string; stderr: string }> => { + return new Promise((resolve, reject) => { + sudo.exec(command, { name }, (error, stdout, stderr) => { + if (error) { + reject(error) + } else { + resolve({ stdout: stdout?.toString() || '', stderr: stderr?.toString() || '' }) + } + }) + }) +} + +interface HostEntry { + ip: string + domain: string + comment?: string +} + +export class HostsManager { + private hostsPath: string + + constructor() { + // Windows hosts 文件路径 + this.hostsPath = join(process.env.SystemRoot || 'C:\\Windows', 'System32', 'drivers', 'etc', 'hosts') + } + + /** + * 获取 hosts 文件内容 + */ + async getHosts(): Promise { + try { + if (!existsSync(this.hostsPath)) { + return [] + } + + const content = readFileSync(this.hostsPath, 'utf-8') + const entries: HostEntry[] = [] + + const lines = content.split('\n') + for (const line of lines) { + const trimmed = line.trim() + + // 跳过空行和注释 + if (!trimmed || trimmed.startsWith('#')) { + continue + } + + // 解析行 + const match = trimmed.match(/^(\S+)\s+(\S+)(?:\s+#\s*(.*))?$/) + if (match) { + entries.push({ + ip: match[1], + domain: match[2], + comment: match[3] + }) + } + } + + return entries + } catch (error) { + console.error('读取 hosts 文件失败:', error) + return [] + } + } + + /** + * 添加 hosts 条目 + */ + async addHost(domain: string, ip: string = '127.0.0.1'): Promise<{ success: boolean; message: string }> { + try { + // 读取现有内容 + let content = '' + if (existsSync(this.hostsPath)) { + content = readFileSync(this.hostsPath, 'utf-8') + } + + // 检查是否已存在 + const regex = new RegExp(`^\\s*\\S+\\s+${this.escapeRegex(domain)}\\s*$`, 'gm') + if (regex.test(content)) { + // 更新现有条目 + content = content.replace(regex, `${ip}\t${domain}`) + } else { + // 添加新条目 + const newEntry = `${ip}\t${domain}\t# Added by PHPer Dev Manager` + content = content.trimEnd() + '\n' + newEntry + '\n' + } + + // 写入文件(需要管理员权限) + await this.writeHostsFile(content) + + return { success: true, message: `已添加 ${domain} -> ${ip}` } + } catch (error: any) { + return { success: false, message: `添加失败: ${error.message}` } + } + } + + /** + * 删除 hosts 条目 + */ + async removeHost(domain: string): Promise<{ success: boolean; message: string }> { + try { + if (!existsSync(this.hostsPath)) { + return { success: false, message: 'hosts 文件不存在' } + } + + let content = readFileSync(this.hostsPath, 'utf-8') + + // 删除匹配的行 + const lines = content.split('\n') + const newLines = lines.filter(line => { + const trimmed = line.trim() + if (!trimmed || trimmed.startsWith('#')) { + return true + } + const match = trimmed.match(/^\S+\s+(\S+)/) + return !match || match[1] !== domain + }) + + content = newLines.join('\n') + + // 写入文件 + await this.writeHostsFile(content) + + return { success: true, message: `已删除 ${domain}` } + } catch (error: any) { + return { success: false, message: `删除失败: ${error.message}` } + } + } + + /** + * 批量添加 hosts 条目 + */ + async addHosts(entries: HostEntry[]): Promise<{ success: boolean; message: string }> { + try { + let content = '' + if (existsSync(this.hostsPath)) { + content = readFileSync(this.hostsPath, 'utf-8') + } + + for (const entry of entries) { + const regex = new RegExp(`^\\s*\\S+\\s+${this.escapeRegex(entry.domain)}\\s*(?:#.*)?$`, 'gm') + if (regex.test(content)) { + // 更新现有条目 + content = content.replace(regex, `${entry.ip}\t${entry.domain}${entry.comment ? `\t# ${entry.comment}` : ''}`) + } else { + // 添加新条目 + const newEntry = `${entry.ip}\t${entry.domain}${entry.comment ? `\t# ${entry.comment}` : ''}` + content = content.trimEnd() + '\n' + newEntry + } + } + + content = content.trimEnd() + '\n' + + await this.writeHostsFile(content) + + return { success: true, message: `已添加 ${entries.length} 个条目` } + } catch (error: any) { + return { success: false, message: `添加失败: ${error.message}` } + } + } + + /** + * 刷新 DNS 缓存 + */ + async flushDns(): Promise<{ success: boolean; message: string }> { + try { + await execAsync('ipconfig /flushdns') + return { success: true, message: 'DNS 缓存已刷新' } + } catch (error: any) { + return { success: false, message: `刷新失败: ${error.message}` } + } + } + + // ==================== 私有方法 ==================== + + private async writeHostsFile(content: string): Promise { + // 直接写入(需要管理员权限运行应用) + try { + writeFileSync(this.hostsPath, content, 'utf-8') + } catch (error: any) { + if (error.code === 'EPERM' || error.code === 'EACCES') { + // 尝试使用 sudo-prompt 提权写入 + const tempPath = join(process.env.TEMP || 'C:\\Temp', 'hosts_phper.tmp') + writeFileSync(tempPath, content, 'utf-8') + + // 使用 copy 命令复制临时文件到 hosts + const command = `copy /Y "${tempPath}" "${this.hostsPath}"` + + try { + await sudoExec(command, 'PHPer Dev Manager') + // 清理临时文件 + try { + unlinkSync(tempPath) + } catch (e) { + // 忽略清理错误 + } + } catch (sudoError: any) { + // 清理临时文件 + try { + unlinkSync(tempPath) + } catch (e) { + // 忽略清理错误 + } + throw new Error(`需要管理员权限修改 hosts 文件: ${sudoError.message}`) + } + } else { + throw error + } + } + } + + private escapeRegex(str: string): string { + return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') + } +} + diff --git a/electron/services/MysqlManager.ts b/electron/services/MysqlManager.ts new file mode 100644 index 0000000..255b68c --- /dev/null +++ b/electron/services/MysqlManager.ts @@ -0,0 +1,1096 @@ +import { ConfigStore } from './ConfigStore' +import { exec, spawn } from 'child_process' +import { promisify } from 'util' +import { existsSync, readFileSync, writeFileSync, readdirSync, unlinkSync, rmdirSync, mkdirSync, renameSync } from 'fs' +import { join, dirname } from 'path' +import https from 'https' +import http from 'http' +import { createWriteStream } from 'fs' +import { sendDownloadProgress } from '../main' + +const execAsync = promisify(exec) + +interface MysqlVersion { + version: string + path: string + isRunning: boolean +} + +interface AvailableMysqlVersion { + version: string + downloadUrl: string + mirrors?: string[] // 备用下载链接 +} + +// 阿里云镜像基础地址 +const ALIYUN_MIRROR_BASE = 'https://mirrors.aliyun.com/mysql/' +// 搜狐镜像作为备用 +const SOHU_MIRROR_BASE = 'https://mirrors.sohu.com/mysql/' + +export class MysqlManager { + private configStore: ConfigStore + private mysqlProcess: any = null + private cachedVersions: AvailableMysqlVersion[] | null = null + private cacheTime: number = 0 + private readonly CACHE_DURATION = 5 * 60 * 1000 // 缓存5分钟 + + constructor(configStore: ConfigStore) { + this.configStore = configStore + } + + /** + * 获取已安装的 MySQL 版本列表 + */ + async getInstalledVersions(): Promise { + const versions: MysqlVersion[] = [] + const mysqlDir = join(this.configStore.getBasePath(), 'mysql') + + if (!existsSync(mysqlDir)) { + return versions + } + + const dirs = readdirSync(mysqlDir, { withFileTypes: true }) + for (const dir of dirs) { + if (dir.isDirectory() && dir.name.startsWith('mysql-')) { + const version = dir.name.replace('mysql-', '') + const mysqlPath = join(mysqlDir, dir.name) + + // 验证 MySQL 是否真的存在 + if (existsSync(join(mysqlPath, 'bin', 'mysqld.exe'))) { + const isRunning = await this.checkIsRunning(version) + versions.push({ + version, + path: mysqlPath, + isRunning + }) + } + } + } + + return versions.sort((a, b) => b.version.localeCompare(a.version)) + } + + /** + * 获取可用的 MySQL 版本列表(从阿里云镜像动态获取) + * 镜像地址: https://mirrors.aliyun.com/mysql/ + */ + async getAvailableVersions(): Promise { + // 检查缓存 + if (this.cachedVersions && Date.now() - this.cacheTime < this.CACHE_DURATION) { + console.log('使用缓存的 MySQL 版本列表') + const installed = await this.getInstalledVersions() + const installedVersions = installed.map(v => v.version) + return this.cachedVersions.filter(v => !installedVersions.includes(v.version)) + } + + let versions: AvailableMysqlVersion[] = [] + + try { + console.log('从阿里云镜像获取 MySQL 版本列表...') + + // 1. 获取主目录页面,解析出 MySQL-X.X 目录 + const mainPageHtml = await this.fetchHtml(ALIYUN_MIRROR_BASE) + + // 匹配 MySQL-5.7, MySQL-8.0 等目录(只获取5.7和8.x系列) + const dirRegex = //g + const mysqlDirs: string[] = [] + let match + + while ((match = dirRegex.exec(mainPageHtml)) !== null) { + mysqlDirs.push(match[1]) + } + + console.log(`找到 ${mysqlDirs.length} 个 MySQL 版本目录: ${mysqlDirs.join(', ')}`) + + // 2. 遍历每个目录,获取 Windows 版本的 zip 文件 + for (const dir of mysqlDirs) { + try { + const dirUrl = `${ALIYUN_MIRROR_BASE}${dir}` + const dirHtml = await this.fetchHtml(dirUrl) + + // 匹配 mysql-X.X.XX-winx64.zip 格式的文件 + const fileRegex = //g + + while ((match = fileRegex.exec(dirHtml)) !== null) { + const fileName = match[1] + const version = match[2] + const downloadUrl = `${ALIYUN_MIRROR_BASE}${dir}${fileName}` + + // 添加搜狐镜像作为备用 + const majorMinor = version.split('.').slice(0, 2).join('.') + const sohuUrl = `${SOHU_MIRROR_BASE}MySQL-${majorMinor}/${fileName}` + + versions.push({ + version, + downloadUrl, + mirrors: [sohuUrl] + }) + } + } catch (dirError) { + console.error(`获取目录 ${dir} 失败:`, dirError) + } + } + + // 按版本号降序排序 + versions.sort((a, b) => { + const aParts = a.version.split('.').map(Number) + const bParts = b.version.split('.').map(Number) + for (let i = 0; i < 3; i++) { + if (bParts[i] !== aParts[i]) { + return bParts[i] - aParts[i] + } + } + return 0 + }) + + // 去重(保留最新版本) + const seen = new Set() + versions = versions.filter(v => { + if (seen.has(v.version)) return false + seen.add(v.version) + return true + }) + + console.log(`从阿里云镜像获取到 ${versions.length} 个 MySQL 版本`) + + // 更新缓存 + this.cachedVersions = versions + this.cacheTime = Date.now() + + } catch (error) { + console.error('从阿里云镜像获取版本列表失败,使用备用列表:', error) + + // 备用列表 + versions = [ + { + version: '8.0.28', + downloadUrl: 'https://mirrors.sohu.com/mysql/MySQL-8.0/mysql-8.0.28-winx64.zip', + mirrors: [] + }, + { + version: '8.0.27', + downloadUrl: 'https://mirrors.sohu.com/mysql/MySQL-8.0/mysql-8.0.27-winx64.zip', + mirrors: [] + }, + { + version: '5.7.38', + downloadUrl: 'https://mirrors.sohu.com/mysql/MySQL-5.7/mysql-5.7.38-winx64.zip', + mirrors: [] + }, + { + version: '5.7.37', + downloadUrl: 'https://mirrors.sohu.com/mysql/MySQL-5.7/mysql-5.7.37-winx64.zip', + mirrors: [] + } + ] + } + + // 过滤掉已安装的版本 + const installed = await this.getInstalledVersions() + const installedVersions = installed.map(v => v.version) + + return versions.filter(v => !installedVersions.includes(v.version)) + } + + /** + * 获取网页 HTML 内容 + */ + private async fetchHtml(url: string): Promise { + return new Promise((resolve, reject) => { + const protocol = url.startsWith('https') ? https : http + + const request = protocol.get(url, { + headers: { + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36' + } + }, (response) => { + // 处理重定向 + if (response.statusCode === 301 || response.statusCode === 302 || response.statusCode === 307) { + const redirectUrl = response.headers.location + if (redirectUrl) { + this.fetchHtml(redirectUrl).then(resolve).catch(reject) + return + } + } + + if (response.statusCode !== 200) { + reject(new Error(`HTTP ${response.statusCode}`)) + return + } + + let html = '' + response.on('data', (chunk) => (html += chunk)) + response.on('end', () => resolve(html)) + response.on('error', reject) + }) + + request.on('error', reject) + request.setTimeout(15000, () => { + request.destroy() + reject(new Error('请求超时')) + }) + }) + } + + /** + * 安装 MySQL 版本 + */ + async install(version: string): Promise<{ success: boolean; message: string }> { + try { + const available = await this.getAvailableVersions() + const versionInfo = available.find(v => v.version === version) + + if (!versionInfo) { + return { success: false, message: `未找到 MySQL ${version} 版本` } + } + + const mysqlPath = this.configStore.getMysqlPath(version) + const tempPath = this.configStore.getTempPath() + const mysqlBaseDir = join(this.configStore.getBasePath(), 'mysql') + const zipPath = join(tempPath, `mysql-${version}.zip`) + const dataPath = join(mysqlPath, 'data') + + // 确保目录存在 + if (!existsSync(tempPath)) { + mkdirSync(tempPath, { recursive: true }) + } + if (!existsSync(mysqlBaseDir)) { + mkdirSync(mysqlBaseDir, { recursive: true }) + } + + // 尝试下载 MySQL(自动尝试备用链接) + const allUrls = [versionInfo.downloadUrl, ...(versionInfo.mirrors || [])] + let downloadSuccess = false + let lastError = '' + + for (const url of allUrls) { + try { + console.log(`尝试下载 MySQL ${version} 从 ${url}`) + await this.downloadFile(url, zipPath) + downloadSuccess = true + console.log('下载完成!') + break + } catch (error: any) { + lastError = error.message + console.log(`下载失败: ${error.message},尝试下一个镜像...`) + // 清理失败的下载 + if (existsSync(zipPath)) { + try { unlinkSync(zipPath) } catch (e) {} + } + } + } + + if (!downloadSuccess) { + return { success: false, message: `所有下载源均失败: ${lastError}` } + } + + console.log('开始解压...') + + // 解压到 mysql 目录 + await this.unzip(zipPath, mysqlBaseDir) + + console.log('解压完成,处理目录...') + + // 重命名目录(MySQL 解压后目录名带完整版本号) + const extractedDir = join(mysqlBaseDir, `mysql-${version}-winx64`) + if (existsSync(extractedDir) && !existsSync(mysqlPath)) { + try { + renameSync(extractedDir, mysqlPath) + console.log(`目录重命名: ${extractedDir} -> ${mysqlPath}`) + } catch (renameError: any) { + console.error('重命名失败,尝试复制:', renameError.message) + // 如果重命名失败,可能是跨盘符,尝试使用 fs-extra 或手动复制 + } + } + + // 删除临时文件 + if (existsSync(zipPath)) { + try { + unlinkSync(zipPath) + } catch (e) { + console.log('清理临时文件失败,忽略') + } + } + + // 创建 data 目录 + if (!existsSync(dataPath)) { + mkdirSync(dataPath, { recursive: true }) + } + + // 创建 my.ini 配置文件 + await this.createDefaultConfig(mysqlPath, version) + + // 初始化 MySQL + console.log('初始化 MySQL 数据库...') + await this.initializeDatabase(mysqlPath) + + // 添加到配置 + this.configStore.addMysqlVersion(version) + + // 启动 MySQL 并设置默认密码 + console.log('启动 MySQL 并设置默认密码...') + const startResult = await this.start(version) + if (startResult.success) { + // 等待 MySQL 完全就绪 + await new Promise(resolve => setTimeout(resolve, 3000)) + + // 设置默认密码 123456 + try { + await this.setInitialPassword(mysqlPath, '123456') + console.log('默认密码设置成功: 123456') + } catch (pwdError: any) { + console.log('设置默认密码失败,root密码为空:', pwdError.message) + } + } + + console.log(`MySQL ${version} 安装完成`) + return { + success: true, + message: `MySQL ${version} 安装成功!\n\n连接信息:\n• 主机:localhost\n• 端口:3306\n• 用户:root\n• 密码:123456` + } + } catch (error: any) { + console.error('MySQL 安装失败:', error) + return { success: false, message: `安装失败: ${error.message}` } + } + } + + /** + * 卸载 MySQL 版本 + */ + async uninstall(version: string): Promise<{ success: boolean; message: string }> { + try { + // 先停止服务 + await this.stop(version) + + const mysqlPath = this.configStore.getMysqlPath(version) + + if (!existsSync(mysqlPath)) { + return { success: false, message: `MySQL ${version} 未安装` } + } + + // 移除 Windows 服务 + try { + await execAsync(`"${join(mysqlPath, 'bin', 'mysqld.exe')}" --remove MySQL${version.replace(/\./g, '')}`) + } catch (e) { + // 忽略服务移除错误 + } + + // 递归删除目录 + this.removeDirectory(mysqlPath) + + // 从配置中移除 + this.configStore.removeMysqlVersion(version) + + return { success: true, message: `MySQL ${version} 已卸载` } + } catch (error: any) { + return { success: false, message: `卸载失败: ${error.message}` } + } + } + + /** + * 启动 MySQL 服务 + */ + async start(version: string): Promise<{ success: boolean; message: string }> { + try { + const mysqlPath = this.configStore.getMysqlPath(version) + const mysqldPath = join(mysqlPath, 'bin', 'mysqld.exe') + const configPath = join(mysqlPath, 'my.ini') + const logsPath = this.configStore.getLogsPath() + const tempPath = this.configStore.getTempPath() + + if (!existsSync(mysqldPath)) { + return { success: false, message: `MySQL ${version} 未安装` } + } + + // 确保临时目录存在 + if (!existsSync(tempPath)) { + mkdirSync(tempPath, { recursive: true }) + } + + // 检查是否已在运行 + const isRunning = await this.checkIsRunning(version) + if (isRunning) { + return { success: true, message: `MySQL ${version} 已经在运行` } + } + + // 检查并修复配置文件(MySQL 8.0 兼容性) + await this.fixConfigIfNeeded(mysqlPath, version) + + // 检查数据目录是否正确初始化 + const dataPath = join(mysqlPath, 'data') + const needsInit = await this.checkNeedsInitialize(dataPath) + if (needsInit) { + console.log('检测到数据库未正确初始化,正在重新初始化...') + // 清空数据目录 + if (existsSync(dataPath)) { + this.removeDirectory(dataPath) + } + mkdirSync(dataPath, { recursive: true }) + // 重新初始化 + await this.initializeDatabase(mysqlPath) + } + + console.log(`启动 MySQL ${version}...`) + console.log(`mysqld 路径: ${mysqldPath}`) + console.log(`配置文件: ${configPath}`) + + // 创建 VBScript 来隐藏窗口启动 MySQL + const vbsPath = join(tempPath, 'start_mysql.vbs') + const vbsContent = ` +Set WshShell = CreateObject("WScript.Shell") +WshShell.Run """${mysqldPath.replace(/\\/g, '\\\\')}""" & " --defaults-file=""${configPath.replace(/\\/g, '\\\\')}"" ", 0, False +` + writeFileSync(vbsPath, vbsContent.trim()) + + // 使用 wscript 执行 VBS,完全无窗口 + const child = spawn('wscript.exe', [vbsPath], { + detached: true, + stdio: 'ignore', + windowsHide: true + }) + + child.unref() + + // 删除临时 VBS 文件(延迟删除) + setTimeout(() => { + try { + if (existsSync(vbsPath)) unlinkSync(vbsPath) + } catch (e) {} + }, 5000) + + // 等待启动(多次检测,增加等待时间给 PowerShell) + await new Promise(resolve => setTimeout(resolve, 2000)) // 先等待 PowerShell 启动进程 + + let running = false + for (let i = 0; i < 15; i++) { + await new Promise(resolve => setTimeout(resolve, 1000)) + running = await this.checkIsRunning(version) + if (running) { + console.log(`MySQL ${version} 启动成功 (${i + 1}秒)`) + break + } + console.log(`等待 MySQL 启动... (${i + 1}/15)`) + } + + if (running) { + return { success: true, message: `MySQL ${version} 启动成功,端口 3306` } + } else { + // 再检查一次进程是否存在 + try { + const { stdout } = await execAsync('tasklist /FI "IMAGENAME eq mysqld.exe" /NH', { windowsHide: true }) + if (stdout.includes('mysqld.exe')) { + console.log('检测到 mysqld 进程存在,可能端口检测有问题') + return { success: true, message: `MySQL ${version} 已启动(进程运行中)` } + } + } catch (e) {} + + // 读取错误日志 + const errorLogPath = join(logsPath, 'mysql-error.log') + let errorInfo = '' + if (existsSync(errorLogPath)) { + try { + const logContent = readFileSync(errorLogPath, 'utf-8') + const lines = logContent.split('\n').slice(-10) // 最后10行 + errorInfo = lines.join('\n') + } catch (e) {} + } + return { + success: false, + message: `MySQL ${version} 启动失败。${errorInfo ? '\n错误日志:\n' + errorInfo : '请检查日志文件'}` + } + } + } catch (error: any) { + console.error(`启动 MySQL 失败:`, error) + return { success: false, message: `启动失败: ${error.message}` } + } + } + + /** + * 停止 MySQL 服务 + */ + async stop(version: string): Promise<{ success: boolean; message: string }> { + try { + const mysqlPath = this.configStore.getMysqlPath(version) + const mysqladminPath = join(mysqlPath, 'bin', 'mysqladmin.exe') + + console.log(`停止 MySQL ${version}...`) + + // 尝试优雅关闭 + try { + await execAsync(`"${mysqladminPath}" -u root shutdown`, { + timeout: 10000, + windowsHide: true + }) + } catch (e) { + console.log('mysqladmin shutdown 失败,尝试 taskkill') + // 如果优雅关闭失败,尝试强制结束进程 + try { + await execAsync('taskkill /F /IM mysqld.exe', { + timeout: 5000, + windowsHide: true + }) + } catch (e2) { + // 进程可能不存在 + console.log('taskkill 失败,进程可能不存在') + } + } + + // 等待进程结束 + await new Promise(resolve => setTimeout(resolve, 2000)) + + const isRunning = await this.checkIsRunning(version) + if (!isRunning) { + console.log(`MySQL ${version} 已停止`) + return { success: true, message: `MySQL ${version} 已停止` } + } else { + return { success: false, message: `MySQL ${version} 停止失败` } + } + } catch (error: any) { + return { success: false, message: `停止失败: ${error.message}` } + } + } + + /** + * 重启 MySQL 服务 + */ + async restart(version: string): Promise<{ success: boolean; message: string }> { + const stopResult = await this.stop(version) + if (!stopResult.success && !stopResult.message.includes('已停止')) { + return stopResult + } + + await new Promise(resolve => setTimeout(resolve, 1000)) + return await this.start(version) + } + + /** + * 获取 MySQL 运行状态 + */ + async getStatus(version: string): Promise<{ running: boolean; pid?: number; uptime?: string }> { + const isRunning = await this.checkIsRunning(version) + + if (!isRunning) { + return { running: false } + } + + try { + const { stdout } = await execAsync('tasklist /FI "IMAGENAME eq mysqld.exe" /FO CSV /NH') + const lines = stdout.trim().split('\n') + if (lines.length > 0 && lines[0].includes('mysqld.exe')) { + const parts = lines[0].split(',') + const pid = parseInt(parts[1].replace(/"/g, '')) + return { running: true, pid } + } + } catch (e) { + // 忽略错误 + } + + return { running: isRunning } + } + + /** + * 修改 root 密码 + */ + async changeRootPassword(version: string, newPassword: string, currentPassword?: string): Promise<{ success: boolean; message: string }> { + try { + const mysqlPath = this.configStore.getMysqlPath(version) + const mysqlExe = join(mysqlPath, 'bin', 'mysql.exe') + + // 检查 MySQL 是否在运行 + const isRunning = await this.checkIsRunning(version) + if (!isRunning) { + return { success: false, message: 'MySQL 未运行,请先启动服务' } + } + + const escapedNewPwd = newPassword.replace(/'/g, "\\'") + + // 修改所有 root 用户的密码 + const sql = ` + ALTER USER 'root'@'localhost' IDENTIFIED BY '${escapedNewPwd}'; + ALTER USER IF EXISTS 'root'@'127.0.0.1' IDENTIFIED BY '${escapedNewPwd}'; + ALTER USER IF EXISTS 'root'@'%' IDENTIFIED BY '${escapedNewPwd}'; + FLUSH PRIVILEGES; + `.replace(/\n/g, ' ').trim() + + // 构建命令,如果提供了当前密码则使用 + let cmd = `"${mysqlExe}" -u root --host=localhost` + if (currentPassword) { + cmd += ` -p"${currentPassword.replace(/"/g, '\\"')}"` + } + cmd += ` -e "${sql}"` + + await execAsync(cmd, { timeout: 15000, windowsHide: true }) + + return { success: true, message: 'root 密码修改成功' } + } catch (error: any) { + // 检查是否是密码错误 + if (error.message.includes('Access denied')) { + return { success: false, message: '当前密码错误,请输入正确的当前密码' } + } + return { success: false, message: `密码修改失败: ${error.message}` } + } + } + + /** + * 获取 my.ini 配置内容 + */ + async getConfig(version: string): Promise { + const mysqlPath = this.configStore.getMysqlPath(version) + const configPath = join(mysqlPath, 'my.ini') + + if (!existsSync(configPath)) { + return '' + } + + return readFileSync(configPath, 'utf-8') + } + + /** + * 保存 my.ini 配置 + */ + async saveConfig(version: string, config: string): Promise<{ success: boolean; message: string }> { + try { + const mysqlPath = this.configStore.getMysqlPath(version) + const configPath = join(mysqlPath, 'my.ini') + + writeFileSync(configPath, config) + return { success: true, message: 'my.ini 保存成功,需要重启 MySQL 生效' } + } catch (error: any) { + return { success: false, message: `保存失败: ${error.message}` } + } + } + + /** + * 设置初始密码(首次安装后使用) + */ + private async setInitialPassword(mysqlPath: string, password: string): Promise { + const mysqlExe = join(mysqlPath, 'bin', 'mysql.exe') + + // 使用空密码连接并设置新密码,同时授权 127.0.0.1 访问 + const sql = ` + ALTER USER 'root'@'localhost' IDENTIFIED BY '${password}'; + CREATE USER IF NOT EXISTS 'root'@'127.0.0.1' IDENTIFIED BY '${password}'; + GRANT ALL PRIVILEGES ON *.* TO 'root'@'127.0.0.1' WITH GRANT OPTION; + CREATE USER IF NOT EXISTS 'root'@'%' IDENTIFIED BY '${password}'; + GRANT ALL PRIVILEGES ON *.* TO 'root'@'%' WITH GRANT OPTION; + FLUSH PRIVILEGES; + `.replace(/\n/g, ' ').trim() + + try { + // 使用 --host=localhost 确保通过 socket 连接 + await execAsync( + `"${mysqlExe}" -u root --host=localhost -e "${sql}"`, + { + timeout: 15000, + windowsHide: true, + cwd: join(mysqlPath, 'bin') + } + ) + } catch (error: any) { + throw new Error(`设置密码失败: ${error.message}`) + } + } + + /** + * 重新初始化数据库(会清空所有数据!) + */ + async reinitialize(version: string): Promise<{ success: boolean; message: string }> { + try { + // 先停止服务 + await this.stop(version) + + const mysqlPath = this.configStore.getMysqlPath(version) + const dataPath = join(mysqlPath, 'data') + + console.log(`重新初始化 MySQL ${version}...`) + + // 删除数据目录 + if (existsSync(dataPath)) { + console.log('删除旧数据目录...') + this.removeDirectory(dataPath) + } + + // 创建新的数据目录 + mkdirSync(dataPath, { recursive: true }) + + // 重新生成配置文件 + await this.createDefaultConfig(mysqlPath, version) + + // 初始化数据库 + await this.initializeDatabase(mysqlPath) + + // 启动并设置默认密码 + console.log('启动 MySQL 并设置默认密码...') + const startResult = await this.start(version) + if (startResult.success) { + await new Promise(resolve => setTimeout(resolve, 3000)) + try { + await this.setInitialPassword(mysqlPath, '123456') + console.log('默认密码设置成功: 123456') + } catch (e) { + console.log('设置默认密码失败') + } + } + + console.log(`MySQL ${version} 重新初始化完成`) + return { + success: true, + message: `MySQL ${version} 重新初始化成功!\n\n连接信息:\n• 用户:root\n• 密码:123456` + } + } catch (error: any) { + console.error('重新初始化失败:', error) + return { success: false, message: `重新初始化失败: ${error.message}` } + } + } + + // ==================== 私有方法 ==================== + + /** + * 检查数据库是否需要初始化 + */ + private async checkNeedsInitialize(dataPath: string): Promise { + if (!existsSync(dataPath)) { + return true + } + + const files = readdirSync(dataPath) + if (files.length === 0) { + return true + } + + // 检查关键的 mysql 系统数据库目录是否存在 + const mysqlDbPath = join(dataPath, 'mysql') + if (!existsSync(mysqlDbPath)) { + console.log('mysql 系统数据库目录不存在,需要重新初始化') + return true + } + + // 检查是否有必要的系统文件 + const requiredFiles = ['ibdata1', 'ib_logfile0'] + for (const file of requiredFiles) { + // MySQL 8.0.30+ 可能没有 ib_logfile,所以只检查 ibdata1 + if (file === 'ibdata1' && !existsSync(join(dataPath, file))) { + console.log(`缺少 ${file},需要重新初始化`) + return true + } + } + + return false + } + + /** + * 检查并修复配置文件(MySQL 8.0 兼容性) + */ + private async fixConfigIfNeeded(mysqlPath: string, version: string): Promise { + const configPath = join(mysqlPath, 'my.ini') + + if (!existsSync(configPath)) { + console.log('配置文件不存在,重新创建') + await this.createDefaultConfig(mysqlPath, version) + return + } + + const majorVersion = parseInt(version.split('.')[0]) + const isMySQL8 = majorVersion >= 8 + + if (isMySQL8) { + let content = readFileSync(configPath, 'utf-8') + let needsUpdate = false + + // 检查是否包含 MySQL 8.0 不支持的配置 + const deprecatedConfigs = [ + /^query_cache_type\s*=.*$/gm, + /^query_cache_size\s*=.*$/gm, + /^query_cache_limit\s*=.*$/gm, + /^innodb_log_file_size\s*=.*$/gm, // 8.0.30+ 自动管理 + ] + + for (const regex of deprecatedConfigs) { + if (regex.test(content)) { + content = content.replace(regex, '# [已移除 - MySQL 8.0 不支持]') + needsUpdate = true + } + } + + if (needsUpdate) { + console.log('检测到 MySQL 8.0 不兼容配置,正在修复...') + writeFileSync(configPath, content) + console.log('配置文件已修复') + } + } + } + + private async checkIsRunning(version: string): Promise { + try { + const mysqlPath = this.configStore.getMysqlPath(version) + const port = this.getPortFromConfig(mysqlPath) + + const { stdout } = await execAsync(`netstat -ano | findstr ":${port}"`) + return stdout.includes('LISTENING') + } catch (e) { + return false + } + } + + private getPortFromConfig(mysqlPath: string): number { + const configPath = join(mysqlPath, 'my.ini') + if (existsSync(configPath)) { + const content = readFileSync(configPath, 'utf-8') + const match = content.match(/port\s*=\s*(\d+)/) + if (match) { + return parseInt(match[1]) + } + } + return 3306 + } + + private async downloadFile(url: string, dest: string): Promise { + // 确保目标目录存在 + const destDir = dirname(dest) + if (!existsSync(destDir)) { + mkdirSync(destDir, { recursive: true }) + } + + return new Promise((resolve, reject) => { + const file = createWriteStream(dest) + const protocol = url.startsWith('https') ? https : http + + console.log(`下载: ${url}`) + + const request = protocol.get(url, { + headers: { + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36' + } + }, (response) => { + // 处理重定向 + if (response.statusCode === 301 || response.statusCode === 302 || response.statusCode === 307) { + const redirectUrl = response.headers.location + if (redirectUrl) { + file.close() + if (existsSync(dest)) unlinkSync(dest) + console.log(`重定向到: ${redirectUrl}`) + this.downloadFile(redirectUrl, dest).then(resolve).catch(reject) + return + } + } + + if (response.statusCode !== 200) { + file.close() + if (existsSync(dest)) unlinkSync(dest) + reject(new Error(`下载失败,状态码: ${response.statusCode}, URL: ${url}`)) + return + } + + const totalSize = parseInt(response.headers['content-length'] || '0', 10) + let downloadedSize = 0 + let lastProgressTime = Date.now() + + response.on('data', (chunk) => { + downloadedSize += chunk.length + const now = Date.now() + // 每500ms发送一次进度 + if (now - lastProgressTime > 500) { + const progress = totalSize > 0 ? Math.round((downloadedSize / totalSize) * 100) : 0 + sendDownloadProgress('mysql', progress, downloadedSize, totalSize) + lastProgressTime = now + } + }) + + response.pipe(file) + + file.on('finish', () => { + file.close() + sendDownloadProgress('mysql', 100, totalSize, totalSize) + console.log('下载完成') + resolve() + }) + + file.on('error', (err) => { + file.close() + if (existsSync(dest)) unlinkSync(dest) + reject(err) + }) + }) + + request.on('error', (err) => { + file.close() + if (existsSync(dest)) unlinkSync(dest) + reject(new Error(`网络错误: ${err.message}`)) + }) + + // 10 分钟超时(MySQL 包较大) + request.setTimeout(600000, () => { + request.destroy() + file.close() + if (existsSync(dest)) unlinkSync(dest) + reject(new Error('下载超时(10分钟)')) + }) + }) + } + + private async unzip(zipPath: string, destPath: string): Promise { + // 确保目标目录存在 + if (!existsSync(destPath)) { + mkdirSync(destPath, { recursive: true }) + } + + const { createReadStream } = await import('fs') + const unzipper = await import('unzipper') + + return new Promise((resolve, reject) => { + console.log(`解压: ${zipPath} -> ${destPath}`) + + const stream = createReadStream(zipPath) + .pipe(unzipper.Extract({ path: destPath })) + + stream.on('close', () => { + console.log('解压完成') + resolve() + }) + + stream.on('error', (err: Error) => { + console.error('解压错误:', err) + reject(new Error(`解压失败: ${err.message}`)) + }) + }) + } + + private async createDefaultConfig(mysqlPath: string, version: string): Promise { + const configPath = join(mysqlPath, 'my.ini') + const dataPath = join(mysqlPath, 'data') + const logsPath = this.configStore.getLogsPath() + + // 确保日志目录存在 + if (!existsSync(logsPath)) { + mkdirSync(logsPath, { recursive: true }) + } + + // 判断是否是 MySQL 8.0+ + const majorVersion = parseInt(version.split('.')[0]) + const isMySQL8 = majorVersion >= 8 + + // MySQL 8.0 移除了 query_cache,需要区分配置 + const cacheConfig = isMySQL8 ? '' : ` +# 查询缓存 (仅 MySQL 5.7) +query_cache_type=1 +query_cache_size=64M` + + const config = `[mysqld] +# 基础配置 +basedir=${mysqlPath.replace(/\\/g, '/')} +datadir=${dataPath.replace(/\\/g, '/')} +port=3306 +bind-address=0.0.0.0 +server-id=1 + +# 字符集 +character-set-server=utf8mb4 +collation-server=utf8mb4_unicode_ci + +# 连接数 +max_connections=200 +max_connect_errors=10 + +# 存储引擎 +default-storage-engine=INNODB + +# 日志 +log-error=${logsPath.replace(/\\/g, '/')}/mysql-error.log +slow_query_log=1 +slow_query_log_file=${logsPath.replace(/\\/g, '/')}/mysql-slow.log +long_query_time=10 +${cacheConfig} + +# 表缓存 +table_open_cache=2000 +tmp_table_size=64M +max_heap_table_size=64M + +# InnoDB +innodb_buffer_pool_size=256M +innodb_log_buffer_size=16M +innodb_flush_log_at_trx_commit=1 +innodb_lock_wait_timeout=50 + +# 跳过时区设置(避免初始化问题) +# default-time-zone='+8:00' + +# 允许本地加载数据 +local_infile=1 + +# 注意:不要启用 skip-name-resolve,否则 localhost 和 127.0.0.1 会被视为不同主机 + +[mysql] +default-character-set=utf8mb4 + +[client] +port=3306 +default-character-set=utf8mb4 +` + + writeFileSync(configPath, config) + console.log(`创建 MySQL ${version} 配置文件: ${configPath}`) + } + + private async initializeDatabase(mysqlPath: string): Promise { + const mysqldPath = join(mysqlPath, 'bin', 'mysqld.exe') + const configPath = join(mysqlPath, 'my.ini') + const dataPath = join(mysqlPath, 'data') + + // 检查是否已经初始化过 + if (existsSync(dataPath)) { + const files = readdirSync(dataPath) + if (files.length > 0) { + console.log('MySQL 数据目录不为空,跳过初始化') + return + } + } + + console.log('开始初始化 MySQL 数据库...') + + // 使用 execAsync 初始化数据库(初始化是一次性操作,可以等待完成) + return new Promise(async (resolve, reject) => { + try { + // 初始化命令需要等待完成 + await execAsync( + `"${mysqldPath}" --defaults-file="${configPath}" --initialize-insecure`, + { + timeout: 120000, + windowsHide: true, + cwd: join(mysqlPath, 'bin') + } + ) + console.log('MySQL 初始化命令执行完成') + } catch (error: any) { + console.log('MySQL 初始化命令返回:', error.message) + } + + // 检查数据目录是否有文件 + setTimeout(() => { + const files = existsSync(dataPath) ? readdirSync(dataPath) : [] + if (files.length > 0) { + console.log('数据目录已创建,初始化成功') + resolve() + } else { + reject(new Error('初始化失败,数据目录为空')) + } + }, 2000) + }) + } + + private removeDirectory(dir: string): void { + if (existsSync(dir)) { + const files = readdirSync(dir, { withFileTypes: true }) + for (const file of files) { + const fullPath = join(dir, file.name) + if (file.isDirectory()) { + this.removeDirectory(fullPath) + } else { + unlinkSync(fullPath) + } + } + rmdirSync(dir) + } + } +} + diff --git a/electron/services/NginxManager.ts b/electron/services/NginxManager.ts new file mode 100644 index 0000000..ba448b6 --- /dev/null +++ b/electron/services/NginxManager.ts @@ -0,0 +1,848 @@ +import { ConfigStore, SiteConfig } from './ConfigStore' +import { exec, spawn } from 'child_process' +import { promisify } from 'util' +import { existsSync, readFileSync, writeFileSync, readdirSync, unlinkSync, rmdirSync, mkdirSync, copyFileSync } from 'fs' +import { join } from 'path' +import https from 'https' +import http from 'http' +import { createWriteStream } from 'fs' +import { sendDownloadProgress } from '../main' + +const execAsync = promisify(exec) + +interface NginxVersion { + version: string + path: string +} + +interface AvailableNginxVersion { + version: string + downloadUrl: string +} + +interface NginxStatus { + running: boolean + pid?: number + activeConnections?: number +} + +export class NginxManager { + private configStore: ConfigStore + + constructor(configStore: ConfigStore) { + this.configStore = configStore + } + + /** + * 获取已安装的 Nginx 版本列表 + */ + async getInstalledVersions(): Promise { + const versions: NginxVersion[] = [] + const nginxDir = this.configStore.getNginxPath() + + if (!existsSync(nginxDir)) { + return versions + } + + // 检查是否存在 nginx.exe + if (existsSync(join(nginxDir, 'nginx.exe'))) { + // 获取版本号 + try { + const { stdout } = await execAsync(`"${join(nginxDir, 'nginx.exe')}" -v 2>&1`) + const match = stdout.match(/nginx\/(\d+\.\d+\.\d+)/) + if (match) { + versions.push({ + version: match[1], + path: nginxDir + }) + } + } catch (error: any) { + // nginx -v 输出到 stderr + const match = error.message?.match(/nginx\/(\d+\.\d+\.\d+)/) || + error.stderr?.match(/nginx\/(\d+\.\d+\.\d+)/) + if (match) { + versions.push({ + version: match[1], + path: nginxDir + }) + } + } + } + + return versions + } + + /** + * 获取可用的 Nginx 版本列表 + */ + async getAvailableVersions(): Promise { + const versions: AvailableNginxVersion[] = [ + { + version: '1.27.3', + downloadUrl: 'https://nginx.org/download/nginx-1.27.3.zip' + }, + { + version: '1.26.2', + downloadUrl: 'https://nginx.org/download/nginx-1.26.2.zip' + }, + { + version: '1.25.5', + downloadUrl: 'https://nginx.org/download/nginx-1.25.5.zip' + }, + { + version: '1.24.0', + downloadUrl: 'https://nginx.org/download/nginx-1.24.0.zip' + } + ] + + return versions + } + + /** + * 安装 Nginx 版本 + */ + async install(version: string): Promise<{ success: boolean; message: string }> { + try { + const available = await this.getAvailableVersions() + const versionInfo = available.find(v => v.version === version) + + if (!versionInfo) { + return { success: false, message: `未找到 Nginx ${version} 版本` } + } + + const nginxPath = this.configStore.getNginxPath() + const tempPath = this.configStore.getTempPath() + const zipPath = join(tempPath, `nginx-${version}.zip`) + + // 如果已有 Nginx 安装,先备份配置 + let oldConfig = '' + const configPath = join(nginxPath, 'conf', 'nginx.conf') + if (existsSync(configPath)) { + oldConfig = readFileSync(configPath, 'utf-8') + } + + // 下载 Nginx + await this.downloadFile(versionInfo.downloadUrl, zipPath) + + // 解压 + const basePath = this.configStore.getBasePath() + await this.unzip(zipPath, basePath) + + // 重命名目录 + const extractedDir = join(basePath, `nginx-${version}`) + if (existsSync(extractedDir) && extractedDir !== nginxPath) { + // 如果目标目录已存在,先删除 + if (existsSync(nginxPath)) { + this.removeDirectory(nginxPath) + } + const { rename } = await import('fs/promises') + await rename(extractedDir, nginxPath) + } + + // 删除临时文件 + if (existsSync(zipPath)) { + unlinkSync(zipPath) + } + + // 创建必要的目录 + const sitesAvailable = this.configStore.getSitesAvailablePath() + const sitesEnabled = this.configStore.getSitesEnabledPath() + const sslPath = this.configStore.getSSLPath() + + if (!existsSync(sitesAvailable)) mkdirSync(sitesAvailable, { recursive: true }) + if (!existsSync(sitesEnabled)) mkdirSync(sitesEnabled, { recursive: true }) + if (!existsSync(sslPath)) mkdirSync(sslPath, { recursive: true }) + + // 恢复或创建配置 + if (oldConfig) { + writeFileSync(configPath, oldConfig) + } else { + await this.createDefaultConfig() + } + + return { success: true, message: `Nginx ${version} 安装成功` } + } catch (error: any) { + return { success: false, message: `安装失败: ${error.message}` } + } + } + + /** + * 卸载 Nginx + */ + async uninstall(version: string): Promise<{ success: boolean; message: string }> { + try { + // 先停止服务 + await this.stop() + + const nginxPath = this.configStore.getNginxPath() + + if (!existsSync(nginxPath)) { + return { success: false, message: 'Nginx 未安装' } + } + + // 递归删除目录(保留 sites 和 ssl 目录) + const items = readdirSync(nginxPath, { withFileTypes: true }) + for (const item of items) { + const itemPath = join(nginxPath, item.name) + if (item.name !== 'sites-available' && item.name !== 'sites-enabled' && item.name !== 'ssl') { + if (item.isDirectory()) { + this.removeDirectory(itemPath) + } else { + unlinkSync(itemPath) + } + } + } + + return { success: true, message: 'Nginx 已卸载' } + } catch (error: any) { + return { success: false, message: `卸载失败: ${error.message}` } + } + } + + /** + * 启动 Nginx + */ + async start(): Promise<{ success: boolean; message: string }> { + try { + const nginxPath = this.configStore.getNginxPath() + const nginxExe = join(nginxPath, 'nginx.exe') + + if (!existsSync(nginxExe)) { + return { success: false, message: 'Nginx 未安装' } + } + + // 检查是否已在运行 + const status = await this.getStatus() + if (status.running) { + return { success: true, message: 'Nginx 已经在运行' } + } + + // 启动 Nginx + const child = spawn(nginxExe, [], { + cwd: nginxPath, + detached: true, + stdio: 'ignore', + windowsHide: true + }) + child.unref() + + // 等待启动 + await new Promise(resolve => setTimeout(resolve, 1000)) + + const newStatus = await this.getStatus() + if (newStatus.running) { + return { success: true, message: 'Nginx 启动成功' } + } else { + return { success: false, message: 'Nginx 启动失败,请检查配置' } + } + } catch (error: any) { + return { success: false, message: `启动失败: ${error.message}` } + } + } + + /** + * 停止 Nginx + */ + async stop(): Promise<{ success: boolean; message: string }> { + try { + const nginxPath = this.configStore.getNginxPath() + const nginxExe = join(nginxPath, 'nginx.exe') + + if (existsSync(nginxExe)) { + try { + await execAsync(`"${nginxExe}" -s stop`, { cwd: nginxPath, timeout: 10000 }) + } catch (e) { + // 如果 -s stop 失败,尝试强制结束 + try { + await execAsync('taskkill /F /IM nginx.exe', { timeout: 5000 }) + } catch (e2) { + // 进程可能不存在 + } + } + } + + await new Promise(resolve => setTimeout(resolve, 1000)) + + const status = await this.getStatus() + if (!status.running) { + return { success: true, message: 'Nginx 已停止' } + } else { + return { success: false, message: 'Nginx 停止失败' } + } + } catch (error: any) { + return { success: false, message: `停止失败: ${error.message}` } + } + } + + /** + * 重启 Nginx + */ + async restart(): Promise<{ success: boolean; message: string }> { + await this.stop() + await new Promise(resolve => setTimeout(resolve, 500)) + return await this.start() + } + + /** + * 重载配置 + */ + async reload(): Promise<{ success: boolean; message: string }> { + try { + const nginxPath = this.configStore.getNginxPath() + const nginxExe = join(nginxPath, 'nginx.exe') + + if (!existsSync(nginxExe)) { + return { success: false, message: 'Nginx 未安装' } + } + + await execAsync(`"${nginxExe}" -s reload`, { cwd: nginxPath }) + return { success: true, message: '配置已重载' } + } catch (error: any) { + return { success: false, message: `重载失败: ${error.message}` } + } + } + + /** + * 获取 Nginx 状态 + */ + async getStatus(): Promise { + try { + const { stdout } = await execAsync('tasklist /FI "IMAGENAME eq nginx.exe" /FO CSV /NH') + const lines = stdout.trim().split('\n') + + if (lines.length > 0 && lines[0].includes('nginx.exe')) { + const parts = lines[0].split(',') + const pid = parseInt(parts[1].replace(/"/g, '')) + return { running: true, pid } + } + } catch (e) { + // 忽略错误 + } + + return { running: false } + } + + /** + * 获取 nginx.conf 配置内容 + */ + async getConfig(): Promise { + const configPath = join(this.configStore.getNginxPath(), 'conf', 'nginx.conf') + + if (!existsSync(configPath)) { + return '' + } + + return readFileSync(configPath, 'utf-8') + } + + /** + * 保存 nginx.conf 配置 + */ + async saveConfig(config: string): Promise<{ success: boolean; message: string }> { + try { + const configPath = join(this.configStore.getNginxPath(), 'conf', 'nginx.conf') + + // 先测试配置 + const tempPath = join(this.configStore.getTempPath(), 'nginx-test.conf') + writeFileSync(tempPath, config) + + const nginxExe = join(this.configStore.getNginxPath(), 'nginx.exe') + try { + await execAsync(`"${nginxExe}" -t -c "${tempPath}"`, { cwd: this.configStore.getNginxPath() }) + } catch (testError: any) { + unlinkSync(tempPath) + return { success: false, message: `配置验证失败: ${testError.stderr || testError.message}` } + } + + unlinkSync(tempPath) + writeFileSync(configPath, config) + return { success: true, message: 'nginx.conf 保存成功' } + } catch (error: any) { + return { success: false, message: `保存失败: ${error.message}` } + } + } + + /** + * 获取站点列表 + */ + async getSites(): Promise { + return this.configStore.getSites() + } + + /** + * 添加站点 + */ + async addSite(site: SiteConfig): Promise<{ success: boolean; message: string }> { + try { + // 生成配置文件 + const config = site.isLaravel + ? this.generateLaravelSiteConfig(site) + : this.generateSiteConfig(site) + + const sitesAvailable = this.configStore.getSitesAvailablePath() + const configPath = join(sitesAvailable, `${site.name}.conf`) + + writeFileSync(configPath, config) + + // 启用站点 + if (site.enabled) { + await this.enableSite(site.name) + } + + // 保存到配置 + this.configStore.addSite(site) + + return { success: true, message: `站点 ${site.name} 创建成功` } + } catch (error: any) { + return { success: false, message: `创建站点失败: ${error.message}` } + } + } + + /** + * 删除站点 + */ + async removeSite(name: string): Promise<{ success: boolean; message: string }> { + try { + const sitesAvailable = this.configStore.getSitesAvailablePath() + const sitesEnabled = this.configStore.getSitesEnabledPath() + + const availablePath = join(sitesAvailable, `${name}.conf`) + const enabledPath = join(sitesEnabled, `${name}.conf`) + + if (existsSync(enabledPath)) unlinkSync(enabledPath) + if (existsSync(availablePath)) unlinkSync(availablePath) + + this.configStore.removeSite(name) + + return { success: true, message: `站点 ${name} 已删除` } + } catch (error: any) { + return { success: false, message: `删除站点失败: ${error.message}` } + } + } + + /** + * 更新站点 + */ + async updateSite(originalName: string, site: SiteConfig): Promise<{ success: boolean; message: string }> { + try { + const sitesAvailable = this.configStore.getSitesAvailablePath() + const sitesEnabled = this.configStore.getSitesEnabledPath() + + // 如果站点名称没变,直接更新配置文件 + const configPath = join(sitesAvailable, `${originalName}.conf`) + const enabledPath = join(sitesEnabled, `${originalName}.conf`) + + // 检查是否之前是启用状态 + const wasEnabled = existsSync(enabledPath) + + // 生成新的配置内容 + const config = site.isLaravel + ? this.generateLaravelSiteConfig(site) + : this.generateSiteConfig(site) + + // 写入配置文件 + writeFileSync(configPath, config) + + // 如果之前是启用状态,更新启用的配置 + if (wasEnabled) { + writeFileSync(enabledPath, config) + } + + // 更新存储的配置 + this.configStore.updateSite(originalName, site) + + return { success: true, message: `站点 ${site.name} 更新成功` } + } catch (error: any) { + return { success: false, message: `更新站点失败: ${error.message}` } + } + } + + /** + * 启用站点 + */ + async enableSite(name: string): Promise<{ success: boolean; message: string }> { + try { + const sitesAvailable = this.configStore.getSitesAvailablePath() + const sitesEnabled = this.configStore.getSitesEnabledPath() + + const availablePath = join(sitesAvailable, `${name}.conf`) + const enabledPath = join(sitesEnabled, `${name}.conf`) + + if (!existsSync(availablePath)) { + return { success: false, message: `站点配置 ${name} 不存在` } + } + + // 复制配置到 enabled 目录 + copyFileSync(availablePath, enabledPath) + + this.configStore.updateSite(name, { enabled: true }) + + return { success: true, message: `站点 ${name} 已启用` } + } catch (error: any) { + return { success: false, message: `启用站点失败: ${error.message}` } + } + } + + /** + * 禁用站点 + */ + async disableSite(name: string): Promise<{ success: boolean; message: string }> { + try { + const sitesEnabled = this.configStore.getSitesEnabledPath() + const enabledPath = join(sitesEnabled, `${name}.conf`) + + if (existsSync(enabledPath)) { + unlinkSync(enabledPath) + } + + this.configStore.updateSite(name, { enabled: false }) + + return { success: true, message: `站点 ${name} 已禁用` } + } catch (error: any) { + return { success: false, message: `禁用站点失败: ${error.message}` } + } + } + + /** + * 生成 Laravel 配置 + */ + async generateLaravelConfig(site: SiteConfig): Promise { + return this.generateLaravelSiteConfig(site) + } + + /** + * 申请 SSL 证书(Let's Encrypt) + */ + async requestSSLCertificate(domain: string, email: string): Promise<{ success: boolean; message: string }> { + try { + // 检查是否安装了 win-acme + const acmePath = join(this.configStore.getBasePath(), 'tools', 'win-acme') + const wacs = join(acmePath, 'wacs.exe') + + if (!existsSync(wacs)) { + return { + success: false, + message: '请先下载 win-acme 工具到 tools/win-acme 目录。下载地址: https://www.win-acme.com/' + } + } + + const sslPath = this.configStore.getSSLPath() + const certPath = join(sslPath, domain) + + if (!existsSync(certPath)) { + mkdirSync(certPath, { recursive: true }) + } + + // 使用 win-acme 申请证书 + const command = `"${wacs}" --target manual --host ${domain} --validation selfhosting --emailaddress ${email} --accepttos --store pemfiles --pemfilespath "${certPath}"` + + await execAsync(command, { timeout: 120000 }) + + return { success: true, message: `SSL 证书已申请成功,保存在 ${certPath}` } + } catch (error: any) { + return { success: false, message: `申请 SSL 证书失败: ${error.message}` } + } + } + + // ==================== 私有方法 ==================== + + private async createDefaultConfig(): Promise { + const nginxPath = this.configStore.getNginxPath() + const configPath = join(nginxPath, 'conf', 'nginx.conf') + const logsPath = this.configStore.getLogsPath() + const sitesEnabled = this.configStore.getSitesEnabledPath() + + const config = ` +worker_processes auto; + +error_log "${logsPath.replace(/\\/g, '/')}/nginx-error.log"; +pid "${nginxPath.replace(/\\/g, '/')}/nginx.pid"; + +events { + worker_connections 1024; +} + +http { + include mime.types; + default_type application/octet-stream; + + log_format main '$remote_addr - $remote_user [$time_local] "$request" ' + '$status $body_bytes_sent "$http_referer" ' + '"$http_user_agent" "$http_x_forwarded_for"'; + + access_log "${logsPath.replace(/\\/g, '/')}/nginx-access.log" main; + + sendfile on; + keepalive_timeout 65; + + # Gzip 压缩 + gzip on; + gzip_vary on; + gzip_proxied any; + gzip_comp_level 6; + gzip_types text/plain text/css text/xml application/json application/javascript application/rss+xml application/atom+xml image/svg+xml; + + # 上传大小限制 + client_max_body_size 100M; + + # 包含所有启用的站点配置 + include "${sitesEnabled.replace(/\\/g, '/')}/*.conf"; + + # 默认服务器 + server { + listen 80; + server_name localhost; + + location / { + root html; + index index.html index.htm index.php; + } + + error_page 500 502 503 504 /50x.html; + location = /50x.html { + root html; + } + } +} +` + + writeFileSync(configPath, config) + } + + private generateSiteConfig(site: SiteConfig): string { + const phpPath = this.configStore.getPhpPath(site.phpVersion) + const logsPath = this.configStore.getLogsPath() + + let config = ` +server { + listen 80; + server_name ${site.domain}; + root "${site.rootPath.replace(/\\/g, '/')}"; + index index.php index.html index.htm; + + access_log "${logsPath.replace(/\\/g, '/')}/${site.name}-access.log"; + error_log "${logsPath.replace(/\\/g, '/')}/${site.name}-error.log"; + + location / { + try_files $uri $uri/ /index.php?$query_string; + } + + location ~ \\.php$ { + fastcgi_pass 127.0.0.1:9000; + fastcgi_index index.php; + fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; + include fastcgi_params; + } + + location ~ /\\.(?!well-known).* { + deny all; + } +} +` + + if (site.ssl) { + const sslPath = join(this.configStore.getSSLPath(), site.domain) + config += ` +server { + listen 443 ssl http2; + server_name ${site.domain}; + root "${site.rootPath.replace(/\\/g, '/')}"; + index index.php index.html index.htm; + + ssl_certificate "${sslPath.replace(/\\/g, '/')}/${site.domain}-chain.pem"; + ssl_certificate_key "${sslPath.replace(/\\/g, '/')}/${site.domain}-key.pem"; + ssl_protocols TLSv1.2 TLSv1.3; + ssl_ciphers HIGH:!aNULL:!MD5; + + access_log "${logsPath.replace(/\\/g, '/')}/${site.name}-ssl-access.log"; + error_log "${logsPath.replace(/\\/g, '/')}/${site.name}-ssl-error.log"; + + location / { + try_files $uri $uri/ /index.php?$query_string; + } + + location ~ \\.php$ { + fastcgi_pass 127.0.0.1:9000; + fastcgi_index index.php; + fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; + include fastcgi_params; + } + + location ~ /\\.(?!well-known).* { + deny all; + } +} +` + } + + return config + } + + private generateLaravelSiteConfig(site: SiteConfig): string { + const logsPath = this.configStore.getLogsPath() + // Laravel 项目 public 目录 + const publicPath = join(site.rootPath, 'public').replace(/\\/g, '/') + + let config = ` +server { + listen 80; + server_name ${site.domain}; + root "${publicPath}"; + index index.php; + + access_log "${logsPath.replace(/\\/g, '/')}/${site.name}-access.log"; + error_log "${logsPath.replace(/\\/g, '/')}/${site.name}-error.log"; + + add_header X-Frame-Options "SAMEORIGIN"; + add_header X-Content-Type-Options "nosniff"; + + charset utf-8; + + location / { + try_files $uri $uri/ /index.php?$query_string; + } + + location = /favicon.ico { access_log off; log_not_found off; } + location = /robots.txt { access_log off; log_not_found off; } + + error_page 404 /index.php; + + location ~ \\.php$ { + fastcgi_pass 127.0.0.1:9000; + fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name; + include fastcgi_params; + } + + location ~ /\\.(?!well-known).* { + deny all; + } +} +` + + if (site.ssl) { + const sslPath = join(this.configStore.getSSLPath(), site.domain) + config += ` +server { + listen 443 ssl http2; + server_name ${site.domain}; + root "${publicPath}"; + index index.php; + + ssl_certificate "${sslPath.replace(/\\/g, '/')}/${site.domain}-chain.pem"; + ssl_certificate_key "${sslPath.replace(/\\/g, '/')}/${site.domain}-key.pem"; + ssl_protocols TLSv1.2 TLSv1.3; + ssl_ciphers HIGH:!aNULL:!MD5; + ssl_prefer_server_ciphers off; + + access_log "${logsPath.replace(/\\/g, '/')}/${site.name}-ssl-access.log"; + error_log "${logsPath.replace(/\\/g, '/')}/${site.name}-ssl-error.log"; + + add_header X-Frame-Options "SAMEORIGIN"; + add_header X-Content-Type-Options "nosniff"; + + charset utf-8; + + location / { + try_files $uri $uri/ /index.php?$query_string; + } + + location = /favicon.ico { access_log off; log_not_found off; } + location = /robots.txt { access_log off; log_not_found off; } + + error_page 404 /index.php; + + location ~ \\.php$ { + fastcgi_pass 127.0.0.1:9000; + fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name; + include fastcgi_params; + } + + location ~ /\\.(?!well-known).* { + deny all; + } +} +` + } + + return config + } + + private async downloadFile(url: string, dest: string): Promise { + return new Promise((resolve, reject) => { + const file = createWriteStream(dest) + const protocol = url.startsWith('https') ? https : http + + const request = protocol.get(url, (response) => { + if (response.statusCode === 301 || response.statusCode === 302) { + const redirectUrl = response.headers.location + if (redirectUrl) { + file.close() + unlinkSync(dest) + this.downloadFile(redirectUrl, dest).then(resolve).catch(reject) + return + } + } + + if (response.statusCode !== 200) { + reject(new Error(`下载失败,状态码: ${response.statusCode}`)) + return + } + + const totalSize = parseInt(response.headers['content-length'] || '0', 10) + let downloadedSize = 0 + let lastProgressTime = Date.now() + + response.on('data', (chunk) => { + downloadedSize += chunk.length + const now = Date.now() + if (now - lastProgressTime > 500) { + const progress = totalSize > 0 ? Math.round((downloadedSize / totalSize) * 100) : 0 + sendDownloadProgress('nginx', progress, downloadedSize, totalSize) + lastProgressTime = now + } + }) + + response.pipe(file) + file.on('finish', () => { + file.close() + sendDownloadProgress('nginx', 100, totalSize, totalSize) + resolve() + }) + }) + + request.on('error', (err) => { + file.close() + if (existsSync(dest)) unlinkSync(dest) + reject(err) + }) + }) + } + + private async unzip(zipPath: string, destPath: string): Promise { + const { createReadStream } = await import('fs') + const unzipper = await import('unzipper') + + return new Promise((resolve, reject) => { + createReadStream(zipPath) + .pipe(unzipper.Extract({ path: destPath })) + .on('close', resolve) + .on('error', reject) + }) + } + + private removeDirectory(dir: string): void { + if (existsSync(dir)) { + const files = readdirSync(dir, { withFileTypes: true }) + for (const file of files) { + const fullPath = join(dir, file.name) + if (file.isDirectory()) { + this.removeDirectory(fullPath) + } else { + unlinkSync(fullPath) + } + } + rmdirSync(dir) + } + } +} + diff --git a/electron/services/NodeManager.ts b/electron/services/NodeManager.ts new file mode 100644 index 0000000..cb28523 --- /dev/null +++ b/electron/services/NodeManager.ts @@ -0,0 +1,460 @@ +import { ConfigStore } from './ConfigStore' +import { exec } from 'child_process' +import { promisify } from 'util' +import { existsSync, mkdirSync, readdirSync, rmSync, readFileSync, writeFileSync, unlinkSync } from 'fs' +import { join } from 'path' +import https from 'https' +import http from 'http' +import { createWriteStream } from 'fs' +import unzipper from 'unzipper' +import { sendDownloadProgress } from '../main' + +const execAsync = promisify(exec) + +interface NodeVersion { + version: string + path: string + isActive: boolean + npmVersion?: string +} + +interface AvailableNodeVersion { + version: string + date: string + lts: string | false + security: boolean + downloadUrl: string +} + +export class NodeManager { + private configStore: ConfigStore + private versionsCache: AvailableNodeVersion[] = [] + private cacheTime: number = 0 + private readonly CACHE_DURATION = 5 * 60 * 1000 // 5 分钟缓存 + + constructor(configStore: ConfigStore) { + this.configStore = configStore + } + + /** + * 获取已安装的 Node.js 版本 + */ + async getInstalledVersions(): Promise { + const versions: NodeVersion[] = [] + const nodePath = this.configStore.getNodePath() + + if (!existsSync(nodePath)) { + return versions + } + + const dirs = readdirSync(nodePath, { withFileTypes: true }) + const activeVersion = this.configStore.get('activeNodeVersion') || '' + + for (const dir of dirs) { + if (dir.isDirectory() && dir.name.startsWith('node-')) { + const versionDir = join(nodePath, dir.name) + const nodeExe = join(versionDir, 'node.exe') + + if (existsSync(nodeExe)) { + const version = dir.name.replace('node-', '').replace('-win-x64', '') + let npmVersion = '' + + // 尝试获取 npm 版本 + try { + const npmPath = join(versionDir, 'npm.cmd') + if (existsSync(npmPath)) { + const { stdout } = await execAsync(`"${npmPath}" --version`, { timeout: 5000 }) + npmVersion = stdout.trim() + } + } catch (e) { + // 忽略错误 + } + + versions.push({ + version, + path: versionDir, + isActive: version === activeVersion, + npmVersion + }) + } + } + } + + // 按版本号排序(降序) + versions.sort((a, b) => { + const aParts = a.version.replace('v', '').split('.').map(Number) + const bParts = b.version.replace('v', '').split('.').map(Number) + for (let i = 0; i < 3; i++) { + if (aParts[i] !== bParts[i]) { + return bParts[i] - aParts[i] + } + } + return 0 + }) + + return versions + } + + /** + * 获取可用的 Node.js 版本列表 + */ + async getAvailableVersions(): Promise { + // 检查缓存 + if (this.versionsCache.length > 0 && Date.now() - this.cacheTime < this.CACHE_DURATION) { + return this.versionsCache + } + + try { + const versions = await this.fetchVersionsFromNodejs() + if (versions.length > 0) { + this.versionsCache = versions + this.cacheTime = Date.now() + return versions + } + } catch (error) { + console.error('获取 Node.js 版本列表失败:', error) + } + + // 返回硬编码的版本列表作为后备 + return this.getFallbackVersions() + } + + /** + * 从 Node.js 官网获取版本列表 + */ + private async fetchVersionsFromNodejs(): Promise { + return new Promise((resolve, reject) => { + const url = 'https://nodejs.org/dist/index.json' + + https.get(url, { + headers: { + 'User-Agent': 'PHPer-Dev-Manager/1.0' + }, + timeout: 30000 + }, (res) => { + if (res.statusCode === 301 || res.statusCode === 302) { + const redirectUrl = res.headers.location + if (redirectUrl) { + https.get(redirectUrl, (redirectRes) => { + this.handleVersionResponse(redirectRes, resolve, reject) + }).on('error', reject) + return + } + } + this.handleVersionResponse(res, resolve, reject) + }).on('error', reject) + .on('timeout', () => reject(new Error('请求超时'))) + }) + } + + private handleVersionResponse(res: http.IncomingMessage, resolve: Function, reject: Function) { + let data = '' + res.on('data', chunk => data += chunk) + res.on('end', () => { + try { + const versions = JSON.parse(data) + const availableVersions: AvailableNodeVersion[] = [] + + for (const v of versions) { + // 只获取有 Windows 64位版本的 + if (v.files && v.files.includes('win-x64-zip')) { + availableVersions.push({ + version: v.version, + date: v.date, + lts: v.lts, + security: v.security, + downloadUrl: `https://nodejs.org/dist/${v.version}/node-${v.version}-win-x64.zip` + }) + } + } + + // 只返回前 30 个版本 + resolve(availableVersions.slice(0, 30)) + } catch (e) { + reject(e) + } + }) + res.on('error', reject) + } + + /** + * 安装 Node.js 版本 + */ + async install(version: string, downloadUrl: string): Promise<{ success: boolean; message: string }> { + try { + const nodePath = this.configStore.getNodePath() + const tempPath = this.configStore.getTempPath() + const zipPath = join(tempPath, `node-${version}.zip`) + const extractDir = join(nodePath, `node-${version}-win-x64`) + + // 确保目录存在 + if (!existsSync(nodePath)) { + mkdirSync(nodePath, { recursive: true }) + } + if (!existsSync(tempPath)) { + mkdirSync(tempPath, { recursive: true }) + } + + // 检查是否已安装 + if (existsSync(extractDir) && existsSync(join(extractDir, 'node.exe'))) { + return { success: false, message: `Node.js ${version} 已安装` } + } + + // 下载 + console.log(`开始下载 Node.js ${version}...`) + await this.downloadFile(downloadUrl, zipPath, `node-${version}`) + + // 解压 + console.log(`开始解压 Node.js ${version}...`) + await this.extractZip(zipPath, nodePath) + + // 清理下载文件 + try { + unlinkSync(zipPath) + } catch (e) { + // 忽略清理错误 + } + + // 验证安装 + if (!existsSync(join(extractDir, 'node.exe'))) { + return { success: false, message: '安装失败:node.exe 不存在' } + } + + // 更新配置 + const nodeVersions = this.configStore.get('nodeVersions') || [] + if (!nodeVersions.includes(version)) { + nodeVersions.push(version) + this.configStore.set('nodeVersions', nodeVersions) + } + + // 如果是第一个版本,设为默认 + if (nodeVersions.length === 1) { + await this.setActive(version) + } + + return { success: true, message: `Node.js ${version} 安装成功` } + } catch (error: any) { + return { success: false, message: `安装失败: ${error.message}` } + } + } + + /** + * 卸载 Node.js 版本 + */ + async uninstall(version: string): Promise<{ success: boolean; message: string }> { + try { + const nodePath = this.configStore.getNodePath() + const versionDir = join(nodePath, `node-${version}-win-x64`) + + if (!existsSync(versionDir)) { + return { success: false, message: `Node.js ${version} 未安装` } + } + + // 如果是当前激活的版本,先取消激活 + const activeVersion = this.configStore.get('activeNodeVersion') + if (activeVersion === version) { + this.configStore.set('activeNodeVersion', '') + } + + // 删除目录 + rmSync(versionDir, { recursive: true, force: true }) + + // 更新配置 + const nodeVersions = this.configStore.get('nodeVersions') || [] + const index = nodeVersions.indexOf(version) + if (index > -1) { + nodeVersions.splice(index, 1) + this.configStore.set('nodeVersions', nodeVersions) + } + + return { success: true, message: `Node.js ${version} 已卸载` } + } catch (error: any) { + return { success: false, message: `卸载失败: ${error.message}` } + } + } + + /** + * 设置活动的 Node.js 版本 + */ + async setActive(version: string): Promise<{ success: boolean; message: string }> { + try { + const nodePath = this.configStore.getNodePath() + const versionDir = join(nodePath, `node-${version}-win-x64`) + + if (!existsSync(join(versionDir, 'node.exe'))) { + return { success: false, message: `Node.js ${version} 未安装` } + } + + // 添加到 PATH + await this.addToPath(versionDir) + + // 更新配置 + this.configStore.set('activeNodeVersion', version) + + return { success: true, message: `已将 Node.js ${version} 设为默认版本` } + } catch (error: any) { + return { success: false, message: `设置失败: ${error.message}` } + } + } + + /** + * 获取 Node.js 信息 + */ + async getNodeInfo(version: string): Promise { + const nodePath = this.configStore.getNodePath() + const versionDir = join(nodePath, `node-${version}-win-x64`) + const nodeExe = join(versionDir, 'node.exe') + + if (!existsSync(nodeExe)) { + return null + } + + try { + const { stdout: nodeVersion } = await execAsync(`"${nodeExe}" --version`, { timeout: 5000 }) + + let npmVersion = '' + const npmCmd = join(versionDir, 'npm.cmd') + if (existsSync(npmCmd)) { + const { stdout } = await execAsync(`"${npmCmd}" --version`, { timeout: 5000 }) + npmVersion = stdout.trim() + } + + return { + nodeVersion: nodeVersion.trim(), + npmVersion, + path: versionDir + } + } catch (error) { + return null + } + } + + // ==================== 私有方法 ==================== + + private async downloadFile(url: string, dest: string, name: string): Promise { + return new Promise((resolve, reject) => { + const protocol = url.startsWith('https') ? https : http + + const request = protocol.get(url, { + headers: { + 'User-Agent': 'PHPer-Dev-Manager/1.0' + }, + timeout: 600000 // 10 分钟超时 + }, (response) => { + // 处理重定向 + if (response.statusCode === 301 || response.statusCode === 302) { + const redirectUrl = response.headers.location + if (redirectUrl) { + this.downloadFile(redirectUrl, dest, name).then(resolve).catch(reject) + return + } + } + + if (response.statusCode !== 200) { + reject(new Error(`下载失败: HTTP ${response.statusCode}`)) + return + } + + const totalSize = parseInt(response.headers['content-length'] || '0', 10) + let downloadedSize = 0 + + const file = createWriteStream(dest) + + let lastProgressTime = 0 + response.on('data', (chunk) => { + downloadedSize += chunk.length + const now = Date.now() + if (now - lastProgressTime > 500) { + const progress = totalSize > 0 ? Math.round((downloadedSize / totalSize) * 100) : 0 + sendDownloadProgress('nodejs', progress, downloadedSize, totalSize) + lastProgressTime = now + } + }) + + response.pipe(file) + + file.on('finish', () => { + file.close() + sendDownloadProgress('nodejs', 100, totalSize, totalSize) + resolve() + }) + + file.on('error', (err) => { + unlinkSync(dest) + reject(err) + }) + }) + + request.on('error', reject) + request.on('timeout', () => { + request.destroy() + reject(new Error('下载超时')) + }) + }) + } + + private async extractZip(zipPath: string, destDir: string): Promise { + return new Promise((resolve, reject) => { + const readStream = require('fs').createReadStream(zipPath) + readStream + .pipe(unzipper.Extract({ path: destDir })) + .on('close', resolve) + .on('error', reject) + }) + } + + private async addToPath(nodePath: string): Promise { + // 使用 PowerShell 更新用户 PATH + const psScript = ` +$ErrorActionPreference = 'Stop' +$newPath = '${nodePath.replace(/\\/g, '\\\\')}' + +# Get current user PATH +$currentPath = [Environment]::GetEnvironmentVariable('Path', 'User') +$pathArray = $currentPath -split ';' | Where-Object { $_ -ne '' } + +# Remove existing Node.js paths (from this manager and common locations) +$filteredPaths = $pathArray | Where-Object { + $p = $_.ToLower() + -not ($p -like '*\\node-v*' -or + $p -like '*\\nodejs*' -or + $p -like '*phper-dev-manager*node*' -or + $p -like '*\\nvm\\*') +} + +# Add new path at the beginning +$newPathArray = @($newPath) + $filteredPaths + +# Join and set +$finalPath = ($newPathArray | Select-Object -Unique) -join ';' +[Environment]::SetEnvironmentVariable('Path', $finalPath, 'User') + +Write-Output "PATH updated successfully" +` + + const tempPs1 = join(this.configStore.getTempPath(), 'update_node_path.ps1') + writeFileSync(tempPs1, psScript, 'utf-8') + + try { + await execAsync(`powershell -ExecutionPolicy Bypass -File "${tempPs1}"`, { timeout: 30000 }) + } finally { + try { + unlinkSync(tempPs1) + } catch (e) { + // 忽略 + } + } + } + + private getFallbackVersions(): AvailableNodeVersion[] { + return [ + { version: 'v22.12.0', date: '2024-12-03', lts: false, security: false, downloadUrl: 'https://nodejs.org/dist/v22.12.0/node-v22.12.0-win-x64.zip' }, + { version: 'v22.11.0', date: '2024-10-29', lts: 'Jod', security: false, downloadUrl: 'https://nodejs.org/dist/v22.11.0/node-v22.11.0-win-x64.zip' }, + { version: 'v20.18.1', date: '2024-11-21', lts: 'Iron', security: false, downloadUrl: 'https://nodejs.org/dist/v20.18.1/node-v20.18.1-win-x64.zip' }, + { version: 'v20.18.0', date: '2024-10-03', lts: 'Iron', security: false, downloadUrl: 'https://nodejs.org/dist/v20.18.0/node-v20.18.0-win-x64.zip' }, + { version: 'v18.20.5', date: '2024-11-21', lts: 'Hydrogen', security: false, downloadUrl: 'https://nodejs.org/dist/v18.20.5/node-v18.20.5-win-x64.zip' }, + { version: 'v18.20.4', date: '2024-08-21', lts: 'Hydrogen', security: true, downloadUrl: 'https://nodejs.org/dist/v18.20.4/node-v18.20.4-win-x64.zip' }, + ] + } +} + diff --git a/electron/services/PhpManager.ts b/electron/services/PhpManager.ts new file mode 100644 index 0000000..cc76d4a --- /dev/null +++ b/electron/services/PhpManager.ts @@ -0,0 +1,2063 @@ +import { ConfigStore } from "./ConfigStore"; +import { exec, execSync } from "child_process"; +import { promisify } from "util"; +import { + existsSync, + readFileSync, + writeFileSync, + readdirSync, + unlinkSync, + rmdirSync, + mkdirSync, +} from "fs"; +import { join } from "path"; +import https from "https"; +import http from "http"; +import { createWriteStream } from "fs"; +import { sendDownloadProgress } from "../main"; + +const execAsync = promisify(exec); + +interface PhpVersion { + version: string; + path: string; + isActive: boolean; +} + +interface PhpExtension { + name: string; + enabled: boolean; + installed: boolean; +} + +interface AvailablePeclExtension { + name: string; + version: string; + downloadUrl: string; + description?: string; + packageName?: string; // Packagist 包名,用于 PIE 安装 +} + +interface AvailablePhpVersion { + version: string; + downloadUrl: string; + type: "nts" | "ts"; + arch: "x64" | "x86"; +} + +export class PhpManager { + private configStore: ConfigStore; + + constructor(configStore: ConfigStore) { + this.configStore = configStore; + } + + /** + * 获取已安装的 PHP 版本列表 + */ + async getInstalledVersions(): Promise { + const versions: PhpVersion[] = []; + const activeVersion = this.configStore.get("activePhpVersion"); + const phpDir = join(this.configStore.getBasePath(), "php"); + + if (!existsSync(phpDir)) { + return versions; + } + + const dirs = readdirSync(phpDir, { withFileTypes: true }); + for (const dir of dirs) { + if (dir.isDirectory() && dir.name.startsWith("php-")) { + const version = dir.name.replace("php-", ""); + const phpPath = join(phpDir, dir.name); + + // 验证 PHP 是否真的存在 + if (existsSync(join(phpPath, "php.exe"))) { + versions.push({ + version, + path: phpPath, + isActive: version === activeVersion, + }); + } + } + } + + return versions.sort((a, b) => b.version.localeCompare(a.version)); + } + + /** + * 从 windows.php.net 自动获取可用的 PHP 版本列表 + */ + async getAvailableVersions(): Promise { + let versions: AvailablePhpVersion[] = []; + + try { + // 尝试从 windows.php.net/downloads/releases/ 获取版本列表 + versions = await this.fetchPhpVersionsFromWeb(); + } catch (error) { + console.error("获取 PHP 版本列表失败:", error); + } + + // 如果网络获取失败或为空,使用备用列表 + if (versions.length === 0) { + console.log("使用备用版本列表"); + versions = this.getFallbackVersions(); + } + + // 过滤掉已安装的版本 + const installed = await this.getInstalledVersions(); + const installedVersions = installed.map((v) => v.version); + + return versions.filter((v) => !installedVersions.includes(v.version)); + } + + /** + * 从网页获取 PHP 版本列表 + */ + private async fetchPhpVersionsFromWeb(): Promise { + return new Promise((resolve, reject) => { + const url = "https://windows.php.net/downloads/releases/"; + + https + .get( + url, + { + headers: { + "User-Agent": + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36", + }, + }, + (response) => { + if (response.statusCode !== 200) { + reject(new Error(`HTTP ${response.statusCode}`)); + return; + } + + let html = ""; + response.on("data", (chunk) => (html += chunk)); + response.on("end", () => { + try { + const versions = this.parsePhpVersionsFromHtml(html); + resolve(versions); + } catch (e) { + reject(e); + } + }); + } + ) + .on("error", reject) + .setTimeout(10000, () => { + reject(new Error("请求超时")); + }); + }); + } + + /** + * 解析 HTML 获取 PHP 版本信息 + */ + private parsePhpVersionsFromHtml(html: string): AvailablePhpVersion[] { + const versions: AvailablePhpVersion[] = []; + const seenVersions = new Set(); + + // 多种正则模式匹配不同格式 + const patterns = [ + // 标准格式: php-8.4.3-nts-Win32-vs17-x64.zip + /php-(\d+\.\d+\.\d+)-nts-Win32-vs(\d+)-x64\.zip/g, + // TS格式: php-8.4.3-Win32-vs17-x64.zip + /php-(\d+\.\d+\.\d+)-Win32-vs(\d+)-x64\.zip/g, + ]; + + // 匹配 NTS 版本 + let match; + const ntsRegex = /php-(\d+\.\d+\.\d+)-nts-Win32-vs(\d+)-x64\.zip/g; + while ((match = ntsRegex.exec(html)) !== null) { + const version = match[1]; + const vsVersion = match[2]; + const versionKey = `${version}-nts`; + + if (!seenVersions.has(versionKey)) { + seenVersions.add(versionKey); + versions.push({ + version: version, + downloadUrl: `https://windows.php.net/downloads/releases/php-${version}-nts-Win32-vs${vsVersion}-x64.zip`, + type: "nts", + arch: "x64", + }); + } + } + + // 匹配 TS 版本 (不含 nts 的) + const tsRegex = /php-(\d+\.\d+\.\d+)-Win32-vs(\d+)-x64\.zip/g; + while ((match = tsRegex.exec(html)) !== null) { + const fullMatch = match[0]; + // 跳过 nts 版本(已经处理过) + if (fullMatch.includes("-nts-")) continue; + + const version = match[1]; + const vsVersion = match[2]; + const versionKey = `${version}-ts`; + + if (!seenVersions.has(versionKey)) { + seenVersions.add(versionKey); + versions.push({ + version: `${version}-ts`, + downloadUrl: `https://windows.php.net/downloads/releases/php-${version}-Win32-vs${vsVersion}-x64.zip`, + type: "ts", + arch: "x64", + }); + } + } + + // 按版本号排序(降序) + versions.sort((a, b) => { + const vA = a.version.replace("-ts", ""); + const vB = b.version.replace("-ts", ""); + return vB.localeCompare(vA, undefined, { numeric: true }); + }); + + console.log(`从 windows.php.net 获取到 ${versions.length} 个 PHP 版本`); + if (versions.length > 0) { + console.log("版本列表:", versions.map((v) => v.version).join(", ")); + } + return versions; + } + + /** + * 备用版本列表(当网络请求失败时使用) + * 基于 https://windows.php.net/download/ (2025-12-25) + */ + private getFallbackVersions(): AvailablePhpVersion[] { + return [ + // PHP 8.4 (VS17) - 最新稳定版 + { + version: "8.4.3", + downloadUrl: + "https://windows.php.net/downloads/releases/php-8.4.3-nts-Win32-vs17-x64.zip", + type: "nts", + arch: "x64", + }, + { + version: "8.4.3-ts", + downloadUrl: + "https://windows.php.net/downloads/releases/php-8.4.3-Win32-vs17-x64.zip", + type: "ts", + arch: "x64", + }, + + // PHP 8.3 (VS16) + { + version: "8.3.15", + downloadUrl: + "https://windows.php.net/downloads/releases/php-8.3.15-nts-Win32-vs16-x64.zip", + type: "nts", + arch: "x64", + }, + { + version: "8.3.15-ts", + downloadUrl: + "https://windows.php.net/downloads/releases/php-8.3.15-Win32-vs16-x64.zip", + type: "ts", + arch: "x64", + }, + + // PHP 8.2 (VS16) + { + version: "8.2.27", + downloadUrl: + "https://windows.php.net/downloads/releases/php-8.2.27-nts-Win32-vs16-x64.zip", + type: "nts", + arch: "x64", + }, + { + version: "8.2.27-ts", + downloadUrl: + "https://windows.php.net/downloads/releases/php-8.2.27-Win32-vs16-x64.zip", + type: "ts", + arch: "x64", + }, + + // PHP 8.1 (VS16) + { + version: "8.1.31", + downloadUrl: + "https://windows.php.net/downloads/releases/php-8.1.31-nts-Win32-vs16-x64.zip", + type: "nts", + arch: "x64", + }, + { + version: "8.1.31-ts", + downloadUrl: + "https://windows.php.net/downloads/releases/php-8.1.31-Win32-vs16-x64.zip", + type: "ts", + arch: "x64", + }, + ]; + } + + /** + * 安装 PHP 版本 + */ + async install( + version: string + ): Promise<{ success: boolean; message: string }> { + try { + const available = await this.getAvailableVersions(); + const versionInfo = available.find((v) => v.version === version); + + if (!versionInfo) { + return { success: false, message: `未找到 PHP ${version} 版本` }; + } + + const phpPath = this.configStore.getPhpPath(version); + const tempPath = this.configStore.getTempPath(); + const zipPath = join(tempPath, `php-${version}.zip`); + + // 确保目录存在 + if (!existsSync(tempPath)) { + mkdirSync(tempPath, { recursive: true }); + } + if (!existsSync(phpPath)) { + mkdirSync(phpPath, { recursive: true }); + } + + console.log(`开始下载 PHP ${version} 从 ${versionInfo.downloadUrl}`); + + // 下载 PHP + await this.downloadFile(versionInfo.downloadUrl, zipPath); + + console.log(`下载完成,开始解压到 ${phpPath}`); + + // 解压 + await this.unzip(zipPath, phpPath); + + console.log("解压完成"); + + // 删除临时文件 + if (existsSync(zipPath)) { + unlinkSync(zipPath); + } + + // 创建默认 php.ini + await this.createDefaultPhpIni(phpPath); + + // 添加到配置 + this.configStore.addPhpVersion(version); + + return { success: true, message: `PHP ${version} 安装成功` }; + } catch (error: any) { + console.error("PHP 安装失败:", error); + return { success: false, message: `安装失败: ${error.message}` }; + } + } + + /** + * 卸载 PHP 版本 + */ + async uninstall( + version: string + ): Promise<{ success: boolean; message: string }> { + try { + const phpPath = this.configStore.getPhpPath(version); + + if (!existsSync(phpPath)) { + return { success: false, message: `PHP ${version} 未安装` }; + } + + // 如果是当前活动版本,先清除环境变量 + const activeVersion = this.configStore.get("activePhpVersion"); + if (activeVersion === version) { + await this.removeFromPath(phpPath); + this.configStore.set("activePhpVersion", ""); + } + + // 递归删除目录 + this.removeDirectory(phpPath); + + // 从配置中移除 + this.configStore.removePhpVersion(version); + + return { success: true, message: `PHP ${version} 已卸载` }; + } catch (error: any) { + return { success: false, message: `卸载失败: ${error.message}` }; + } + } + + /** + * 设置活动的 PHP 版本(添加到环境变量) + */ + async setActive( + version: string + ): Promise<{ success: boolean; message: string }> { + try { + const phpPath = this.configStore.getPhpPath(version); + + if (!existsSync(phpPath)) { + return { success: false, message: `PHP ${version} 未安装` }; + } + + // 检查 php.exe 是否存在 + if (!existsSync(join(phpPath, "php.exe"))) { + return { + success: false, + message: `PHP ${version} 安装不完整,找不到 php.exe`, + }; + } + + console.log(`设置 PHP ${version} 为默认版本,路径: ${phpPath}`); + + // 添加新的 PHP 路径到环境变量(会自动移除旧的 PHP 路径) + await this.addToPath(phpPath); + + // 更新配置 + this.configStore.set("activePhpVersion", version); + + return { + success: true, + message: `PHP ${version} 已设置为默认版本\n\n环境变量已更新,新开的终端窗口中将生效。\n路径: ${phpPath}`, + }; + } catch (error: any) { + console.error("设置默认 PHP 版本失败:", error); + return { success: false, message: `设置失败: ${error.message}` }; + } + } + + /** + * 打开扩展目录(用于手动安装扩展) + */ + async openExtensionDir( + version: string + ): Promise<{ success: boolean; message: string; path?: string }> { + try { + const phpPath = this.configStore.getPhpPath(version); + const extDir = join(phpPath, "ext"); + + if (!existsSync(extDir)) { + mkdirSync(extDir, { recursive: true }); + } + + // 使用 Electron shell 打开文件夹(更可靠) + const { shell } = await import("electron"); + const result = await shell.openPath(extDir); + + if (result) { + // result 非空表示有错误 + return { success: false, message: `打开失败: ${result}` }; + } + + return { + success: true, + message: `已打开扩展目录: ${extDir}`, + path: extDir, + }; + } catch (error: any) { + return { success: false, message: `打开失败: ${error.message}` }; + } + } + + /** + * 获取 PHP 扩展列表 + */ + async getExtensions(version: string): Promise { + const phpPath = this.configStore.getPhpPath(version); + const extDir = join(phpPath, "ext"); + const iniPath = join(phpPath, "php.ini"); + + if (!existsSync(extDir)) { + return []; + } + + const extensions: PhpExtension[] = []; + const iniContent = existsSync(iniPath) + ? readFileSync(iniPath, "utf-8") + : ""; + + // 将 ini 内容按行分割,用于精确匹配 + const iniLines = iniContent.split("\n"); + + const files = readdirSync(extDir); + for (const file of files) { + if (file.startsWith("php_") && file.endsWith(".dll")) { + const extName = file.replace("php_", "").replace(".dll", ""); + + // 检查是否有未被注释的 extension= 行 + const isEnabled = iniLines.some((line) => { + const trimmedLine = line.trim(); + // 跳过注释行 + if (trimmedLine.startsWith(";")) { + return false; + } + // 检查各种可能的格式 + return ( + trimmedLine === `extension=${extName}` || + trimmedLine === `extension=php_${extName}.dll` || + trimmedLine === `extension=${extName}.dll` || + trimmedLine.startsWith(`extension=${extName} `) || + trimmedLine.startsWith(`extension=php_${extName}.dll `) || + trimmedLine.startsWith(`extension=${extName}.dll `) + ); + }); + + extensions.push({ + name: extName, + enabled: isEnabled, + installed: true, + }); + } + } + + return extensions.sort((a, b) => a.name.localeCompare(b.name)); + } + + /** + * 启用扩展 + */ + async enableExtension( + version: string, + ext: string + ): Promise<{ success: boolean; message: string }> { + try { + const phpPath = this.configStore.getPhpPath(version); + const iniPath = join(phpPath, "php.ini"); + + if (!existsSync(iniPath)) { + return { success: false, message: "php.ini 文件不存在" }; + } + + let content = readFileSync(iniPath, "utf-8"); + const lines = content.split("\n"); + let found = false; + let alreadyEnabled = false; + + // 扩展可能的格式 + const patterns = [ + new RegExp(`^;?\\s*extension\\s*=\\s*${ext}\\s*$`, "i"), + new RegExp(`^;?\\s*extension\\s*=\\s*php_${ext}\\.dll\\s*$`, "i"), + new RegExp(`^;?\\s*extension\\s*=\\s*${ext}\\.dll\\s*$`, "i"), + ]; + + for (let i = 0; i < lines.length; i++) { + for (const pattern of patterns) { + if (pattern.test(lines[i])) { + found = true; + if (lines[i].trim().startsWith(";")) { + // 取消注释 + lines[i] = lines[i].replace(/^(\s*);/, "$1"); + } else { + alreadyEnabled = true; + } + break; + } + } + } + + if (alreadyEnabled) { + return { success: true, message: `扩展 ${ext} 已经启用` }; + } + + if (!found) { + // 添加新的扩展配置 + // 查找 Dynamic Extensions 区域或文件末尾 + let insertIndex = lines.findIndex( + (l) => l.includes("[PHP]") || l.includes("Dynamic Extensions") + ); + if (insertIndex === -1) { + insertIndex = lines.length; + } else { + // 在该区域后找到合适位置 + for (let i = insertIndex + 1; i < lines.length; i++) { + if (lines[i].startsWith("[") && !lines[i].includes("Dynamic")) { + insertIndex = i; + break; + } + if (lines[i].includes("extension=")) { + insertIndex = i + 1; + } + } + } + lines.splice(insertIndex, 0, `extension=${ext}`); + } + + writeFileSync(iniPath, lines.join("\n")); + return { success: true, message: `扩展 ${ext} 已启用,重启 PHP 后生效` }; + } catch (error: any) { + return { success: false, message: `启用扩展失败: ${error.message}` }; + } + } + + /** + * 禁用扩展 + */ + async disableExtension( + version: string, + ext: string + ): Promise<{ success: boolean; message: string }> { + try { + const phpPath = this.configStore.getPhpPath(version); + const iniPath = join(phpPath, "php.ini"); + + if (!existsSync(iniPath)) { + return { success: false, message: "php.ini 文件不存在" }; + } + + let content = readFileSync(iniPath, "utf-8"); + const lines = content.split("\n"); + let found = false; + + // 扩展可能的格式 + const patterns = [ + new RegExp(`^\\s*extension\\s*=\\s*${ext}\\s*$`, "i"), + new RegExp(`^\\s*extension\\s*=\\s*php_${ext}\\.dll\\s*$`, "i"), + new RegExp(`^\\s*extension\\s*=\\s*${ext}\\.dll\\s*$`, "i"), + ]; + + for (let i = 0; i < lines.length; i++) { + for (const pattern of patterns) { + if (pattern.test(lines[i])) { + found = true; + if (!lines[i].trim().startsWith(";")) { + // 注释掉 + lines[i] = ";" + lines[i]; + } + break; + } + } + } + + if (!found) { + return { success: true, message: `扩展 ${ext} 未找到或已禁用` }; + } + + writeFileSync(iniPath, lines.join("\n")); + return { success: true, message: `扩展 ${ext} 已禁用,重启 PHP 后生效` }; + } catch (error: any) { + return { success: false, message: `禁用扩展失败: ${error.message}` }; + } + } + + /** + * 获取可安装的 PECL 扩展列表(从 pecl.php.net 搜索) + */ + async getAvailableExtensions( + version: string, + searchKeyword?: string + ): Promise { + const extensions: AvailablePeclExtension[] = []; + + try { + // 获取 PHP 版本信息 + const phpPath = this.configStore.getPhpPath(version); + const phpInfo = await this.getPhpInfo(phpPath); + + if (!phpInfo) { + console.error("无法获取 PHP 信息"); + return this.getDefaultExtensionList(version); + } + + const { majorMinor, isNts } = phpInfo; + console.log(`PHP Info: ${majorMinor}, NTS: ${isNts}`); + + // 使用 Packagist API 获取扩展列表(PIE 推荐方式) + // https://packagist.org/extensions + const keyword = searchKeyword || ""; + const searchUrl = keyword + ? `https://packagist.org/search.json?q=${encodeURIComponent( + keyword + )}&type=php-ext` + : `https://packagist.org/search.json?type=php-ext`; + + console.log(`从 Packagist 搜索扩展: ${keyword || "(全部)"}`); + + let foundPackages: { + name: string; + description: string; + packageName: string; + }[] = []; + + try { + const jsonStr = await this.fetchHtmlContent(searchUrl); + const data = JSON.parse(jsonStr); + + if (data.results && Array.isArray(data.results)) { + for (const pkg of data.results) { + // Packagist 包名格式:vendor/package + const packageName = pkg.name || ""; + // 扩展名通常是包名的最后一部分,或从 description 提取 + let extName = packageName.split("/").pop() || ""; + // 移除常见前缀 + extName = extName + .replace(/^php[-_]?/, "") + .replace(/[-_]?extension$/, ""); + + foundPackages.push({ + name: extName, + description: pkg.description || "", + packageName: packageName, + }); + } + } + console.log(`从 Packagist 找到 ${foundPackages.length} 个扩展包`); + } catch (e: any) { + console.log(`Packagist API 请求失败: ${e.message},尝试使用预定义列表`); + } + + // 如果 Packagist 无结果,使用预定义的常用扩展列表 + if (foundPackages.length === 0) { + foundPackages = this.getPopularExtensionsList(keyword); + console.log(`使用预定义扩展列表: ${foundPackages.length} 个`); + } + + // 获取已安装的扩展 + const installedExts = await this.getExtensions(version); + const installedNames = installedExts.map((e) => e.name.toLowerCase()); + + // 过滤已安装的扩展 + const availablePackages = foundPackages.filter( + (pkg) => !installedNames.includes(pkg.name.toLowerCase()) + ); + + // 限制数量 + const checkPackages = availablePackages.slice(0, searchKeyword ? 50 : 20); + + for (const pkg of checkPackages) { + extensions.push({ + name: pkg.name, + version: "latest", + downloadUrl: "", // PIE 会自动处理 + description: pkg.description, + packageName: pkg.packageName, + } as AvailablePeclExtension & { packageName?: string }); + } + + console.log(`找到 ${extensions.length} 个可安装的扩展`); + return extensions.sort((a, b) => a.name.localeCompare(b.name)); + } catch (error: any) { + console.error("获取可用扩展列表失败:", error); + return this.getDefaultExtensionList(version); + } + } + + /** + * 获取常用 PHP 扩展列表(带 Packagist 包名) + * PIE 包名格式参考: https://packagist.org/extensions + */ + private getPopularExtensionsList( + keyword?: string + ): { name: string; description: string; packageName: string }[] { + // PIE 兼容的扩展包名(vendor/package 格式) + const popularExtensions = [ + { + name: "redis", + description: "PHP extension for Redis", + packageName: "phpredis/phpredis", // 正确的 PIE 包名 + }, + { + name: "mongodb", + description: "MongoDB driver for PHP", + packageName: "mongodb/mongodb", // MongoDB 官方扩展 + }, + { + name: "memcached", + description: "PHP extension for Memcached", + packageName: "php-memcached-dev/php-memcached", + }, + { + name: "imagick", + description: "ImageMagick for PHP", + packageName: "imagick/imagick", + }, + { + name: "xdebug", + description: "Debugging and profiling for PHP", + packageName: "xdebug/xdebug", + }, + { + name: "swoole", + description: "High-performance coroutine framework", + packageName: "openswoole/swoole", // OpenSwoole(PIE 兼容) + }, + { + name: "yaml", + description: "YAML parser and emitter", + packageName: "php/pecl-file_formats-yaml", + }, + { + name: "apcu", + description: "APCu - APC User Cache", + packageName: "apcu/apcu", // 正确的 PIE 包名 + }, + { name: "grpc", description: "gRPC for PHP", packageName: "grpc/grpc" }, + { + name: "protobuf", + description: "Protocol Buffers", + packageName: "google/protobuf", + }, + { + name: "igbinary", + description: "Binary serialization", + packageName: "igbinary/igbinary", + }, + { + name: "msgpack", + description: "MessagePack serialization", + packageName: "msgpack/msgpack-php", + }, + { + name: "sodium", + description: "Modern cryptography library", + packageName: "php/pecl-crypto-sodium", + }, + { + name: "zip", + description: "ZIP file support", + packageName: "php/pecl-file_formats-zip", + }, + { + name: "rar", + description: "RAR archive support", + packageName: "php/pecl-file_formats-rar", + }, + { + name: "amqp", + description: "AMQP messaging library", + packageName: "php-amqp/php-amqp", + }, + { + name: "oauth", + description: "OAuth consumer extension", + packageName: "php/pecl-web_services-oauth", + }, + { + name: "ssh2", + description: "SSH2 bindings", + packageName: "php/pecl-networking-ssh2", + }, + { + name: "event", + description: "Event-based I/O", + packageName: "php/pecl-event", + }, + { + name: "uv", + description: "libuv bindings", + packageName: "amphp/ext-uv", + }, + ]; + + if (!keyword) { + return popularExtensions; + } + + const lowerKeyword = keyword.toLowerCase(); + return popularExtensions.filter( + (ext) => + ext.name.toLowerCase().includes(lowerKeyword) || + ext.description.toLowerCase().includes(lowerKeyword) || + ext.packageName.toLowerCase().includes(lowerKeyword) + ); + } + + /** + * 解码 HTML 实体 + */ + private decodeHtmlEntities(html: string): string { + return html + .replace(/./g, ".") + .replace(///g, "/") + .replace(/&/g, "&") + .replace(/</g, "<") + .replace(/>/g, ">") + .replace(/"/g, '"') + .replace(/&#(\d+);/g, (_, code) => + String.fromCharCode(parseInt(code, 10)) + ) + .replace(/ /g, " "); + } + + /** + * 从 PECL 详情页获取扩展信息 + * 流程: + * 1. 访问 https://pecl.php.net/package/扩展名 获取最新稳定版本 + * 2. 访问 https://pecl.php.net/package/扩展名/版本/windows 获取 Windows DLL 链接 + */ + private async getExtensionFromPecl( + extName: string, + phpInfo: { majorMinor: string; isNts: boolean; compiler: string } + ): Promise { + const { majorMinor, isNts } = phpInfo; + + try { + // 1. 获取扩展详情页,找到最新稳定版本 + const packageUrl = `https://pecl.php.net/package/${extName}`; + console.log(`获取扩展详情: ${packageUrl}`); + let packageHtml = await this.fetchHtmlContent(packageUrl); + + console.log(`获取到 HTML 长度: ${packageHtml.length}`); + if (packageHtml.length < 1000) { + console.log( + `HTML 内容过短,可能获取失败: ${packageHtml.substring(0, 500)}` + ); + } + + // 解码 HTML 实体(如 . -> . , / -> /) + packageHtml = this.decodeHtmlEntities(packageHtml); + console.log(`解码后 HTML 长度: ${packageHtml.length}`); + + // 解析版本列表,找到最新的稳定版本(state=stable 且有 DLL 链接) + // 分步解析: + // 1. 找到所有表格行 ... + // 2. 检查每行是否包含 DLL 链接和版本信息 + + let latestStableVersion: string | null = null; + let latestBetaVersion: string | null = null; + + // 方法1:直接搜索带 DLL 链接的版本 + // 格式: /package/xxx/VERSION/windows">...DLL + const dllVersionRegex = /\/package\/[^/]+\/([\d.]+(?:RC\d+)?)\/windows/gi; + const versionsWithDll: string[] = []; + let dllMatch; + + // 调试:打印部分 HTML 内容查看格式 + const windowsLinkIndex = packageHtml.indexOf("/windows"); + if (windowsLinkIndex > 0) { + console.log( + `HTML 样本 (解码后): ${packageHtml.substring( + Math.max(0, windowsLinkIndex - 100), + windowsLinkIndex + 50 + )}` + ); + } else { + // 可能解码不完整,检查是否还有编码的 windows + console.log("解码后未找到 /windows,检查原始 HTML..."); + // 尝试多种可能的编码形式 + const patterns = ['windows"', "windows<", "DLL", "/windows"]; + for (const pattern of patterns) { + const idx = packageHtml.indexOf(pattern); + if (idx > 0) { + console.log( + `找到 "${pattern}" 在位置 ${idx}: ${packageHtml.substring( + Math.max(0, idx - 80), + idx + 40 + )}` + ); + break; + } + } + } + + while ((dllMatch = dllVersionRegex.exec(packageHtml)) !== null) { + const ver = dllMatch[1]; + if (!versionsWithDll.includes(ver)) { + versionsWithDll.push(ver); + console.log(`找到带 DLL 的版本: ${ver}`); + } + } + + // 对每个有 DLL 的版本,检查其状态 + for (const ver of versionsWithDll) { + // 在 HTML 中找到这个版本对应的行,检查是 stable 还是 beta + // 格式: >VERSION...stable 或 >VERSION...beta + const stateRegex = new RegExp( + `>${ver.replace( + /\./g, + "\\." + )}[\\s\\S]*?]*>\\s*(stable|beta|alpha)\\s*`, + "i" + ); + const stateMatch = stateRegex.exec(packageHtml); + + if (stateMatch) { + const state = stateMatch[1].toLowerCase(); + console.log(`版本 ${ver} 状态: ${state}`); + + if (state === "stable" && !latestStableVersion) { + latestStableVersion = ver; + break; // 找到第一个稳定版本就停止 + } else if ( + (state === "beta" || state === "alpha") && + !latestBetaVersion + ) { + latestBetaVersion = ver; + } + } + } + + let targetVersion = latestStableVersion || latestBetaVersion; + + // 如果正则匹配失败,直接使用第一个有 DLL 的版本 + if (!targetVersion && versionsWithDll.length > 0) { + targetVersion = versionsWithDll[0]; + console.log(`未能确定状态,使用第一个有 DLL 的版本: ${targetVersion}`); + } + + if (!targetVersion) { + console.log(`扩展 ${extName} 没有 Windows DLL`); + return null; + } + + console.log(`扩展 ${extName} 最新版本: ${targetVersion}`); + + // 2. 获取 Windows DLL 页面 + const windowsUrl = `https://pecl.php.net/package/${extName}/${targetVersion}/windows`; + console.log(`获取 Windows DLL 列表: ${windowsUrl}`); + const windowsHtml = await this.fetchHtmlContent(windowsUrl); + + // 3. 查找匹配当前 PHP 版本的 DLL 链接 + // 实际 URL 格式:https://downloads.php.net/~windows/pecl/releases/redis/6.3.0/php_redis-6.3.0-8.3-nts-vs16-x64.zip + // 注意:URL 中的下划线可能被编码为 %5F,如 php%5Fredis + // 链接文本有换行和大量空格 + + const tsType = isNts ? "nts" : "ts"; + + // 提取所有 pecl releases 的 zip 链接 + const allLinksRegex = + / x86 + let matchedUrl: string | null = null; + + for (const url of allLinks) { + // 解码 URL(%5F -> _) + const decodedUrl = decodeURIComponent(url).toLowerCase(); + + // 检查是否匹配 PHP 版本和 NTS/TS + // 格式: -8.3-nts- 或 -8.3-ts- + const versionPattern = `-${majorMinor}-${tsType}-`; + + if (decodedUrl.includes(versionPattern)) { + // 优先选择 x64 + if (decodedUrl.includes("x64")) { + matchedUrl = url; + console.log(`匹配到 x64: ${url}`); + break; + } else if (!matchedUrl && decodedUrl.includes("x86")) { + matchedUrl = url; + console.log(`匹配到 x86: ${url}`); + // 继续查找,看有没有 x64 + } + } + } + + if (matchedUrl) { + console.log(`找到 ${extName} ${targetVersion} 的 DLL: ${matchedUrl}`); + + return { + name: extName, + version: targetVersion, + downloadUrl: matchedUrl, + description: await this.getExtensionDescription(extName), + }; + } + + console.log( + `扩展 ${extName} 没有适用于 PHP ${majorMinor} ${ + isNts ? "NTS" : "TS" + } 的 DLL` + ); + console.log(`可用链接: ${allLinks.slice(0, 5).join(", ")}...`); + return null; + } catch (error: any) { + console.error(`获取扩展 ${extName} 失败:`, error.message); + return null; + } + } + + /** + * 从 windows.php.net 获取扩展列表(备用方法) + */ + private async getExtensionsFromWindowsPhp( + version: string, + phpInfo: { majorMinor: string; isNts: boolean; compiler: string }, + searchKeyword?: string + ): Promise { + const extensions: AvailablePeclExtension[] = []; + const { majorMinor, isNts, compiler } = phpInfo; + + try { + const peclUrl = "https://windows.php.net/downloads/pecl/releases/"; + const html = await this.fetchHtmlContent(peclUrl); + + // 解析扩展目录 + const extDirRegex = //g; + let match; + const extNames: string[] = []; + + while ((match = extDirRegex.exec(html)) !== null) { + const extName = match[1]; + if (extName && !extName.startsWith(".") && extName !== "snaps") { + extNames.push(extName); + } + } + + // 获取已安装的扩展 + const installedExts = await this.getExtensions(version); + const installedNames = installedExts.map((e) => e.name.toLowerCase()); + + // 过滤搜索关键词 + let filteredNames = extNames; + if (searchKeyword) { + const keyword = searchKeyword.toLowerCase(); + filteredNames = extNames.filter((name) => + name.toLowerCase().includes(keyword) + ); + } + + // 限制检查数量 + const checkNames = filteredNames.slice(0, searchKeyword ? 100 : 30); + + for (const extName of checkNames) { + if (installedNames.includes(extName.toLowerCase())) continue; + + try { + const extUrl = `${peclUrl}${extName}/`; + const extHtml = await this.fetchHtmlContent(extUrl); + + const versionDirRegex = //g; + const versions: string[] = []; + let vMatch; + + while ((vMatch = versionDirRegex.exec(extHtml)) !== null) { + versions.push(vMatch[1]); + } + + if (versions.length > 0) { + versions.sort((a, b) => + b.localeCompare(a, undefined, { numeric: true }) + ); + const latestVersion = versions[0]; + + const tsType = isNts ? "nts" : "ts"; + const dllPattern = `php_${extName}-${latestVersion}-${majorMinor}-${tsType}-${compiler}-x64.zip`; + const dllUrl = `${extUrl}${latestVersion}/${dllPattern}`; + + const exists = await this.checkUrlExists(dllUrl); + + if (exists) { + extensions.push({ + name: extName, + version: latestVersion, + downloadUrl: dllUrl, + }); + } + } + } catch (e) { + continue; + } + } + + return extensions.sort((a, b) => a.name.localeCompare(b.name)); + } catch (error) { + console.error("从 windows.php.net 获取扩展失败:", error); + return this.getDefaultExtensionList(version); + } + } + + /** + * 获取扩展描述(简化版) + */ + private async getExtensionDescription( + extName: string + ): Promise { + const descriptions: Record = { + redis: "PHP Redis 客户端扩展", + memcached: "Memcached 缓存客户端", + mongodb: "MongoDB 数据库驱动", + imagick: "ImageMagick 图像处理", + xdebug: "调试和性能分析工具", + apcu: "用户数据缓存", + yaml: "YAML 数据格式支持", + swoole: "高性能异步网络框架", + igbinary: "高效二进制序列化", + ssh2: "SSH2 协议支持", + grpc: "gRPC 远程调用支持", + protobuf: "Protocol Buffers 支持", + rar: "RAR 压缩文件支持", + zip: "ZIP 压缩文件支持", + oauth: "OAuth 认证支持", + mailparse: "邮件解析扩展", + uuid: "UUID 生成支持", + xlswriter: "Excel 文件写入", + event: "事件驱动扩展", + ev: "libev 事件循环", + }; + return descriptions[extName.toLowerCase()]; + } + + /** + * 构建 PECL DLL 直接下载链接 + */ + private async buildPeclDownloadUrl( + extName: string, + phpInfo: { majorMinor: string; isNts: boolean; compiler: string } + ): Promise { + const { majorMinor, isNts, compiler } = phpInfo; + const tsType = isNts ? "nts" : "ts"; + + // 常用扩展的 PECL 包名映射 + const extNameMapping: { [key: string]: string } = { + redis: "redis", + mongodb: "mongodb", + memcached: "memcached", + imagick: "imagick", + xdebug: "xdebug", + swoole: "swoole", + yaml: "yaml", + apcu: "apcu", + igbinary: "igbinary", + msgpack: "msgpack", + grpc: "grpc", + protobuf: "protobuf", + amqp: "amqp", + ssh2: "ssh2", + event: "event", + oauth: "oauth", + rar: "rar", + zip: "zip", + }; + + const peclExtName = + extNameMapping[extName.toLowerCase()] || extName.toLowerCase(); + + try { + // 先获取最新版本 + const packageUrl = `https://pecl.php.net/package/${peclExtName}`; + console.log(`从 PECL 获取 ${peclExtName} 版本列表: ${packageUrl}`); + let html = await this.fetchHtmlContent(packageUrl); + + console.log(`获取到 HTML 长度: ${html.length}`); + + // 解码 HTML 实体 + html = this.decodeHtmlEntities(html); + + // 查找有 DLL 的版本 - 格式: /package/redis/6.3.0/windows + const dllVersionRegex = /\/package\/[^\/]+\/([\d.]+(?:RC\d+)?)\/windows/g; + const matches = html.match(dllVersionRegex); + + console.log(`找到 DLL 链接: ${matches ? matches.length : 0} 个`); + + let latestVersion: string | null = null; + if (matches && matches.length > 0) { + // 从第一个匹配中提取版本号 + const versionMatch = matches[0].match(/\/([\d.]+(?:RC\d+)?)\/windows/); + if (versionMatch) { + latestVersion = versionMatch[1]; + } + } + + if (!latestVersion) { + console.log(`未找到 ${peclExtName} 的 Windows DLL 版本`); + // 尝试直接用最新版本号 + const anyVersionMatch = html.match(/\/package\/[^\/]+\/([\d.]+)["'>]/); + if (anyVersionMatch) { + latestVersion = anyVersionMatch[1]; + console.log(`尝试使用版本: ${latestVersion}`); + } else { + return null; + } + } + + console.log(`找到 ${peclExtName} 版本: ${latestVersion}`); + + // 构建下载链接 + // 格式: https://downloads.php.net/~windows/pecl/releases/redis/6.3.0/php_redis-6.3.0-8.4-nts-vs17-x64.zip + const possibleUrls = [ + `https://downloads.php.net/~windows/pecl/releases/${peclExtName}/${latestVersion}/php_${peclExtName}-${latestVersion}-${majorMinor}-${tsType}-${compiler}-x64.zip`, + `https://downloads.php.net/~windows/pecl/releases/${peclExtName}/${latestVersion}/php_${peclExtName}-${latestVersion}-${majorMinor}-${tsType}-${compiler}-x86.zip`, + // 备选格式(不同编译器版本) + `https://downloads.php.net/~windows/pecl/releases/${peclExtName}/${latestVersion}/php_${peclExtName}-${latestVersion}-${majorMinor}-${tsType}-vs16-x64.zip`, + `https://downloads.php.net/~windows/pecl/releases/${peclExtName}/${latestVersion}/php_${peclExtName}-${latestVersion}-${majorMinor}-${tsType}-vc15-x64.zip`, + ]; + + // 检查哪个 URL 有效 + for (const url of possibleUrls) { + console.log(`检查 URL: ${url}`); + const exists = await this.checkUrlExists(url); + if (exists) { + console.log(`找到有效的 PECL DLL: ${url}`); + return url; + } + } + + // 如果精确匹配失败,尝试从 Windows 页面解析 + const windowsUrl = `https://pecl.php.net/package/${peclExtName}/${latestVersion}/windows`; + console.log(`从 Windows 页面查找: ${windowsUrl}`); + const windowsHtml = await this.fetchHtmlContent(windowsUrl); + + // 查找匹配的 DLL 链接 + const allLinksRegex = + / { + try { + const phpExe = join(phpPath, "php.exe"); + if (!existsSync(phpExe)) return null; + + const { stdout } = await execAsync(`"${phpExe}" -i`, { + windowsHide: true, + timeout: 10000, + }); + + // 解析 PHP 版本 + const versionMatch = stdout.match(/PHP Version => (\d+\.\d+)/); + const majorMinor = versionMatch ? versionMatch[1] : "8.3"; + + // 检查是否是 NTS + const isNts = stdout.includes("Thread Safety => disabled"); + + // 解析编译器版本 + const compilerMatch = + stdout.match(/Compiler => MSVC(\d+)/) || + stdout.match(/Visual C\+\+ (\d{4})/); + let compiler = "vc15"; // 默认值 + if (compilerMatch) { + const msvcVersion = parseInt(compilerMatch[1]); + if (msvcVersion >= 1930 || compilerMatch[1] === "2022") { + compiler = "vs17"; + } else if (msvcVersion >= 1920 || compilerMatch[1] === "2019") { + compiler = "vs16"; + } else if (msvcVersion >= 1910 || compilerMatch[1] === "2017") { + compiler = "vc15"; + } + } + + // 也从目录名获取信息 + const pathParts = phpPath.toLowerCase(); + if (pathParts.includes("vs17")) compiler = "vs17"; + else if (pathParts.includes("vs16")) compiler = "vs16"; + + console.log( + `PHP Info: ${majorMinor}, NTS: ${isNts}, Compiler: ${compiler}` + ); + return { majorMinor, isNts, compiler }; + } catch (error) { + console.error("获取 PHP 信息失败:", error); + return null; + } + } + + /** + * 获取默认扩展列表(当在线获取失败时使用) + */ + private getDefaultExtensionList(version: string): AvailablePeclExtension[] { + // 常用的 PECL 扩展 + const commonExtensions = [ + { name: "redis", description: "Redis 缓存扩展" }, + { name: "memcached", description: "Memcached 缓存扩展" }, + { name: "mongodb", description: "MongoDB 数据库扩展" }, + { name: "imagick", description: "图像处理扩展" }, + { name: "xdebug", description: "调试和分析扩展" }, + { name: "apcu", description: "APCu 用户缓存扩展" }, + { name: "yaml", description: "YAML 解析扩展" }, + { name: "swoole", description: "高性能网络框架扩展" }, + { name: "igbinary", description: "高效序列化扩展" }, + { name: "ssh2", description: "SSH2 连接扩展" }, + ]; + + return commonExtensions.map((ext) => ({ + name: ext.name, + version: "latest", + downloadUrl: `https://windows.php.net/downloads/pecl/releases/${ext.name}/`, + description: ext.description, + })); + } + + /** + * 确保 PIE (PHP Installer for Extensions) 已安装 + * 优先使用 Windows 可执行文件版本(实验性),备用 phar 版本 + * 参考: https://github.com/php/pie/blob/1.4.x/docs/usage.md + */ + private async ensurePieInstalled(): Promise<{ + path: string; + isExe: boolean; + } | null> { + const pieDir = join(this.configStore.getBasePath(), "tools"); + const pieExePath = join(pieDir, "pie.exe"); + const piePharPath = join(pieDir, "pie.phar"); + + // 优先检查 exe 版本 + if (existsSync(pieExePath)) { + return { path: pieExePath, isExe: true }; + } + + // 检查 phar 版本 + if (existsSync(piePharPath)) { + return { path: piePharPath, isExe: false }; + } + + // 下载 PIE + console.log("正在下载 PIE (PHP Installer for Extensions)..."); + mkdirSync(pieDir, { recursive: true }); + + // 尝试下载 Windows 可执行文件版本(实验性但更可靠) + try { + const pieExeUrl = "https://php.github.io/pie/pie-Windows-X64.exe"; + console.log(`尝试下载 PIE Windows 可执行文件: ${pieExeUrl}`); + await this.downloadFile(pieExeUrl, pieExePath); + console.log("PIE Windows 可执行文件下载完成"); + return { path: pieExePath, isExe: true }; + } catch (error: any) { + console.error("下载 PIE exe 失败,尝试 phar 版本:", error.message); + } + + // 备用:下载 phar 版本 + try { + const piePharUrl = + "https://github.com/php/pie/releases/latest/download/pie.phar"; + console.log(`下载 PIE phar: ${piePharUrl}`); + await this.downloadFile(piePharUrl, piePharPath); + console.log("PIE phar 下载完成"); + return { path: piePharPath, isExe: false }; + } catch (error: any) { + console.error("下载 PIE phar 失败:", error.message); + return null; + } + } + + /** + * 使用 PIE 安装扩展 + * 参考: https://github.com/php/pie/blob/1.4.x/docs/usage.md + * + * Windows 上使用 --with-php-path 指定目标 PHP 版本: + * pie install --with-php-path=C:\php-8.3.6\php.exe vendor/package + */ + private async installWithPie( + phpPath: string, + extName: string, + packageName?: string + ): Promise<{ success: boolean; message: string }> { + const pieInfo = await this.ensurePieInstalled(); + if (!pieInfo) { + return { success: false, message: "PIE 下载失败,无法使用 PIE 安装" }; + } + + const phpExe = join(phpPath, "php.exe"); + // 使用 package name(如 phpredis/phpredis)或扩展名 + const pkg = packageName || extName; + + try { + console.log(`使用 PIE 安装扩展: ${pkg}`); + + let cmd: string; + if (pieInfo.isExe) { + // 使用 PIE Windows 可执行文件,通过 --with-php-path 指定目标 PHP + cmd = `"${pieInfo.path}" install --with-php-path="${phpExe}" ${pkg}`; + } else { + // 使用 phar 版本,需要 PHP 来运行 + cmd = `"${phpExe}" "${pieInfo.path}" install ${pkg}`; + } + + console.log(`执行命令: ${cmd}`); + + const { stdout, stderr } = await execAsync(cmd, { + timeout: 300000, // 5 分钟超时 + windowsHide: true, + env: { + ...process.env, + // 跳过 Box Requirements Checker(如果有问题) + BOX_REQUIREMENT_CHECKER: "0", + }, + }); + + console.log("PIE 输出:", stdout); + if (stderr) console.log("PIE stderr:", stderr); + + // 检查是否安装成功 + // 成功信息示例: "Install complete", "Already installed", "Extension is enabled and loaded" + if ( + stdout.includes("Install complete") || + stdout.includes("Already installed") || + stdout.includes("Extension is enabled") + ) { + return { success: true, message: `${extName} 扩展通过 PIE 安装成功` }; + } else if (stdout.includes("extension=")) { + return { + success: true, + message: `${extName} 扩展安装成功,请检查 php.ini 配置`, + }; + } + + // 如果有任何输出但没有明显错误,可能也是成功的 + if ( + stdout && + !stdout.includes("Error") && + !stdout.includes("Exception") + ) { + return { + success: true, + message: `${extName} 扩展安装完成\n\n${stdout}`, + }; + } + + return { + success: false, + message: `PIE 安装输出: ${stdout}\n${stderr || ""}`, + }; + } catch (error: any) { + console.error("PIE 安装失败:", error.message); + // 提取有用的错误信息 + let errorMsg = error.message; + if (error.stdout) errorMsg += `\n输出: ${error.stdout}`; + if (error.stderr) errorMsg += `\n错误: ${error.stderr}`; + return { success: false, message: `PIE 安装失败: ${errorMsg}` }; + } + } + + /** + * 安装扩展(使用 PIE) + */ + async installExtension( + version: string, + extName: string, + downloadUrl?: string, + packageName?: string + ): Promise<{ success: boolean; message: string }> { + try { + const phpPath = this.configStore.getPhpPath(version); + const extDir = join(phpPath, "ext"); + + if (!existsSync(extDir)) { + return { success: false, message: "PHP 扩展目录不存在" }; + } + + // 获取 PHP 信息 + const phpInfo = await this.getPhpInfo(phpPath); + if (!phpInfo) { + return { success: false, message: "无法获取 PHP 版本信息" }; + } + + // 使用 PIE 安装 + console.log(`使用 PIE 安装扩展 ${extName}...`); + const pieResult = await this.installWithPie( + phpPath, + extName, + packageName + ); + + return pieResult; + } catch (error: any) { + console.error(`安装扩展 ${extName} 失败:`, error); + return { success: false, message: `安装失败: ${error.message}` }; + } + } + + /** + * 下载扩展文件 + */ + private async downloadExtension(url: string, dest: string): Promise { + return new Promise((resolve, reject) => { + const file = createWriteStream(dest); + const protocol = url.startsWith("https") ? https : http; + + const request = protocol.get( + url, + { + headers: { + "User-Agent": + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36", + }, + }, + (response) => { + if ( + response.statusCode === 301 || + response.statusCode === 302 || + response.statusCode === 307 + ) { + const redirectUrl = response.headers.location; + if (redirectUrl) { + file.close(); + if (existsSync(dest)) unlinkSync(dest); + this.downloadExtension(redirectUrl, dest) + .then(resolve) + .catch(reject); + return; + } + } + + if (response.statusCode !== 200) { + reject(new Error(`下载失败,状态码: ${response.statusCode}`)); + return; + } + + const totalSize = parseInt( + response.headers["content-length"] || "0", + 10 + ); + let downloadedSize = 0; + let lastProgressTime = Date.now(); + + response.on("data", (chunk) => { + downloadedSize += chunk.length; + const now = Date.now(); + if (now - lastProgressTime > 300) { + const progress = + totalSize > 0 + ? Math.round((downloadedSize / totalSize) * 100) + : 0; + sendDownloadProgress( + "php-ext", + progress, + downloadedSize, + totalSize + ); + lastProgressTime = now; + } + }); + + response.pipe(file); + file.on("finish", () => { + file.close(); + sendDownloadProgress("php-ext", 100, totalSize, totalSize); + resolve(); + }); + } + ); + + request.on("error", (err) => { + file.close(); + if (existsSync(dest)) unlinkSync(dest); + reject(err); + }); + + request.setTimeout(120000, () => { + request.destroy(); + reject(new Error("下载超时")); + }); + }); + } + + /** + * 解压 ZIP 文件 + */ + private async unzipFile(zipPath: string, destPath: string): Promise { + const { createReadStream } = await import("fs"); + const unzipper = await import("unzipper"); + const { pipeline } = await import("stream/promises"); + + await pipeline( + createReadStream(zipPath), + unzipper.Extract({ path: destPath }) + ); + } + + /** + * 检查 URL 是否存在 + */ + private async checkUrlExists(url: string): Promise { + return new Promise((resolve) => { + const protocol = url.startsWith("https") ? https : http; + const request = protocol.request( + url, + { method: "HEAD", timeout: 5000 }, + (response) => { + resolve(response.statusCode === 200); + } + ); + request.on("error", () => resolve(false)); + request.on("timeout", () => { + request.destroy(); + resolve(false); + }); + request.end(); + }); + } + + /** + * 获取 HTML 内容 + */ + private async fetchHtmlContent(url: string): Promise { + return new Promise((resolve, reject) => { + const protocol = url.startsWith("https") ? https : http; + const request = protocol.get( + url, + { + headers: { + "User-Agent": + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36", + }, + timeout: 15000, + }, + (response) => { + if (response.statusCode === 301 || response.statusCode === 302) { + const redirectUrl = response.headers.location; + if (redirectUrl) { + this.fetchHtmlContent(redirectUrl).then(resolve).catch(reject); + return; + } + } + + if (response.statusCode !== 200) { + reject(new Error(`HTTP ${response.statusCode}`)); + return; + } + + let html = ""; + response.on("data", (chunk) => (html += chunk)); + response.on("end", () => resolve(html)); + } + ); + + request.on("error", reject); + request.on("timeout", () => { + request.destroy(); + reject(new Error("请求超时")); + }); + }); + } + + /** + * 获取 php.ini 配置内容 + */ + async getConfig(version: string): Promise { + const phpPath = this.configStore.getPhpPath(version); + const iniPath = join(phpPath, "php.ini"); + + if (!existsSync(iniPath)) { + return ""; + } + + return readFileSync(iniPath, "utf-8"); + } + + /** + * 保存 php.ini 配置 + */ + async saveConfig( + version: string, + config: string + ): Promise<{ success: boolean; message: string }> { + try { + const phpPath = this.configStore.getPhpPath(version); + const iniPath = join(phpPath, "php.ini"); + + writeFileSync(iniPath, config); + return { success: true, message: "php.ini 保存成功" }; + } catch (error: any) { + return { success: false, message: `保存失败: ${error.message}` }; + } + } + + // ==================== 私有方法 ==================== + + private async downloadFile(url: string, dest: string): Promise { + return new Promise((resolve, reject) => { + // 确保目标目录存在 + const destDir = require("path").dirname(dest); + if (!existsSync(destDir)) { + mkdirSync(destDir, { recursive: true }); + } + + const file = createWriteStream(dest); + const protocol = url.startsWith("https") ? https : http; + + console.log(`开始下载: ${url}`); + + const request = protocol.get( + url, + { + headers: { + "User-Agent": + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36", + }, + }, + (response) => { + // 处理重定向 + if (response.statusCode === 301 || response.statusCode === 302) { + const redirectUrl = response.headers.location; + if (redirectUrl) { + file.close(); + if (existsSync(dest)) unlinkSync(dest); + console.log(`重定向到: ${redirectUrl}`); + this.downloadFile(redirectUrl, dest).then(resolve).catch(reject); + return; + } + } + + if (response.statusCode !== 200) { + file.close(); + if (existsSync(dest)) unlinkSync(dest); + reject( + new Error(`下载失败,状态码: ${response.statusCode},URL: ${url}`) + ); + return; + } + + const totalSize = parseInt( + response.headers["content-length"] || "0", + 10 + ); + let downloadedSize = 0; + let lastProgressTime = Date.now(); + + response.on("data", (chunk) => { + downloadedSize += chunk.length; + const now = Date.now(); + // 每500ms发送一次进度 + if (now - lastProgressTime > 500) { + const progress = + totalSize > 0 + ? Math.round((downloadedSize / totalSize) * 100) + : 0; + sendDownloadProgress("php", progress, downloadedSize, totalSize); + lastProgressTime = now; + } + }); + + response.pipe(file); + file.on("finish", () => { + file.close(); + sendDownloadProgress("php", 100, totalSize, totalSize); + console.log("下载完成"); + resolve(); + }); + file.on("error", (err) => { + file.close(); + if (existsSync(dest)) unlinkSync(dest); + reject(err); + }); + } + ); + + request.on("error", (err) => { + file.close(); + if (existsSync(dest)) unlinkSync(dest); + reject(new Error(`网络错误: ${err.message}`)); + }); + + // 设置超时 15 分钟(PHP包较大,国际网络可能较慢) + request.setTimeout(900000, () => { + request.destroy(); + file.close(); + if (existsSync(dest)) unlinkSync(dest); + reject(new Error("下载超时(15分钟)")); + }); + }); + } + + private async unzip(zipPath: string, destPath: string): Promise { + // 确保目标目录存在 + if (!existsSync(destPath)) { + mkdirSync(destPath, { recursive: true }); + } + + const { createReadStream } = await import("fs"); + const unzipper = await import("unzipper"); + + return new Promise((resolve, reject) => { + const stream = createReadStream(zipPath).pipe( + unzipper.Extract({ path: destPath }) + ); + + stream.on("close", () => { + console.log("解压完成:", destPath); + resolve(); + }); + + stream.on("error", (err: Error) => { + console.error("解压错误:", err); + reject(new Error(`解压失败: ${err.message}`)); + }); + }); + } + + private async createDefaultPhpIni(phpPath: string): Promise { + const devIniPath = join(phpPath, "php.ini-development"); + const iniPath = join(phpPath, "php.ini"); + + if (existsSync(devIniPath) && !existsSync(iniPath)) { + let content = readFileSync(devIniPath, "utf-8"); + + // 设置常用配置 + content = content.replace( + /;extension_dir = "ext"/, + 'extension_dir = "ext"' + ); + content = content.replace(/;extension=curl/, "extension=curl"); + content = content.replace(/;extension=fileinfo/, "extension=fileinfo"); + content = content.replace(/;extension=gd/, "extension=gd"); + content = content.replace(/;extension=mbstring/, "extension=mbstring"); + content = content.replace(/;extension=mysqli/, "extension=mysqli"); + content = content.replace(/;extension=openssl/, "extension=openssl"); + content = content.replace(/;extension=pdo_mysql/, "extension=pdo_mysql"); + content = content.replace(/;extension=zip/, "extension=zip"); + + // 设置时区 + content = content.replace( + /;date.timezone =/, + "date.timezone = Asia/Shanghai" + ); + + writeFileSync(iniPath, content); + } + } + + private async addToPath(phpPath: string): Promise { + try { + // 先移除所有 PHP 相关路径,再添加新路径 + const tempScriptPath = join( + this.configStore.getTempPath(), + "update_path.ps1" + ); + mkdirSync(this.configStore.getTempPath(), { recursive: true }); + + // 将路径作为参数传递给脚本,避免转义问题 + // 使用纯英文避免编码问题 + const psScript = ` +param([string]$NewPhpPath) + +$userPath = [Environment]::GetEnvironmentVariable('PATH', 'User') +if ($userPath -eq $null) { $userPath = '' } + +Write-Host "Original PATH length: $($userPath.Length)" +Write-Host "New PHP path: $NewPhpPath" + +$paths = $userPath -split ';' | Where-Object { $_ -ne '' -and $_.Trim() -ne '' } + +Write-Host "Original path count: $($paths.Count)" + +$filteredPaths = @() +foreach ($p in $paths) { + $pathLower = $p.ToLower() + $isPhpPath = $false + + if ($pathLower -like '*\\php\\php-*' -or + $pathLower -like '*\\php-*\\*' -or + $pathLower -like '*phpenv*php*' -or + $pathLower -like '*phper*php*' -or + $pathLower -like '*xampp*php*' -or + $pathLower -like '*wamp*php*' -or + $pathLower -like '*laragon*php*' -or + $pathLower -match '\\\\php\\\\?$' -or + $pathLower -match '\\\\php-\\d') { + $isPhpPath = $true + Write-Host "Removing PHP path: $p" + } + + if (-not $isPhpPath -and (Test-Path (Join-Path $p 'php.exe') -ErrorAction SilentlyContinue)) { + $isPhpPath = $true + Write-Host "Removing path with php.exe: $p" + } + + if (-not $isPhpPath) { + $filteredPaths += $p + } +} + +Write-Host "Filtered path count: $($filteredPaths.Count)" + +$allPaths = @($NewPhpPath) + $filteredPaths +$newPath = $allPaths -join ';' + +Write-Host "New PATH starts with: $($newPath.Substring(0, [Math]::Min(150, $newPath.Length)))..." + +[Environment]::SetEnvironmentVariable('PATH', $newPath, 'User') + +Start-Sleep -Milliseconds 500 +$verifyPath = [Environment]::GetEnvironmentVariable('PATH', 'User') +if ($verifyPath -and $verifyPath.Contains($NewPhpPath)) { + Write-Host "SUCCESS: PHP path added to user PATH" +} else { + Write-Host "WARNING: PATH update may not have taken effect" +} +`; + + writeFileSync(tempScriptPath, psScript, "utf-8"); + + // 使用参数传递路径 + const { stdout, stderr } = await execAsync( + `powershell -ExecutionPolicy Bypass -File "${tempScriptPath}" -NewPhpPath "${phpPath}"`, + { windowsHide: true, timeout: 30000 } + ); + + console.log("PATH 更新输出:", stdout); + if (stderr) console.error("PATH stderr:", stderr); + + // 检查是否成功 + if (stdout.includes("SUCCESS")) { + console.log("PATH 更新成功"); + } else { + console.warn("PATH 更新可能未完全成功"); + } + + // 清理临时脚本 + if (existsSync(tempScriptPath)) { + unlinkSync(tempScriptPath); + } + } catch (error: any) { + console.error("添加 PATH 失败:", error); + throw new Error(`设置环境变量失败: ${error.message}`); + } + } + + private async removeFromPath(phpPath: string): Promise { + try { + const escapedPhpPath = phpPath.replace(/\\/g, "\\\\"); + const psCommand = ` + $userPath = [Environment]::GetEnvironmentVariable('PATH', 'User') + if ($userPath -eq $null) { $userPath = '' } + $paths = $userPath -split ';' | Where-Object { $_ -ne '' -and $_ -ne '${escapedPhpPath}' } + $newPath = $paths -join ';' + [Environment]::SetEnvironmentVariable('PATH', $newPath, 'User') + Write-Host "PATH removed successfully" + `; + const { stdout, stderr } = await execAsync( + `powershell -Command "${psCommand}"`, + { windowsHide: true } + ); + console.log("移除 PATH 成功:", stdout); + if (stderr) console.error("PATH stderr:", stderr); + } catch (error: any) { + console.error("移除 PATH 失败:", error); + } + } + + private removeDirectory(dir: string): void { + if (existsSync(dir)) { + const files = readdirSync(dir, { withFileTypes: true }); + for (const file of files) { + const fullPath = join(dir, file.name); + if (file.isDirectory()) { + this.removeDirectory(fullPath); + } else { + unlinkSync(fullPath); + } + } + rmdirSync(dir); + } + } +} diff --git a/electron/services/RedisManager.ts b/electron/services/RedisManager.ts new file mode 100644 index 0000000..dada3c3 --- /dev/null +++ b/electron/services/RedisManager.ts @@ -0,0 +1,666 @@ +import { ConfigStore } from './ConfigStore' +import { exec, spawn } from 'child_process' +import { promisify } from 'util' +import { existsSync, readFileSync, writeFileSync, readdirSync, unlinkSync, rmdirSync, mkdirSync } from 'fs' +import { join } from 'path' +import https from 'https' +import http from 'http' +import { createWriteStream } from 'fs' +import { sendDownloadProgress } from '../main' + +const execAsync = promisify(exec) + +interface RedisVersion { + version: string + path: string + isRunning: boolean +} + +interface AvailableRedisVersion { + version: string + downloadUrl: string +} + +interface RedisStatus { + running: boolean + pid?: number + port?: number + memory?: string +} + +export class RedisManager { + private configStore: ConfigStore + + constructor(configStore: ConfigStore) { + this.configStore = configStore + } + + /** + * 获取已安装的 Redis 版本列表 + */ + async getInstalledVersions(): Promise { + const versions: RedisVersion[] = [] + const redisPath = this.configStore.getRedisPath() + + if (!existsSync(redisPath)) { + return versions + } + + // 检查是否存在 redis-server.exe + if (existsSync(join(redisPath, 'redis-server.exe'))) { + try { + // 尝试获取版本 + const { stdout } = await execAsync(`"${join(redisPath, 'redis-server.exe')}" --version`) + const match = stdout.match(/v=(\d+\.\d+\.\d+)/) + if (match) { + const isRunning = await this.checkIsRunning() + versions.push({ + version: match[1], + path: redisPath, + isRunning + }) + } + } catch (error) { + // 默认版本 + const isRunning = await this.checkIsRunning() + versions.push({ + version: 'unknown', + path: redisPath, + isRunning + }) + } + } + + return versions + } + + // 缓存版本列表 + private versionCache: { versions: AvailableRedisVersion[]; timestamp: number } | null = null + private readonly CACHE_TTL = 5 * 60 * 1000 // 5分钟缓存 + + /** + * 获取可用的 Redis 版本列表(Windows 版本) + * 动态从 GitHub releases 获取 + */ + async getAvailableVersions(): Promise { + // 检查缓存 + if (this.versionCache && (Date.now() - this.versionCache.timestamp) < this.CACHE_TTL) { + console.log('使用缓存的 Redis 版本列表') + return this.versionCache.versions + } + + let versions: AvailableRedisVersion[] = [] + + try { + // 从 redis-windows GitHub releases 获取版本列表 + console.log('从 GitHub 获取 Redis Windows 版本列表...') + const releases = await this.fetchGitHubReleases('redis-windows', 'redis-windows') + + for (const release of releases) { + const version = release.tag_name.replace(/^v/, '') + + // 查找 Windows x64 ZIP 文件 + const asset = release.assets?.find((a: any) => + a.name.includes('Windows-x64') && a.name.endsWith('.zip') + ) + + if (asset) { + versions.push({ + version, + downloadUrl: asset.browser_download_url + }) + } + } + + console.log(`从 GitHub 获取到 ${versions.length} 个 Redis 版本`) + } catch (error: any) { + console.error('从 GitHub 获取 Redis 版本失败:', error.message) + } + + // 如果获取失败或为空,使用备用列表 + if (versions.length === 0) { + console.log('使用备用 Redis 版本列表') + versions = this.getFallbackVersions() + } + + // 更新缓存 + this.versionCache = { versions, timestamp: Date.now() } + + return versions + } + + /** + * 从 GitHub API 获取 releases + */ + private async fetchGitHubReleases(owner: string, repo: string): Promise { + return new Promise((resolve, reject) => { + const options = { + hostname: 'api.github.com', + path: `/repos/${owner}/${repo}/releases?per_page=20`, + method: 'GET', + headers: { + 'User-Agent': 'PHPer-Dev-Manager', + 'Accept': 'application/vnd.github.v3+json' + } + } + + const request = https.request(options, (response) => { + let data = '' + response.on('data', chunk => data += chunk) + response.on('end', () => { + try { + if (response.statusCode === 200) { + resolve(JSON.parse(data)) + } else { + reject(new Error(`GitHub API 返回 ${response.statusCode}`)) + } + } catch (e) { + reject(e) + } + }) + }) + + request.on('error', reject) + request.setTimeout(15000, () => { + request.destroy() + reject(new Error('请求超时')) + }) + request.end() + }) + } + + /** + * 备用版本列表 + */ + private getFallbackVersions(): AvailableRedisVersion[] { + return [ + { + version: '7.4.2', + downloadUrl: 'https://github.com/redis-windows/redis-windows/releases/download/7.4.2/Redis-7.4.2-Windows-x64.zip' + }, + { + version: '7.2.7', + downloadUrl: 'https://github.com/redis-windows/redis-windows/releases/download/7.2.7/Redis-7.2.7-Windows-x64.zip' + }, + { + version: '7.0.15', + downloadUrl: 'https://github.com/redis-windows/redis-windows/releases/download/7.0.15/Redis-7.0.15-Windows-x64.zip' + }, + { + version: '6.2.16', + downloadUrl: 'https://github.com/redis-windows/redis-windows/releases/download/6.2.16/Redis-6.2.16-Windows-x64.zip' + } + ] + } + + /** + * 安装 Redis 版本 + */ + async install(version: string): Promise<{ success: boolean; message: string }> { + try { + const available = await this.getAvailableVersions() + const versionInfo = available.find(v => v.version === version) + + if (!versionInfo) { + return { success: false, message: `未找到 Redis ${version} 版本` } + } + + const redisPath = this.configStore.getRedisPath() + const tempPath = this.configStore.getTempPath() + const zipPath = join(tempPath, `redis-${version}.zip`) + + // 如果已有 Redis,先备份配置 + let oldConfig = '' + const configPath = join(redisPath, 'redis.windows.conf') + if (existsSync(configPath)) { + oldConfig = readFileSync(configPath, 'utf-8') + } + + // 下载 Redis + await this.downloadFile(versionInfo.downloadUrl, zipPath) + + // 清理旧版本(保留配置) + if (existsSync(redisPath)) { + const files = readdirSync(redisPath, { withFileTypes: true }) + for (const file of files) { + if (!file.name.endsWith('.conf')) { + const fullPath = join(redisPath, file.name) + if (file.isDirectory()) { + this.removeDirectory(fullPath) + } else { + unlinkSync(fullPath) + } + } + } + } else { + mkdirSync(redisPath, { recursive: true }) + } + + // 解压 + await this.unzip(zipPath, redisPath) + + // 删除临时文件 + if (existsSync(zipPath)) { + unlinkSync(zipPath) + } + + // 移动解压后的文件(某些版本解压后有子目录) + await this.flattenDirectory(redisPath) + + // 恢复或创建配置 + if (oldConfig) { + writeFileSync(configPath, oldConfig) + } else { + await this.createDefaultConfig() + } + + return { success: true, message: `Redis ${version} 安装成功` } + } catch (error: any) { + return { success: false, message: `安装失败: ${error.message}` } + } + } + + /** + * 卸载 Redis + */ + async uninstall(version: string): Promise<{ success: boolean; message: string }> { + try { + // 先停止服务 + await this.stop() + + const redisPath = this.configStore.getRedisPath() + + if (!existsSync(redisPath)) { + return { success: false, message: 'Redis 未安装' } + } + + // 递归删除目录 + this.removeDirectory(redisPath) + + return { success: true, message: 'Redis 已卸载' } + } catch (error: any) { + return { success: false, message: `卸载失败: ${error.message}` } + } + } + + /** + * 启动 Redis + */ + async start(): Promise<{ success: boolean; message: string }> { + try { + const redisPath = this.configStore.getRedisPath() + const redisServer = join(redisPath, 'redis-server.exe') + const configPath = join(redisPath, 'redis.windows.conf') + + if (!existsSync(redisServer)) { + return { success: false, message: 'Redis 未安装' } + } + + // 检查是否已在运行 + const isRunning = await this.checkIsRunning() + if (isRunning) { + return { success: true, message: 'Redis 已经在运行' } + } + + // 确保配置文件存在 + if (!existsSync(configPath)) { + await this.createDefaultConfig() + } + + // 使用相对路径启动(避免 Cygwin 路径问题) + // Redis Windows 版本使用 Cygwin,需要在正确的工作目录下用相对路径 + const configFileName = 'redis.windows.conf' + const child = spawn(redisServer, [configFileName], { + cwd: redisPath, + detached: true, + stdio: 'ignore', + windowsHide: true, + shell: false + }) + child.unref() + + // 等待启动 + await new Promise(resolve => setTimeout(resolve, 2000)) + + const running = await this.checkIsRunning() + if (running) { + return { success: true, message: 'Redis 启动成功' } + } else { + // 检查日志获取错误信息 + const logsPath = this.configStore.getLogsPath() + const logFile = join(logsPath, 'redis.log') + let errorInfo = '' + if (existsSync(logFile)) { + try { + const logContent = readFileSync(logFile, 'utf-8') + const lines = logContent.split('\n').slice(-10) + errorInfo = '\n日志: ' + lines.join('\n') + } catch (e) {} + } + return { success: false, message: 'Redis 启动失败,请检查配置' + errorInfo } + } + } catch (error: any) { + return { success: false, message: `启动失败: ${error.message}` } + } + } + + /** + * 停止 Redis + */ + async stop(): Promise<{ success: boolean; message: string }> { + try { + const redisPath = this.configStore.getRedisPath() + const redisCli = join(redisPath, 'redis-cli.exe') + + if (existsSync(redisCli)) { + try { + await execAsync(`"${redisCli}" shutdown`, { timeout: 10000 }) + } catch (e) { + // 尝试强制结束 + try { + await execAsync('taskkill /F /IM redis-server.exe', { timeout: 5000 }) + } catch (e2) { + // 进程可能不存在 + } + } + } + + await new Promise(resolve => setTimeout(resolve, 1000)) + + const isRunning = await this.checkIsRunning() + if (!isRunning) { + return { success: true, message: 'Redis 已停止' } + } else { + return { success: false, message: 'Redis 停止失败' } + } + } catch (error: any) { + return { success: false, message: `停止失败: ${error.message}` } + } + } + + /** + * 重启 Redis + */ + async restart(): Promise<{ success: boolean; message: string }> { + await this.stop() + await new Promise(resolve => setTimeout(resolve, 500)) + return await this.start() + } + + /** + * 获取 Redis 状态 + */ + async getStatus(): Promise { + const isRunning = await this.checkIsRunning() + + if (!isRunning) { + return { running: false } + } + + try { + const redisPath = this.configStore.getRedisPath() + const redisCli = join(redisPath, 'redis-cli.exe') + + if (existsSync(redisCli)) { + const { stdout } = await execAsync(`"${redisCli}" INFO server`, { timeout: 5000 }) + + const portMatch = stdout.match(/tcp_port:(\d+)/) + const memMatch = stdout.match(/used_memory_human:(\S+)/) + + // 获取 PID + const { stdout: taskOutput } = await execAsync('tasklist /FI "IMAGENAME eq redis-server.exe" /FO CSV /NH') + let pid: number | undefined + if (taskOutput.includes('redis-server.exe')) { + const parts = taskOutput.split(',') + pid = parseInt(parts[1].replace(/"/g, '')) + } + + return { + running: true, + pid, + port: portMatch ? parseInt(portMatch[1]) : 6379, + memory: memMatch ? memMatch[1] : undefined + } + } + } catch (e) { + // 忽略错误 + } + + return { running: isRunning } + } + + /** + * 获取 Redis 配置内容 + */ + async getConfig(): Promise { + const redisPath = this.configStore.getRedisPath() + const configPath = join(redisPath, 'redis.windows.conf') + + if (!existsSync(configPath)) { + return '' + } + + return readFileSync(configPath, 'utf-8') + } + + /** + * 保存 Redis 配置 + */ + async saveConfig(config: string): Promise<{ success: boolean; message: string }> { + try { + const redisPath = this.configStore.getRedisPath() + const configPath = join(redisPath, 'redis.windows.conf') + + writeFileSync(configPath, config) + return { success: true, message: 'redis.windows.conf 保存成功,需要重启 Redis 生效' } + } catch (error: any) { + return { success: false, message: `保存失败: ${error.message}` } + } + } + + // ==================== 私有方法 ==================== + + private async checkIsRunning(): Promise { + try { + const { stdout } = await execAsync('tasklist /FI "IMAGENAME eq redis-server.exe" /FO CSV /NH') + return stdout.includes('redis-server.exe') + } catch (e) { + return false + } + } + + /** + * 将 Windows 路径转换为 Cygwin 路径 + */ + private toCygwinPath(winPath: string): string { + // C:\Users\... -> /cygdrive/c/Users/... + const match = winPath.match(/^([A-Za-z]):[\\\/](.*)$/) + if (match) { + const drive = match[1].toLowerCase() + const rest = match[2].replace(/\\/g, '/') + return `/cygdrive/${drive}/${rest}` + } + return winPath.replace(/\\/g, '/') + } + + private async createDefaultConfig(): Promise { + const redisPath = this.configStore.getRedisPath() + const configPath = join(redisPath, 'redis.windows.conf') + const logsPath = this.configStore.getLogsPath() + + // 确保日志目录存在 + if (!existsSync(logsPath)) { + mkdirSync(logsPath, { recursive: true }) + } + + // 转换为 Cygwin 路径格式 + const cygwinLogsPath = this.toCygwinPath(logsPath) + const cygwinRedisPath = this.toCygwinPath(redisPath) + + // 检查是否有示例配置文件 + const sampleConfig = join(redisPath, 'redis.windows-service.conf') + if (existsSync(sampleConfig)) { + let config = readFileSync(sampleConfig, 'utf-8') + + // 修改一些配置 + config = config.replace(/^# bind 127.0.0.1/m, 'bind 127.0.0.1') + config = config.replace(/^logfile ""/m, `logfile "${cygwinLogsPath}/redis.log"`) + + writeFileSync(configPath, config) + return + } + + // 创建基本配置(使用 Cygwin 路径格式) + const config = `# Redis Configuration File + +# Bind address +bind 127.0.0.1 + +# Port +port 6379 + +# Log file (Cygwin path format) +logfile "${cygwinLogsPath}/redis.log" + +# Log level +loglevel notice + +# Number of databases +databases 16 + +# Persistence +save 900 1 +save 300 10 +save 60 10000 + +# Data file +dbfilename dump.rdb +dir "${cygwinRedisPath}" + +# Max memory +maxmemory 256mb +maxmemory-policy allkeys-lru + +# Timeout +timeout 0 +tcp-keepalive 300 + +# Protected mode +protected-mode yes +` + + writeFileSync(configPath, config) + } + + private async flattenDirectory(dir: string): Promise { + const items = readdirSync(dir, { withFileTypes: true }) + + // 检查是否只有一个子目录 + const subdirs = items.filter(item => item.isDirectory()) + if (subdirs.length === 1 && items.filter(item => !item.isDirectory()).length === 0) { + const subdir = join(dir, subdirs[0].name) + const subItems = readdirSync(subdir) + + const { rename } = await import('fs/promises') + + // 移动子目录中的所有文件到父目录 + for (const item of subItems) { + const srcPath = join(subdir, item) + const destPath = join(dir, item) + await rename(srcPath, destPath) + } + + // 删除空的子目录 + rmdirSync(subdir) + } + } + + private async downloadFile(url: string, dest: string): Promise { + return new Promise((resolve, reject) => { + const file = createWriteStream(dest) + const protocol = url.startsWith('https') ? https : http + + const request = protocol.get(url, { + headers: { + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36' + } + }, (response) => { + // 处理重定向 + if (response.statusCode === 301 || response.statusCode === 302) { + const redirectUrl = response.headers.location + if (redirectUrl) { + file.close() + if (existsSync(dest)) unlinkSync(dest) + this.downloadFile(redirectUrl, dest).then(resolve).catch(reject) + return + } + } + + if (response.statusCode !== 200) { + reject(new Error(`下载失败,状态码: ${response.statusCode}`)) + return + } + + const totalSize = parseInt(response.headers['content-length'] || '0', 10) + let downloadedSize = 0 + let lastProgressTime = Date.now() + + response.on('data', (chunk) => { + downloadedSize += chunk.length + const now = Date.now() + if (now - lastProgressTime > 500) { + const progress = totalSize > 0 ? Math.round((downloadedSize / totalSize) * 100) : 0 + sendDownloadProgress('redis', progress, downloadedSize, totalSize) + lastProgressTime = now + } + }) + + response.pipe(file) + file.on('finish', () => { + file.close() + sendDownloadProgress('redis', 100, totalSize, totalSize) + resolve() + }) + }) + + request.on('error', (err) => { + file.close() + if (existsSync(dest)) unlinkSync(dest) + reject(err) + }) + + request.setTimeout(300000, () => { + request.destroy() + reject(new Error('下载超时')) + }) + }) + } + + private async unzip(zipPath: string, destPath: string): Promise { + const { createReadStream } = await import('fs') + const unzipper = await import('unzipper') + + return new Promise((resolve, reject) => { + createReadStream(zipPath) + .pipe(unzipper.Extract({ path: destPath })) + .on('close', resolve) + .on('error', reject) + }) + } + + private removeDirectory(dir: string): void { + if (existsSync(dir)) { + const files = readdirSync(dir, { withFileTypes: true }) + for (const file of files) { + const fullPath = join(dir, file.name) + if (file.isDirectory()) { + this.removeDirectory(fullPath) + } else { + unlinkSync(fullPath) + } + } + rmdirSync(dir) + } + } +} + diff --git a/electron/services/ServiceManager.ts b/electron/services/ServiceManager.ts new file mode 100644 index 0000000..8222554 --- /dev/null +++ b/electron/services/ServiceManager.ts @@ -0,0 +1,356 @@ +import { ConfigStore } from './ConfigStore' +import { exec, spawn } from 'child_process' +import { promisify } from 'util' +import { existsSync, writeFileSync, readFileSync, mkdirSync } from 'fs' +import { join } from 'path' + +const execAsync = promisify(exec) + +interface ServiceStatus { + name: string + displayName: string + running: boolean + autoStart: boolean +} + +export class ServiceManager { + private configStore: ConfigStore + private startupDir: string + + constructor(configStore: ConfigStore) { + this.configStore = configStore + // Windows 启动目录 + this.startupDir = join(process.env.APPDATA || '', 'Microsoft', 'Windows', 'Start Menu', 'Programs', 'Startup') + } + + /** + * 获取所有服务状态 + */ + async getAllServices(): Promise { + const services: ServiceStatus[] = [] + + // 检查 Nginx + const nginxPath = this.configStore.getNginxPath() + if (existsSync(join(nginxPath, 'nginx.exe'))) { + const running = await this.checkProcess('nginx.exe') + const autoStart = this.checkAutoStart('nginx') + services.push({ + name: 'nginx', + displayName: 'Nginx', + running, + autoStart + }) + } + + // 检查 MySQL + const mysqlVersions = this.configStore.get('mysqlVersions') + for (const version of mysqlVersions) { + const mysqlPath = this.configStore.getMysqlPath(version) + if (existsSync(join(mysqlPath, 'bin', 'mysqld.exe'))) { + const running = await this.checkProcess('mysqld.exe') + const autoStart = this.checkAutoStart(`mysql-${version}`) + services.push({ + name: `mysql-${version}`, + displayName: `MySQL ${version}`, + running, + autoStart + }) + } + } + + // 检查 Redis + const redisPath = this.configStore.getRedisPath() + if (existsSync(join(redisPath, 'redis-server.exe'))) { + const running = await this.checkProcess('redis-server.exe') + const autoStart = this.checkAutoStart('redis') + services.push({ + name: 'redis', + displayName: 'Redis', + running, + autoStart + }) + } + + // 检查 PHP-CGI + const activePhp = this.configStore.get('activePhpVersion') + if (activePhp) { + const phpPath = this.configStore.getPhpPath(activePhp) + if (existsSync(join(phpPath, 'php-cgi.exe'))) { + const running = await this.checkProcess('php-cgi.exe') + const autoStart = this.checkAutoStart('php-cgi') + services.push({ + name: 'php-cgi', + displayName: `PHP-CGI (${activePhp})`, + running, + autoStart + }) + } + } + + return services + } + + /** + * 设置服务开机自启 + */ + async setAutoStart(service: string, enabled: boolean): Promise<{ success: boolean; message: string }> { + try { + const batPath = join(this.startupDir, `phper-${service}.bat`) + + if (enabled) { + // 创建启动脚本 + let script = '@echo off\n' + script += `cd /d "${this.configStore.getBasePath()}"\n` + + if (service === 'nginx') { + const nginxPath = this.configStore.getNginxPath() + script += `start "" /B "${join(nginxPath, 'nginx.exe')}"\n` + } else if (service.startsWith('mysql-')) { + const version = service.replace('mysql-', '') + const mysqlPath = this.configStore.getMysqlPath(version) + script += `start "" /B "${join(mysqlPath, 'bin', 'mysqld.exe')}" --defaults-file="${join(mysqlPath, 'my.ini')}"\n` + } else if (service === 'redis') { + const redisPath = this.configStore.getRedisPath() + script += `start "" /B "${join(redisPath, 'redis-server.exe')}" "${join(redisPath, 'redis.windows.conf')}"\n` + } else if (service === 'php-cgi') { + const activePhp = this.configStore.get('activePhpVersion') + if (activePhp) { + const phpPath = this.configStore.getPhpPath(activePhp) + script += `start "" /B "${join(phpPath, 'php-cgi.exe')}" -b 127.0.0.1:9000\n` + } + } + + writeFileSync(batPath, script) + + // 更新配置 + const autoStart = this.configStore.get('autoStart') + if (service === 'nginx') autoStart.nginx = true + else if (service.startsWith('mysql')) autoStart.mysql = true + else if (service === 'redis') autoStart.redis = true + this.configStore.set('autoStart', autoStart) + + return { success: true, message: `${service} 开机自启已启用` } + } else { + // 删除启动脚本 + if (existsSync(batPath)) { + const { unlinkSync } = await import('fs') + unlinkSync(batPath) + } + + // 更新配置 + const autoStart = this.configStore.get('autoStart') + if (service === 'nginx') autoStart.nginx = false + else if (service.startsWith('mysql')) autoStart.mysql = false + else if (service === 'redis') autoStart.redis = false + this.configStore.set('autoStart', autoStart) + + return { success: true, message: `${service} 开机自启已禁用` } + } + } catch (error: any) { + return { success: false, message: `设置失败: ${error.message}` } + } + } + + /** + * 检查服务是否开机自启 + */ + getAutoStart(service: string): boolean { + return this.checkAutoStart(service) + } + + /** + * 启动所有已安装的服务 + */ + async startAll(): Promise<{ success: boolean; message: string; details: string[] }> { + const details: string[] = [] + + try { + // 启动 Nginx + const nginxPath = this.configStore.getNginxPath() + if (existsSync(join(nginxPath, 'nginx.exe'))) { + if (!(await this.checkProcess('nginx.exe'))) { + await this.startProcess(join(nginxPath, 'nginx.exe'), [], nginxPath) + details.push('Nginx 已启动') + } else { + details.push('Nginx 已在运行') + } + } + + // 启动 MySQL (启动第一个已安装的版本) + const mysqlVersions = this.configStore.get('mysqlVersions') + if (mysqlVersions.length > 0) { + if (!(await this.checkProcess('mysqld.exe'))) { + for (const version of mysqlVersions) { + const mysqlPath = this.configStore.getMysqlPath(version) + const mysqld = join(mysqlPath, 'bin', 'mysqld.exe') + if (existsSync(mysqld)) { + // 使用 VBScript 隐藏窗口启动 + const vbsPath = join(mysqlPath, 'start_mysql.vbs') + const vbsContent = `Set WshShell = CreateObject("WScript.Shell")\nWshShell.Run """${mysqld}"" --defaults-file=""${join(mysqlPath, 'my.ini')}""", 0, False` + writeFileSync(vbsPath, vbsContent) + await execAsync(`cscript //nologo "${vbsPath}"`, { cwd: mysqlPath }) + details.push(`MySQL ${version} 已启动`) + break // 只启动第一个版本 + } + } + } else { + details.push('MySQL 已在运行') + } + } + + // 启动 Redis + const redisPath = this.configStore.getRedisPath() + const redisServer = join(redisPath, 'redis-server.exe') + if (existsSync(redisServer)) { + if (!(await this.checkProcess('redis-server.exe'))) { + const configFile = join(redisPath, 'redis.windows.conf') + const args = existsSync(configFile) ? ['redis.windows.conf'] : [] + await this.startProcess(redisServer, args, redisPath) + details.push('Redis 已启动') + } else { + details.push('Redis 已在运行') + } + } + + if (details.length === 0) { + return { success: true, message: '没有已安装的服务', details } + } + + return { success: true, message: '服务启动完成', details } + } catch (error: any) { + return { success: false, message: `启动失败: ${error.message}`, details } + } + } + + /** + * 停止所有服务 + */ + async stopAll(): Promise<{ success: boolean; message: string; details: string[] }> { + const details: string[] = [] + + try { + // 停止 PHP-CGI + if (await this.checkProcess('php-cgi.exe')) { + await execAsync('taskkill /F /IM php-cgi.exe', { timeout: 5000 }).catch(() => {}) + details.push('PHP-CGI 已停止') + } + + // 停止 Nginx + if (await this.checkProcess('nginx.exe')) { + const nginxPath = this.configStore.getNginxPath() + try { + await execAsync(`"${join(nginxPath, 'nginx.exe')}" -s stop`, { cwd: nginxPath, timeout: 5000 }) + } catch (e) { + await execAsync('taskkill /F /IM nginx.exe', { timeout: 5000 }).catch(() => {}) + } + details.push('Nginx 已停止') + } + + // 停止 MySQL + if (await this.checkProcess('mysqld.exe')) { + await execAsync('taskkill /F /IM mysqld.exe', { timeout: 5000 }).catch(() => {}) + details.push('MySQL 已停止') + } + + // 停止 Redis + if (await this.checkProcess('redis-server.exe')) { + const redisPath = this.configStore.getRedisPath() + const redisCli = join(redisPath, 'redis-cli.exe') + if (existsSync(redisCli)) { + try { + await execAsync(`"${redisCli}" shutdown`, { timeout: 5000 }) + } catch (e) { + await execAsync('taskkill /F /IM redis-server.exe', { timeout: 5000 }).catch(() => {}) + } + } else { + await execAsync('taskkill /F /IM redis-server.exe', { timeout: 5000 }).catch(() => {}) + } + details.push('Redis 已停止') + } + + return { success: true, message: '所有服务已停止', details } + } catch (error: any) { + return { success: false, message: `停止失败: ${error.message}`, details } + } + } + + /** + * 启动 PHP-CGI 进程(FastCGI) + */ + async startPhpCgi(): Promise<{ success: boolean; message: string }> { + try { + const activePhp = this.configStore.get('activePhpVersion') + if (!activePhp) { + return { success: false, message: '未设置活动的 PHP 版本' } + } + + const phpPath = this.configStore.getPhpPath(activePhp) + const phpCgi = join(phpPath, 'php-cgi.exe') + + if (!existsSync(phpCgi)) { + return { success: false, message: 'php-cgi.exe 不存在' } + } + + // 检查是否已在运行 + if (await this.checkProcess('php-cgi.exe')) { + return { success: true, message: 'PHP-CGI 已经在运行' } + } + + // 启动 PHP-CGI + await this.startProcess(phpCgi, ['-b', '127.0.0.1:9000'], phpPath) + + // 等待启动 + await new Promise(resolve => setTimeout(resolve, 1000)) + + if (await this.checkProcess('php-cgi.exe')) { + return { success: true, message: 'PHP-CGI 启动成功' } + } else { + return { success: false, message: 'PHP-CGI 启动失败' } + } + } catch (error: any) { + return { success: false, message: `启动失败: ${error.message}` } + } + } + + /** + * 停止 PHP-CGI 进程 + */ + async stopPhpCgi(): Promise<{ success: boolean; message: string }> { + try { + await execAsync('taskkill /F /IM php-cgi.exe', { timeout: 5000 }) + return { success: true, message: 'PHP-CGI 已停止' } + } catch (error: any) { + if (error.message.includes('not found')) { + return { success: true, message: 'PHP-CGI 未运行' } + } + return { success: false, message: `停止失败: ${error.message}` } + } + } + + // ==================== 私有方法 ==================== + + private async checkProcess(name: string): Promise { + try { + const { stdout } = await execAsync(`tasklist /FI "IMAGENAME eq ${name}" /FO CSV /NH`) + return stdout.includes(name) + } catch (e) { + return false + } + } + + private checkAutoStart(service: string): boolean { + const batPath = join(this.startupDir, `phper-${service}.bat`) + return existsSync(batPath) + } + + private async startProcess(exe: string, args: string[], cwd: string): Promise { + const child = spawn(exe, args, { + cwd, + detached: true, + stdio: 'ignore', + windowsHide: true + }) + child.unref() + } +} + diff --git a/index.html b/index.html new file mode 100644 index 0000000..026c197 --- /dev/null +++ b/index.html @@ -0,0 +1,14 @@ + + + + + + + PHPer 开发环境管理器 + + +
+ + + + diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..c09156c --- /dev/null +++ b/package-lock.json @@ -0,0 +1,6807 @@ +{ + "name": "phper-dev-manager", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "phper-dev-manager", + "version": "1.0.0", + "license": "MIT", + "dependencies": { + "@element-plus/icons-vue": "^2.3.1", + "axios": "^1.6.2", + "electron-store": "^8.1.0", + "element-plus": "^2.4.3", + "node-windows": "^1.0.0-beta.8", + "pinia": "^2.1.7", + "sudo-prompt": "^9.2.1", + "unzipper": "^0.12.3", + "vue": "^3.3.9", + "vue-router": "^4.2.5" + }, + "devDependencies": { + "@types/node": "^20.10.0", + "@vitejs/plugin-vue": "^4.5.0", + "concurrently": "^8.2.2", + "electron": "^28.0.0", + "electron-builder": "^24.9.1", + "sass": "^1.69.5", + "typescript": "^5.3.2", + "vite": "^5.0.0", + "vite-plugin-electron": "^0.15.5", + "vite-plugin-electron-renderer": "^0.14.5", + "vue-tsc": "^1.8.25", + "wait-on": "^7.2.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.5.tgz", + "integrity": "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.5" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.4.tgz", + "integrity": "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.5.tgz", + "integrity": "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==", + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@ctrl/tinycolor": { + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/@ctrl/tinycolor/-/tinycolor-3.6.1.tgz", + "integrity": "sha512-SITSV6aIXsuVNV3f3O0f2n/cgyEDWoSqtZMYiAmcsYHydcKrOz3gUxB/iXd/Qf08+IZX4KpgNbvUdMBmWz+kcA==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/@develar/schema-utils": { + "version": "2.6.5", + "resolved": "https://registry.npmjs.org/@develar/schema-utils/-/schema-utils-2.6.5.tgz", + "integrity": "sha512-0cp4PsWQ/9avqTVMCtZ+GirikIA36ikvjtHweU4/j8yLtgObI0+JUPhYFScgwlteveGB1rt3Cm8UhN04XayDig==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.12.0", + "ajv-keywords": "^3.4.1" + }, + "engines": { + "node": ">= 8.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/@electron/asar": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/@electron/asar/-/asar-3.4.1.tgz", + "integrity": "sha512-i4/rNPRS84t0vSRa2HorerGRXWyF4vThfHesw0dmcWHp+cspK743UanA0suA5Q5y8kzY2y6YKrvbIUn69BCAiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "commander": "^5.0.0", + "glob": "^7.1.6", + "minimatch": "^3.0.4" + }, + "bin": { + "asar": "bin/asar.js" + }, + "engines": { + "node": ">=10.12.0" + } + }, + "node_modules/@electron/asar/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@electron/asar/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@electron/get": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@electron/get/-/get-2.0.3.tgz", + "integrity": "sha512-Qkzpg2s9GnVV2I2BjRksUi43U5e6+zaQMcjoJy0C+C5oxaKl+fmckGDQFtRpZpZV0NQekuZZ+tGz7EA9TVnQtQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.1.1", + "env-paths": "^2.2.0", + "fs-extra": "^8.1.0", + "got": "^11.8.5", + "progress": "^2.0.3", + "semver": "^6.2.0", + "sumchecker": "^3.0.1" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "global-agent": "^3.0.0" + } + }, + "node_modules/@electron/notarize": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@electron/notarize/-/notarize-2.2.1.tgz", + "integrity": "sha512-aL+bFMIkpR0cmmj5Zgy0LMKEpgy43/hw5zadEArgmAMWWlKc5buwFvFT9G/o/YJkvXAJm5q3iuTuLaiaXW39sg==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.1.1", + "fs-extra": "^9.0.1", + "promise-retry": "^2.0.1" + }, + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/@electron/notarize/node_modules/fs-extra": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@electron/notarize/node_modules/jsonfile": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", + "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/@electron/notarize/node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/@electron/osx-sign": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@electron/osx-sign/-/osx-sign-1.0.5.tgz", + "integrity": "sha512-k9ZzUQtamSoweGQDV2jILiRIHUu7lYlJ3c6IEmjv1hC17rclE+eb9U+f6UFlOOETo0JzY1HNlXy4YOlCvl+Lww==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "compare-version": "^0.1.2", + "debug": "^4.3.4", + "fs-extra": "^10.0.0", + "isbinaryfile": "^4.0.8", + "minimist": "^1.2.6", + "plist": "^3.0.5" + }, + "bin": { + "electron-osx-flat": "bin/electron-osx-flat.js", + "electron-osx-sign": "bin/electron-osx-sign.js" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/@electron/osx-sign/node_modules/fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@electron/osx-sign/node_modules/isbinaryfile": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-4.0.10.tgz", + "integrity": "sha512-iHrqe5shvBUcFbmZq9zOQHBoeOhZJu6RQGrDpBgenUm/Am+F3JM2MgQj+rK3Z601fzrL5gLZWtAPH2OBaSVcyw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/gjtorikian/" + } + }, + "node_modules/@electron/osx-sign/node_modules/jsonfile": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", + "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/@electron/osx-sign/node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/@electron/universal": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/@electron/universal/-/universal-1.5.1.tgz", + "integrity": "sha512-kbgXxyEauPJiQQUNG2VgUeyfQNFk6hBF11ISN2PNI6agUgPl55pv4eQmaqHzTAzchBvqZ2tQuRVaPStGf0mxGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@electron/asar": "^3.2.1", + "@malept/cross-spawn-promise": "^1.1.0", + "debug": "^4.3.1", + "dir-compare": "^3.0.0", + "fs-extra": "^9.0.1", + "minimatch": "^3.0.4", + "plist": "^3.0.4" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/@electron/universal/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@electron/universal/node_modules/fs-extra": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@electron/universal/node_modules/jsonfile": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", + "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/@electron/universal/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@electron/universal/node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/@element-plus/icons-vue": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/@element-plus/icons-vue/-/icons-vue-2.3.2.tgz", + "integrity": "sha512-OzIuTaIfC8QXEPmJvB4Y4kw34rSXdCJzxcD1kFStBvr8bK6X1zQAYDo0CNMjojnfTqRQCJ0I7prlErcoRiET2A==", + "license": "MIT", + "peerDependencies": { + "vue": "^3.2.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@floating-ui/core": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.3.tgz", + "integrity": "sha512-sGnvb5dmrJaKEZ+LDIpguvdX3bDlEllmv4/ClQ9awcmCZrlx5jQyyMWFM5kBI+EyNOCDDiKk8il0zeuX3Zlg/w==", + "license": "MIT", + "dependencies": { + "@floating-ui/utils": "^0.2.10" + } + }, + "node_modules/@floating-ui/dom": { + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.4.tgz", + "integrity": "sha512-OOchDgh4F2CchOX94cRVqhvy7b3AFb+/rQXyswmzmGakRfkMgoWVjfnLWkRirfLEfuD4ysVW16eXzwt3jHIzKA==", + "license": "MIT", + "dependencies": { + "@floating-ui/core": "^1.7.3", + "@floating-ui/utils": "^0.2.10" + } + }, + "node_modules/@floating-ui/utils": { + "version": "0.2.10", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.10.tgz", + "integrity": "sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==", + "license": "MIT" + }, + "node_modules/@hapi/hoek": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz", + "integrity": "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@hapi/topo": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-5.1.0.tgz", + "integrity": "sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@hapi/hoek": "^9.0.0" + } + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@isaacs/cliui/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@isaacs/cliui/node_modules/strip-ansi": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", + "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "license": "MIT" + }, + "node_modules/@malept/cross-spawn-promise": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@malept/cross-spawn-promise/-/cross-spawn-promise-1.1.1.tgz", + "integrity": "sha512-RTBGWL5FWQcg9orDOCcp4LvItNzUPcyEU9bwaeJX0rJ1IQxzucC48Y0/sQLp/g6t99IQgAlGIaesJS+gTn7tVQ==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/malept" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/subscription/pkg/npm-.malept-cross-spawn-promise?utm_medium=referral&utm_source=npm_fund" + } + ], + "license": "Apache-2.0", + "dependencies": { + "cross-spawn": "^7.0.1" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/@malept/flatpak-bundler": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@malept/flatpak-bundler/-/flatpak-bundler-0.4.0.tgz", + "integrity": "sha512-9QOtNffcOF/c1seMCDnjckb3R9WHcG34tky+FHpNKKCW0wc/scYLwMtO+ptyGUfMW0/b/n4qRiALlaFHc9Oj7Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.1.1", + "fs-extra": "^9.0.0", + "lodash": "^4.17.15", + "tmp-promise": "^3.0.2" + }, + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/@malept/flatpak-bundler/node_modules/fs-extra": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@malept/flatpak-bundler/node_modules/jsonfile": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", + "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/@malept/flatpak-bundler/node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/@parcel/watcher": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.1.tgz", + "integrity": "sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "dependencies": { + "detect-libc": "^1.0.3", + "is-glob": "^4.0.3", + "micromatch": "^4.0.5", + "node-addon-api": "^7.0.0" + }, + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "@parcel/watcher-android-arm64": "2.5.1", + "@parcel/watcher-darwin-arm64": "2.5.1", + "@parcel/watcher-darwin-x64": "2.5.1", + "@parcel/watcher-freebsd-x64": "2.5.1", + "@parcel/watcher-linux-arm-glibc": "2.5.1", + "@parcel/watcher-linux-arm-musl": "2.5.1", + "@parcel/watcher-linux-arm64-glibc": "2.5.1", + "@parcel/watcher-linux-arm64-musl": "2.5.1", + "@parcel/watcher-linux-x64-glibc": "2.5.1", + "@parcel/watcher-linux-x64-musl": "2.5.1", + "@parcel/watcher-win32-arm64": "2.5.1", + "@parcel/watcher-win32-ia32": "2.5.1", + "@parcel/watcher-win32-x64": "2.5.1" + } + }, + "node_modules/@parcel/watcher-android-arm64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.1.tgz", + "integrity": "sha512-KF8+j9nNbUN8vzOFDpRMsaKBHZ/mcjEjMToVMJOhTozkDonQFFrRcfdLWn6yWKCmJKmdVxSgHiYvTCef4/qcBA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-darwin-arm64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.1.tgz", + "integrity": "sha512-eAzPv5osDmZyBhou8PoF4i6RQXAfeKL9tjb3QzYuccXFMQU0ruIc/POh30ePnaOyD1UXdlKguHBmsTs53tVoPw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-darwin-x64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.1.tgz", + "integrity": "sha512-1ZXDthrnNmwv10A0/3AJNZ9JGlzrF82i3gNQcWOzd7nJ8aj+ILyW1MTxVk35Db0u91oD5Nlk9MBiujMlwmeXZg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-freebsd-x64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.1.tgz", + "integrity": "sha512-SI4eljM7Flp9yPuKi8W0ird8TI/JK6CSxju3NojVI6BjHsTyK7zxA9urjVjEKJ5MBYC+bLmMcbAWlZ+rFkLpJQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm-glibc": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.1.tgz", + "integrity": "sha512-RCdZlEyTs8geyBkkcnPWvtXLY44BCeZKmGYRtSgtwwnHR4dxfHRG3gR99XdMEdQ7KeiDdasJwwvNSF5jKtDwdA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm-musl": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.1.tgz", + "integrity": "sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm64-glibc": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.1.tgz", + "integrity": "sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm64-musl": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.1.tgz", + "integrity": "sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-x64-glibc": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.1.tgz", + "integrity": "sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-x64-musl": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.1.tgz", + "integrity": "sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-arm64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.1.tgz", + "integrity": "sha512-RFzklRvmc3PkjKjry3hLF9wD7ppR4AKcWNzH7kXR7GUe0Igb3Nz8fyPwtZCSquGrhU5HhUNDr/mKBqj7tqA2Vw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-ia32": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.1.tgz", + "integrity": "sha512-c2KkcVN+NJmuA7CGlaGD1qJh1cLfDnQsHjE89E60vUEMlqduHGCdCLJCID5geFVM0dOtA3ZiIO8BoEQmzQVfpQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-x64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.1.tgz", + "integrity": "sha512-9lHBdJITeNR++EvSQVUcaZoWupyHfXe1jZvGZ06O/5MflPcuPLtEphScIBL+AiCWBO46tDSHzWyD0uDmmZqsgA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher/node_modules/node-addon-api": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", + "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@popperjs/core": { + "name": "@sxzz/popperjs-es", + "version": "2.11.7", + "resolved": "https://registry.npmjs.org/@sxzz/popperjs-es/-/popperjs-es-2.11.7.tgz", + "integrity": "sha512-Ccy0NlLkzr0Ex2FKvh2X+OyERHXJ88XJ1MXtsI9y9fGexlaXaVTPzBCRBwIxFkORuOb+uBqeu+RqnpgYTEZRUQ==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/popperjs" + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.54.0.tgz", + "integrity": "sha512-OywsdRHrFvCdvsewAInDKCNyR3laPA2mc9bRYJ6LBp5IyvF3fvXbbNR0bSzHlZVFtn6E0xw2oZlyjg4rKCVcng==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.54.0.tgz", + "integrity": "sha512-Skx39Uv+u7H224Af+bDgNinitlmHyQX1K/atIA32JP3JQw6hVODX5tkbi2zof/E69M1qH2UoN3Xdxgs90mmNYw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.54.0.tgz", + "integrity": "sha512-k43D4qta/+6Fq+nCDhhv9yP2HdeKeP56QrUUTW7E6PhZP1US6NDqpJj4MY0jBHlJivVJD5P8NxrjuobZBJTCRw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.54.0.tgz", + "integrity": "sha512-cOo7biqwkpawslEfox5Vs8/qj83M/aZCSSNIWpVzfU2CYHa2G3P1UN5WF01RdTHSgCkri7XOlTdtk17BezlV3A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.54.0.tgz", + "integrity": "sha512-miSvuFkmvFbgJ1BevMa4CPCFt5MPGw094knM64W9I0giUIMMmRYcGW/JWZDriaw/k1kOBtsWh1z6nIFV1vPNtA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.54.0.tgz", + "integrity": "sha512-KGXIs55+b/ZfZsq9aR026tmr/+7tq6VG6MsnrvF4H8VhwflTIuYh+LFUlIsRdQSgrgmtM3fVATzEAj4hBQlaqQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.54.0.tgz", + "integrity": "sha512-EHMUcDwhtdRGlXZsGSIuXSYwD5kOT9NVnx9sqzYiwAc91wfYOE1g1djOEDseZJKKqtHAHGwnGPQu3kytmfaXLQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.54.0.tgz", + "integrity": "sha512-+pBrqEjaakN2ySv5RVrj/qLytYhPKEUwk+e3SFU5jTLHIcAtqh2rLrd/OkbNuHJpsBgxsD8ccJt5ga/SeG0JmA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.54.0.tgz", + "integrity": "sha512-NSqc7rE9wuUaRBsBp5ckQ5CVz5aIRKCwsoa6WMF7G01sX3/qHUw/z4pv+D+ahL1EIKy6Enpcnz1RY8pf7bjwng==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.54.0.tgz", + "integrity": "sha512-gr5vDbg3Bakga5kbdpqx81m2n9IX8M6gIMlQQIXiLTNeQW6CucvuInJ91EuCJ/JYvc+rcLLsDFcfAD1K7fMofg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.54.0.tgz", + "integrity": "sha512-gsrtB1NA3ZYj2vq0Rzkylo9ylCtW/PhpLEivlgWe0bpgtX5+9j9EZa0wtZiCjgu6zmSeZWyI/e2YRX1URozpIw==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.54.0.tgz", + "integrity": "sha512-y3qNOfTBStmFNq+t4s7Tmc9hW2ENtPg8FeUD/VShI7rKxNW7O4fFeaYbMsd3tpFlIg1Q8IapFgy7Q9i2BqeBvA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.54.0.tgz", + "integrity": "sha512-89sepv7h2lIVPsFma8iwmccN7Yjjtgz0Rj/Ou6fEqg3HDhpCa+Et+YSufy27i6b0Wav69Qv4WBNl3Rs6pwhebQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.54.0.tgz", + "integrity": "sha512-ZcU77ieh0M2Q8Ur7D5X7KvK+UxbXeDHwiOt/CPSBTI1fBmeDMivW0dPkdqkT4rOgDjrDDBUed9x4EgraIKoR2A==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.54.0.tgz", + "integrity": "sha512-2AdWy5RdDF5+4YfG/YesGDDtbyJlC9LHmL6rZw6FurBJ5n4vFGupsOBGfwMRjBYH7qRQowT8D/U4LoSvVwOhSQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.54.0.tgz", + "integrity": "sha512-WGt5J8Ij/rvyqpFexxk3ffKqqbLf9AqrTBbWDk7ApGUzaIs6V+s2s84kAxklFwmMF/vBNGrVdYgbblCOFFezMQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.54.0.tgz", + "integrity": "sha512-JzQmb38ATzHjxlPHuTH6tE7ojnMKM2kYNzt44LO/jJi8BpceEC8QuXYA908n8r3CNuG/B3BV8VR3Hi1rYtmPiw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.54.0.tgz", + "integrity": "sha512-huT3fd0iC7jigGh7n3q/+lfPcXxBi+om/Rs3yiFxjvSxbSB6aohDFXbWvlspaqjeOh+hx7DDHS+5Es5qRkWkZg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.54.0.tgz", + "integrity": "sha512-c2V0W1bsKIKfbLMBu/WGBz6Yci8nJ/ZJdheE0EwB73N3MvHYKiKGs3mVilX4Gs70eGeDaMqEob25Tw2Gb9Nqyw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.54.0.tgz", + "integrity": "sha512-woEHgqQqDCkAzrDhvDipnSirm5vxUXtSKDYTVpZG3nUdW/VVB5VdCYA2iReSj/u3yCZzXID4kuKG7OynPnB3WQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.54.0.tgz", + "integrity": "sha512-dzAc53LOuFvHwbCEOS0rPbXp6SIhAf2txMP5p6mGyOXXw5mWY8NGGbPMPrs4P1WItkfApDathBj/NzMLUZ9rtQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.54.0.tgz", + "integrity": "sha512-hYT5d3YNdSh3mbCU1gwQyPgQd3T2ne0A3KG8KSBdav5TiBg6eInVmV+TeR5uHufiIgSFg0XsOWGW5/RhNcSvPg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@sideway/address": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.5.tgz", + "integrity": "sha512-IqO/DUQHUkPeixNQ8n0JA6102hT9CmaljNTPmQ1u8MEhBo/R4Q8eKLN/vGZxuebwOroDB4cbpjheD4+/sKFK4Q==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@hapi/hoek": "^9.0.0" + } + }, + "node_modules/@sideway/formula": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sideway/formula/-/formula-3.0.1.tgz", + "integrity": "sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@sideway/pinpoint": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@sideway/pinpoint/-/pinpoint-2.0.0.tgz", + "integrity": "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@sindresorhus/is": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz", + "integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/is?sponsor=1" + } + }, + "node_modules/@szmarczak/http-timer": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.6.tgz", + "integrity": "sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==", + "dev": true, + "license": "MIT", + "dependencies": { + "defer-to-connect": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@tootallnate/once": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", + "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, + "node_modules/@types/cacheable-request": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.3.tgz", + "integrity": "sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/http-cache-semantics": "*", + "@types/keyv": "^3.1.4", + "@types/node": "*", + "@types/responselike": "^1.0.0" + } + }, + "node_modules/@types/debug": { + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", + "integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/ms": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/fs-extra": { + "version": "9.0.13", + "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-9.0.13.tgz", + "integrity": "sha512-nEnwB++1u5lVDM2UI4c1+5R+FYaKfaAzS4OococimjVm3nQw3TuzH5UNsocrcTBbhnerblyHj4A49qXbIiZdpA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/http-cache-semantics": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.4.tgz", + "integrity": "sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/keyv": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.4.tgz", + "integrity": "sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-FOvQ0YPD5NOfPgMzJihoT+Za5pdkDJWcbpuj1DjaKZIr/gxodQjY/uWEFlTNqW2ugXHUiL8lRQgw63dzKHZdeQ==", + "license": "MIT" + }, + "node_modules/@types/lodash-es": { + "version": "4.17.12", + "resolved": "https://registry.npmjs.org/@types/lodash-es/-/lodash-es-4.17.12.tgz", + "integrity": "sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==", + "license": "MIT", + "dependencies": { + "@types/lodash": "*" + } + }, + "node_modules/@types/ms": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", + "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "20.19.27", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.27.tgz", + "integrity": "sha512-N2clP5pJhB2YnZJ3PIHFk5RkygRX5WO/5f0WC08tp0wd+sv0rsJk3MqWn3CbNmT2J505a5336jaQj4ph1AdMug==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@types/plist": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@types/plist/-/plist-3.0.5.tgz", + "integrity": "sha512-E6OCaRmAe4WDmWNsL/9RMqdkkzDCY1etutkflWk4c+AcjDU07Pcz1fQwTX0TQz+Pxqn9i4L1TU3UFpjnrcDgxA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@types/node": "*", + "xmlbuilder": ">=11.0.1" + } + }, + "node_modules/@types/responselike": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.3.tgz", + "integrity": "sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/verror": { + "version": "1.10.11", + "resolved": "https://registry.npmjs.org/@types/verror/-/verror-1.10.11.tgz", + "integrity": "sha512-RlDm9K7+o5stv0Co8i8ZRGxDbrTxhJtgjqjFyVh/tXQyl/rYtTKlnTvZ88oSTeYREWurwx20Js4kTuKCsFkUtg==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/@types/web-bluetooth": { + "version": "0.0.20", + "resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.20.tgz", + "integrity": "sha512-g9gZnnXVq7gM7v3tJCWV/qw7w+KeOlSHAhgF9RytFyifW6AF61hdT2ucrYhPq9hLs5JIryeupHV3qGk95dH9ow==", + "license": "MIT" + }, + "node_modules/@types/yauzl": { + "version": "2.10.3", + "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz", + "integrity": "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@vitejs/plugin-vue": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-4.6.2.tgz", + "integrity": "sha512-kqf7SGFoG+80aZG6Pf+gsZIVvGSCKE98JbiWqcCV9cThtg91Jav0yvYFC9Zb+jKetNGF6ZKeoaxgZfND21fWKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "peerDependencies": { + "vite": "^4.0.0 || ^5.0.0", + "vue": "^3.2.25" + } + }, + "node_modules/@volar/language-core": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@volar/language-core/-/language-core-1.11.1.tgz", + "integrity": "sha512-dOcNn3i9GgZAcJt43wuaEykSluAuOkQgzni1cuxLxTV0nJKanQztp7FxyswdRILaKH+P2XZMPRp2S4MV/pElCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/source-map": "1.11.1" + } + }, + "node_modules/@volar/source-map": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@volar/source-map/-/source-map-1.11.1.tgz", + "integrity": "sha512-hJnOnwZ4+WT5iupLRnuzbULZ42L7BWWPMmruzwtLhJfpDVoZLjNBxHDi2sY2bgZXCKlpU5XcsMFoYrsQmPhfZg==", + "dev": true, + "license": "MIT", + "dependencies": { + "muggle-string": "^0.3.1" + } + }, + "node_modules/@volar/typescript": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@volar/typescript/-/typescript-1.11.1.tgz", + "integrity": "sha512-iU+t2mas/4lYierSnoFOeRFQUhAEMgsFuQxoxvwn5EdQopw43j+J27a4lt9LMInx1gLJBC6qL14WYGlgymaSMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/language-core": "1.11.1", + "path-browserify": "^1.0.1" + } + }, + "node_modules/@vue/compiler-core": { + "version": "3.5.26", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.26.tgz", + "integrity": "sha512-vXyI5GMfuoBCnv5ucIT7jhHKl55Y477yxP6fc4eUswjP8FG3FFVFd41eNDArR+Uk3QKn2Z85NavjaxLxOC19/w==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.28.5", + "@vue/shared": "3.5.26", + "entities": "^7.0.0", + "estree-walker": "^2.0.2", + "source-map-js": "^1.2.1" + } + }, + "node_modules/@vue/compiler-dom": { + "version": "3.5.26", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.26.tgz", + "integrity": "sha512-y1Tcd3eXs834QjswshSilCBnKGeQjQXB6PqFn/1nxcQw4pmG42G8lwz+FZPAZAby6gZeHSt/8LMPfZ4Rb+Bd/A==", + "license": "MIT", + "dependencies": { + "@vue/compiler-core": "3.5.26", + "@vue/shared": "3.5.26" + } + }, + "node_modules/@vue/compiler-sfc": { + "version": "3.5.26", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.26.tgz", + "integrity": "sha512-egp69qDTSEZcf4bGOSsprUr4xI73wfrY5oRs6GSgXFTiHrWj4Y3X5Ydtip9QMqiCMCPVwLglB9GBxXtTadJ3mA==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.28.5", + "@vue/compiler-core": "3.5.26", + "@vue/compiler-dom": "3.5.26", + "@vue/compiler-ssr": "3.5.26", + "@vue/shared": "3.5.26", + "estree-walker": "^2.0.2", + "magic-string": "^0.30.21", + "postcss": "^8.5.6", + "source-map-js": "^1.2.1" + } + }, + "node_modules/@vue/compiler-ssr": { + "version": "3.5.26", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.26.tgz", + "integrity": "sha512-lZT9/Y0nSIRUPVvapFJEVDbEXruZh2IYHMk2zTtEgJSlP5gVOqeWXH54xDKAaFS4rTnDeDBQUYDtxKyoW9FwDw==", + "license": "MIT", + "dependencies": { + "@vue/compiler-dom": "3.5.26", + "@vue/shared": "3.5.26" + } + }, + "node_modules/@vue/devtools-api": { + "version": "6.6.4", + "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.6.4.tgz", + "integrity": "sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==", + "license": "MIT" + }, + "node_modules/@vue/language-core": { + "version": "1.8.27", + "resolved": "https://registry.npmjs.org/@vue/language-core/-/language-core-1.8.27.tgz", + "integrity": "sha512-L8Kc27VdQserNaCUNiSFdDl9LWT24ly8Hpwf1ECy3aFb9m6bDhBGQYOujDm21N7EW3moKIOKEanQwe1q5BK+mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/language-core": "~1.11.1", + "@volar/source-map": "~1.11.1", + "@vue/compiler-dom": "^3.3.0", + "@vue/shared": "^3.3.0", + "computeds": "^0.0.1", + "minimatch": "^9.0.3", + "muggle-string": "^0.3.1", + "path-browserify": "^1.0.1", + "vue-template-compiler": "^2.7.14" + }, + "peerDependencies": { + "typescript": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@vue/language-core/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@vue/reactivity": { + "version": "3.5.26", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.26.tgz", + "integrity": "sha512-9EnYB1/DIiUYYnzlnUBgwU32NNvLp/nhxLXeWRhHUEeWNTn1ECxX8aGO7RTXeX6PPcxe3LLuNBFoJbV4QZ+CFQ==", + "license": "MIT", + "dependencies": { + "@vue/shared": "3.5.26" + } + }, + "node_modules/@vue/runtime-core": { + "version": "3.5.26", + "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.26.tgz", + "integrity": "sha512-xJWM9KH1kd201w5DvMDOwDHYhrdPTrAatn56oB/LRG4plEQeZRQLw0Bpwih9KYoqmzaxF0OKSn6swzYi84e1/Q==", + "license": "MIT", + "dependencies": { + "@vue/reactivity": "3.5.26", + "@vue/shared": "3.5.26" + } + }, + "node_modules/@vue/runtime-dom": { + "version": "3.5.26", + "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.26.tgz", + "integrity": "sha512-XLLd/+4sPC2ZkN/6+V4O4gjJu6kSDbHAChvsyWgm1oGbdSO3efvGYnm25yCjtFm/K7rrSDvSfPDgN1pHgS4VNQ==", + "license": "MIT", + "dependencies": { + "@vue/reactivity": "3.5.26", + "@vue/runtime-core": "3.5.26", + "@vue/shared": "3.5.26", + "csstype": "^3.2.3" + } + }, + "node_modules/@vue/server-renderer": { + "version": "3.5.26", + "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.26.tgz", + "integrity": "sha512-TYKLXmrwWKSodyVuO1WAubucd+1XlLg4set0YoV+Hu8Lo79mp/YMwWV5mC5FgtsDxX3qo1ONrxFaTP1OQgy1uA==", + "license": "MIT", + "dependencies": { + "@vue/compiler-ssr": "3.5.26", + "@vue/shared": "3.5.26" + }, + "peerDependencies": { + "vue": "3.5.26" + } + }, + "node_modules/@vue/shared": { + "version": "3.5.26", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.26.tgz", + "integrity": "sha512-7Z6/y3uFI5PRoKeorTOSXKcDj0MSasfNNltcslbFrPpcw6aXRUALq4IfJlaTRspiWIUOEZbrpM+iQGmCOiWe4A==", + "license": "MIT" + }, + "node_modules/@vueuse/core": { + "version": "10.11.1", + "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-10.11.1.tgz", + "integrity": "sha512-guoy26JQktXPcz+0n3GukWIy/JDNKti9v6VEMu6kV2sYBsWuGiTU8OWdg+ADfUbHg3/3DlqySDe7JmdHrktiww==", + "license": "MIT", + "dependencies": { + "@types/web-bluetooth": "^0.0.20", + "@vueuse/metadata": "10.11.1", + "@vueuse/shared": "10.11.1", + "vue-demi": ">=0.14.8" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@vueuse/metadata": { + "version": "10.11.1", + "resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-10.11.1.tgz", + "integrity": "sha512-IGa5FXd003Ug1qAZmyE8wF3sJ81xGLSqTqtQ6jaVfkeZ4i5kS2mwQF61yhVqojRnenVew5PldLyRgvdl4YYuSw==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@vueuse/shared": { + "version": "10.11.1", + "resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-10.11.1.tgz", + "integrity": "sha512-LHpC8711VFZlDaYUXEBbFBCQ7GS3dVU9mjOhhMhXP6txTV4EhYQg/KGnQuvt/sPAtoUKq7VVUnL6mVtFoL42sA==", + "license": "MIT", + "dependencies": { + "vue-demi": ">=0.14.8" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@xmldom/xmldom": { + "version": "0.8.11", + "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.11.tgz", + "integrity": "sha512-cQzWCtO6C8TQiYl1ruKNn2U6Ao4o4WBBcbL61yJl84x+j5sOWWFU9X7DpND8XZG3daDppSsigMdfAIl2upQBRw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/7zip-bin": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/7zip-bin/-/7zip-bin-5.2.0.tgz", + "integrity": "sha512-ukTPVhqG4jNzMro2qA9HSCSSVJN3aN7tlb+hfqYCt3ER0yWroeA2VR38MNrOHLQ/cVj+DaIMad0kFCtWWowh/A==", + "dev": true, + "license": "MIT" + }, + "node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", + "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/ajv-formats/node_modules/ajv": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "license": "MIT" + }, + "node_modules/ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "ajv": "^6.9.1" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/app-builder-bin": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/app-builder-bin/-/app-builder-bin-4.0.0.tgz", + "integrity": "sha512-xwdG0FJPQMe0M0UA4Tz0zEB8rBJTRA5a476ZawAqiBkMv16GRK5xpXThOjMaEOFnZ6zabejjG4J3da0SXG63KA==", + "dev": true, + "license": "MIT" + }, + "node_modules/app-builder-lib": { + "version": "24.13.3", + "resolved": "https://registry.npmjs.org/app-builder-lib/-/app-builder-lib-24.13.3.tgz", + "integrity": "sha512-FAzX6IBit2POXYGnTCT8YHFO/lr5AapAII6zzhQO3Rw4cEDOgK+t1xhLc5tNcKlicTHlo9zxIwnYCX9X2DLkig==", + "dev": true, + "license": "MIT", + "dependencies": { + "@develar/schema-utils": "~2.6.5", + "@electron/notarize": "2.2.1", + "@electron/osx-sign": "1.0.5", + "@electron/universal": "1.5.1", + "@malept/flatpak-bundler": "^0.4.0", + "@types/fs-extra": "9.0.13", + "async-exit-hook": "^2.0.1", + "bluebird-lst": "^1.0.9", + "builder-util": "24.13.1", + "builder-util-runtime": "9.2.4", + "chromium-pickle-js": "^0.2.0", + "debug": "^4.3.4", + "ejs": "^3.1.8", + "electron-publish": "24.13.1", + "form-data": "^4.0.0", + "fs-extra": "^10.1.0", + "hosted-git-info": "^4.1.0", + "is-ci": "^3.0.0", + "isbinaryfile": "^5.0.0", + "js-yaml": "^4.1.0", + "lazy-val": "^1.0.5", + "minimatch": "^5.1.1", + "read-config-file": "6.3.2", + "sanitize-filename": "^1.6.3", + "semver": "^7.3.8", + "tar": "^6.1.12", + "temp-file": "^3.4.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "dmg-builder": "24.13.3", + "electron-builder-squirrel-windows": "24.13.3" + } + }, + "node_modules/app-builder-lib/node_modules/fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/app-builder-lib/node_modules/jsonfile": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", + "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/app-builder-lib/node_modules/semver": { + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/app-builder-lib/node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/archiver": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/archiver/-/archiver-5.3.2.tgz", + "integrity": "sha512-+25nxyyznAXF7Nef3y0EbBeqmGZgeN/BxHX29Rs39djAfaFalmQ89SE6CWyDCHzGL0yt/ycBtNOmGTW0FyGWNw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "archiver-utils": "^2.1.0", + "async": "^3.2.4", + "buffer-crc32": "^0.2.1", + "readable-stream": "^3.6.0", + "readdir-glob": "^1.1.2", + "tar-stream": "^2.2.0", + "zip-stream": "^4.1.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/archiver-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-2.1.0.tgz", + "integrity": "sha512-bEL/yUb/fNNiNTuUz979Z0Yg5L+LzLxGJz8x79lYmR54fmTIb6ob/hNQgkQnIUDWIFjZVQwl9Xs356I6BAMHfw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "glob": "^7.1.4", + "graceful-fs": "^4.2.0", + "lazystream": "^1.0.0", + "lodash.defaults": "^4.2.0", + "lodash.difference": "^4.5.0", + "lodash.flatten": "^4.4.0", + "lodash.isplainobject": "^4.0.6", + "lodash.union": "^4.6.0", + "normalize-path": "^3.0.0", + "readable-stream": "^2.0.0" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/archiver-utils/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/archiver-utils/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/archiver-utils/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/astral-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", + "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/async": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", + "dev": true, + "license": "MIT" + }, + "node_modules/async-exit-hook": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/async-exit-hook/-/async-exit-hook-2.0.1.tgz", + "integrity": "sha512-NW2cX8m1Q7KPA7a5M2ULQeZ2wR5qI5PAbw5L0UOMxdioVk9PMZ0h1TmyZEkPYrCvYjDlFICusOu1dlEKAAeXBw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/async-validator": { + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/async-validator/-/async-validator-4.2.5.tgz", + "integrity": "sha512-7HhHjtERjqlNbZtqNqy2rckN/SpOOlmDliet+lP7k+eKZEjPk3DgyeU9lIXLdeLz0uBbbVp+9Qdow9wJWgwwfg==", + "license": "MIT" + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/at-least-node": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", + "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/atomically": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/atomically/-/atomically-1.7.0.tgz", + "integrity": "sha512-Xcz9l0z7y9yQ9rdDaxlmaI4uJHf/T8g9hOEzJcsEqX2SjCj4J20uK7+ldkDHMbpJDK76wF7xEIgxc/vSlsfw5w==", + "license": "MIT", + "engines": { + "node": ">=10.12.0" + } + }, + "node_modules/axios": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.2.tgz", + "integrity": "sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.4", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/bluebird": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", + "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", + "license": "MIT" + }, + "node_modules/bluebird-lst": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/bluebird-lst/-/bluebird-lst-1.0.9.tgz", + "integrity": "sha512-7B1Rtx82hjnSD4PGLAjVWeYH3tHAcVUmChh85a3lltKQm6FresXh9ErQo6oAv6CqxttczC3/kEg8SY5NluPuUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "bluebird": "^3.5.5" + } + }, + "node_modules/boolean": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/boolean/-/boolean-3.2.0.tgz", + "integrity": "sha512-d0II/GO9uf9lfUHH2BQsjxzRJZBdsjgsBiW4BvhWk/3qoKwQFjIDVN19PfX8F2D/r9PCMTtLWjYVCFrpeYUzsw==", + "deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/buffer-equal": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal/-/buffer-equal-1.0.1.tgz", + "integrity": "sha512-QoV3ptgEaQpvVwbXdSO39iqPQTCxSF7A5U99AxbHYqUdCizL/lH2Z0A2y6nbZucxMEOtNyZfG2s6gsVugGpKkg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/builder-util": { + "version": "24.13.1", + "resolved": "https://registry.npmjs.org/builder-util/-/builder-util-24.13.1.tgz", + "integrity": "sha512-NhbCSIntruNDTOVI9fdXz0dihaqX2YuE1D6zZMrwiErzH4ELZHE6mdiB40wEgZNprDia+FghRFgKoAqMZRRjSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/debug": "^4.1.6", + "7zip-bin": "~5.2.0", + "app-builder-bin": "4.0.0", + "bluebird-lst": "^1.0.9", + "builder-util-runtime": "9.2.4", + "chalk": "^4.1.2", + "cross-spawn": "^7.0.3", + "debug": "^4.3.4", + "fs-extra": "^10.1.0", + "http-proxy-agent": "^5.0.0", + "https-proxy-agent": "^5.0.1", + "is-ci": "^3.0.0", + "js-yaml": "^4.1.0", + "source-map-support": "^0.5.19", + "stat-mode": "^1.0.0", + "temp-file": "^3.4.0" + } + }, + "node_modules/builder-util-runtime": { + "version": "9.2.4", + "resolved": "https://registry.npmjs.org/builder-util-runtime/-/builder-util-runtime-9.2.4.tgz", + "integrity": "sha512-upp+biKpN/XZMLim7aguUyW8s0FUpDvOtK6sbanMFDAMBzpHDqdhgVYm6zc9HJ6nWo7u2Lxk60i2M6Jd3aiNrA==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.3.4", + "sax": "^1.2.4" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/builder-util/node_modules/fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/builder-util/node_modules/jsonfile": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", + "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/builder-util/node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/cacheable-lookup": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz", + "integrity": "sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.6.0" + } + }, + "node_modules/cacheable-request": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-7.0.4.tgz", + "integrity": "sha512-v+p6ongsrp0yTGbJXjgxPow2+DL93DASP4kXCDKb8/bwRtt9OEF3whggkkDkGNzgcWy2XaF4a8nZglC7uElscg==", + "dev": true, + "license": "MIT", + "dependencies": { + "clone-response": "^1.0.2", + "get-stream": "^5.1.0", + "http-cache-semantics": "^4.0.0", + "keyv": "^4.0.0", + "lowercase-keys": "^2.0.0", + "normalize-url": "^6.0.1", + "responselike": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chalk/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/chromium-pickle-js": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/chromium-pickle-js/-/chromium-pickle-js-0.2.0.tgz", + "integrity": "sha512-1R5Fho+jBq0DDydt+/vHWj5KJNJCKdARKOCwZUen84I5BreWoLqRLANH1U87eJy1tiASPtMnGqJJq0ZsLoRPOw==", + "dev": true, + "license": "MIT" + }, + "node_modules/ci-info": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", + "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cli-truncate": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-2.1.0.tgz", + "integrity": "sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "slice-ansi": "^3.0.0", + "string-width": "^4.2.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/clone-response": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.3.tgz", + "integrity": "sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-response": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/commander": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-5.1.0.tgz", + "integrity": "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/compare-version": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/compare-version/-/compare-version-0.1.2.tgz", + "integrity": "sha512-pJDh5/4wrEnXX/VWRZvruAGHkzKdr46z11OlTPN+VrATlWWhSKewNCJ1futCO5C7eJB3nPMFZA1LeYtcFboZ2A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/compress-commons": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-4.1.2.tgz", + "integrity": "sha512-D3uMHtGc/fcO1Gt1/L7i1e33VOvD4A9hfQLP+6ewd+BvG/gQ84Yh4oftEhAdjSMgBgwGL+jsppT7JYNpo6MHHg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "buffer-crc32": "^0.2.13", + "crc32-stream": "^4.0.2", + "normalize-path": "^3.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/computeds": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/computeds/-/computeds-0.0.1.tgz", + "integrity": "sha512-7CEBgcMjVmitjYo5q8JTJVra6X5mQ20uTThdK+0kR7UEaDrAWEQcRiBtWJzga4eRpP6afNwwLsX2SET2JhVB1Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/concurrently": { + "version": "8.2.2", + "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-8.2.2.tgz", + "integrity": "sha512-1dP4gpXFhei8IOtlXRE/T/4H88ElHgTiUzh71YUmtjTEHMSRS2Z/fgOxHSxxusGHogsRfxNq1vyAwxSC+EVyDg==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.1.2", + "date-fns": "^2.30.0", + "lodash": "^4.17.21", + "rxjs": "^7.8.1", + "shell-quote": "^1.8.1", + "spawn-command": "0.0.2", + "supports-color": "^8.1.1", + "tree-kill": "^1.2.2", + "yargs": "^17.7.2" + }, + "bin": { + "conc": "dist/bin/concurrently.js", + "concurrently": "dist/bin/concurrently.js" + }, + "engines": { + "node": "^14.13.0 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/open-cli-tools/concurrently?sponsor=1" + } + }, + "node_modules/conf": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/conf/-/conf-10.2.0.tgz", + "integrity": "sha512-8fLl9F04EJqjSqH+QjITQfJF8BrOVaYr1jewVgSRAEWePfxT0sku4w2hrGQ60BC/TNLGQ2pgxNlTbWQmMPFvXg==", + "license": "MIT", + "dependencies": { + "ajv": "^8.6.3", + "ajv-formats": "^2.1.1", + "atomically": "^1.7.0", + "debounce-fn": "^4.0.0", + "dot-prop": "^6.0.1", + "env-paths": "^2.2.1", + "json-schema-typed": "^7.0.3", + "onetime": "^5.1.2", + "pkg-up": "^3.1.0", + "semver": "^7.3.5" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/conf/node_modules/ajv": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/conf/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "license": "MIT" + }, + "node_modules/conf/node_modules/semver": { + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/config-file-ts": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/config-file-ts/-/config-file-ts-0.2.6.tgz", + "integrity": "sha512-6boGVaglwblBgJqGyxm4+xCmEGcWgnWHSWHY5jad58awQhB6gftq0G8HbzU39YqCIYHMLAiL1yjwiZ36m/CL8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "glob": "^10.3.10", + "typescript": "^5.3.3" + } + }, + "node_modules/config-file-ts/node_modules/glob": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", + "dev": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/config-file-ts/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/config-file-ts/node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==", + "license": "MIT" + }, + "node_modules/crc": { + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/crc/-/crc-3.8.0.tgz", + "integrity": "sha512-iX3mfgcTMIq3ZKLIsVFAbv7+Mc10kxabAGQb8HvjA1o3T1PIYprbakQ65d3I+2HGHt6nSKkM9PYjgoJO2KcFBQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "buffer": "^5.1.0" + } + }, + "node_modules/crc-32": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz", + "integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "bin": { + "crc32": "bin/crc32.njs" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/crc32-stream": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-4.0.3.tgz", + "integrity": "sha512-NT7w2JVU7DFroFdYkeq8cywxrgjPHWkdX1wjpRQXPX5Asews3tA+Ght6lddQO5Mkumffp3X7GEqku3epj2toIw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "crc-32": "^1.2.0", + "readable-stream": "^3.4.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "license": "MIT" + }, + "node_modules/date-fns": { + "version": "2.30.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz", + "integrity": "sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.21.0" + }, + "engines": { + "node": ">=0.11" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/date-fns" + } + }, + "node_modules/dayjs": { + "version": "1.11.19", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.19.tgz", + "integrity": "sha512-t5EcLVS6QPBNqM2z8fakk/NKel+Xzshgt8FFKAn+qwlD1pzZWxh0nVCrvFK7ZDb6XucZeF9z8C7CBWTRIVApAw==", + "license": "MIT" + }, + "node_modules/de-indent": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/de-indent/-/de-indent-1.0.2.tgz", + "integrity": "sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==", + "dev": true, + "license": "MIT" + }, + "node_modules/debounce-fn": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/debounce-fn/-/debounce-fn-4.0.0.tgz", + "integrity": "sha512-8pYCQiL9Xdcg0UPSD3d+0KMlOjp+KGU5EPwYddgzQ7DATsg4fuUDjQtsYLmWjnk2obnNHgV3vE2Y4jejSOJVBQ==", + "license": "MIT", + "dependencies": { + "mimic-fn": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-response": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/decompress-response/node_modules/mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/defer-to-connect": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz", + "integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/detect-libc": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", + "integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "bin": { + "detect-libc": "bin/detect-libc.js" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/detect-node": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz", + "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/dir-compare": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/dir-compare/-/dir-compare-3.3.0.tgz", + "integrity": "sha512-J7/et3WlGUCxjdnD3HAAzQ6nsnc0WL6DD7WcwJb7c39iH1+AWfg+9OqzJNaI6PkBwBvm1mhZNL9iY/nRiZXlPg==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-equal": "^1.0.0", + "minimatch": "^3.0.4" + } + }, + "node_modules/dir-compare/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/dir-compare/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/dmg-builder": { + "version": "24.13.3", + "resolved": "https://registry.npmjs.org/dmg-builder/-/dmg-builder-24.13.3.tgz", + "integrity": "sha512-rcJUkMfnJpfCboZoOOPf4L29TRtEieHNOeAbYPWPxlaBw/Z1RKrRA86dOI9rwaI4tQSc/RD82zTNHprfUHXsoQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "app-builder-lib": "24.13.3", + "builder-util": "24.13.1", + "builder-util-runtime": "9.2.4", + "fs-extra": "^10.1.0", + "iconv-lite": "^0.6.2", + "js-yaml": "^4.1.0" + }, + "optionalDependencies": { + "dmg-license": "^1.0.11" + } + }, + "node_modules/dmg-builder/node_modules/fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/dmg-builder/node_modules/jsonfile": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", + "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/dmg-builder/node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/dmg-license": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/dmg-license/-/dmg-license-1.0.11.tgz", + "integrity": "sha512-ZdzmqwKmECOWJpqefloC5OJy1+WZBBse5+MR88z9g9Zn4VY+WYUkAyojmhzJckH5YbbZGcYIuGAkY5/Ys5OM2Q==", + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "dependencies": { + "@types/plist": "^3.0.1", + "@types/verror": "^1.10.3", + "ajv": "^6.10.0", + "crc": "^3.8.0", + "iconv-corefoundation": "^1.1.7", + "plist": "^3.0.4", + "smart-buffer": "^4.0.2", + "verror": "^1.10.0" + }, + "bin": { + "dmg-license": "bin/dmg-license.js" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/dot-prop": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-6.0.1.tgz", + "integrity": "sha512-tE7ztYzXHIeyvc7N+hR3oi7FIbf/NIjVP9hmAt3yMXzrQ072/fpjGLx2GxNxGxUl5V73MEqYzioOMoVhGMJ5cA==", + "license": "MIT", + "dependencies": { + "is-obj": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/dotenv": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-9.0.2.tgz", + "integrity": "sha512-I9OvvrHp4pIARv4+x9iuewrWycX6CcZtoAu1XrzPxc5UygMJXJZYmBsynku8IkrJwgypE5DGNjDPmPRhDCptUg==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=10" + } + }, + "node_modules/dotenv-expand": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-5.1.0.tgz", + "integrity": "sha512-YXQl1DSa4/PQyRfgrv6aoNjhasp/p4qs9FjJ4q4cQk+8m4r6k4ZSiEyytKG8f8W9gi8WsQtIObNmKd+tMzNTmA==", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/duplexer2": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz", + "integrity": "sha512-asLFVfWWtJ90ZyOUHMqk7/S2w2guQKxUI2itj3d92ADHhxUSbCMGi1f1cBcJ7xM1To+pE/Khbwo1yuNbMEPKeA==", + "license": "BSD-3-Clause", + "dependencies": { + "readable-stream": "^2.0.2" + } + }, + "node_modules/duplexer2/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/duplexer2/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, + "node_modules/duplexer2/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true, + "license": "MIT" + }, + "node_modules/ejs": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz", + "integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "jake": "^10.8.5" + }, + "bin": { + "ejs": "bin/cli.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/electron": { + "version": "28.3.3", + "resolved": "https://registry.npmjs.org/electron/-/electron-28.3.3.tgz", + "integrity": "sha512-ObKMLSPNhomtCOBAxFS8P2DW/4umkh72ouZUlUKzXGtYuPzgr1SYhskhFWgzAsPtUzhL2CzyV2sfbHcEW4CXqw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "@electron/get": "^2.0.0", + "@types/node": "^18.11.18", + "extract-zip": "^2.0.1" + }, + "bin": { + "electron": "cli.js" + }, + "engines": { + "node": ">= 12.20.55" + } + }, + "node_modules/electron-builder": { + "version": "24.13.3", + "resolved": "https://registry.npmjs.org/electron-builder/-/electron-builder-24.13.3.tgz", + "integrity": "sha512-yZSgVHft5dNVlo31qmJAe4BVKQfFdwpRw7sFp1iQglDRCDD6r22zfRJuZlhtB5gp9FHUxCMEoWGq10SkCnMAIg==", + "dev": true, + "license": "MIT", + "dependencies": { + "app-builder-lib": "24.13.3", + "builder-util": "24.13.1", + "builder-util-runtime": "9.2.4", + "chalk": "^4.1.2", + "dmg-builder": "24.13.3", + "fs-extra": "^10.1.0", + "is-ci": "^3.0.0", + "lazy-val": "^1.0.5", + "read-config-file": "6.3.2", + "simple-update-notifier": "2.0.0", + "yargs": "^17.6.2" + }, + "bin": { + "electron-builder": "cli.js", + "install-app-deps": "install-app-deps.js" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/electron-builder-squirrel-windows": { + "version": "24.13.3", + "resolved": "https://registry.npmjs.org/electron-builder-squirrel-windows/-/electron-builder-squirrel-windows-24.13.3.tgz", + "integrity": "sha512-oHkV0iogWfyK+ah9ZIvMDpei1m9ZRpdXcvde1wTpra2U8AFDNNpqJdnin5z+PM1GbQ5BoaKCWas2HSjtR0HwMg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "app-builder-lib": "24.13.3", + "archiver": "^5.3.1", + "builder-util": "24.13.1", + "fs-extra": "^10.1.0" + } + }, + "node_modules/electron-builder-squirrel-windows/node_modules/fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/electron-builder-squirrel-windows/node_modules/jsonfile": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", + "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/electron-builder-squirrel-windows/node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/electron-builder/node_modules/fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/electron-builder/node_modules/jsonfile": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", + "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/electron-builder/node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/electron-publish": { + "version": "24.13.1", + "resolved": "https://registry.npmjs.org/electron-publish/-/electron-publish-24.13.1.tgz", + "integrity": "sha512-2ZgdEqJ8e9D17Hwp5LEq5mLQPjqU3lv/IALvgp+4W8VeNhryfGhYEQC/PgDPMrnWUp+l60Ou5SJLsu+k4mhQ8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/fs-extra": "^9.0.11", + "builder-util": "24.13.1", + "builder-util-runtime": "9.2.4", + "chalk": "^4.1.2", + "fs-extra": "^10.1.0", + "lazy-val": "^1.0.5", + "mime": "^2.5.2" + } + }, + "node_modules/electron-publish/node_modules/fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/electron-publish/node_modules/jsonfile": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", + "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/electron-publish/node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/electron-store": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/electron-store/-/electron-store-8.2.0.tgz", + "integrity": "sha512-ukLL5Bevdil6oieAOXz3CMy+OgaItMiVBg701MNlG6W5RaC0AHN7rvlqTCmeb6O7jP0Qa1KKYTE0xV0xbhF4Hw==", + "license": "MIT", + "dependencies": { + "conf": "^10.2.0", + "type-fest": "^2.17.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/electron/node_modules/@types/node": { + "version": "18.19.130", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.130.tgz", + "integrity": "sha512-GRaXQx6jGfL8sKfaIDD6OupbIHBr9jv7Jnaml9tB7l4v068PAOXqfcujMMo5PhbIs6ggR1XODELqahT2R8v0fg==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/electron/node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "dev": true, + "license": "MIT" + }, + "node_modules/element-plus": { + "version": "2.13.0", + "resolved": "https://registry.npmjs.org/element-plus/-/element-plus-2.13.0.tgz", + "integrity": "sha512-qjxS+SBChvqCl6lU6ShiliLMN6WqFHiXQENYbAY3GKNflG+FS3jqn8JmQq0CBZq4koFqsi95NT1M6SL4whZfrA==", + "license": "MIT", + "dependencies": { + "@ctrl/tinycolor": "^3.4.1", + "@element-plus/icons-vue": "^2.3.2", + "@floating-ui/dom": "^1.0.1", + "@popperjs/core": "npm:@sxzz/popperjs-es@^2.11.7", + "@types/lodash": "^4.17.20", + "@types/lodash-es": "^4.17.12", + "@vueuse/core": "^10.11.0", + "async-validator": "^4.2.5", + "dayjs": "^1.11.19", + "lodash": "^4.17.21", + "lodash-es": "^4.17.21", + "lodash-unified": "^1.0.3", + "memoize-one": "^6.0.0", + "normalize-wheel-es": "^1.2.0" + }, + "peerDependencies": { + "vue": "^3.3.0" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/end-of-stream": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", + "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/entities": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-7.0.0.tgz", + "integrity": "sha512-FDWG5cmEYf2Z00IkYRhbFrwIwvdFKH07uV8dvNy0omp/Qb1xcyCWp2UDtcwJF4QZZvk0sLudP6/hAu42TaqVhQ==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/err-code": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz", + "integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==", + "dev": true, + "license": "MIT" + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es6-error": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz", + "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/esbuild": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "license": "MIT" + }, + "node_modules/extract-zip": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", + "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "debug": "^4.1.1", + "get-stream": "^5.1.0", + "yauzl": "^2.10.0" + }, + "bin": { + "extract-zip": "cli.js" + }, + "engines": { + "node": ">= 10.17.0" + }, + "optionalDependencies": { + "@types/yauzl": "^2.9.1" + } + }, + "node_modules/extsprintf": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.4.1.tgz", + "integrity": "sha512-Wrk35e8ydCKDj/ArClo1VrPVmN8zph5V4AtHwIuHhvMXsKf73UT3BOD+azBIW+3wOJ4FhEH7zyaJCFvChjYvMA==", + "dev": true, + "engines": [ + "node >=0.6.0" + ], + "license": "MIT", + "optional": true + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "license": "MIT" + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", + "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/fd-slicer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", + "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "pend": "~1.2.0" + } + }, + "node_modules/filelist": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz", + "integrity": "sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "minimatch": "^5.0.1" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "license": "MIT", + "dependencies": { + "locate-path": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/follow-redirects": { + "version": "1.15.11", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", + "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "dev": true, + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/form-data": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/fs-extra": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + }, + "engines": { + "node": ">=6 <7 || >=8" + } + }, + "node_modules/fs-minipass": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/fs-minipass/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true, + "license": "ISC" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/glob/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/global-agent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/global-agent/-/global-agent-3.0.0.tgz", + "integrity": "sha512-PT6XReJ+D07JvGoxQMkT6qji/jVNfX/h364XHZOWeRzy64sSFr+xJ5OX7LI3b4MPQzdL4H8Y8M0xzPpsVMwA8Q==", + "dev": true, + "license": "BSD-3-Clause", + "optional": true, + "dependencies": { + "boolean": "^3.0.1", + "es6-error": "^4.1.1", + "matcher": "^3.0.0", + "roarr": "^2.15.3", + "semver": "^7.3.2", + "serialize-error": "^7.0.1" + }, + "engines": { + "node": ">=10.0" + } + }, + "node_modules/global-agent/node_modules/semver": { + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "dev": true, + "license": "ISC", + "optional": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/globalthis": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", + "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "define-properties": "^1.2.1", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/got": { + "version": "11.8.6", + "resolved": "https://registry.npmjs.org/got/-/got-11.8.6.tgz", + "integrity": "sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sindresorhus/is": "^4.0.0", + "@szmarczak/http-timer": "^4.0.5", + "@types/cacheable-request": "^6.0.1", + "@types/responselike": "^1.0.0", + "cacheable-lookup": "^5.0.3", + "cacheable-request": "^7.0.2", + "decompress-response": "^6.0.0", + "http2-wrapper": "^1.0.0-beta.5.2", + "lowercase-keys": "^2.0.0", + "p-cancelable": "^2.0.0", + "responselike": "^2.0.0" + }, + "engines": { + "node": ">=10.19.0" + }, + "funding": { + "url": "https://github.com/sindresorhus/got?sponsor=1" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "license": "ISC" + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true, + "license": "MIT", + "bin": { + "he": "bin/he" + } + }, + "node_modules/hosted-git-info": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz", + "integrity": "sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==", + "dev": true, + "license": "ISC", + "dependencies": { + "lru-cache": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/http-cache-semantics": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz", + "integrity": "sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/http-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", + "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@tootallnate/once": "2", + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/http2-wrapper": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-1.0.3.tgz", + "integrity": "sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "quick-lru": "^5.1.1", + "resolve-alpn": "^1.0.0" + }, + "engines": { + "node": ">=10.19.0" + } + }, + "node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/iconv-corefoundation": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/iconv-corefoundation/-/iconv-corefoundation-1.1.7.tgz", + "integrity": "sha512-T10qvkw0zz4wnm560lOEg0PovVqUXuOFhhHAkixw8/sycy7TJt7v/RrkEKEQnAw2viPSJu6iAkErxnzR0g8PpQ==", + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "dependencies": { + "cli-truncate": "^2.1.0", + "node-addon-api": "^1.6.3" + }, + "engines": { + "node": "^8.11.2 || >=10" + } + }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/immutable": { + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.4.tgz", + "integrity": "sha512-p6u1bG3YSnINT5RQmx/yRZBpenIl30kVxkTLDyHLIMk0gict704Q9n+thfDI7lTRm9vXdDYutVzXhzcThxTnXA==", + "dev": true, + "license": "MIT" + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "dev": true, + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/is-ci": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-3.0.1.tgz", + "integrity": "sha512-ZYvCgrefwqoQ6yTyYUbQu64HsITZ3NfKX1lzaEYdkTDcfKzzCI/wthRRYKkdjHKFVgNiXKAKm65Zo1pk2as/QQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ci-info": "^3.2.0" + }, + "bin": { + "is-ci": "bin.js" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-obj": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", + "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "license": "MIT" + }, + "node_modules/isbinaryfile": { + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-5.0.7.tgz", + "integrity": "sha512-gnWD14Jh3FzS3CPhF0AxNOJ8CxqeblPTADzI38r0wt8ZyQl5edpy75myt08EG2oKvpyiqSqsx+Wkz9vtkbTqYQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 18.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/gjtorikian/" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/jake": { + "version": "10.9.4", + "resolved": "https://registry.npmjs.org/jake/-/jake-10.9.4.tgz", + "integrity": "sha512-wpHYzhxiVQL+IV05BLE2Xn34zW1S223hvjtqk0+gsPrwd/8JNLXJgZZM/iPFsYc1xyphF+6M6EvdE5E9MBGkDA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "async": "^3.2.6", + "filelist": "^1.0.4", + "picocolors": "^1.1.1" + }, + "bin": { + "jake": "bin/cli.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/joi": { + "version": "17.13.3", + "resolved": "https://registry.npmjs.org/joi/-/joi-17.13.3.tgz", + "integrity": "sha512-otDA4ldcIx+ZXsKHWmp0YizCweVRZG96J10b0FevjfuncLO1oX59THoAmHkNubYJ+9gWsYsp5k8v4ib6oDv1fA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@hapi/hoek": "^9.3.0", + "@hapi/topo": "^5.1.0", + "@sideway/address": "^4.1.5", + "@sideway/formula": "^3.0.1", + "@sideway/pinpoint": "^2.0.0" + } + }, + "node_modules/js-yaml": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-typed": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/json-schema-typed/-/json-schema-typed-7.0.3.tgz", + "integrity": "sha512-7DE8mpG+/fVw+dTpjbxnx47TaMnDfOI1jwft9g1VybltZCduyRQPJPvc+zzKY9WPHxhPWczyFuYa6I8Mw4iU5A==", + "license": "BSD-2-Clause" + }, + "node_modules/json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", + "dev": true, + "license": "ISC", + "optional": true + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", + "dev": true, + "license": "MIT", + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/lazy-val": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/lazy-val/-/lazy-val-1.0.5.tgz", + "integrity": "sha512-0/BnGCCfyUMkBpeDgWihanIAF9JmZhHBgUhEqzvf+adhNGLoP6TaiI5oF8oyb3I45P+PcnrqihSf01M0l0G5+Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/lazystream": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/lazystream/-/lazystream-1.0.1.tgz", + "integrity": "sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "readable-stream": "^2.0.5" + }, + "engines": { + "node": ">= 0.6.3" + } + }, + "node_modules/lazystream/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/lazystream/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/lazystream/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "license": "MIT", + "dependencies": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "license": "MIT" + }, + "node_modules/lodash-es": { + "version": "4.17.22", + "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.22.tgz", + "integrity": "sha512-XEawp1t0gxSi9x01glktRZ5HDy0HXqrM0x5pXQM98EaI0NxO6jVM7omDOxsuEo5UIASAnm2bRp1Jt/e0a2XU8Q==", + "license": "MIT" + }, + "node_modules/lodash-unified": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/lodash-unified/-/lodash-unified-1.0.3.tgz", + "integrity": "sha512-WK9qSozxXOD7ZJQlpSqOT+om2ZfcT4yO+03FuzAHD0wF6S0l0090LRPDx3vhTTLZ8cFKpBn+IOcVXK6qOcIlfQ==", + "license": "MIT", + "peerDependencies": { + "@types/lodash-es": "*", + "lodash": "*", + "lodash-es": "*" + } + }, + "node_modules/lodash.defaults": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", + "integrity": "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/lodash.difference": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.difference/-/lodash.difference-4.5.0.tgz", + "integrity": "sha512-dS2j+W26TQ7taQBGN8Lbbq04ssV3emRw4NY58WErlTO29pIqS0HmoT5aJ9+TUQ1N3G+JOZSji4eugsWwGp9yPA==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/lodash.flatten": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz", + "integrity": "sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/lodash.union": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.union/-/lodash.union-4.6.0.tgz", + "integrity": "sha512-c4pB2CdGrGdjMKYLA+XiRDO7Y0PRQbm/Gzg8qMj+QH+pFVAoTp5sBpO0odL3FjoPCGjK96p6qsP+yQoiLoOBcw==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/lowercase-keys": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", + "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/matcher": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/matcher/-/matcher-3.0.0.tgz", + "integrity": "sha512-OkeDaAZ/bQCxeFAozM55PKcKU0yJMPGifLwV4Qgjitu+5MoAfSQN4lsLJeXZ1b8w0x+/Emda6MZgXS1jvsapng==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "escape-string-regexp": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/memoize-one": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-6.0.0.tgz", + "integrity": "sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==", + "license": "MIT" + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mime": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", + "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", + "dev": true, + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-fn": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-3.1.0.tgz", + "integrity": "sha512-Ysbi9uYW9hFyfrThdDEQuykN4Ey6BuwPD2kpI5ES/nFTDn/98yxYNLZJcgUAKPT/mcrLLKaGzJR9YVxJrIdASQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/mimic-response": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", + "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/minipass": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", + "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=8" + } + }, + "node_modules/minizlib": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "dev": true, + "license": "MIT", + "dependencies": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minizlib/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true, + "license": "MIT", + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/muggle-string": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/muggle-string/-/muggle-string-0.3.1.tgz", + "integrity": "sha512-ckmWDJjphvd/FvZawgygcUeQCxzvohjFO5RxTjj4eq8kw359gFF3E1brjfI+viLMxss5JrHTDRHZvu2/tuy0Qg==", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/node-addon-api": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-1.7.2.tgz", + "integrity": "sha512-ibPK3iA+vaY1eEjESkQkM0BbCqFOaZMiXRTtdB0u7b4djtY6JnsjvPdUHVMg6xQt3B8fpTTWHI9A+ADjM9frzg==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/node-int64": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", + "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", + "license": "MIT" + }, + "node_modules/node-windows": { + "version": "1.0.0-beta.8", + "resolved": "https://registry.npmjs.org/node-windows/-/node-windows-1.0.0-beta.8.tgz", + "integrity": "sha512-uLekXnSeem3nW5escID224Fd0U/1VtvE796JpSpOY+c73Cslz/Qn2WUHRJyPQJEMrNGAy/FMRFjjhh4z1alZTA==", + "license": "MIT", + "dependencies": { + "xml": "1.0.1", + "yargs": "^17.5.1" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/normalize-url": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz", + "integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/normalize-wheel-es": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/normalize-wheel-es/-/normalize-wheel-es-1.2.0.tgz", + "integrity": "sha512-Wj7+EJQ8mSuXr2iWfnujrimU35R2W4FAErEyTmJoJ7ucwTn2hOUSsRehMb5RSYkxXGTM7Y9QpvPmp++w5ftoJw==", + "license": "BSD-3-Clause" + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "license": "MIT", + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/onetime/node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/p-cancelable": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.1.tgz", + "integrity": "sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "license": "MIT", + "dependencies": { + "p-limit": "^2.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "dev": true, + "license": "BlueOak-1.0.0" + }, + "node_modules/path-browserify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz", + "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==", + "dev": true, + "license": "MIT" + }, + "node_modules/path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/pend": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", + "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", + "dev": true, + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pinia": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/pinia/-/pinia-2.3.1.tgz", + "integrity": "sha512-khUlZSwt9xXCaTbbxFYBKDc/bWAGWJjOgvxETwkTN7KRm66EeT1ZdZj6i2ceh9sP2Pzqsbc704r2yngBrxBVug==", + "license": "MIT", + "dependencies": { + "@vue/devtools-api": "^6.6.3", + "vue-demi": "^0.14.10" + }, + "funding": { + "url": "https://github.com/sponsors/posva" + }, + "peerDependencies": { + "typescript": ">=4.4.4", + "vue": "^2.7.0 || ^3.5.11" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/pkg-up": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/pkg-up/-/pkg-up-3.1.0.tgz", + "integrity": "sha512-nDywThFk1i4BQK4twPQ6TA4RT8bDY96yeuCVBWL3ePARCiEKDRSrNGbFIgUJpLp+XeIR65v8ra7WuJOFUBtkMA==", + "license": "MIT", + "dependencies": { + "find-up": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/plist": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/plist/-/plist-3.1.0.tgz", + "integrity": "sha512-uysumyrvkUX0rX/dEVqt8gC3sTBzd4zoWfLeS29nb53imdaXVvLINYXTI2GNqzaMuvacNx4uJQ8+b3zXR0pkgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@xmldom/xmldom": "^0.8.8", + "base64-js": "^1.5.1", + "xmlbuilder": "^15.1.1" + }, + "engines": { + "node": ">=10.4.0" + } + }, + "node_modules/postcss": { + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "license": "MIT" + }, + "node_modules/progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/promise-retry": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz", + "integrity": "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "err-code": "^2.0.2", + "retry": "^0.12.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "license": "MIT" + }, + "node_modules/pump": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.3.tgz", + "integrity": "sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==", + "dev": true, + "license": "MIT", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/quick-lru": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", + "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/read-config-file": { + "version": "6.3.2", + "resolved": "https://registry.npmjs.org/read-config-file/-/read-config-file-6.3.2.tgz", + "integrity": "sha512-M80lpCjnE6Wt6zb98DoW8WHR09nzMSpu8XHtPkiTHrJ5Az9CybfeQhTJ8D7saeBHpGhLPIVyA8lcL6ZmdKwY6Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "config-file-ts": "^0.2.4", + "dotenv": "^9.0.2", + "dotenv-expand": "^5.1.0", + "js-yaml": "^4.1.0", + "json5": "^2.2.0", + "lazy-val": "^1.0.4" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/readdir-glob": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/readdir-glob/-/readdir-glob-1.1.3.tgz", + "integrity": "sha512-v05I2k7xN8zXvPD9N+z/uhXPaj0sUFCe2rcWZIpBsqxfP7xXFQ0tipAd/wjj1YxWyWtUS5IDJpOG82JKt2EAVA==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "minimatch": "^5.1.0" + } + }, + "node_modules/readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve-alpn": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.1.tgz", + "integrity": "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==", + "dev": true, + "license": "MIT" + }, + "node_modules/responselike": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/responselike/-/responselike-2.0.1.tgz", + "integrity": "sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw==", + "dev": true, + "license": "MIT", + "dependencies": { + "lowercase-keys": "^2.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/roarr": { + "version": "2.15.4", + "resolved": "https://registry.npmjs.org/roarr/-/roarr-2.15.4.tgz", + "integrity": "sha512-CHhPh+UNHD2GTXNYhPWLnU8ONHdI+5DI+4EYIAOaiD63rHeYlZvyh8P+in5999TTSFgUYuKUAjzRI4mdh/p+2A==", + "dev": true, + "license": "BSD-3-Clause", + "optional": true, + "dependencies": { + "boolean": "^3.0.1", + "detect-node": "^2.0.4", + "globalthis": "^1.0.1", + "json-stringify-safe": "^5.0.1", + "semver-compare": "^1.0.0", + "sprintf-js": "^1.1.2" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/rollup": { + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.54.0.tgz", + "integrity": "sha512-3nk8Y3a9Ea8szgKhinMlGMhGMw89mqule3KWczxhIzqudyHdCIOHw8WJlj/r329fACjKLEh13ZSk7oE22kyeIw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.54.0", + "@rollup/rollup-android-arm64": "4.54.0", + "@rollup/rollup-darwin-arm64": "4.54.0", + "@rollup/rollup-darwin-x64": "4.54.0", + "@rollup/rollup-freebsd-arm64": "4.54.0", + "@rollup/rollup-freebsd-x64": "4.54.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.54.0", + "@rollup/rollup-linux-arm-musleabihf": "4.54.0", + "@rollup/rollup-linux-arm64-gnu": "4.54.0", + "@rollup/rollup-linux-arm64-musl": "4.54.0", + "@rollup/rollup-linux-loong64-gnu": "4.54.0", + "@rollup/rollup-linux-ppc64-gnu": "4.54.0", + "@rollup/rollup-linux-riscv64-gnu": "4.54.0", + "@rollup/rollup-linux-riscv64-musl": "4.54.0", + "@rollup/rollup-linux-s390x-gnu": "4.54.0", + "@rollup/rollup-linux-x64-gnu": "4.54.0", + "@rollup/rollup-linux-x64-musl": "4.54.0", + "@rollup/rollup-openharmony-arm64": "4.54.0", + "@rollup/rollup-win32-arm64-msvc": "4.54.0", + "@rollup/rollup-win32-ia32-msvc": "4.54.0", + "@rollup/rollup-win32-x64-gnu": "4.54.0", + "@rollup/rollup-win32-x64-msvc": "4.54.0", + "fsevents": "~2.3.2" + } + }, + "node_modules/rxjs": { + "version": "7.8.2", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", + "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "peer": true + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true, + "license": "MIT" + }, + "node_modules/sanitize-filename": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/sanitize-filename/-/sanitize-filename-1.6.3.tgz", + "integrity": "sha512-y/52Mcy7aw3gRm7IrcGDFx/bCk4AhRh2eI9luHOQM86nZsqwiRkkq2GekHXBBD+SmPidc8i2PqtYZl+pWJ8Oeg==", + "dev": true, + "license": "WTFPL OR ISC", + "dependencies": { + "truncate-utf8-bytes": "^1.0.0" + } + }, + "node_modules/sass": { + "version": "1.97.1", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.97.1.tgz", + "integrity": "sha512-uf6HoO8fy6ClsrShvMgaKUn14f2EHQLQRtpsZZLeU/Mv0Q1K5P0+x2uvH6Cub39TVVbWNSrraUhDAoFph6vh0A==", + "dev": true, + "license": "MIT", + "dependencies": { + "chokidar": "^4.0.0", + "immutable": "^5.0.2", + "source-map-js": ">=0.6.2 <2.0.0" + }, + "bin": { + "sass": "sass.js" + }, + "engines": { + "node": ">=14.0.0" + }, + "optionalDependencies": { + "@parcel/watcher": "^2.4.1" + } + }, + "node_modules/sax": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.3.tgz", + "integrity": "sha512-yqYn1JhPczigF94DMS+shiDMjDowYO6y9+wB/4WgO0Y19jWYk0lQ4tuG5KI7kj4FTp1wxPj5IFfcrz/s1c3jjQ==", + "dev": true, + "license": "BlueOak-1.0.0" + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/semver-compare": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/semver-compare/-/semver-compare-1.0.0.tgz", + "integrity": "sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/serialize-error": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-7.0.1.tgz", + "integrity": "sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "type-fest": "^0.13.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/serialize-error/node_modules/type-fest": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.13.1.tgz", + "integrity": "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "optional": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/shell-quote": { + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.3.tgz", + "integrity": "sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/simple-update-notifier": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", + "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/simple-update-notifier/node_modules/semver": { + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/slice-ansi": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-3.0.0.tgz", + "integrity": "sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/smart-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/spawn-command": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/spawn-command/-/spawn-command-0.0.2.tgz", + "integrity": "sha512-zC8zGoGkmc8J9ndvml8Xksr1Amk9qBujgbF0JAIWO7kXr43w0h/0GJNM/Vustixu+YE8N/MTrQ7N31FvHUACxQ==", + "dev": true + }, + "node_modules/sprintf-js": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz", + "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==", + "dev": true, + "license": "BSD-3-Clause", + "optional": true + }, + "node_modules/stat-mode": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/stat-mode/-/stat-mode-1.0.0.tgz", + "integrity": "sha512-jH9EhtKIjuXZ2cWxmXS8ZP80XyC3iasQxMDV8jzhNJpfDb7VbQLVW4Wvsxz9QZvzV+G4YoSfBUVKDOyxLzi/sg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/sudo-prompt": { + "version": "9.2.1", + "resolved": "https://registry.npmjs.org/sudo-prompt/-/sudo-prompt-9.2.1.tgz", + "integrity": "sha512-Mu7R0g4ig9TUuGSxJavny5Rv0egCEtpZRNMrZaYS1vxkiIxGiGUwoezU3LazIQ+KE04hTrTfNPgxU5gzi7F5Pw==", + "deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.", + "license": "MIT" + }, + "node_modules/sumchecker": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/sumchecker/-/sumchecker-3.0.1.tgz", + "integrity": "sha512-MvjXzkz/BOfyVDkG0oFOtBxHX2u3gKbMHIF/dXblZsgD3BWOFLmHovIpZY7BykJdAjcqRCBi1WYBNdEC9yI7vg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "debug": "^4.1.0" + }, + "engines": { + "node": ">= 8.0" + } + }, + "node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/tar": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", + "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", + "dev": true, + "license": "ISC", + "dependencies": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^5.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/temp-file": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/temp-file/-/temp-file-3.4.0.tgz", + "integrity": "sha512-C5tjlC/HCtVUOi3KWVokd4vHVViOmGjtLwIh4MuzPo/nMYTV/p1urt3RnMz2IWXDdKEGJH3k5+KPxtqRsUYGtg==", + "dev": true, + "license": "MIT", + "dependencies": { + "async-exit-hook": "^2.0.1", + "fs-extra": "^10.0.0" + } + }, + "node_modules/temp-file/node_modules/fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/temp-file/node_modules/jsonfile": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", + "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/temp-file/node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/tmp": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.5.tgz", + "integrity": "sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.14" + } + }, + "node_modules/tmp-promise": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/tmp-promise/-/tmp-promise-3.0.3.tgz", + "integrity": "sha512-RwM7MoPojPxsOBYnyd2hy0bxtIlVrihNs9pj5SUvY8Zz1sQcQG2tG1hSr8PDxfgEB8RNKDhqbIlroIarSNDNsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "tmp": "^0.2.0" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/tree-kill": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", + "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", + "dev": true, + "license": "MIT", + "bin": { + "tree-kill": "cli.js" + } + }, + "node_modules/truncate-utf8-bytes": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/truncate-utf8-bytes/-/truncate-utf8-bytes-1.0.2.tgz", + "integrity": "sha512-95Pu1QXQvruGEhv62XCMO3Mm90GscOCClvrIUwCM0PYOXK3kaF3l3sIHxx71ThJfcbM2O5Au6SO3AWCSEfW4mQ==", + "dev": true, + "license": "WTFPL", + "dependencies": { + "utf8-byte-length": "^1.0.1" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD" + }, + "node_modules/type-fest": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", + "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "devOptional": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/unzipper": { + "version": "0.12.3", + "resolved": "https://registry.npmjs.org/unzipper/-/unzipper-0.12.3.tgz", + "integrity": "sha512-PZ8hTS+AqcGxsaQntl3IRBw65QrBI6lxzqDEL7IAo/XCEqRTKGfOX56Vea5TH9SZczRVxuzk1re04z/YjuYCJA==", + "license": "MIT", + "dependencies": { + "bluebird": "~3.7.2", + "duplexer2": "~0.1.4", + "fs-extra": "^11.2.0", + "graceful-fs": "^4.2.2", + "node-int64": "^0.4.0" + } + }, + "node_modules/unzipper/node_modules/fs-extra": { + "version": "11.3.3", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.3.tgz", + "integrity": "sha512-VWSRii4t0AFm6ixFFmLLx1t7wS1gh+ckoa84aOeapGum0h+EZd1EhEumSB+ZdDLnEPuucsVB9oB7cxJHap6Afg==", + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/unzipper/node_modules/jsonfile": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", + "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/unzipper/node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/utf8-byte-length": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/utf8-byte-length/-/utf8-byte-length-1.0.5.tgz", + "integrity": "sha512-Xn0w3MtiQ6zoz2vFyUVruaCL53O/DwUvkEeOvj+uulMm0BkUGYWmBYVyElqZaSLhY6ZD0ulfU3aBra2aVT4xfA==", + "dev": true, + "license": "(WTFPL OR MIT)" + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT" + }, + "node_modules/verror": { + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.1.tgz", + "integrity": "sha512-veufcmxri4e3XSrT0xwfUR7kguIkaxBeosDg00yDWhk49wdwkSUrvvsm7nc75e1PUyvIeZj6nS8VQRYz2/S4Xg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + }, + "engines": { + "node": ">=0.6.0" + } + }, + "node_modules/vite": { + "version": "5.4.21", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz", + "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/vite-plugin-electron": { + "version": "0.15.6", + "resolved": "https://registry.npmjs.org/vite-plugin-electron/-/vite-plugin-electron-0.15.6.tgz", + "integrity": "sha512-sbjzNiH8N6Was67oIUgglW6tefahkiCTItM3MsdOAQ0xhUMxizeQMF9DhSvEBwvLvUGKYboIdIYskSchV7Akrg==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "tree-kill": "*", + "vite-plugin-electron-renderer": "*" + }, + "peerDependenciesMeta": { + "tree-kill": { + "optional": true + }, + "vite-plugin-electron-renderer": { + "optional": true + } + } + }, + "node_modules/vite-plugin-electron-renderer": { + "version": "0.14.6", + "resolved": "https://registry.npmjs.org/vite-plugin-electron-renderer/-/vite-plugin-electron-renderer-0.14.6.tgz", + "integrity": "sha512-oqkWFa7kQIkvHXG7+Mnl1RTroA4sP0yesKatmAy0gjZC4VwUqlvF9IvOpHd1fpLWsqYX/eZlVxlhULNtaQ78Jw==", + "dev": true, + "license": "MIT" + }, + "node_modules/vue": { + "version": "3.5.26", + "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.26.tgz", + "integrity": "sha512-SJ/NTccVyAoNUJmkM9KUqPcYlY+u8OVL1X5EW9RIs3ch5H2uERxyyIUI4MRxVCSOiEcupX9xNGde1tL9ZKpimA==", + "license": "MIT", + "dependencies": { + "@vue/compiler-dom": "3.5.26", + "@vue/compiler-sfc": "3.5.26", + "@vue/runtime-dom": "3.5.26", + "@vue/server-renderer": "3.5.26", + "@vue/shared": "3.5.26" + }, + "peerDependencies": { + "typescript": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/vue-demi": { + "version": "0.14.10", + "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.10.tgz", + "integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==", + "hasInstallScript": true, + "license": "MIT", + "bin": { + "vue-demi-fix": "bin/vue-demi-fix.js", + "vue-demi-switch": "bin/vue-demi-switch.js" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@vue/composition-api": "^1.0.0-rc.1", + "vue": "^3.0.0-0 || ^2.6.0" + }, + "peerDependenciesMeta": { + "@vue/composition-api": { + "optional": true + } + } + }, + "node_modules/vue-router": { + "version": "4.6.4", + "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.6.4.tgz", + "integrity": "sha512-Hz9q5sa33Yhduglwz6g9skT8OBPii+4bFn88w6J+J4MfEo4KRRpmiNG/hHHkdbRFlLBOqxN8y8gf2Fb0MTUgVg==", + "license": "MIT", + "dependencies": { + "@vue/devtools-api": "^6.6.4" + }, + "funding": { + "url": "https://github.com/sponsors/posva" + }, + "peerDependencies": { + "vue": "^3.5.0" + } + }, + "node_modules/vue-template-compiler": { + "version": "2.7.16", + "resolved": "https://registry.npmjs.org/vue-template-compiler/-/vue-template-compiler-2.7.16.tgz", + "integrity": "sha512-AYbUWAJHLGGQM7+cNTELw+KsOG9nl2CnSv467WobS5Cv9uk3wFcnr1Etsz2sEIHEZvw1U+o9mRlEO6QbZvUPGQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "de-indent": "^1.0.2", + "he": "^1.2.0" + } + }, + "node_modules/vue-tsc": { + "version": "1.8.27", + "resolved": "https://registry.npmjs.org/vue-tsc/-/vue-tsc-1.8.27.tgz", + "integrity": "sha512-WesKCAZCRAbmmhuGl3+VrdWItEvfoFIPXOvUJkjULi+x+6G/Dy69yO3TBRJDr9eUlmsNAwVmxsNZxvHKzbkKdg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/typescript": "~1.11.1", + "@vue/language-core": "1.8.27", + "semver": "^7.5.4" + }, + "bin": { + "vue-tsc": "bin/vue-tsc.js" + }, + "peerDependencies": { + "typescript": "*" + } + }, + "node_modules/vue-tsc/node_modules/semver": { + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/wait-on": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/wait-on/-/wait-on-7.2.0.tgz", + "integrity": "sha512-wCQcHkRazgjG5XoAq9jbTMLpNIjoSlZslrJ2+N9MxDsGEv1HnFoVjOCexL0ESva7Y9cu350j+DWADdk54s4AFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "axios": "^1.6.1", + "joi": "^17.11.0", + "lodash": "^4.17.21", + "minimist": "^1.2.8", + "rxjs": "^7.8.1" + }, + "bin": { + "wait-on": "bin/wait-on" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/xml": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/xml/-/xml-1.0.1.tgz", + "integrity": "sha512-huCv9IH9Tcf95zuYCsQraZtWnJvBtLVE0QHMOs8bWyZAFZNDcYjsPq1nEx8jKA9y+Beo9v+7OBPRisQTjinQMw==", + "license": "MIT" + }, + "node_modules/xmlbuilder": { + "version": "15.1.1", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-15.1.1.tgz", + "integrity": "sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.0" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true, + "license": "ISC" + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/yauzl": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-crc32": "~0.2.3", + "fd-slicer": "~1.1.0" + } + }, + "node_modules/zip-stream": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-4.1.1.tgz", + "integrity": "sha512-9qv4rlDiopXg4E69k+vMHjNN63YFMe9sZMrdlvKnCjlCRWeCBswPPMPUfx+ipsAWq1LXHe70RcbaHdJJpS6hyQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "archiver-utils": "^3.0.4", + "compress-commons": "^4.1.2", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/zip-stream/node_modules/archiver-utils": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-3.0.4.tgz", + "integrity": "sha512-KVgf4XQVrTjhyWmx6cte4RxonPLR9onExufI1jhvw/MQ4BB6IsZD5gT8Lq+u/+pRkWna/6JoHpiQioaqFP5Rzw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "glob": "^7.2.3", + "graceful-fs": "^4.2.0", + "lazystream": "^1.0.0", + "lodash.defaults": "^4.2.0", + "lodash.difference": "^4.5.0", + "lodash.flatten": "^4.4.0", + "lodash.isplainobject": "^4.0.6", + "lodash.union": "^4.6.0", + "normalize-path": "^3.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">= 10" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..2e30d5c --- /dev/null +++ b/package.json @@ -0,0 +1,83 @@ +{ + "name": "phper-dev-manager", + "version": "1.0.0", + "description": "PHP开发环境管理器 - 管理PHP、MySQL、Nginx、Redis服务", + "main": "dist-electron/main.js", + "scripts": { + "dev": "vite", + "build": "vue-tsc --noEmit && vite build && electron-builder", + "preview": "vite preview", + "electron:dev": "vite", + "electron:build": "vite build && electron-builder" + }, + "author": "PHPer", + "license": "MIT", + "devDependencies": { + "@types/node": "^20.10.0", + "@vitejs/plugin-vue": "^4.5.0", + "concurrently": "^8.2.2", + "electron": "^28.0.0", + "electron-builder": "^24.9.1", + "sass": "^1.69.5", + "typescript": "^5.3.2", + "vite": "^5.0.0", + "vite-plugin-electron": "^0.15.5", + "vite-plugin-electron-renderer": "^0.14.5", + "vue-tsc": "^1.8.25", + "wait-on": "^7.2.0" + }, + "dependencies": { + "@element-plus/icons-vue": "^2.3.1", + "axios": "^1.6.2", + "electron-store": "^8.1.0", + "element-plus": "^2.4.3", + "node-windows": "^1.0.0-beta.8", + "pinia": "^2.1.7", + "sudo-prompt": "^9.2.1", + "unzipper": "^0.12.3", + "vue": "^3.3.9", + "vue-router": "^4.2.5" + }, + "build": { + "appId": "com.phper.devmanager", + "productName": "PHPer开发环境管理器", + "copyright": "Copyright © 2024 PHPer", + "directories": { + "output": "release" + }, + "files": [ + "dist/**/*", + "dist-electron/**/*" + ], + "win": { + "target": [ + { + "target": "nsis", + "arch": ["x64"] + } + ], + "requestedExecutionLevel": "requireAdministrator" + }, + "nsis": { + "oneClick": false, + "perMachine": true, + "allowToChangeInstallationDirectory": true, + "allowElevation": true, + "createDesktopShortcut": true, + "createStartMenuShortcut": true, + "shortcutName": "PHPer开发环境管理器", + "installerLanguages": ["zh_CN", "en_US"], + "language": "2052", + "runAfterFinish": true, + "deleteAppDataOnUninstall": false, + "include": "build/installer.nsh" + }, + "extraResources": [ + { + "from": "public/", + "to": "public/", + "filter": ["**/*"] + } + ] + } +} diff --git a/pecl_redis_page.html b/pecl_redis_page.html new file mode 100644 index 0000000..a7152e8 --- /dev/null +++ b/pecl_redis_page.html @@ -0,0 +1,1762 @@ + + + + PECL :: Package :: redis + + + + + + + +
+ + + + + + + + + + + +
+ + Login + +  |  + Packages +  |  + Support +  |  + Bugs + +
+ + + + + + + + + +
+ +Top Level :: Database + :: redis + + +

+ redis

+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Package Information
SummaryPHP extension for interfacing with key-value stores
Maintainers + Michael Grunder < + michael dot grunder at gmail dot com > + (lead) + [details]
+ Pavlo Yatsukhnenko (lead) + [details]
+
LicensePHP
DescriptionThis extension provides an API for communicating with RESP-based key-value
+ stores, such as Redis, Valkey, and KeyDB.
Homepage + + https://github.com/phpredis/phpredis/ +
+
+ + +
+ + + + + + + + + + + + + + +
[ Latest Tarball ][ + + Changelog + + ] + [ + View Statistics + ] +
+ [ Browse Source ] + + [ Package Bugs ] + + [ + View Documentation + ] +
+ +
+ + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Available Releases
VersionStateRelease DateDownloads 
+ 6.3.0 + stable2025-11-06redis-6.3.0.tgz (389.9kB)  DLL + + [ + + Changelog + + ] + +
+ 6.3.0RC1 + beta2025-10-15redis-6.3.0RC1.tgz (390.3kB) + + [ + + Changelog + + ] + +
+ 6.2.0 + stable2025-03-24redis-6.2.0.tgz (371.0kB)  DLL + + [ + + Changelog + + ] + +
+ 6.1.0 + stable2024-10-04redis-6.1.0.tgz (365.0kB)  DLL + + [ + + Changelog + + ] + +
+ 6.1.0RC2 + beta2024-09-23redis-6.1.0RC2.tgz (364.9kB) + + [ + + Changelog + + ] + +
+ 6.1.0RC1 + beta2024-08-04redis-6.1.0RC1.tgz (362.7kB) + + [ + + Changelog + + ] + +
+ 6.0.2 + stable2023-10-22redis-6.0.2.tgz (357.4kB)  DLL + + [ + + Changelog + + ] + +
+ 6.0.1 + stable2023-09-23redis-6.0.1.tgz (357.1kB)  DLL + + [ + + Changelog + + ] + +
+ 6.0.0 + stable2023-09-11redis-6.0.0.tgz (356.7kB)  DLL + + [ + + Changelog + + ] + +
+ 6.0.0RC2 + beta2023-08-20redis-6.0.0RC2.tgz (356.8kB) + + [ + + Changelog + + ] + +
+ 6.0.0RC1 + alpha2023-08-01redis-6.0.0RC1.tgz (356.7kB) + + [ + + Changelog + + ] + +
+ 5.3.7 + stable2022-02-15redis-5.3.7.tgz (268.8kB)  DLL + + [ + + Changelog + + ] + +
+ 5.3.7RC2 + beta2022-02-12redis-5.3.7RC2.tgz (268.8kB)  DLL + + [ + + Changelog + + ] + +
+ 5.3.7RC1 + alpha2022-02-02redis-5.3.7RC1.tgz (268.8kB)  DLL + + [ + + Changelog + + ] + +
+ 5.3.6 + stable2022-01-17redis-5.3.6.tgz (267.8kB)  DLL + + [ + + Changelog + + ] + +
+ 5.3.5 + stable2021-12-18redis-5.3.5.tgz (267.7kB)  DLL + + [ + + Changelog + + ] + +
+ 5.3.5RC1 + beta2021-11-16redis-5.3.5RC1.tgz (267.7kB)  DLL + + [ + + Changelog + + ] + +
+ 5.3.4 + stable2021-03-24redis-5.3.4.tgz (261.9kB)  DLL + + [ + + Changelog + + ] + +
+ 5.3.3 + stable2021-02-01redis-5.3.3.tgz (260.7kB)  DLL + + [ + + Changelog + + ] + +
+ 5.3.2 + stable2020-10-22redis-5.3.2.tgz (260.6kB)  DLL + + [ + + Changelog + + ] + +
+ 5.3.2RC2 + beta2020-10-14redis-5.3.2RC2.tgz (260.5kB)  DLL + + [ + + Changelog + + ] + +
+ 5.3.2RC1 + alpha2020-10-07redis-5.3.2RC1.tgz (260.4kB)  DLL + + [ + + Changelog + + ] + +
+ 5.3.1 + stable2020-07-07redis-5.3.1.tgz (258.3kB)  DLL + + [ + + Changelog + + ] + +
+ 5.3.0 + stable2020-06-30redis-5.3.0.tgz (257.8kB)  DLL + + [ + + Changelog + + ] + +
+ 5.3.0RC2 + beta2020-06-26redis-5.3.0RC2.tgz (257.9kB)  DLL + + [ + + Changelog + + ] + +
+ 5.3.0RC1 + alpha2020-06-25redis-5.3.0RC1.tgz (257.7kB)  DLL + + [ + + Changelog + + ] + +
+ 5.2.2 + stable2020-05-05redis-5.2.2.tgz (245.7kB)  DLL + + [ + + Changelog + + ] + +
+ 5.2.1 + stable2020-03-19redis-5.2.1.tgz (245.7kB)  DLL + + [ + + Changelog + + ] + +
+ 5.2.0 + stable2020-03-02redis-5.2.0.tgz (245.6kB)  DLL + + [ + + Changelog + + ] + +
+ 5.2.0RC2 + alpha2020-02-21redis-5.2.0RC2.tgz (245.5kB)  DLL + + [ + + Changelog + + ] + +
+ 5.2.0RC1 + alpha2020-02-15redis-5.2.0RC1.tgz (244.8kB)  DLL + + [ + + Changelog + + ] + +
+ 5.1.1 + stable2019-11-11redis-5.1.1.tgz (239.5kB)  DLL + + [ + + Changelog + + ] + +
+ 5.1.0 + stable2019-10-31redis-5.1.0.tgz (239.1kB)  DLL + + [ + + Changelog + + ] + +
+ 5.1.0RC2 + beta2019-10-21redis-5.1.0RC2.tgz (238.7kB)  DLL + + [ + + Changelog + + ] + +
+ 5.1.0RC1 + alpha2019-10-09redis-5.1.0RC1.tgz (238.9kB) + + [ + + Changelog + + ] + +
+ 5.0.2 + stable2019-07-29redis-5.0.2.tgz (237.6kB)  DLL + + [ + + Changelog + + ] + +
+ 5.0.1 + stable2019-07-12redis-5.0.1.tgz (237.8kB)  DLL + + [ + + Changelog + + ] + +
+ 5.0.0 + stable2019-07-02redis-5.0.0.tgz (237.8kB) + + [ + + Changelog + + ] + +
+ 5.0.0RC2 + beta2019-06-25redis-5.0.0RC2.tgz (237.6kB)  DLL + + [ + + Changelog + + ] + +
+ 5.0.0RC1 + alpha2019-06-20redis-5.0.0RC1.tgz (237.5kB)  DLL + + [ + + Changelog + + ] + +
+ 4.3.0 + stable2019-03-13redis-4.3.0.tgz (236.2kB)  DLL + + [ + + Changelog + + ] + +
+ 4.3.0RC2 + beta2019-03-06redis-4.3.0RC2.tgz (236.2kB)  DLL + + [ + + Changelog + + ] + +
+ 4.3.0RC1 + alpha2019-02-25redis-4.3.0RC1.tgz (236.0kB)  DLL + + [ + + Changelog + + ] + +
+ 4.2.0 + stable2018-11-20redis-4.2.0.tgz (230.0kB)  DLL + + [ + + Changelog + + ] + +
+ 4.2.0RC3 + beta2018-11-08redis-4.2.0RC3.tgz (229.9kB)  DLL + + [ + + Changelog + + ] + +
+ 4.2.0RC2 + beta2018-10-26redis-4.2.0RC2.tgz (229.4kB)  DLL + + [ + + Changelog + + ] + +
+ 4.2.0RC1 + alpha2018-10-11redis-4.2.0RC1.tgz (229.1kB)  DLL + + [ + + Changelog + + ] + +
+ 4.1.1 + stable2018-08-01redis-4.1.1.tgz (215.7kB)  DLL + + [ + + Changelog + + ] + +
+ 4.1.0 + stable2018-07-10redis-4.1.0.tgz (215.6kB)  DLL + + [ + + Changelog + + ] + +
+ 4.1.0RC3 + beta2018-06-22redis-4.1.0RC3.tgz (215.6kB)  DLL + + [ + + Changelog + + ] + +
+ 4.1.0RC2 + beta2018-06-22redis-4.1.0RC2.tgz (215.4kB) + + [ + + Changelog + + ] + +
+ 4.1.0RC1 + alpha2018-06-08redis-4.1.0RC1.tgz (214.4kB) + + [ + + Changelog + + ] + +
+ 4.0.2 + stable2018-04-25redis-4.0.2.tgz (207.6kB)  DLL + + [ + + Changelog + + ] + +
+ 4.0.1 + stable2018-04-18redis-4.0.1.tgz (207.4kB)  DLL + + [ + + Changelog + + ] + +
+ 4.0.0 + stable2018-03-17redis-4.0.0.tgz (207.4kB)  DLL + + [ + + Changelog + + ] + +
+ 4.0.0RC2 + beta2018-03-03redis-4.0.0RC2.tgz (207.4kB)  DLL + + [ + + Changelog + + ] + +
+ 4.0.0RC1 + alpha2018-02-07redis-4.0.0RC1.tgz (205.7kB)  DLL + + [ + + Changelog + + ] + +
+ 3.1.6 + stable2018-01-03redis-3.1.6.tgz (195.2kB)  DLL + + [ + + Changelog + + ] + +
+ 3.1.5 + stable2017-12-20redis-3.1.5.tgz (195.2kB)  DLL + + [ + + Changelog + + ] + +
+ 3.1.5RC2 + beta2017-12-10redis-3.1.5RC2.tgz (195.1kB)  DLL + + [ + + Changelog + + ] + +
+ 3.1.5RC1 + beta2017-12-01redis-3.1.5RC1.tgz (195.0kB)  DLL + + [ + + Changelog + + ] + +
+ 3.1.4 + stable2017-09-27redis-3.1.4.tgz (194.9kB)  DLL + + [ + + Changelog + + ] + +
+ 3.1.4RC3 + beta2017-09-13redis-3.1.4RC3.tgz (194.9kB)  DLL + + [ + + Changelog + + ] + +
+ 3.1.4RC2 + beta2017-09-13redis-3.1.4RC2.tgz (194.9kB)  DLL + + [ + + Changelog + + ] + +
+ 3.1.4RC1 + beta2017-08-31redis-3.1.4RC1.tgz (195.9kB)  DLL + + [ + + Changelog + + ] + +
+ 3.1.3 + stable2017-07-15redis-3.1.3.tgz (191.6kB)  DLL + + [ + + Changelog + + ] + +
+ 3.1.3RC2 + beta2017-06-27redis-3.1.3RC2.tgz (191.6kB)  DLL + + [ + + Changelog + + ] + +
+ 3.1.3RC1 + beta2017-06-01redis-3.1.3RC1.tgz (191.6kB)  DLL + + [ + + Changelog + + ] + +
+ 3.1.2 + stable2017-03-24redis-3.1.2.tgz (194.4kB)  DLL + + [ + + Changelog + + ] + +
+ 3.1.1 + stable2017-02-01redis-3.1.1.tgz (193.9kB)  DLL + + [ + + Changelog + + ] + +
+ 3.1.1RC2 + beta2017-01-16redis-3.1.1RC2.tgz (193.9kB)  DLL + + [ + + Changelog + + ] + +
+ 3.1.0 + stable2016-12-14redis-3.1.0.tgz (192.7kB)  DLL + + [ + + Changelog + + ] + +
+ 3.0.0 + stable2016-06-10redis-3.0.0.tgz (185.3kB)  DLL + + [ + + Changelog + + ] + +
+ 2.2.8 + stable2016-06-08redis-2.2.8.tgz (187.6kB) + + [ + + Changelog + + ] + +
+ 2.2.7 + stable2015-03-03redis-2.2.7.tgz (131.2kB)  DLL + + [ + + Changelog + + ] + +
+ 2.2.5 + stable2014-03-19redis-2.2.5.tgz (94.7kB) + + [ + + Changelog + + ] + +
+ 2.2.4 + stable2013-09-02redis-2.2.4.tgz (88.8kB) + + [ + + Changelog + + ] + +
+ 2.2.3 + stable2013-04-29redis-2.2.3.tgz (84.6kB) + + [ + + Changelog + + ] + +
+
+ +

+ + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
Dependencies
Release 6.3.0:PHP Version: PHP 7.4.0 or newer
PEAR Package: PEAR 1.4.0b1 or newer
Release 6.3.0RC1:PHP Version: PHP 7.4.0 or newer
PEAR Package: PEAR 1.4.0b1 or newer
Release 6.2.0:PHP Version: PHP 7.4.0 or newer
PEAR Package: PEAR 1.4.0b1 or newer
+ Dependencies for older releases can be found on the release overview page. +
+
+ + +
+ + + + + + + + + + +
+ PRIVACY POLICY +  |  + CREDITS +
+
+ + Copyright © 2001-2025 The PHP Group
+ All rights reserved.
+
+
+ + Last updated: Wed Sep 03 10:50:24 2025 UTC
+ Bandwidth and hardware provided by: pair Networks +
+
+ + + + diff --git a/pecl_search.html b/pecl_search.html new file mode 100644 index 0000000..6df0db5 --- /dev/null +++ b/pecl_search.html @@ -0,0 +1,992 @@ + + + + PECL :: Package search + + + + + + + + +
+ + + + + + + + + + + +
+ + Login + +  |  + Packages +  |  + Support +  |  + Bugs + +
+ + + + + + + + + +
+ +

Package search

+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Search Options
Search for: + +
Maintainer: + + +
Category: + +
 
With a release...
On: + + + + +
Before: + + + + + + +
Since: + + + + +
  + + +
+
+ + + +

+ + + + +
+ + + + + + + +
+ + + + + +
Search results (1 - 1 of 1)
+ + + + + + +
+ redis + PHP extension for interfacing with key-value stores
+ +
+
+ +
+ + + + + + + + + + +
+ PRIVACY POLICY +  |  + CREDITS +
+
+ + Copyright © 2001-2025 The PHP Group
+ All rights reserved.
+
+
+ + Last updated: Wed Sep 03 10:50:24 2025 UTC
+ Bandwidth and hardware provided by: pair Networks +
+
+ + + + diff --git a/pecl_test.html b/pecl_test.html new file mode 100644 index 0000000..0c1956a --- /dev/null +++ b/pecl_test.html @@ -0,0 +1,388 @@ + + + + PECL :: Package :: redis 6.3.0 for Windows + + + + + + + +
+ + + + + + + + + + + +
+ + Login + +  |  + Packages +  |  + Support +  |  + Bugs + +
+ + + + + + + + + +
+ +Top Level :: Database + :: redis + + :: 6.3.0 + + :: Windows + +

+ redis 6.3.0 for Windows

+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Package Information
SummaryPHP extension for interfacing with key-value stores
Maintainers + Michael Grunder < + michael dot grunder at gmail dot com > + (lead) + [details]
+ Pavlo Yatsukhnenko (lead) + [details]
+
LicensePHP
DescriptionThis extension provides an API for communicating with RESP-based key-value
+ stores, such as Redis, Valkey, and KeyDB.
Homepage + + https://github.com/phpredis/phpredis/ +
+ Release notes
+ Version 6.3.0
+ (stable) +
+ --- Sponsors ---

A-VISION Advertising - https://github.com/A-VISION-BV
Avtandil Kikabidze - https://github.com/akalongman
Geoffrey Hoffman - https://github.com/phpguru
Object Cache Pro for WordPress - https://objectcache.pro/
Open LMS - https://openlms.net/
Relay - https://relay.so
Salvatore Sanfilippo - https://github.com/antirez
Ty Karok - https://github.com/karock

--- 6.3.0 ---

This release introduces support for dozens of new commands, including hash
field expiration, Valkey?s DELIFEQ, and Redis vector set commands. It also
includes many bug fixes and performance improvements.

Fixed:

* Cloning our objects should not segfault [770034cc] (michael-grunder)
* Fix return type for `RedisCluster` `vgetattr` and `vsetattr`
[834d2b37] (michael-grunder)
+
+ +
 
+ + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
DLL List
PHP 8.5 + + 8.5 Non Thread Safe (NTS) x64 +
+ + 8.5 Thread Safe (TS) x64 +
+ + 8.5 Non Thread Safe (NTS) x86 +
+ + 8.5 Thread Safe (TS) x86 +
+
PHP 8.4 + + 8.4 Non Thread Safe (NTS) x64 +
+ + 8.4 Thread Safe (TS) x64 +
+ + 8.4 Non Thread Safe (NTS) x86 +
+ + 8.4 Thread Safe (TS) x86 +
+
PHP 8.3 + + 8.3 Non Thread Safe (NTS) x64 +
+ + 8.3 Thread Safe (TS) x64 +
+ + 8.3 Non Thread Safe (NTS) x86 +
+ + 8.3 Thread Safe (TS) x86 +
+
PHP 8.2 + + 8.2 Non Thread Safe (NTS) x64 +
+ + 8.2 Thread Safe (TS) x64 +
+ + 8.2 Non Thread Safe (NTS) x86 +
+ + 8.2 Thread Safe (TS) x86 +
+
PHP 8.1 + + 8.1 Non Thread Safe (NTS) x64 +
+ + 8.1 Thread Safe (TS) x64 +
+ + 8.1 Non Thread Safe (NTS) x86 +
+ + 8.1 Thread Safe (TS) x86 +
+
PHP 8.0 + + 8.0 Non Thread Safe (NTS) x64 +
+ + 8.0 Thread Safe (TS) x64 +
+ + 8.0 Non Thread Safe (NTS) x86 +
+ + 8.0 Thread Safe (TS) x86 +
+
PHP 7.4 + + 7.4 Non Thread Safe (NTS) x64 +
+ + 7.4 Thread Safe (TS) x64 +
+ + 7.4 Non Thread Safe (NTS) x86 +
+ + 7.4 Thread Safe (TS) x86 +
+
+
+

In case of missing DLLs, consider to contact the + Windows Internals List + (subscribe first).

+ +
+ + + + + + + + + + + + + + +
[ Latest Tarball ][ + + Changelog + + ] + [ + View Statistics + ] +
+ [ Browse Source ] + + [ Package Bugs ] + + [ + View Documentation + ] +
+ +
+ + + + + + +
+ + + + + + + + + + + + +
Dependencies for release 6.3.0
PHP Version: PHP 7.4.0 or newer
PEAR Package: PEAR 1.4.0b1 or newer
+
+ + +
+ + + + + + + + + + +
+ PRIVACY POLICY +  |  + CREDITS +
+
+ + Copyright © 2001-2025 The PHP Group
+ All rights reserved.
+
+
+ + Last updated: Wed Sep 03 10:50:24 2025 UTC
+ Bandwidth and hardware provided by: pair Networks +
+
+ + + + diff --git a/public/favicon.svg b/public/favicon.svg new file mode 100644 index 0000000..02d324c --- /dev/null +++ b/public/favicon.svg @@ -0,0 +1,12 @@ + + + + + + + + + P + + + diff --git a/redis_detail.html b/redis_detail.html new file mode 100644 index 0000000..a7152e8 --- /dev/null +++ b/redis_detail.html @@ -0,0 +1,1762 @@ + + + + PECL :: Package :: redis + + + + + + + +
+ + + + + + + + + + + +
+ + Login + +  |  + Packages +  |  + Support +  |  + Bugs + +
+ + + + + + + + + +
+ +Top Level :: Database + :: redis + + +

+ redis

+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Package Information
SummaryPHP extension for interfacing with key-value stores
Maintainers + Michael Grunder < + michael dot grunder at gmail dot com > + (lead) + [details]
+ Pavlo Yatsukhnenko (lead) + [details]
+
LicensePHP
DescriptionThis extension provides an API for communicating with RESP-based key-value
+ stores, such as Redis, Valkey, and KeyDB.
Homepage + + https://github.com/phpredis/phpredis/ +
+
+ + +
+ + + + + + + + + + + + + + +
[ Latest Tarball ][ + + Changelog + + ] + [ + View Statistics + ] +
+ [ Browse Source ] + + [ Package Bugs ] + + [ + View Documentation + ] +
+ +
+ + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Available Releases
VersionStateRelease DateDownloads 
+ 6.3.0 + stable2025-11-06redis-6.3.0.tgz (389.9kB)  DLL + + [ + + Changelog + + ] + +
+ 6.3.0RC1 + beta2025-10-15redis-6.3.0RC1.tgz (390.3kB) + + [ + + Changelog + + ] + +
+ 6.2.0 + stable2025-03-24redis-6.2.0.tgz (371.0kB)  DLL + + [ + + Changelog + + ] + +
+ 6.1.0 + stable2024-10-04redis-6.1.0.tgz (365.0kB)  DLL + + [ + + Changelog + + ] + +
+ 6.1.0RC2 + beta2024-09-23redis-6.1.0RC2.tgz (364.9kB) + + [ + + Changelog + + ] + +
+ 6.1.0RC1 + beta2024-08-04redis-6.1.0RC1.tgz (362.7kB) + + [ + + Changelog + + ] + +
+ 6.0.2 + stable2023-10-22redis-6.0.2.tgz (357.4kB)  DLL + + [ + + Changelog + + ] + +
+ 6.0.1 + stable2023-09-23redis-6.0.1.tgz (357.1kB)  DLL + + [ + + Changelog + + ] + +
+ 6.0.0 + stable2023-09-11redis-6.0.0.tgz (356.7kB)  DLL + + [ + + Changelog + + ] + +
+ 6.0.0RC2 + beta2023-08-20redis-6.0.0RC2.tgz (356.8kB) + + [ + + Changelog + + ] + +
+ 6.0.0RC1 + alpha2023-08-01redis-6.0.0RC1.tgz (356.7kB) + + [ + + Changelog + + ] + +
+ 5.3.7 + stable2022-02-15redis-5.3.7.tgz (268.8kB)  DLL + + [ + + Changelog + + ] + +
+ 5.3.7RC2 + beta2022-02-12redis-5.3.7RC2.tgz (268.8kB)  DLL + + [ + + Changelog + + ] + +
+ 5.3.7RC1 + alpha2022-02-02redis-5.3.7RC1.tgz (268.8kB)  DLL + + [ + + Changelog + + ] + +
+ 5.3.6 + stable2022-01-17redis-5.3.6.tgz (267.8kB)  DLL + + [ + + Changelog + + ] + +
+ 5.3.5 + stable2021-12-18redis-5.3.5.tgz (267.7kB)  DLL + + [ + + Changelog + + ] + +
+ 5.3.5RC1 + beta2021-11-16redis-5.3.5RC1.tgz (267.7kB)  DLL + + [ + + Changelog + + ] + +
+ 5.3.4 + stable2021-03-24redis-5.3.4.tgz (261.9kB)  DLL + + [ + + Changelog + + ] + +
+ 5.3.3 + stable2021-02-01redis-5.3.3.tgz (260.7kB)  DLL + + [ + + Changelog + + ] + +
+ 5.3.2 + stable2020-10-22redis-5.3.2.tgz (260.6kB)  DLL + + [ + + Changelog + + ] + +
+ 5.3.2RC2 + beta2020-10-14redis-5.3.2RC2.tgz (260.5kB)  DLL + + [ + + Changelog + + ] + +
+ 5.3.2RC1 + alpha2020-10-07redis-5.3.2RC1.tgz (260.4kB)  DLL + + [ + + Changelog + + ] + +
+ 5.3.1 + stable2020-07-07redis-5.3.1.tgz (258.3kB)  DLL + + [ + + Changelog + + ] + +
+ 5.3.0 + stable2020-06-30redis-5.3.0.tgz (257.8kB)  DLL + + [ + + Changelog + + ] + +
+ 5.3.0RC2 + beta2020-06-26redis-5.3.0RC2.tgz (257.9kB)  DLL + + [ + + Changelog + + ] + +
+ 5.3.0RC1 + alpha2020-06-25redis-5.3.0RC1.tgz (257.7kB)  DLL + + [ + + Changelog + + ] + +
+ 5.2.2 + stable2020-05-05redis-5.2.2.tgz (245.7kB)  DLL + + [ + + Changelog + + ] + +
+ 5.2.1 + stable2020-03-19redis-5.2.1.tgz (245.7kB)  DLL + + [ + + Changelog + + ] + +
+ 5.2.0 + stable2020-03-02redis-5.2.0.tgz (245.6kB)  DLL + + [ + + Changelog + + ] + +
+ 5.2.0RC2 + alpha2020-02-21redis-5.2.0RC2.tgz (245.5kB)  DLL + + [ + + Changelog + + ] + +
+ 5.2.0RC1 + alpha2020-02-15redis-5.2.0RC1.tgz (244.8kB)  DLL + + [ + + Changelog + + ] + +
+ 5.1.1 + stable2019-11-11redis-5.1.1.tgz (239.5kB)  DLL + + [ + + Changelog + + ] + +
+ 5.1.0 + stable2019-10-31redis-5.1.0.tgz (239.1kB)  DLL + + [ + + Changelog + + ] + +
+ 5.1.0RC2 + beta2019-10-21redis-5.1.0RC2.tgz (238.7kB)  DLL + + [ + + Changelog + + ] + +
+ 5.1.0RC1 + alpha2019-10-09redis-5.1.0RC1.tgz (238.9kB) + + [ + + Changelog + + ] + +
+ 5.0.2 + stable2019-07-29redis-5.0.2.tgz (237.6kB)  DLL + + [ + + Changelog + + ] + +
+ 5.0.1 + stable2019-07-12redis-5.0.1.tgz (237.8kB)  DLL + + [ + + Changelog + + ] + +
+ 5.0.0 + stable2019-07-02redis-5.0.0.tgz (237.8kB) + + [ + + Changelog + + ] + +
+ 5.0.0RC2 + beta2019-06-25redis-5.0.0RC2.tgz (237.6kB)  DLL + + [ + + Changelog + + ] + +
+ 5.0.0RC1 + alpha2019-06-20redis-5.0.0RC1.tgz (237.5kB)  DLL + + [ + + Changelog + + ] + +
+ 4.3.0 + stable2019-03-13redis-4.3.0.tgz (236.2kB)  DLL + + [ + + Changelog + + ] + +
+ 4.3.0RC2 + beta2019-03-06redis-4.3.0RC2.tgz (236.2kB)  DLL + + [ + + Changelog + + ] + +
+ 4.3.0RC1 + alpha2019-02-25redis-4.3.0RC1.tgz (236.0kB)  DLL + + [ + + Changelog + + ] + +
+ 4.2.0 + stable2018-11-20redis-4.2.0.tgz (230.0kB)  DLL + + [ + + Changelog + + ] + +
+ 4.2.0RC3 + beta2018-11-08redis-4.2.0RC3.tgz (229.9kB)  DLL + + [ + + Changelog + + ] + +
+ 4.2.0RC2 + beta2018-10-26redis-4.2.0RC2.tgz (229.4kB)  DLL + + [ + + Changelog + + ] + +
+ 4.2.0RC1 + alpha2018-10-11redis-4.2.0RC1.tgz (229.1kB)  DLL + + [ + + Changelog + + ] + +
+ 4.1.1 + stable2018-08-01redis-4.1.1.tgz (215.7kB)  DLL + + [ + + Changelog + + ] + +
+ 4.1.0 + stable2018-07-10redis-4.1.0.tgz (215.6kB)  DLL + + [ + + Changelog + + ] + +
+ 4.1.0RC3 + beta2018-06-22redis-4.1.0RC3.tgz (215.6kB)  DLL + + [ + + Changelog + + ] + +
+ 4.1.0RC2 + beta2018-06-22redis-4.1.0RC2.tgz (215.4kB) + + [ + + Changelog + + ] + +
+ 4.1.0RC1 + alpha2018-06-08redis-4.1.0RC1.tgz (214.4kB) + + [ + + Changelog + + ] + +
+ 4.0.2 + stable2018-04-25redis-4.0.2.tgz (207.6kB)  DLL + + [ + + Changelog + + ] + +
+ 4.0.1 + stable2018-04-18redis-4.0.1.tgz (207.4kB)  DLL + + [ + + Changelog + + ] + +
+ 4.0.0 + stable2018-03-17redis-4.0.0.tgz (207.4kB)  DLL + + [ + + Changelog + + ] + +
+ 4.0.0RC2 + beta2018-03-03redis-4.0.0RC2.tgz (207.4kB)  DLL + + [ + + Changelog + + ] + +
+ 4.0.0RC1 + alpha2018-02-07redis-4.0.0RC1.tgz (205.7kB)  DLL + + [ + + Changelog + + ] + +
+ 3.1.6 + stable2018-01-03redis-3.1.6.tgz (195.2kB)  DLL + + [ + + Changelog + + ] + +
+ 3.1.5 + stable2017-12-20redis-3.1.5.tgz (195.2kB)  DLL + + [ + + Changelog + + ] + +
+ 3.1.5RC2 + beta2017-12-10redis-3.1.5RC2.tgz (195.1kB)  DLL + + [ + + Changelog + + ] + +
+ 3.1.5RC1 + beta2017-12-01redis-3.1.5RC1.tgz (195.0kB)  DLL + + [ + + Changelog + + ] + +
+ 3.1.4 + stable2017-09-27redis-3.1.4.tgz (194.9kB)  DLL + + [ + + Changelog + + ] + +
+ 3.1.4RC3 + beta2017-09-13redis-3.1.4RC3.tgz (194.9kB)  DLL + + [ + + Changelog + + ] + +
+ 3.1.4RC2 + beta2017-09-13redis-3.1.4RC2.tgz (194.9kB)  DLL + + [ + + Changelog + + ] + +
+ 3.1.4RC1 + beta2017-08-31redis-3.1.4RC1.tgz (195.9kB)  DLL + + [ + + Changelog + + ] + +
+ 3.1.3 + stable2017-07-15redis-3.1.3.tgz (191.6kB)  DLL + + [ + + Changelog + + ] + +
+ 3.1.3RC2 + beta2017-06-27redis-3.1.3RC2.tgz (191.6kB)  DLL + + [ + + Changelog + + ] + +
+ 3.1.3RC1 + beta2017-06-01redis-3.1.3RC1.tgz (191.6kB)  DLL + + [ + + Changelog + + ] + +
+ 3.1.2 + stable2017-03-24redis-3.1.2.tgz (194.4kB)  DLL + + [ + + Changelog + + ] + +
+ 3.1.1 + stable2017-02-01redis-3.1.1.tgz (193.9kB)  DLL + + [ + + Changelog + + ] + +
+ 3.1.1RC2 + beta2017-01-16redis-3.1.1RC2.tgz (193.9kB)  DLL + + [ + + Changelog + + ] + +
+ 3.1.0 + stable2016-12-14redis-3.1.0.tgz (192.7kB)  DLL + + [ + + Changelog + + ] + +
+ 3.0.0 + stable2016-06-10redis-3.0.0.tgz (185.3kB)  DLL + + [ + + Changelog + + ] + +
+ 2.2.8 + stable2016-06-08redis-2.2.8.tgz (187.6kB) + + [ + + Changelog + + ] + +
+ 2.2.7 + stable2015-03-03redis-2.2.7.tgz (131.2kB)  DLL + + [ + + Changelog + + ] + +
+ 2.2.5 + stable2014-03-19redis-2.2.5.tgz (94.7kB) + + [ + + Changelog + + ] + +
+ 2.2.4 + stable2013-09-02redis-2.2.4.tgz (88.8kB) + + [ + + Changelog + + ] + +
+ 2.2.3 + stable2013-04-29redis-2.2.3.tgz (84.6kB) + + [ + + Changelog + + ] + +
+
+ +

+ + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
Dependencies
Release 6.3.0:PHP Version: PHP 7.4.0 or newer
PEAR Package: PEAR 1.4.0b1 or newer
Release 6.3.0RC1:PHP Version: PHP 7.4.0 or newer
PEAR Package: PEAR 1.4.0b1 or newer
Release 6.2.0:PHP Version: PHP 7.4.0 or newer
PEAR Package: PEAR 1.4.0b1 or newer
+ Dependencies for older releases can be found on the release overview page. +
+
+ + +
+ + + + + + + + + + +
+ PRIVACY POLICY +  |  + CREDITS +
+
+ + Copyright © 2001-2025 The PHP Group
+ All rights reserved.
+
+
+ + Last updated: Wed Sep 03 10:50:24 2025 UTC
+ Bandwidth and hardware provided by: pair Networks +
+
+ + + + diff --git a/src/App.vue b/src/App.vue new file mode 100644 index 0000000..4f879f5 --- /dev/null +++ b/src/App.vue @@ -0,0 +1,373 @@ + + + + + + diff --git a/src/main.ts b/src/main.ts new file mode 100644 index 0000000..9c91a2e --- /dev/null +++ b/src/main.ts @@ -0,0 +1,23 @@ +import { createApp } from 'vue' +import { createPinia } from 'pinia' +import ElementPlus from 'element-plus' +import * as ElementPlusIconsVue from '@element-plus/icons-vue' +import 'element-plus/dist/index.css' +import 'element-plus/theme-chalk/dark/css-vars.css' +import App from './App.vue' +import router from './router' +import './styles/main.scss' + +const app = createApp(App) + +// 注册所有 Element Plus 图标 +for (const [key, component] of Object.entries(ElementPlusIconsVue)) { + app.component(key, component) +} + +app.use(createPinia()) +app.use(router) +app.use(ElementPlus) + +app.mount('#app') + diff --git a/src/router/index.ts b/src/router/index.ts new file mode 100644 index 0000000..1e62ff0 --- /dev/null +++ b/src/router/index.ts @@ -0,0 +1,64 @@ +import { createRouter, createWebHashHistory } from 'vue-router' + +const router = createRouter({ + history: createWebHashHistory(), + routes: [ + { + path: '/', + name: 'dashboard', + component: () => import('@/views/Dashboard.vue'), + meta: { title: '仪表盘' } + }, + { + path: '/php', + name: 'php', + component: () => import('@/views/PhpManager.vue'), + meta: { title: 'PHP 管理' } + }, + { + path: '/mysql', + name: 'mysql', + component: () => import('@/views/MysqlManager.vue'), + meta: { title: 'MySQL 管理' } + }, + { + path: '/nginx', + name: 'nginx', + component: () => import('@/views/NginxManager.vue'), + meta: { title: 'Nginx 管理' } + }, + { + path: '/redis', + name: 'redis', + component: () => import('@/views/RedisManager.vue'), + meta: { title: 'Redis 管理' } + }, + { + path: '/nodejs', + name: 'nodejs', + component: () => import('@/views/NodeManager.vue'), + meta: { title: 'Node.js 管理' } + }, + { + path: '/sites', + name: 'sites', + component: () => import('@/views/SitesManager.vue'), + meta: { title: '站点管理' } + }, + { + path: '/hosts', + name: 'hosts', + component: () => import('@/views/HostsManager.vue'), + meta: { title: 'Hosts 管理' } + }, + { + path: '/settings', + name: 'settings', + component: () => import('@/views/Settings.vue'), + meta: { title: '设置' } + } + ] +}) + +export default router + diff --git a/src/styles/main.scss b/src/styles/main.scss new file mode 100644 index 0000000..4735866 --- /dev/null +++ b/src/styles/main.scss @@ -0,0 +1,549 @@ +// ==================== 变量定义 ==================== +:root { + // 浅色主题(默认) + --bg-primary: #f8fafc; + --bg-sidebar: #ffffff; + --bg-content: #f1f5f9; + --bg-card: #ffffff; + --bg-titlebar: #ffffff; + --bg-hover: rgba(0, 0, 0, 0.05); + --bg-input: #f8fafc; + + --text-primary: #1e293b; + --text-secondary: #64748b; + --text-muted: #94a3b8; + + --border-color: #e2e8f0; + --border-light: #f1f5f9; + + --accent-color: #7c3aed; + --accent-light: #a78bfa; + --accent-gradient: linear-gradient(135deg, #7c3aed 0%, #a855f7 100%); + + --success-color: #10b981; + --warning-color: #f59e0b; + --error-color: #ef4444; + --info-color: #3b82f6; + + --shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.05); + --shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1); + --shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1); +} + +// 深色主题 +.dark { + --bg-primary: #0f0f1a; + --bg-sidebar: #151525; + --bg-content: #0f0f1a; + --bg-card: #1a1a2e; + --bg-titlebar: #151525; + --bg-hover: rgba(255, 255, 255, 0.08); + --bg-input: #1a1a2e; + + --text-primary: #f1f5f9; + --text-secondary: #94a3b8; + --text-muted: #64748b; + + --border-color: #2d2d44; + --border-light: #232338; + + --accent-color: #a78bfa; + --accent-light: #c4b5fd; + --accent-gradient: linear-gradient(135deg, #7c3aed 0%, #a855f7 100%); + + --shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.3); + --shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.4); + --shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.5); +} + +// ==================== 基础样式 ==================== +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +html, body { + font-family: 'Inter', 'Noto Sans SC', 'Microsoft YaHei', -apple-system, BlinkMacSystemFont, sans-serif; + font-size: 14px; + line-height: 1.6; + color: var(--text-primary); + background: var(--bg-primary); + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +// 滚动条样式 +::-webkit-scrollbar { + width: 8px; + height: 8px; +} + +::-webkit-scrollbar-track { + background: transparent; +} + +::-webkit-scrollbar-thumb { + background: var(--border-color); + border-radius: 4px; + + &:hover { + background: var(--text-muted); + } +} + +// ==================== 通用组件样式 ==================== +.page-container { + animation: fadeIn 0.3s ease; +} + +@keyframes fadeIn { + from { + opacity: 0; + transform: translateY(10px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +.page-header { + margin-bottom: 24px; + + .page-title { + font-size: 28px; + font-weight: 700; + color: var(--text-primary); + margin-bottom: 8px; + display: flex; + align-items: center; + gap: 12px; + + .title-icon { + width: 36px; + height: 36px; + display: flex; + align-items: center; + justify-content: center; + background: var(--accent-gradient); + border-radius: 10px; + color: white; + font-size: 20px; + } + } + + .page-description { + color: var(--text-secondary); + font-size: 14px; + } +} + +.card { + background: var(--bg-card); + border-radius: 16px; + border: 1px solid var(--border-color); + padding: 24px; + margin-bottom: 24px; + transition: all 0.3s; + + &:hover { + box-shadow: var(--shadow-md); + } + + .card-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 20px; + padding-bottom: 16px; + border-bottom: 1px solid var(--border-light); + + .card-title { + font-size: 18px; + font-weight: 600; + color: var(--text-primary); + display: flex; + align-items: center; + gap: 10px; + } + + .card-actions { + display: flex; + gap: 8px; + } + } + + .card-content { + // 内容区域样式 + } +} + +// 状态标签 +.status-tag { + display: inline-flex; + align-items: center; + gap: 6px; + padding: 4px 12px; + border-radius: 20px; + font-size: 12px; + font-weight: 500; + + &.running { + background: rgba(16, 185, 129, 0.15); + color: var(--success-color); + } + + &.stopped { + background: rgba(239, 68, 68, 0.15); + color: var(--error-color); + } + + &.warning { + background: rgba(245, 158, 11, 0.15); + color: var(--warning-color); + } + + .status-dot { + width: 6px; + height: 6px; + border-radius: 50%; + background: currentColor; + animation: pulse 2s infinite; + } +} + +@keyframes pulse { + 0%, 100% { + opacity: 1; + } + 50% { + opacity: 0.5; + } +} + +// 版本卡片 +.version-card { + display: flex; + align-items: center; + justify-content: space-between; + padding: 16px 20px; + background: var(--bg-input); + border-radius: 12px; + border: 1px solid var(--border-color); + margin-bottom: 12px; + transition: all 0.2s; + + &:hover { + border-color: var(--accent-color); + box-shadow: 0 0 0 3px rgba(124, 58, 237, 0.1); + } + + &.active { + border-color: var(--accent-color); + background: rgba(124, 58, 237, 0.05); + } + + .version-info { + display: flex; + align-items: center; + gap: 16px; + + .version-icon { + width: 48px; + height: 48px; + display: flex; + align-items: center; + justify-content: center; + background: var(--accent-gradient); + border-radius: 12px; + color: white; + font-size: 24px; + } + + .version-details { + .version-name { + font-size: 16px; + font-weight: 600; + color: var(--text-primary); + margin-bottom: 4px; + } + + .version-path { + font-size: 12px; + color: var(--text-muted); + font-family: 'Fira Code', 'Consolas', monospace; + } + } + } + + .version-actions { + display: flex; + align-items: center; + gap: 8px; + } +} + +// ==================== Element Plus 覆盖 ==================== +.el-button { + border-radius: 8px; + font-weight: 500; + + &--primary { + background: var(--accent-gradient); + border: none; + + &:hover, &:focus { + background: linear-gradient(135deg, #6d28d9 0%, #9333ea 100%); + } + } +} + +.el-input { + --el-input-bg-color: var(--bg-input); + --el-input-border-color: var(--border-color); + --el-input-text-color: var(--text-primary); + --el-input-placeholder-color: var(--text-muted); + + .el-input__wrapper { + border-radius: 10px; + box-shadow: none; + border: 1px solid var(--border-color); + background: var(--bg-input); + + &:hover { + border-color: var(--accent-light); + } + + &.is-focus { + border-color: var(--accent-color); + box-shadow: 0 0 0 3px rgba(124, 58, 237, 0.15); + } + } +} + +.el-select { + .el-input__wrapper { + border-radius: 10px; + } +} + +.el-dialog { + border-radius: 16px; + overflow: hidden; + + .el-dialog__header { + background: var(--bg-sidebar); + border-bottom: 1px solid var(--border-color); + padding: 20px 24px; + margin: 0; + } + + .el-dialog__body { + padding: 24px; + } + + .el-dialog__footer { + padding: 16px 24px; + border-top: 1px solid var(--border-color); + } +} + +.el-table { + --el-table-bg-color: transparent; + --el-table-tr-bg-color: transparent; + --el-table-header-bg-color: var(--bg-input); + --el-table-row-hover-bg-color: var(--bg-hover); + --el-table-border-color: var(--border-color); + --el-table-text-color: var(--text-primary); + --el-table-header-text-color: var(--text-secondary); + + border-radius: 12px; + overflow: hidden; + border: 1px solid var(--border-color); + + th.el-table__cell { + font-weight: 600; + } +} + +.el-message { + border-radius: 10px; + border: none; + box-shadow: var(--shadow-lg); +} + +.el-notification { + border-radius: 12px; + border: 1px solid var(--border-color); +} + +// 暗色主题下的 Element Plus 调整 +.dark { + .el-button { + --el-button-bg-color: var(--bg-input); + --el-button-border-color: var(--border-color); + --el-button-text-color: var(--text-primary); + + &:hover { + --el-button-hover-bg-color: var(--bg-hover); + --el-button-hover-border-color: var(--accent-light); + --el-button-hover-text-color: var(--accent-light); + } + } + + .el-dialog { + --el-dialog-bg-color: var(--bg-card); + } + + .el-message-box { + --el-messagebox-title-color: var(--text-primary); + --el-messagebox-content-color: var(--text-secondary); + background: var(--bg-card); + border: 1px solid var(--border-color); + border-radius: 16px; + } + + .el-select-dropdown { + --el-bg-color-overlay: var(--bg-card); + border: 1px solid var(--border-color); + border-radius: 10px; + } + + .el-popper { + --el-bg-color-overlay: var(--bg-card); + } +} + +// ==================== 代码编辑器样式 ==================== +.code-editor { + font-family: 'Fira Code', 'Consolas', 'Monaco', monospace; + font-size: 13px; + line-height: 1.6; + background: var(--bg-input); + border: 1px solid var(--border-color); + border-radius: 12px; + padding: 16px; + min-height: 300px; + resize: vertical; + color: var(--text-primary); + + &:focus { + outline: none; + border-color: var(--accent-color); + box-shadow: 0 0 0 3px rgba(124, 58, 237, 0.15); + } +} + +// ==================== 动画效果 ==================== +.slide-up-enter-active, +.slide-up-leave-active { + transition: all 0.3s ease; +} + +.slide-up-enter-from { + opacity: 0; + transform: translateY(20px); +} + +.slide-up-leave-to { + opacity: 0; + transform: translateY(-20px); +} + +// 加载状态 +.loading-overlay { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: rgba(var(--bg-primary), 0.8); + display: flex; + align-items: center; + justify-content: center; + z-index: 100; + backdrop-filter: blur(4px); +} + +.spinner { + width: 40px; + height: 40px; + border: 3px solid var(--border-color); + border-top-color: var(--accent-color); + border-radius: 50%; + animation: spin 0.8s linear infinite; +} + +@keyframes spin { + to { + transform: rotate(360deg); + } +} + +// 空状态 +.empty-state { + text-align: center; + padding: 60px 20px; + + .empty-icon { + font-size: 64px; + color: var(--text-muted); + margin-bottom: 16px; + } + + .empty-title { + font-size: 18px; + font-weight: 600; + color: var(--text-primary); + margin-bottom: 8px; + } + + .empty-description { + color: var(--text-secondary); + margin-bottom: 24px; + } +} + +// 下载进度条 +.download-progress { + margin-top: 20px; + padding: 16px; + background: var(--bg-input); + border-radius: 12px; + border: 1px solid var(--border-color); + + .progress-info { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 10px; + font-size: 13px; + color: var(--text-secondary); + + span:first-child { + font-weight: 500; + color: var(--text-primary); + } + } + + .el-progress { + .el-progress-bar__outer { + background-color: var(--border-color); + border-radius: 8px; + } + + .el-progress-bar__inner { + background: var(--accent-gradient); + border-radius: 8px; + transition: width 0.3s ease; + } + + .el-progress__text { + color: var(--accent-color); + font-weight: 600; + font-size: 13px !important; + } + } +} + diff --git a/src/views/Dashboard.vue b/src/views/Dashboard.vue new file mode 100644 index 0000000..e4af5de --- /dev/null +++ b/src/views/Dashboard.vue @@ -0,0 +1,597 @@ + + + + + + diff --git a/src/views/HostsManager.vue b/src/views/HostsManager.vue new file mode 100644 index 0000000..40fbc47 --- /dev/null +++ b/src/views/HostsManager.vue @@ -0,0 +1,205 @@ + + + + + + diff --git a/src/views/MysqlManager.vue b/src/views/MysqlManager.vue new file mode 100644 index 0000000..ad91f7d --- /dev/null +++ b/src/views/MysqlManager.vue @@ -0,0 +1,555 @@ + + + + + + diff --git a/src/views/NginxManager.vue b/src/views/NginxManager.vue new file mode 100644 index 0000000..8a25d18 --- /dev/null +++ b/src/views/NginxManager.vue @@ -0,0 +1,517 @@ + + + + + + diff --git a/src/views/NodeManager.vue b/src/views/NodeManager.vue new file mode 100644 index 0000000..b8de7ea --- /dev/null +++ b/src/views/NodeManager.vue @@ -0,0 +1,397 @@ + + + + + + diff --git a/src/views/PhpManager.vue b/src/views/PhpManager.vue new file mode 100644 index 0000000..c3be74c --- /dev/null +++ b/src/views/PhpManager.vue @@ -0,0 +1,835 @@ + + + + + + diff --git a/src/views/RedisManager.vue b/src/views/RedisManager.vue new file mode 100644 index 0000000..b4b5040 --- /dev/null +++ b/src/views/RedisManager.vue @@ -0,0 +1,529 @@ + + + + + + diff --git a/src/views/Settings.vue b/src/views/Settings.vue new file mode 100644 index 0000000..1e06454 --- /dev/null +++ b/src/views/Settings.vue @@ -0,0 +1,244 @@ + + + + + + diff --git a/src/views/SitesManager.vue b/src/views/SitesManager.vue new file mode 100644 index 0000000..4b54fab --- /dev/null +++ b/src/views/SitesManager.vue @@ -0,0 +1,697 @@ + + + + + + diff --git a/src/vite-env.d.ts b/src/vite-env.d.ts new file mode 100644 index 0000000..a584741 --- /dev/null +++ b/src/vite-env.d.ts @@ -0,0 +1,110 @@ +/// + +declare module '*.vue' { + import type { DefineComponent } from 'vue' + const component: DefineComponent<{}, {}, any> + export default component +} + +interface Window { + electronAPI: { + minimize: () => Promise + maximize: () => Promise + close: () => Promise + openExternal: (url: string) => Promise + openPath: (path: string) => Promise + + php: { + getVersions: () => Promise + getAvailableVersions: () => Promise + install: (version: string) => Promise<{ success: boolean; message: string }> + uninstall: (version: string) => Promise<{ success: boolean; message: string }> + setActive: (version: string) => Promise<{ success: boolean; message: string }> + getExtensions: (version: string) => Promise + enableExtension: (version: string, ext: string) => Promise<{ success: boolean; message: string }> + disableExtension: (version: string, ext: string) => Promise<{ success: boolean; message: string }> + installExtension: (version: string, ext: string) => Promise<{ success: boolean; message: string }> + getConfig: (version: string) => Promise + saveConfig: (version: string, config: string) => Promise<{ success: boolean; message: string }> + } + + mysql: { + getVersions: () => Promise + getAvailableVersions: () => Promise + install: (version: string) => Promise<{ success: boolean; message: string }> + uninstall: (version: string) => Promise<{ success: boolean; message: string }> + start: (version: string) => Promise<{ success: boolean; message: string }> + stop: (version: string) => Promise<{ success: boolean; message: string }> + restart: (version: string) => Promise<{ success: boolean; message: string }> + getStatus: (version: string) => Promise + changePassword: (version: string, newPassword: string) => Promise<{ success: boolean; message: string }> + getConfig: (version: string) => Promise + saveConfig: (version: string, config: string) => Promise<{ success: boolean; message: string }> + } + + nginx: { + getVersions: () => Promise + getAvailableVersions: () => Promise + install: (version: string) => Promise<{ success: boolean; message: string }> + uninstall: (version: string) => Promise<{ success: boolean; message: string }> + start: () => Promise<{ success: boolean; message: string }> + stop: () => Promise<{ success: boolean; message: string }> + restart: () => Promise<{ success: boolean; message: string }> + reload: () => Promise<{ success: boolean; message: string }> + getStatus: () => Promise + getConfig: () => Promise + saveConfig: (config: string) => Promise<{ success: boolean; message: string }> + getSites: () => Promise + addSite: (site: any) => Promise<{ success: boolean; message: string }> + removeSite: (name: string) => Promise<{ success: boolean; message: string }> + enableSite: (name: string) => Promise<{ success: boolean; message: string }> + disableSite: (name: string) => Promise<{ success: boolean; message: string }> + generateLaravelConfig: (site: any) => Promise + requestSSL: (domain: string, email: string) => Promise<{ success: boolean; message: string }> + } + + redis: { + getVersions: () => Promise + getAvailableVersions: () => Promise + install: (version: string) => Promise<{ success: boolean; message: string }> + uninstall: (version: string) => Promise<{ success: boolean; message: string }> + start: () => Promise<{ success: boolean; message: string }> + stop: () => Promise<{ success: boolean; message: string }> + restart: () => Promise<{ success: boolean; message: string }> + getStatus: () => Promise + getConfig: () => Promise + saveConfig: (config: string) => Promise<{ success: boolean; message: string }> + } + + node: { + getVersions: () => Promise + getAvailableVersions: () => Promise + install: (version: string, downloadUrl: string) => Promise<{ success: boolean; message: string }> + uninstall: (version: string) => Promise<{ success: boolean; message: string }> + setActive: (version: string) => Promise<{ success: boolean; message: string }> + getInfo: (version: string) => Promise + } + + service: { + getAll: () => Promise + setAutoStart: (service: string, enabled: boolean) => Promise<{ success: boolean; message: string }> + getAutoStart: (service: string) => Promise + startAll: () => Promise<{ success: boolean; message: string; details: string[] }> + stopAll: () => Promise<{ success: boolean; message: string; details: string[] }> + } + + hosts: { + get: () => Promise + add: (domain: string, ip: string) => Promise<{ success: boolean; message: string }> + remove: (domain: string) => Promise<{ success: boolean; message: string }> + } + + config: { + get: (key: string) => Promise + set: (key: string, value: any) => Promise + getBasePath: () => Promise + setBasePath: (path: string) => Promise + } + } +} + diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..b06d19c --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,26 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "module": "ESNext", + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "skipLibCheck": true, + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "preserve", + "strict": true, + "noUnusedLocals": false, + "noUnusedParameters": false, + "noFallthroughCasesInSwitch": true, + "paths": { + "@/*": ["./src/*"] + }, + "types": ["node"] + }, + "include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue", "electron/**/*.ts"], + "references": [{ "path": "./tsconfig.node.json" }] +} + diff --git a/tsconfig.node.json b/tsconfig.node.json new file mode 100644 index 0000000..e428d50 --- /dev/null +++ b/tsconfig.node.json @@ -0,0 +1,11 @@ +{ + "compilerOptions": { + "composite": true, + "skipLibCheck": true, + "module": "ESNext", + "moduleResolution": "bundler", + "allowSyntheticDefaultImports": true + }, + "include": ["vite.config.ts"] +} + diff --git a/vite.config.ts b/vite.config.ts new file mode 100644 index 0000000..f648bc1 --- /dev/null +++ b/vite.config.ts @@ -0,0 +1,70 @@ +import { defineConfig } from 'vite' +import vue from '@vitejs/plugin-vue' +import electron from 'vite-plugin-electron' +import renderer from 'vite-plugin-electron-renderer' +import { resolve } from 'path' + +// https://vitejs.dev/config/ +export default defineConfig(({ command }) => { + const isServe = command === 'serve' + const isBuild = command === 'build' + + return { + plugins: [ + vue(), + electron([ + { + entry: 'electron/main.ts', + onstart(options) { + options.startup() + }, + vite: { + build: { + outDir: 'dist-electron', + minify: isBuild, + rollupOptions: { + external: [ + 'electron', + 'node-windows', + 'sudo-prompt', + 'electron-store', + 'unzipper', + 'fs', + 'path', + 'child_process', + 'https', + 'http', + 'stream', + 'util' + ] + } + } + } + }, + { + entry: 'electron/preload.ts', + onstart(options) { + options.reload() + }, + vite: { + build: { + outDir: 'dist-electron', + minify: isBuild + } + } + } + ]), + renderer() + ], + resolve: { + alias: { + '@': resolve(__dirname, 'src') + } + }, + server: { + port: 5173 + }, + clearScreen: false + } +}) +