commit 29a79b1c6b2f0c8abc1440f6d57a7d42d3cbc389 Author: kjqwer <2990346238@qq.com> Date: Thu Aug 21 10:43:04 2025 +0800 初始化 diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 0000000..f816068 --- /dev/null +++ b/.eslintrc.js @@ -0,0 +1,25 @@ +module.exports = { + env: { + es2021: true, + node: true, + }, + extends: ['standard'], + parserOptions: { + ecmaVersion: 12, + sourceType: 'module', + }, + rules: { + curly: 'off', + camelcase: 'off', + 'no-case-declarations': 'off', + semi: ['error', 'always'], + 'comma-dangle': ['error', 'only-multiline'], + 'space-before-function-paren': 'off', + 'no-tabs': 'off', + indent: 'off', + eqeqeq: 'off', + 'no-async-promise-executor': 'off', + 'no-control-regex': 'off', + 'prefer-promise-reject-errors': 'off', + }, +}; diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b7494f8 --- /dev/null +++ b/.gitignore @@ -0,0 +1,66 @@ +/test +/package-lock.json + +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# TypeScript v1 declaration files +typings/ + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variables file +.env + +# next.js build output +.next + +old/ diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..317e358 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,9 @@ +{ + "printWidth": 200, + "singleQuote": true, + "trailingComma": "es5", + "arrowParens": "avoid", + "useTabs": false, + "tabWidth": 2, + "semi": true +} diff --git a/backend/README.md b/backend/README.md new file mode 100644 index 0000000..1243fdd --- /dev/null +++ b/backend/README.md @@ -0,0 +1,217 @@ +# Pixiv 后端服务 + +这是一个优雅的 Pixiv 后端服务架构,提供作品信息获取、作者信息查询、文件下载等功能。 + +## 🏗️ 项目架构 + +``` +backend/ +├── server.js # 主服务器文件 +├── core.js # 核心后端逻辑 +├── auth.js # 认证模块 +├── config.js # 代理配置 +├── start.js # 启动脚本 +├── test-login.js # 登录测试脚本 +├── middleware/ # 中间件 +│ ├── auth.js # 认证中间件 +│ └── errorHandler.js # 错误处理中间件 +├── routes/ # 路由模块 +│ ├── auth.js # 认证路由 +│ ├── artwork.js # 作品路由 +│ ├── artist.js # 作者路由 +│ └── download.js # 下载路由 +├── services/ # 服务层 +│ ├── artwork.js # 作品服务 +│ ├── artist.js # 作者服务 +│ └── download.js # 下载服务 +└── utils/ # 工具类 + └── response.js # 响应工具 +``` + +## 🚀 快速开始 + +### 1. 安装依赖 + +```bash +npm install +``` + +### 2. 启动服务器 + +```bash +# 开发模式 +npm run dev + +# 生产模式 +npm start + +# 或直接运行 +node backend/start.js +``` + +### 3. 测试登录 + +```bash +node backend/test-login.js +``` + +## 📡 API 接口 + +### 认证相关 + +- `GET /api/auth/status` - 获取登录状态 +- `GET /api/auth/login-url` - 获取登录URL +- `POST /api/auth/callback` - 处理登录回调 +- `POST /api/auth/relogin` - 重新登录 +- `POST /api/auth/logout` - 登出 + +### 作品相关 + +- `GET /api/artwork/:id` - 获取作品详情 +- `GET /api/artwork/:id/preview` - 获取作品预览 +- `GET /api/artwork/:id/images` - 获取作品图片URL +- `GET /api/artwork/search` - 搜索作品 + +### 作者相关 + +- `GET /api/artist/:id` - 获取作者信息 +- `GET /api/artist/:id/artworks` - 获取作者作品列表 +- `GET /api/artist/:id/following` - 获取作者关注列表 +- `GET /api/artist/:id/followers` - 获取作者粉丝列表 +- `POST /api/artist/:id/follow` - 关注/取消关注作者 + +### 下载相关 + +- `POST /api/download/artwork/:id` - 下载单个作品 +- `POST /api/download/artworks` - 批量下载作品 +- `POST /api/download/artist/:id` - 下载作者作品 +- `GET /api/download/progress/:taskId` - 获取下载进度 +- `DELETE /api/download/cancel/:taskId` - 取消下载任务 +- `GET /api/download/history` - 获取下载历史 + +## 🔧 配置说明 + +### 代理配置 + +在 `config.js` 中配置代理设置: + +```javascript +const proxyConfig = { + system: { + host: '127.0.0.1', + port: 7897, + protocol: 'http' + } +}; +``` + +### 环境变量 + +- `PORT` - 服务器端口 (默认: 3000) +- `NODE_ENV` - 运行环境 (development/production) +- `FRONTEND_URL` - 前端URL (用于CORS) + +## 📁 文件结构说明 + +### 核心模块 + +- **server.js**: 主服务器类,负责初始化、配置和启动服务器 +- **core.js**: 核心后端逻辑,管理认证状态和配置 +- **auth.js**: 认证模块,处理OAuth2.0登录流程 + +### 中间件 + +- **auth.js**: 认证中间件,验证用户登录状态 +- **errorHandler.js**: 全局错误处理中间件 + +### 路由模块 + +- **auth.js**: 认证相关路由 +- **artwork.js**: 作品相关路由 +- **artist.js**: 作者相关路由 +- **download.js**: 下载相关路由 + +### 服务层 + +- **artwork.js**: 作品服务,处理作品API调用 +- **artist.js**: 作者服务,处理作者API调用 +- **download.js**: 下载服务,处理文件下载 + +### 工具类 + +- **response.js**: 统一API响应格式工具 + +## 🎯 主要功能 + +### 1. 作品信息获取 +- 获取作品详细信息 +- 获取作品预览信息 +- 获取作品图片URL +- 搜索作品 + +### 2. 作者信息查询 +- 获取作者基本信息 +- 获取作者作品列表 +- 获取作者关注/粉丝列表 +- 关注/取消关注作者 + +### 3. 文件下载 +- 下载单个作品 +- 批量下载作品 +- 下载作者作品 +- 下载进度跟踪 +- 下载历史记录 + +### 4. 认证管理 +- OAuth2.0 登录流程 +- 自动刷新令牌 +- 登录状态管理 + +## 🔒 安全特性 + +- 统一的错误处理 +- 请求参数验证 +- 认证中间件保护 +- CORS 配置 +- 代理支持 + +## 📊 监控和日志 + +- 请求日志记录 +- 错误日志记录 +- 健康检查接口 +- 下载进度跟踪 + +## 🛠️ 开发指南 + +### 添加新路由 + +1. 在 `routes/` 目录下创建新的路由文件 +2. 在 `server.js` 中注册路由 +3. 添加相应的中间件保护 + +### 添加新服务 + +1. 在 `services/` 目录下创建新的服务类 +2. 实现相应的业务逻辑 +3. 在路由中调用服务 + +### 添加新中间件 + +1. 在 `middleware/` 目录下创建新的中间件文件 +2. 在 `server.js` 中注册中间件 + +## 📝 注意事项 + +1. 确保代理配置正确 +2. 首次使用需要登录获取访问令牌 +3. 下载功能需要足够的磁盘空间 +4. 建议在生产环境中使用PM2等进程管理器 + +## 🤝 贡献 + +欢迎提交 Issue 和 Pull Request! + +## �� 许可证 + +MIT License \ No newline at end of file diff --git a/backend/auth.js b/backend/auth.js new file mode 100644 index 0000000..52fc14c --- /dev/null +++ b/backend/auth.js @@ -0,0 +1,274 @@ +const axios = require('axios'); +const Crypto = require('crypto'); +const { Base64 } = require('js-base64'); +const { stringify } = require('qs'); +const moment = require('moment'); +const { ProxyAgent } = require('proxy-agent'); + +// OAuth 2.0 配置 +const CLIENT_ID = 'MOBrBDS8blbauoSck0ZfDbtuzpyT'; +const CLIENT_SECRET = 'lsACyCD94FhDUtGTXi3QzcFE2uU1hqtDaKeqrdwj'; +const REDIRECT_URI = 'https://app-api.pixiv.net/web/v1/users/auth/pixiv/callback'; +const LOGIN_URL = 'https://app-api.pixiv.net/web/v1/login'; +const HASH_SECRET = '28c1fdd170a5204386cb1313c7077b34f83e4aaf4aa829ce78c231e05b0bae2c'; + +class PixivAuth { + constructor(proxy = null) { + this.accessToken = null; + this.refreshToken = null; + this.user = null; + this.proxy = proxy; + + // 创建 axios 实例,支持代理 + this.axiosInstance = this.createAxiosInstance(); + } + + /** + * 创建支持代理的 axios 实例 + */ + createAxiosInstance() { + const config = { + timeout: 30000, // 30秒超时 + headers: this.getDefaultHeaders() + }; + + // 如果设置了代理,添加代理配置 + if (this.proxy) { + console.log('使用代理:', this.proxy); + config.httpsAgent = new ProxyAgent(this.proxy); + } else { + // 尝试使用系统代理 + const systemProxy = process.env.HTTP_PROXY || process.env.HTTPS_PROXY || process.env.http_proxy || process.env.https_proxy; + if (systemProxy) { + console.log('使用系统代理:', systemProxy); + config.httpsAgent = new ProxyAgent(systemProxy); + } + } + + return axios.create(config); + } + + /** + * 设置代理 + */ + setProxy(proxy) { + this.proxy = proxy; + this.axiosInstance = this.createAxiosInstance(); + } + + /** + * 获取默认头部信息 + */ + getDefaultHeaders() { + const datetime = moment().format(); + return { + 'App-OS': 'android', + 'Accept-Language': 'en-us', + 'App-OS-Version': '9.0', + 'App-Version': '5.0.234', + 'User-Agent': 'PixivAndroidApp/5.0.234 (Android 9.0; Pixel 3)', + 'X-Client-Time': datetime, + 'X-Client-Hash': Crypto.createHash('md5').update(`${datetime}${HASH_SECRET}`).digest('hex') + }; + } + + /** + * 生成 PKCE 参数 + */ + generatePKCE() { + const codeVerifier = Base64.fromUint8Array(Crypto.randomBytes(32), true); + const codeChallenge = Base64.encodeURI(Crypto.createHash('sha256').update(codeVerifier).digest()); + + return { + code_verifier: codeVerifier, + code_challenge: codeChallenge + }; + } + + /** + * 获取登录URL + */ + getLoginUrl() { + const pkce = this.generatePKCE(); + + const params = { + code_challenge: pkce.code_challenge, + code_challenge_method: 'S256', + client: 'pixiv-android' + }; + + const loginUrl = `${LOGIN_URL}?${stringify(params)}`; + + return { + login_url: loginUrl, + code_verifier: pkce.code_verifier + }; + } + + /** + * 使用授权码获取访问令牌 + */ + async getAccessToken(code, codeVerifier) { + try { + console.log('正在获取访问令牌...'); + console.log('Code:', code); + console.log('Code Verifier:', codeVerifier); + + const data = { + client_id: CLIENT_ID, + client_secret: CLIENT_SECRET, + code: code, + code_verifier: codeVerifier, + redirect_uri: REDIRECT_URI, + grant_type: 'authorization_code', + include_policy: true + }; + + console.log('请求数据:', data); + + const headers = { + ...this.getDefaultHeaders(), + 'Content-Type': 'application/x-www-form-urlencoded' + }; + + console.log('请求头部:', headers); + + const response = await this.axiosInstance.post('https://oauth.secure.pixiv.net/auth/token', + stringify(data), + { headers } + ); + + console.log('响应状态:', response.status); + console.log('响应数据:', JSON.stringify(response.data, null, 2)); + + const tokenData = response.data.response; + + this.accessToken = tokenData.access_token; + this.refreshToken = tokenData.refresh_token; + this.user = tokenData.user; + + console.log('获取访问令牌成功'); + return { + success: true, + access_token: tokenData.access_token, + refresh_token: tokenData.refresh_token, + user: tokenData.user + }; + + } catch (error) { + console.error('获取访问令牌失败:'); + console.error('错误对象:', error); + console.error('响应状态:', error.response?.status); + console.error('响应数据:', error.response?.data); + console.error('错误消息:', error.message); + + return { + success: false, + error: error.response?.data || error.message + }; + } + } + + /** + * 使用刷新令牌更新访问令牌 + */ + async refreshAccessToken(refreshToken) { + try { + console.log('正在刷新访问令牌...'); + + const data = { + client_id: CLIENT_ID, + client_secret: CLIENT_SECRET, + get_secure_url: true, + include_policy: true, + grant_type: 'refresh_token', + refresh_token: refreshToken + }; + + const headers = { + ...this.getDefaultHeaders(), + 'Content-Type': 'application/x-www-form-urlencoded' + }; + + const response = await this.axiosInstance.post('https://oauth.secure.pixiv.net/auth/token', + stringify(data), + { headers } + ); + + const tokenData = response.data.response; + + this.accessToken = tokenData.access_token; + this.refreshToken = tokenData.refresh_token; + + console.log('刷新访问令牌成功'); + return { + success: true, + access_token: tokenData.access_token, + refresh_token: tokenData.refresh_token + }; + + } catch (error) { + console.error('刷新访问令牌失败:', error.response?.data || error.message); + return { + success: false, + error: error.response?.data || error.message + }; + } + } + + /** + * 获取用户信息 + */ + async getUserInfo() { + if (!this.accessToken) { + return { success: false, error: '未登录' }; + } + + try { + const headers = { + ...this.getDefaultHeaders(), + 'Authorization': `Bearer ${this.accessToken}` + }; + + const response = await this.axiosInstance.get('https://app-api.pixiv.net/v1/user/me', { + headers + }); + + return { + success: true, + user: response.data.user + }; + + } catch (error) { + console.error('获取用户信息失败:', error.response?.data || error.message); + return { + success: false, + error: error.response?.data || error.message + }; + } + } + + /** + * 登出 + */ + logout() { + this.accessToken = null; + this.refreshToken = null; + this.user = null; + console.log('已登出'); + return { success: true }; + } + + /** + * 获取当前状态 + */ + getStatus() { + return { + isLoggedIn: !!this.accessToken, + user: this.user, + hasRefreshToken: !!this.refreshToken + }; + } +} + +module.exports = PixivAuth; \ No newline at end of file diff --git a/backend/config.js b/backend/config.js new file mode 100644 index 0000000..d48c2c6 --- /dev/null +++ b/backend/config.js @@ -0,0 +1,36 @@ +// 代理配置 +const proxyConfig = { + // 系统代理配置 + system: { + host: '127.0.0.1', + port: 7897, + protocol: 'http' + }, + + // 代理URL格式 + get proxyUrl() { + return `${this.system.protocol}://${this.system.host}:${this.system.port}`; + }, + + // 环境变量设置 + setEnvironmentVariables() { + process.env.HTTP_PROXY = this.proxyUrl; + process.env.HTTPS_PROXY = this.proxyUrl; + process.env.http_proxy = this.proxyUrl; + process.env.https_proxy = this.proxyUrl; + + console.log('代理环境变量已设置:', this.proxyUrl); + }, + + // 清除环境变量 + clearEnvironmentVariables() { + delete process.env.HTTP_PROXY; + delete process.env.HTTPS_PROXY; + delete process.env.http_proxy; + delete process.env.https_proxy; + + console.log('代理环境变量已清除'); + } +}; + +module.exports = proxyConfig; \ No newline at end of file diff --git a/backend/core.js b/backend/core.js new file mode 100644 index 0000000..e79adae --- /dev/null +++ b/backend/core.js @@ -0,0 +1,258 @@ +const Fse = require('fs-extra'); +const Path = require('path'); +const PixivAuth = require('./auth'); + +// 配置文件路径 +const CONFIG_FILE_DIR = require('appdata-path').getAppDataPath('pxder'); +const CONFIG_FILE = Path.resolve(CONFIG_FILE_DIR, 'config.json'); + +// 默认配置 +const defaultConfig = { + download: { + thread: 5, + timeout: 30, + path: null + }, + refresh_token: null, + access_token: null, + user: null, + proxy: null +}; + +class PixivBackend { + constructor() { + this.config = null; + this.auth = null; + this.isLoggedIn = false; + } + + /** + * 初始化后端 + */ + async init() { + console.log('正在初始化 Pixiv 后端...'); + + // 初始化配置 + this.initConfig(); + this.config = this.readConfig(); + + // 创建认证实例,传入代理配置 + this.auth = new PixivAuth(this.config.proxy); + + // 检查登录状态 + if (this.config.refresh_token) { + console.log('检测到已保存的登录信息,正在验证...'); + await this.relogin(); + } else { + console.log('未检测到登录信息,需要先登录'); + } + + return this; + } + + /** + * 初始化配置文件 + */ + initConfig() { + Fse.ensureDirSync(CONFIG_FILE_DIR); + if (!Fse.existsSync(CONFIG_FILE)) { + Fse.writeJSONSync(CONFIG_FILE, defaultConfig); + } + } + + /** + * 读取配置 + */ + readConfig() { + try { + const config = Fse.readJsonSync(CONFIG_FILE); + // 合并默认配置 + return { ...defaultConfig, ...config }; + } catch (error) { + console.error('读取配置文件失败:', error.message); + return { ...defaultConfig }; + } + } + + /** + * 保存配置 + */ + saveConfig() { + try { + Fse.writeJsonSync(CONFIG_FILE, this.config); + console.log('配置已保存'); + } catch (error) { + console.error('保存配置失败:', error.message); + } + } + + /** + * 获取登录URL + */ + getLoginUrl() { + const loginData = this.auth.getLoginUrl(); + this.config.code_verifier = loginData.code_verifier; + this.saveConfig(); + + return { + login_url: loginData.login_url, + code_verifier: loginData.code_verifier + }; + } + + /** + * 处理登录回调 + */ + async handleLoginCallback(code) { + try { + console.log('正在处理登录回调...'); + + if (!this.config.code_verifier) { + throw new Error('缺少 code_verifier,请重新获取登录URL'); + } + + // 使用新的认证模块进行登录 + const result = await this.auth.getAccessToken(code, this.config.code_verifier); + + if (result.success) { + // 保存登录信息 + this.config.refresh_token = result.refresh_token; + this.config.access_token = result.access_token; + this.config.user = result.user; + + // 清理临时数据 + delete this.config.code_verifier; + + this.saveConfig(); + this.isLoggedIn = true; + + console.log(`登录成功!用户: ${result.user.account}`); + return { + success: true, + user: result.user + }; + } else { + throw new Error(result.error); + } + + } catch (error) { + console.error('登录失败:', error.message); + return { + success: false, + error: error.message + }; + } + } + + /** + * 重新登录(使用保存的 refresh_token) + */ + async relogin() { + try { + if (!this.config.refresh_token) { + throw new Error('没有保存的登录信息'); + } + + console.log('正在使用保存的登录信息重新登录...'); + + const result = await this.auth.refreshAccessToken(this.config.refresh_token); + + if (result.success) { + // 更新配置 + this.config.access_token = result.access_token; + this.config.refresh_token = result.refresh_token; + this.saveConfig(); + + this.isLoggedIn = true; + console.log('重新登录成功!'); + + return { success: true }; + } else { + throw new Error(result.error); + } + + } catch (error) { + console.error('重新登录失败:', error.message); + // 清除无效的登录信息 + this.config.refresh_token = null; + this.config.access_token = null; + this.config.user = null; + this.saveConfig(); + this.isLoggedIn = false; + + return { + success: false, + error: error.message + }; + } + } + + /** + * 登出 + */ + logout() { + this.auth.logout(); + this.config.refresh_token = null; + this.config.access_token = null; + this.config.user = null; + this.isLoggedIn = false; + + this.saveConfig(); + console.log('已登出'); + + return { success: true }; + } + + /** + * 获取登录状态 + */ + getLoginStatus() { + const status = this.auth.getStatus(); + return { + isLoggedIn: status.isLoggedIn, + username: this.config.user?.account, + user_id: this.config.user?.id + }; + } + + /** + * 设置下载路径 + */ + setDownloadPath(path) { + this.config.download.path = path; + this.saveConfig(); + console.log(`下载路径已设置为: ${path}`); + return { success: true }; + } + + /** + * 获取配置信息 + */ + getConfig() { + return { + download: this.config.download, + proxy: this.config.proxy, + isLoggedIn: this.isLoggedIn + }; + } + + /** + * 设置代理 + */ + setProxy(proxy) { + this.config.proxy = proxy; + this.auth.setProxy(proxy); + this.saveConfig(); + console.log(`代理已设置为: ${proxy}`); + return { success: true }; + } + + /** + * 获取认证实例(用于后续API调用) + */ + getAuth() { + return this.auth; + } +} + +module.exports = PixivBackend; \ No newline at end of file diff --git a/backend/middleware/auth.js b/backend/middleware/auth.js new file mode 100644 index 0000000..df7e649 --- /dev/null +++ b/backend/middleware/auth.js @@ -0,0 +1,50 @@ +/** + * 认证中间件 + */ +const authMiddleware = (req, res, next) => { + try { + // 检查后端是否已登录 + if (!req.backend || !req.backend.isLoggedIn) { + return res.status(401).json({ + error: true, + message: 'Authentication required', + code: 'AUTH_REQUIRED' + }); + } + + // 检查访问令牌是否有效 + const auth = req.backend.getAuth(); + if (!auth || !auth.accessToken) { + return res.status(401).json({ + error: true, + message: 'Invalid access token', + code: 'INVALID_TOKEN' + }); + } + + next(); + } catch (error) { + next(error); + } +}; + +/** + * 可选的认证中间件(不强制要求登录) + */ +const optionalAuthMiddleware = (req, res, next) => { + try { + // 如果后端已登录,将用户信息添加到请求对象 + if (req.backend && req.backend.isLoggedIn) { + req.user = req.backend.config.user; + } + + next(); + } catch (error) { + next(error); + } +}; + +module.exports = { + authMiddleware, + optionalAuthMiddleware +}; \ No newline at end of file diff --git a/backend/middleware/errorHandler.js b/backend/middleware/errorHandler.js new file mode 100644 index 0000000..96d3e3d --- /dev/null +++ b/backend/middleware/errorHandler.js @@ -0,0 +1,62 @@ +/** + * 全局错误处理中间件 + */ +const errorHandler = (err, req, res, next) => { + console.error('错误详情:', err); + + // 默认错误信息 + let statusCode = 500; + let message = 'Internal Server Error'; + let details = null; + + // 根据错误类型设置状态码和消息 + if (err.name === 'ValidationError') { + statusCode = 400; + message = 'Validation Error'; + details = err.message; + } else if (err.name === 'UnauthorizedError') { + statusCode = 401; + message = 'Unauthorized'; + } else if (err.name === 'ForbiddenError') { + statusCode = 403; + message = 'Forbidden'; + } else if (err.name === 'NotFoundError') { + statusCode = 404; + message = 'Not Found'; + } else if (err.code === 'ENOTFOUND') { + statusCode = 503; + message = 'Service Unavailable'; + details = 'Network connection failed'; + } else if (err.code === 'ECONNREFUSED') { + statusCode = 503; + message = 'Service Unavailable'; + details = 'Connection refused'; + } else if (err.response) { + // Axios 错误 + statusCode = err.response.status || 500; + message = err.response.statusText || 'Request Failed'; + details = err.response.data; + } else if (err.message) { + message = err.message; + } + + // 构建错误响应 + const errorResponse = { + error: true, + message, + statusCode, + timestamp: new Date().toISOString(), + path: req.originalUrl, + method: req.method + }; + + // 在开发环境下添加详细信息 + if (process.env.NODE_ENV === 'development') { + errorResponse.details = details; + errorResponse.stack = err.stack; + } + + res.status(statusCode).json(errorResponse); +}; + +module.exports = { errorHandler }; \ No newline at end of file diff --git a/backend/routes/artist.js b/backend/routes/artist.js new file mode 100644 index 0000000..1ed5cfa --- /dev/null +++ b/backend/routes/artist.js @@ -0,0 +1,225 @@ +const express = require('express'); +const router = express.Router(); +const ArtistService = require('../services/artist'); + +/** + * 获取作者信息 + * GET /api/artist/:id + */ +router.get('/:id', async (req, res) => { + try { + const { id } = req.params; + + if (!id || isNaN(parseInt(id))) { + return res.status(400).json({ + success: false, + error: 'Invalid artist ID' + }); + } + + const artistService = new ArtistService(req.backend.getAuth()); + const result = await artistService.getArtistInfo(parseInt(id)); + + if (result.success) { + res.json({ + success: true, + data: result.data + }); + } else { + res.status(404).json({ + success: false, + error: result.error + }); + } + } catch (error) { + res.status(500).json({ + success: false, + error: error.message + }); + } +}); + +/** + * 获取作者作品列表 + * GET /api/artist/:id/artworks + */ +router.get('/:id/artworks', async (req, res) => { + try { + const { id } = req.params; + const { + type = 'art', + filter = 'for_ios', + offset = 0, + limit = 30 + } = req.query; + + if (!id || isNaN(parseInt(id))) { + return res.status(400).json({ + success: false, + error: 'Invalid artist ID' + }); + } + + const artistService = new ArtistService(req.backend.getAuth()); + const result = await artistService.getArtistArtworks(parseInt(id), { + type, + filter, + offset: parseInt(offset), + limit: parseInt(limit) + }); + + if (result.success) { + res.json({ + success: true, + data: result.data + }); + } else { + res.status(404).json({ + success: false, + error: result.error + }); + } + } catch (error) { + res.status(500).json({ + success: false, + error: error.message + }); + } +}); + +/** + * 获取作者关注列表 + * GET /api/artist/:id/following + */ +router.get('/:id/following', async (req, res) => { + try { + const { id } = req.params; + const { + restrict = 'public', + offset = 0, + limit = 30 + } = req.query; + + if (!id || isNaN(parseInt(id))) { + return res.status(400).json({ + success: false, + error: 'Invalid artist ID' + }); + } + + const artistService = new ArtistService(req.backend.getAuth()); + const result = await artistService.getArtistFollowing(parseInt(id), { + restrict, + offset: parseInt(offset), + limit: parseInt(limit) + }); + + if (result.success) { + res.json({ + success: true, + data: result.data + }); + } else { + res.status(404).json({ + success: false, + error: result.error + }); + } + } catch (error) { + res.status(500).json({ + success: false, + error: error.message + }); + } +}); + +/** + * 获取作者粉丝列表 + * GET /api/artist/:id/followers + */ +router.get('/:id/followers', async (req, res) => { + try { + const { id } = req.params; + const { + offset = 0, + limit = 30 + } = req.query; + + if (!id || isNaN(parseInt(id))) { + return res.status(400).json({ + success: false, + error: 'Invalid artist ID' + }); + } + + const artistService = new ArtistService(req.backend.getAuth()); + const result = await artistService.getArtistFollowers(parseInt(id), { + offset: parseInt(offset), + limit: parseInt(limit) + }); + + if (result.success) { + res.json({ + success: true, + data: result.data + }); + } else { + res.status(404).json({ + success: false, + error: result.error + }); + } + } catch (error) { + res.status(500).json({ + success: false, + error: error.message + }); + } +}); + +/** + * 关注/取消关注作者 + * POST /api/artist/:id/follow + */ +router.post('/:id/follow', async (req, res) => { + try { + const { id } = req.params; + const { action = 'follow' } = req.body; + + if (!id || isNaN(parseInt(id))) { + return res.status(400).json({ + success: false, + error: 'Invalid artist ID' + }); + } + + if (!['follow', 'unfollow'].includes(action)) { + return res.status(400).json({ + success: false, + error: 'Invalid action. Must be "follow" or "unfollow"' + }); + } + + const artistService = new ArtistService(req.backend.getAuth()); + const result = await artistService.followArtist(parseInt(id), action); + + if (result.success) { + res.json({ + success: true, + message: `Artist ${action === 'follow' ? 'followed' : 'unfollowed'} successfully` + }); + } else { + res.status(400).json({ + success: false, + error: result.error + }); + } + } catch (error) { + res.status(500).json({ + success: false, + error: error.message + }); + } +}); + +module.exports = router; \ No newline at end of file diff --git a/backend/routes/artwork.js b/backend/routes/artwork.js new file mode 100644 index 0000000..a9ea801 --- /dev/null +++ b/backend/routes/artwork.js @@ -0,0 +1,172 @@ +const express = require('express'); +const router = express.Router(); +const ArtworkService = require('../services/artwork'); + +/** + * 搜索作品 + * GET /api/artwork/search + */ +router.get('/search', async (req, res) => { + try { + const { + keyword, + type = 'all', + sort = 'date_desc', + duration = 'all', + offset = 0, + limit = 30 + } = req.query; + + if (!keyword) { + return res.status(400).json({ + success: false, + error: 'Search keyword is required' + }); + } + + const artworkService = new ArtworkService(req.backend.getAuth()); + const result = await artworkService.searchArtworks({ + keyword, + type, + sort, + duration, + offset: parseInt(offset), + limit: parseInt(limit) + }); + + if (result.success) { + res.json({ + success: true, + data: result.data + }); + } else { + res.status(400).json({ + success: false, + error: result.error + }); + } + } catch (error) { + res.status(500).json({ + success: false, + error: error.message + }); + } +}); + +/** + * 获取作品详情 + * GET /api/artwork/:id + */ +router.get('/:id', async (req, res) => { + try { + const { id } = req.params; + const { include_user = 'true', include_series = 'false' } = req.query; + + if (!id || isNaN(parseInt(id))) { + return res.status(400).json({ + success: false, + error: 'Invalid artwork ID' + }); + } + + const artworkService = new ArtworkService(req.backend.getAuth()); + const result = await artworkService.getArtworkDetail(parseInt(id), { + include_user: include_user === 'true', + include_series: include_series === 'true' + }); + + if (result.success) { + res.json({ + success: true, + data: result.data + }); + } else { + res.status(404).json({ + success: false, + error: result.error + }); + } + } catch (error) { + res.status(500).json({ + success: false, + error: error.message + }); + } +}); + +/** + * 获取作品预览信息 + * GET /api/artwork/:id/preview + */ +router.get('/:id/preview', async (req, res) => { + try { + const { id } = req.params; + + if (!id || isNaN(parseInt(id))) { + return res.status(400).json({ + success: false, + error: 'Invalid artwork ID' + }); + } + + const artworkService = new ArtworkService(req.backend.getAuth()); + const result = await artworkService.getArtworkPreview(parseInt(id)); + + if (result.success) { + res.json({ + success: true, + data: result.data + }); + } else { + res.status(404).json({ + success: false, + error: result.error + }); + } + } catch (error) { + res.status(500).json({ + success: false, + error: error.message + }); + } +}); + +/** + * 获取作品图片URL + * GET /api/artwork/:id/images + */ +router.get('/:id/images', async (req, res) => { + try { + const { id } = req.params; + const { size = 'medium' } = req.query; + + if (!id || isNaN(parseInt(id))) { + return res.status(400).json({ + success: false, + error: 'Invalid artwork ID' + }); + } + + const artworkService = new ArtworkService(req.backend.getAuth()); + const result = await artworkService.getArtworkImages(parseInt(id), size); + + if (result.success) { + res.json({ + success: true, + data: result.data + }); + } else { + res.status(404).json({ + success: false, + error: result.error + }); + } + } catch (error) { + res.status(500).json({ + success: false, + error: error.message + }); + } +}); + +module.exports = router; \ No newline at end of file diff --git a/backend/routes/auth.js b/backend/routes/auth.js new file mode 100644 index 0000000..1961fc6 --- /dev/null +++ b/backend/routes/auth.js @@ -0,0 +1,124 @@ +const express = require('express'); +const router = express.Router(); + +/** + * 获取登录状态 + * GET /api/auth/status + */ +router.get('/status', (req, res) => { + try { + const status = req.backend.getLoginStatus(); + res.json({ + success: true, + data: status + }); + } catch (error) { + res.status(500).json({ + success: false, + error: error.message + }); + } +}); + +/** + * 获取登录URL + * GET /api/auth/login-url + */ +router.get('/login-url', (req, res) => { + try { + const loginData = req.backend.getLoginUrl(); + res.json({ + success: true, + data: loginData + }); + } catch (error) { + res.status(500).json({ + success: false, + error: error.message + }); + } +}); + +/** + * 处理登录回调 + * POST /api/auth/callback + */ +router.post('/callback', async (req, res) => { + try { + const { code } = req.body; + + if (!code) { + return res.status(400).json({ + success: false, + error: 'Authorization code is required' + }); + } + + const result = await req.backend.handleLoginCallback(code); + + if (result.success) { + res.json({ + success: true, + data: result + }); + } else { + res.status(400).json({ + success: false, + error: result.error + }); + } + } catch (error) { + res.status(500).json({ + success: false, + error: error.message + }); + } +}); + +/** + * 重新登录 + * POST /api/auth/relogin + */ +router.post('/relogin', async (req, res) => { + try { + const result = await req.backend.relogin(); + + if (result.success) { + res.json({ + success: true, + message: 'Relogin successful' + }); + } else { + res.status(400).json({ + success: false, + error: result.error + }); + } + } catch (error) { + res.status(500).json({ + success: false, + error: error.message + }); + } +}); + +/** + * 登出 + * POST /api/auth/logout + */ +router.post('/logout', (req, res) => { + try { + const result = req.backend.logout(); + res.json({ + success: true, + message: 'Logout successful' + }); + } catch (error) { + res.status(500).json({ + success: false, + error: error.message + }); + } +}); + +module.exports = router; \ No newline at end of file diff --git a/backend/routes/download.js b/backend/routes/download.js new file mode 100644 index 0000000..c9b0ee2 --- /dev/null +++ b/backend/routes/download.js @@ -0,0 +1,270 @@ +const express = require('express'); +const router = express.Router(); +const DownloadService = require('../services/download'); + +/** + * 下载单个作品 + * POST /api/download/artwork/:id + */ +router.post('/artwork/:id', async (req, res) => { + try { + const { id } = req.params; + const { + size = 'original', + quality = 'high', + format = 'auto' + } = req.body; + + if (!id || isNaN(parseInt(id))) { + return res.status(400).json({ + success: false, + error: 'Invalid artwork ID' + }); + } + + const downloadService = new DownloadService(req.backend.getAuth()); + const result = await downloadService.downloadArtwork(parseInt(id), { + size, + quality, + format + }); + + if (result.success) { + res.json({ + success: true, + data: result.data + }); + } else { + res.status(400).json({ + success: false, + error: result.error + }); + } + } catch (error) { + res.status(500).json({ + success: false, + error: error.message + }); + } +}); + +/** + * 批量下载作品 + * POST /api/download/artworks + */ +router.post('/artworks', async (req, res) => { + try { + const { + artworkIds, + size = 'original', + quality = 'high', + format = 'auto', + concurrent = 3 + } = req.body; + + if (!artworkIds || !Array.isArray(artworkIds) || artworkIds.length === 0) { + return res.status(400).json({ + success: false, + error: 'Artwork IDs array is required' + }); + } + + if (artworkIds.length > 50) { + return res.status(400).json({ + success: false, + error: 'Maximum 50 artworks can be downloaded at once' + }); + } + + const downloadService = new DownloadService(req.backend.getAuth()); + const result = await downloadService.downloadMultipleArtworks(artworkIds, { + size, + quality, + format, + concurrent: parseInt(concurrent) + }); + + if (result.success) { + res.json({ + success: true, + data: result.data + }); + } else { + res.status(400).json({ + success: false, + error: result.error + }); + } + } catch (error) { + res.status(500).json({ + success: false, + error: error.message + }); + } +}); + +/** + * 下载作者作品 + * POST /api/download/artist/:id + */ +router.post('/artist/:id', async (req, res) => { + try { + const { id } = req.params; + const { + type = 'art', + filter = 'for_ios', + size = 'original', + quality = 'high', + format = 'auto', + limit = 50, + concurrent = 3 + } = req.body; + + if (!id || isNaN(parseInt(id))) { + return res.status(400).json({ + success: false, + error: 'Invalid artist ID' + }); + } + + const downloadService = new DownloadService(req.backend.getAuth()); + const result = await downloadService.downloadArtistArtworks(parseInt(id), { + type, + filter, + size, + quality, + format, + limit: parseInt(limit), + concurrent: parseInt(concurrent) + }); + + if (result.success) { + res.json({ + success: true, + data: result.data + }); + } else { + res.status(400).json({ + success: false, + error: result.error + }); + } + } catch (error) { + res.status(500).json({ + success: false, + error: error.message + }); + } +}); + +/** + * 获取下载进度 + * GET /api/download/progress/:taskId + */ +router.get('/progress/:taskId', async (req, res) => { + try { + const { taskId } = req.params; + + if (!taskId) { + return res.status(400).json({ + success: false, + error: 'Task ID is required' + }); + } + + const downloadService = new DownloadService(req.backend.getAuth()); + const result = await downloadService.getDownloadProgress(taskId); + + if (result.success) { + res.json({ + success: true, + data: result.data + }); + } else { + res.status(404).json({ + success: false, + error: result.error + }); + } + } catch (error) { + res.status(500).json({ + success: false, + error: error.message + }); + } +}); + +/** + * 取消下载任务 + * DELETE /api/download/cancel/:taskId + */ +router.delete('/cancel/:taskId', async (req, res) => { + try { + const { taskId } = req.params; + + if (!taskId) { + return res.status(400).json({ + success: false, + error: 'Task ID is required' + }); + } + + const downloadService = new DownloadService(req.backend.getAuth()); + const result = await downloadService.cancelDownload(taskId); + + if (result.success) { + res.json({ + success: true, + message: 'Download task cancelled successfully' + }); + } else { + res.status(400).json({ + success: false, + error: result.error + }); + } + } catch (error) { + res.status(500).json({ + success: false, + error: error.message + }); + } +}); + +/** + * 获取下载历史 + * GET /api/download/history + */ +router.get('/history', async (req, res) => { + try { + const { + offset = 0, + limit = 20 + } = req.query; + + const downloadService = new DownloadService(req.backend.getAuth()); + const result = await downloadService.getDownloadHistory({ + offset: parseInt(offset), + limit: parseInt(limit) + }); + + if (result.success) { + res.json({ + success: true, + data: result.data + }); + } else { + res.status(400).json({ + success: false, + error: result.error + }); + } + } catch (error) { + res.status(500).json({ + success: false, + error: error.message + }); + } +}); + +module.exports = router; \ No newline at end of file diff --git a/backend/server.js b/backend/server.js new file mode 100644 index 0000000..7d0b6ef --- /dev/null +++ b/backend/server.js @@ -0,0 +1,155 @@ +const express = require('express'); +const cors = require('cors'); +const morgan = require('morgan'); +const path = require('path'); + +// 导入路由模块 - 临时注释掉来定位问题 +const authRoutes = require('./routes/auth'); +const artworkRoutes = require('./routes/artwork'); +const artistRoutes = require('./routes/artist'); +const downloadRoutes = require('./routes/download'); + +// 导入中间件 - 临时注释掉来定位问题 +const { errorHandler } = require('./middleware/errorHandler'); +const { authMiddleware } = require('./middleware/auth'); + +// 导入核心模块 +const PixivBackend = require('./core'); +const proxyConfig = require('./config'); + +class PixivServer { + constructor() { + this.app = express(); + this.backend = null; + this.port = process.env.PORT || 3000; + } + + /** + * 初始化服务器 + */ + async init() { + console.log('正在初始化 Pixiv 后端服务器...'); + + // 设置代理 + proxyConfig.setEnvironmentVariables(); + + // 初始化 Pixiv 后端 + this.backend = new PixivBackend(); + await this.backend.init(); + + // 配置中间件 + this.setupMiddleware(); + + // 配置路由 + this.setupRoutes(); + + // 配置错误处理 - 临时注释掉 + this.setupErrorHandling(); + + console.log('服务器初始化完成'); + } + + /** + * 配置中间件 + */ + setupMiddleware() { + // 日志中间件 + this.app.use(morgan('combined')); + + // CORS 中间件 + this.app.use(cors({ + origin: process.env.FRONTEND_URL || 'http://localhost:3001', + credentials: true + })); + + // JSON 解析中间件 + this.app.use(express.json({ limit: '10mb' })); + this.app.use(express.urlencoded({ extended: true, limit: '10mb' })); + + // 静态文件服务 - 临时注释掉 + this.app.use('/downloads', express.static(path.join(__dirname, '../downloads'))); + + // 将后端实例注入到请求对象中 + this.app.use((req, res, next) => { + req.backend = this.backend; + next(); + }); + } + + /** + * 配置路由 + */ + setupRoutes() { + // 健康检查 + this.app.get('/health', (req, res) => { + res.json({ + status: 'ok', + timestamp: new Date().toISOString(), + backend: { + isLoggedIn: req.backend.isLoggedIn, + user: req.backend.config.user?.account + } + }); + }); + + // API 路由 - 临时注释掉来定位问题 + this.app.use('/api/auth', authRoutes); + this.app.use('/api/artwork', authMiddleware, artworkRoutes); + this.app.use('/api/artist', authMiddleware, artistRoutes); + this.app.use('/api/download', authMiddleware, downloadRoutes); + + // 404 处理 + this.app.use((req, res) => { + res.status(404).json({ + error: 'Not Found', + message: `Route ${req.originalUrl} not found` + }); + }); + } + + /** + * 配置错误处理 + */ + setupErrorHandling() { + this.app.use(errorHandler); + } + + /** + * 启动服务器 + */ + start() { + this.app.listen(this.port, () => { + console.log(`🚀 Pixiv 后端服务器已启动`); + console.log(`📍 地址: http://localhost:${this.port}`); + console.log(`🔗 健康检查: http://localhost:${this.port}/health`); + console.log(`📊 登录状态: ${this.backend.isLoggedIn ? '已登录' : '未登录'}`); + if (this.backend.isLoggedIn) { + console.log(`👤 用户: ${this.backend.config.user?.account}`); + } + }); + } + + /** + * 优雅关闭 + */ + async shutdown() { + console.log('正在关闭服务器...'); + // 清理代理环境变量 + proxyConfig.clearEnvironmentVariables(); + process.exit(0); + } +} + +// 如果直接运行此文件 +if (require.main === module) { + const server = new PixivServer(); + + // 处理进程信号 + process.on('SIGINT', () => server.shutdown()); + process.on('SIGTERM', () => server.shutdown()); + + // 启动服务器 + server.init().then(() => server.start()).catch(console.error); +} + +module.exports = PixivServer; \ No newline at end of file diff --git a/backend/services/artist.js b/backend/services/artist.js new file mode 100644 index 0000000..6ff055a --- /dev/null +++ b/backend/services/artist.js @@ -0,0 +1,344 @@ +const axios = require('axios'); +const { stringify } = require('qs'); + +class ArtistService { + constructor(auth) { + this.auth = auth; + this.baseURL = 'https://app-api.pixiv.net'; + } + + /** + * 获取作者信息 + */ + async getArtistInfo(artistId) { + try { + const response = await this.makeRequest( + 'GET', + '/v1/user/detail', + { user_id: artistId } + ); + + return { + success: true, + data: response.user + }; + + } catch (error) { + return { + success: false, + error: error.message + }; + } + } + + /** + * 获取作者作品列表 + */ + async getArtistArtworks(artistId, options = {}) { + try { + const { + type = 'art', + filter = 'for_ios', + offset = 0, + limit = 30 + } = options; + + const params = { + user_id: artistId, + type, + filter, + offset + }; + + const response = await this.makeRequest( + 'GET', + `/v1/user/illusts?${stringify(params)}` + ); + + return { + success: true, + data: { + artworks: response.illusts, + next_url: response.next_url, + total: response.illusts.length + } + }; + + } catch (error) { + return { + success: false, + error: error.message + }; + } + } + + /** + * 获取作者关注列表 + */ + async getArtistFollowing(artistId, options = {}) { + try { + const { + restrict = 'public', + offset = 0, + limit = 30 + } = options; + + const params = { + user_id: artistId, + restrict, + offset + }; + + const response = await this.makeRequest( + 'GET', + `/v1/user/following?${stringify(params)}` + ); + + return { + success: true, + data: { + users: response.user_previews, + next_url: response.next_url, + total: response.user_previews.length + } + }; + + } catch (error) { + return { + success: false, + error: error.message + }; + } + } + + /** + * 获取作者粉丝列表 + */ + async getArtistFollowers(artistId, options = {}) { + try { + const { + offset = 0, + limit = 30 + } = options; + + const params = { + user_id: artistId, + offset + }; + + const response = await this.makeRequest( + 'GET', + `/v1/user/follower?${stringify(params)}` + ); + + return { + success: true, + data: { + users: response.user_previews, + next_url: response.next_url, + total: response.user_previews.length + } + }; + + } catch (error) { + return { + success: false, + error: error.message + }; + } + } + + /** + * 关注/取消关注作者 + */ + async followArtist(artistId, action = 'follow') { + try { + const data = { + user_id: artistId, + restrict: 'public' + }; + + const endpoint = action === 'follow' ? '/v1/user/follow/add' : '/v1/user/follow/delete'; + + const response = await this.makeRequest( + 'POST', + endpoint, + data + ); + + return { + success: true, + data: response + }; + + } catch (error) { + return { + success: false, + error: error.message + }; + } + } + + /** + * 搜索作者 + */ + async searchArtists(searchOptions) { + try { + const { + keyword, + sort = 'date_desc', + duration = 'all', + offset = 0, + limit = 30 + } = searchOptions; + + const params = { + word: keyword, + sort, + duration, + offset, + filter: 'for_ios' + }; + + const response = await this.makeRequest( + 'GET', + `/v1/search/user?${stringify(params)}` + ); + + return { + success: true, + data: { + users: response.user_previews, + next_url: response.next_url, + search_span_limit: response.search_span_limit, + total: response.user_previews.length + } + }; + + } catch (error) { + return { + success: false, + error: error.message + }; + } + } + + /** + * 获取推荐作者 + */ + async getRecommendedArtists(options = {}) { + try { + const { + offset = 0, + limit = 30 + } = options; + + const params = { + offset, + filter: 'for_ios' + }; + + const response = await this.makeRequest( + 'GET', + `/v1/user/recommended?${stringify(params)}` + ); + + return { + success: true, + data: { + users: response.user_previews, + next_url: response.next_url + } + }; + + } catch (error) { + return { + success: false, + error: error.message + }; + } + } + + /** + * 获取作者统计信息 + */ + async getArtistStats(artistId) { + try { + const response = await this.makeRequest( + 'GET', + '/v1/user/detail', + { user_id: artistId } + ); + + const user = response.user; + + const stats = { + user_id: user.id, + total_illusts: user.total_illusts, + total_manga: user.total_manga, + total_novels: user.total_novels, + total_bookmarked_illust: user.total_bookmarked_illust, + total_following: user.total_following, + total_followers: user.total_followers, + total_illust_bookmarks_public: user.total_illust_bookmarks_public, + total_illust_series: user.total_illust_series, + total_novel_series: user.total_novel_series, + background: user.background, + twitter_account: user.twitter_account, + twitter_url: user.twitter_url, + pawoo_url: user.pawoo_url, + is_followed: user.is_followed, + is_following: user.is_following, + is_friend: user.is_friend, + is_blocking: user.is_blocking, + is_blocked: user.is_blocked, + accept_request: user.accept_request + }; + + return { + success: true, + data: stats + }; + + } catch (error) { + return { + success: false, + error: error.message + }; + } + } + + /** + * 发送API请求 + */ + async makeRequest(method, endpoint, data = null) { + const headers = { + 'Authorization': `Bearer ${this.auth.accessToken}`, + 'Accept-Language': 'en-us', + 'App-OS': 'android', + 'App-OS-Version': '9.0', + 'App-Version': '5.0.234', + 'User-Agent': 'PixivAndroidApp/5.0.234 (Android 9.0; Pixel 3)' + }; + + const config = { + method, + url: `${this.baseURL}${endpoint}`, + headers, + timeout: 30000 + }; + + if (data) { + if (method === 'GET') { + config.params = data; + } else { + config.data = data; + } + } + + const response = await axios(config); + return response.data; + } +} + +module.exports = ArtistService; \ No newline at end of file diff --git a/backend/services/artwork.js b/backend/services/artwork.js new file mode 100644 index 0000000..fae13bc --- /dev/null +++ b/backend/services/artwork.js @@ -0,0 +1,307 @@ +const axios = require('axios'); +const { stringify } = require('qs'); + +class ArtworkService { + constructor(auth) { + this.auth = auth; + this.baseURL = 'https://app-api.pixiv.net'; + } + + /** + * 获取作品详情 + */ + async getArtworkDetail(artworkId, options = {}) { + try { + const { include_user = true, include_series = false } = options; + + const params = { + include_user, + include_series + }; + + const response = await this.makeRequest( + 'GET', + `/v1/illust/detail?${stringify(params)}`, + { illust_id: artworkId } + ); + + return { + success: true, + data: response.illust + }; + + } catch (error) { + return { + success: false, + error: error.message + }; + } + } + + /** + * 获取作品预览信息 + */ + async getArtworkPreview(artworkId) { + try { + const response = await this.makeRequest( + 'GET', + '/v1/illust/detail', + { illust_id: artworkId } + ); + + const artwork = response.illust; + + // 构建预览信息 + const preview = { + id: artwork.id, + title: artwork.title, + description: artwork.caption, + user: { + id: artwork.user.id, + name: artwork.user.name, + account: artwork.user.account + }, + image_urls: artwork.image_urls, + tags: artwork.tags.map(tag => tag.name), + create_date: artwork.create_date, + update_date: artwork.update_date, + type: artwork.type, + width: artwork.width, + height: artwork.height, + page_count: artwork.page_count, + is_bookmarked: artwork.is_bookmarked, + total_bookmarks: artwork.total_bookmarks, + total_view: artwork.total_view, + is_muted: artwork.is_muted, + meta_single_page: artwork.meta_single_page, + meta_pages: artwork.meta_pages + }; + + return { + success: true, + data: preview + }; + + } catch (error) { + return { + success: false, + error: error.message + }; + } + } + + /** + * 获取作品图片URL + */ + async getArtworkImages(artworkId, size = 'medium') { + try { + const response = await this.makeRequest( + 'GET', + '/v1/illust/detail', + { illust_id: artworkId } + ); + + const artwork = response.illust; + const images = []; + + if (artwork.meta_single_page && artwork.meta_single_page.original_image_url) { + // 单页作品 + images.push({ + page: 1, + original: artwork.meta_single_page.original_image_url, + large: artwork.meta_single_page.large_image_url, + medium: artwork.image_urls.medium, + square_medium: artwork.image_urls.square_medium + }); + } else if (artwork.meta_pages && artwork.meta_pages.length > 0) { + // 多页作品 + artwork.meta_pages.forEach((page, index) => { + images.push({ + page: index + 1, + original: page.image_urls.original, + large: page.image_urls.large, + medium: page.image_urls.medium, + square_medium: page.image_urls.square_medium + }); + }); + } + + return { + success: true, + data: { + artwork_id: artworkId, + total_pages: artwork.page_count, + images: images, + selected_size: size + } + }; + + } catch (error) { + return { + success: false, + error: error.message + }; + } + } + + /** + * 搜索作品 + */ + async searchArtworks(searchOptions) { + try { + const { + keyword, + type = 'all', + sort = 'date_desc', + duration = 'all', + offset = 0, + limit = 30 + } = searchOptions; + + const params = { + word: keyword, + search_target: type, + sort: sort, + duration: duration, + offset, + filter: 'for_ios' + }; + + const response = await this.makeRequest( + 'GET', + `/v1/search/illust?${stringify(params)}` + ); + + return { + success: true, + data: { + artworks: response.illusts, + next_url: response.next_url, + search_span_limit: response.search_span_limit, + total: response.illusts.length + } + }; + + } catch (error) { + return { + success: false, + error: error.message + }; + } + } + + /** + * 获取推荐作品 + */ + async getRecommendedArtworks(options = {}) { + try { + const { + offset = 0, + limit = 30, + include_ranking_illusts = true, + include_privacy_policy = false + } = options; + + const params = { + offset, + include_ranking_illusts, + include_privacy_policy, + filter: 'for_ios' + }; + + const response = await this.makeRequest( + 'GET', + `/v1/illust/recommended?${stringify(params)}` + ); + + return { + success: true, + data: { + artworks: response.illusts, + next_url: response.next_url, + ranking_illusts: response.ranking_illusts || [] + } + }; + + } catch (error) { + return { + success: false, + error: error.message + }; + } + } + + /** + * 获取排行榜作品 + */ + async getRankingArtworks(options = {}) { + try { + const { + mode = 'day', + filter = 'for_ios', + offset = 0 + } = options; + + const params = { + mode, + filter, + offset + }; + + const response = await this.makeRequest( + 'GET', + `/v1/illust/ranking?${stringify(params)}` + ); + + return { + success: true, + data: { + artworks: response.illusts, + next_url: response.next_url, + mode, + date: response.date + } + }; + + } catch (error) { + return { + success: false, + error: error.message + }; + } + } + + /** + * 发送API请求 + */ + async makeRequest(method, endpoint, data = null) { + const headers = { + 'Authorization': `Bearer ${this.auth.accessToken}`, + 'Accept-Language': 'en-us', + 'App-OS': 'android', + 'App-OS-Version': '9.0', + 'App-Version': '5.0.234', + 'User-Agent': 'PixivAndroidApp/5.0.234 (Android 9.0; Pixel 3)' + }; + + const config = { + method, + url: `${this.baseURL}${endpoint}`, + headers, + timeout: 30000 + }; + + if (data) { + if (method === 'GET') { + config.params = data; + } else { + config.data = data; + } + } + + const response = await axios(config); + return response.data; + } +} + +module.exports = ArtworkService; \ No newline at end of file diff --git a/backend/services/download.js b/backend/services/download.js new file mode 100644 index 0000000..2171125 --- /dev/null +++ b/backend/services/download.js @@ -0,0 +1,444 @@ +const axios = require('axios'); +const fs = require('fs-extra'); +const path = require('path'); +const { v4: uuidv4 } = require('uuid'); +const ArtworkService = require('./artwork'); +const ArtistService = require('./artist'); + +class DownloadService { + constructor(auth) { + this.auth = auth; + this.artworkService = new ArtworkService(auth); + this.artistService = new ArtistService(auth); + this.downloadPath = path.join(__dirname, '../../downloads'); + this.tasks = new Map(); // 存储下载任务状态 + + // 确保下载目录存在 + this.ensureDownloadDir(); + } + + /** + * 确保下载目录存在 + */ + async ensureDownloadDir() { + try { + await fs.ensureDir(this.downloadPath); + console.log('下载目录已创建:', this.downloadPath); + } catch (error) { + console.error('创建下载目录失败:', error); + } + } + + /** + * 下载单个作品 + */ + async downloadArtwork(artworkId, options = {}) { + const taskId = uuidv4(); + const { size = 'original', quality = 'high', format = 'auto' } = options; + + try { + // 创建任务记录 + this.tasks.set(taskId, { + id: taskId, + type: 'artwork', + artwork_id: artworkId, + status: 'downloading', + progress: 0, + total: 1, + completed: 0, + failed: 0, + files: [], + start_time: new Date(), + end_time: null + }); + + // 获取作品信息 + const artworkResult = await this.artworkService.getArtworkDetail(artworkId); + if (!artworkResult.success) { + throw new Error(`获取作品信息失败: ${artworkResult.error}`); + } + + const artwork = artworkResult.data; + const artistName = artwork.user.name.replace(/[<>:"/\\|?*]/g, '_'); + const artworkTitle = artwork.title.replace(/[<>:"/\\|?*]/g, '_'); + + // 创建作品目录 + const artworkDir = path.join(this.downloadPath, `${artistName}_${artworkId}`, artworkTitle); + await fs.ensureDir(artworkDir); + + // 获取图片URL + const imagesResult = await this.artworkService.getArtworkImages(artworkId, size); + if (!imagesResult.success) { + throw new Error(`获取图片URL失败: ${imagesResult.error}`); + } + + const images = imagesResult.data.images; + const task = this.tasks.get(taskId); + task.total = images.length; + + // 下载所有图片 + const downloadPromises = images.map(async (image, index) => { + try { + const imageUrl = image[size] || image.original; + const fileExt = this.getFileExtension(imageUrl); + const fileName = `${artworkTitle}_${artworkId}_${index + 1}${fileExt}`; + const filePath = path.join(artworkDir, fileName); + + await this.downloadFile(imageUrl, filePath); + + task.completed++; + task.progress = Math.round((task.completed / task.total) * 100); + task.files.push({ + path: filePath, + url: imageUrl, + size: size + }); + + return { success: true, file: fileName }; + } catch (error) { + task.failed++; + console.error(`下载图片失败 ${index + 1}:`, error.message); + return { success: false, error: error.message }; + } + }); + + await Promise.all(downloadPromises); + + // 保存作品信息 + const infoPath = path.join(artworkDir, 'artwork_info.json'); + await fs.writeJson(infoPath, artwork, { spaces: 2 }); + + // 更新任务状态 + task.status = task.failed === 0 ? 'completed' : 'partial'; + task.end_time = new Date(); + + return { + success: true, + data: { + task_id: taskId, + artwork_id: artworkId, + artist_name: artistName, + artwork_title: artworkTitle, + download_path: artworkDir, + total_files: task.total, + completed_files: task.completed, + failed_files: task.failed, + files: task.files + } + }; + + } catch (error) { + const task = this.tasks.get(taskId); + if (task) { + task.status = 'failed'; + task.end_time = new Date(); + } + + return { + success: false, + error: error.message + }; + } + } + + /** + * 批量下载作品 + */ + async downloadMultipleArtworks(artworkIds, options = {}) { + const taskId = uuidv4(); + const { concurrent = 3, size = 'original', quality = 'high', format = 'auto' } = options; + + try { + // 创建任务记录 + this.tasks.set(taskId, { + id: taskId, + type: 'batch', + artwork_ids: artworkIds, + status: 'downloading', + progress: 0, + total: artworkIds.length, + completed: 0, + failed: 0, + results: [], + start_time: new Date(), + end_time: null + }); + + const task = this.tasks.get(taskId); + const results = []; + + // 分批下载 + for (let i = 0; i < artworkIds.length; i += concurrent) { + const batch = artworkIds.slice(i, i + concurrent); + const batchPromises = batch.map(async (artworkId) => { + try { + const result = await this.downloadArtwork(artworkId, { size, quality, format }); + task.completed++; + results.push({ artwork_id: artworkId, ...result }); + return result; + } catch (error) { + task.failed++; + results.push({ artwork_id: artworkId, success: false, error: error.message }); + return { success: false, error: error.message }; + } + }); + + await Promise.all(batchPromises); + task.progress = Math.round((task.completed / task.total) * 100); + } + + // 更新任务状态 + task.status = task.failed === 0 ? 'completed' : 'partial'; + task.end_time = new Date(); + task.results = results; + + return { + success: true, + data: { + task_id: taskId, + total_artworks: task.total, + completed_artworks: task.completed, + failed_artworks: task.failed, + results: results + } + }; + + } catch (error) { + const task = this.tasks.get(taskId); + if (task) { + task.status = 'failed'; + task.end_time = new Date(); + } + + return { + success: false, + error: error.message + }; + } + } + + /** + * 下载作者作品 + */ + async downloadArtistArtworks(artistId, options = {}) { + const taskId = uuidv4(); + const { + type = 'art', + filter = 'for_ios', + size = 'original', + quality = 'high', + format = 'auto', + limit = 50, + concurrent = 3 + } = options; + + try { + // 获取作者信息 + const artistResult = await this.artistService.getArtistInfo(artistId); + if (!artistResult.success) { + throw new Error(`获取作者信息失败: ${artistResult.error}`); + } + + const artist = artistResult.data; + const artistName = artist.name.replace(/[<>:"/\\|?*]/g, '_'); + + // 获取作者作品列表 + const artworksResult = await this.artistService.getArtistArtworks(artistId, { + type, + filter, + limit + }); + + if (!artworksResult.success) { + throw new Error(`获取作者作品列表失败: ${artworksResult.error}`); + } + + const artworks = artworksResult.data.artworks; + const artworkIds = artworks.map(artwork => artwork.id); + + // 创建任务记录 + this.tasks.set(taskId, { + id: taskId, + type: 'artist', + artist_id: artistId, + artist_name: artistName, + status: 'downloading', + progress: 0, + total: artworkIds.length, + completed: 0, + failed: 0, + results: [], + start_time: new Date(), + end_time: null + }); + + // 批量下载作品 + const batchResult = await this.downloadMultipleArtworks(artworkIds, { + concurrent, + size, + quality, + format + }); + + if (batchResult.success) { + const task = this.tasks.get(taskId); + task.status = batchResult.data.failed_artworks === 0 ? 'completed' : 'partial'; + task.end_time = new Date(); + task.results = batchResult.data.results; + + return { + success: true, + data: { + task_id: taskId, + artist_id: artistId, + artist_name: artistName, + total_artworks: batchResult.data.total_artworks, + completed_artworks: batchResult.data.completed_artworks, + failed_artworks: batchResult.data.failed_artworks, + results: batchResult.data.results + } + }; + } else { + throw new Error(batchResult.error); + } + + } catch (error) { + const task = this.tasks.get(taskId); + if (task) { + task.status = 'failed'; + task.end_time = new Date(); + } + + return { + success: false, + error: error.message + }; + } + } + + /** + * 获取下载进度 + */ + async getDownloadProgress(taskId) { + const task = this.tasks.get(taskId); + + if (!task) { + return { + success: false, + error: 'Task not found' + }; + } + + return { + success: true, + data: { + id: task.id, + type: task.type, + status: task.status, + progress: task.progress, + total: task.total, + completed: task.completed, + failed: task.failed, + start_time: task.start_time, + end_time: task.end_time, + files: task.files || [], + results: task.results || [] + } + }; + } + + /** + * 取消下载任务 + */ + async cancelDownload(taskId) { + const task = this.tasks.get(taskId); + + if (!task) { + return { + success: false, + error: 'Task not found' + }; + } + + if (task.status === 'completed' || task.status === 'failed') { + return { + success: false, + error: 'Task already finished' + }; + } + + task.status = 'cancelled'; + task.end_time = new Date(); + + return { + success: true, + message: 'Download task cancelled successfully' + }; + } + + /** + * 获取下载历史 + */ + async getDownloadHistory(options = {}) { + const { offset = 0, limit = 20 } = options; + + try { + const tasks = Array.from(this.tasks.values()) + .filter(task => task.status === 'completed' || task.status === 'partial') + .sort((a, b) => b.end_time - a.end_time) + .slice(offset, offset + limit); + + return { + success: true, + data: { + tasks: tasks, + total: this.tasks.size, + offset, + limit + } + }; + + } catch (error) { + return { + success: false, + error: error.message + }; + } + } + + /** + * 下载单个文件 + */ + async downloadFile(url, filePath) { + const headers = { + 'Referer': 'https://app-api.pixiv.net/', + 'User-Agent': 'PixivAndroidApp/5.0.234 (Android 9.0; Pixel 3)' + }; + + const response = await axios({ + method: 'GET', + url: url, + headers, + responseType: 'stream', + timeout: 60000 + }); + + const writer = fs.createWriteStream(filePath); + response.data.pipe(writer); + + return new Promise((resolve, reject) => { + writer.on('finish', resolve); + writer.on('error', reject); + }); + } + + /** + * 获取文件扩展名 + */ + getFileExtension(url) { + const match = url.match(/\.([a-zA-Z0-9]+)(?:\?|$)/); + return match ? `.${match[1]}` : '.jpg'; + } +} + +module.exports = DownloadService; \ No newline at end of file diff --git a/backend/start.js b/backend/start.js new file mode 100644 index 0000000..2fa1f73 --- /dev/null +++ b/backend/start.js @@ -0,0 +1,47 @@ +#!/usr/bin/env node + +/** + * Pixiv 后端服务器启动脚本 + */ + +const PixivServer = require('./server'); + +// 设置环境变量 +process.env.NODE_ENV = process.env.NODE_ENV || 'development'; + +console.log('🚀 启动 Pixiv 后端服务器...'); +console.log(`📊 环境: ${process.env.NODE_ENV}`); +console.log(`🌐 端口: ${process.env.PORT || 3000}`); + +// 创建服务器实例 +const server = new PixivServer(); + +// 处理进程信号 +process.on('SIGINT', async () => { + console.log('\n🛑 收到 SIGINT 信号,正在关闭服务器...'); + await server.shutdown(); +}); + +process.on('SIGTERM', async () => { + console.log('\n🛑 收到 SIGTERM 信号,正在关闭服务器...'); + await server.shutdown(); +}); + +// 处理未捕获的异常 +process.on('uncaughtException', (error) => { + console.error('❌ 未捕获的异常:', error); + process.exit(1); +}); + +process.on('unhandledRejection', (reason, promise) => { + console.error('❌ 未处理的 Promise 拒绝:', reason); + process.exit(1); +}); + +// 启动服务器 +server.init() + .then(() => server.start()) + .catch((error) => { + console.error('❌ 服务器启动失败:', error); + process.exit(1); + }); \ No newline at end of file diff --git a/backend/test-login.js b/backend/test-login.js new file mode 100644 index 0000000..2285277 --- /dev/null +++ b/backend/test-login.js @@ -0,0 +1,198 @@ +const PixivBackend = require('./core'); +const proxyConfig = require('./config'); +const readline = require('readline'); + +// 创建命令行交互接口 +const rl = readline.createInterface({ + input: process.stdin, + output: process.stdout +}); + +// 询问用户输入 +function askQuestion(question) { + return new Promise((resolve) => { + rl.question(question, (answer) => { + resolve(answer); + }); + }); +} + +// 测试登录流程 +async function testLogin() { + console.log('=== Pixiv 登录测试脚本 ===\n'); + + try { + // 1. 设置代理环境变量 + console.log('1. 设置代理配置...'); + proxyConfig.setEnvironmentVariables(); + + // 2. 初始化后端 + console.log('\n2. 初始化 Pixiv 后端...'); + const backend = new PixivBackend(); + await backend.init(); + + // 3. 检查登录状态 + console.log('\n3. 检查当前登录状态...'); + const loginStatus = backend.getLoginStatus(); + console.log('登录状态:', loginStatus); + + if (loginStatus.isLoggedIn) { + console.log('✅ 已登录,用户:', loginStatus.username); + return; + } + + // 4. 获取登录URL + console.log('\n4. 获取登录URL...'); + const loginData = backend.getLoginUrl(); + console.log('请访问以下URL进行登录:'); + console.log(loginData.login_url); + console.log('\n登录完成后,请复制回调URL中的code参数'); + + // 5. 等待用户输入授权码 + const code = await askQuestion('\n请输入授权码 (code参数): '); + + if (!code || code.trim() === '') { + console.log('❌ 未输入授权码,测试终止'); + return; + } + + // 6. 处理登录回调 + console.log('\n5. 处理登录回调...'); + const loginResult = await backend.handleLoginCallback(code.trim()); + + if (loginResult.success) { + console.log('✅ 登录成功!'); + console.log('用户信息:', loginResult.user); + + // 7. 再次检查登录状态 + console.log('\n6. 验证登录状态...'); + const finalStatus = backend.getLoginStatus(); + console.log('最终登录状态:', finalStatus); + + // 8. 测试获取用户信息 + console.log('\n7. 测试获取用户信息...'); + const auth = backend.getAuth(); + const userInfo = await auth.getUserInfo(); + + if (userInfo.success) { + console.log('✅ 获取用户信息成功:', userInfo.user); + } else { + console.log('❌ 获取用户信息失败:', userInfo.error); + } + + } else { + console.log('❌ 登录失败:', loginResult.error); + } + + } catch (error) { + console.error('❌ 测试过程中发生错误:', error.message); + console.error('错误详情:', error); + } finally { + // 清理资源 + rl.close(); + console.log('\n=== 测试完成 ==='); + } +} + +// 测试重新登录功能 +async function testRelogin() { + console.log('=== 测试重新登录功能 ===\n'); + + try { + // 设置代理 + proxyConfig.setEnvironmentVariables(); + + // 初始化后端 + const backend = new PixivBackend(); + await backend.init(); + + // 检查是否有保存的登录信息 + const loginStatus = backend.getLoginStatus(); + + if (loginStatus.isLoggedIn) { + console.log('✅ 检测到已保存的登录信息'); + console.log('用户:', loginStatus.username); + console.log('用户ID:', loginStatus.user_id); + } else { + console.log('❌ 没有保存的登录信息,无法测试重新登录'); + } + + } catch (error) { + console.error('❌ 重新登录测试失败:', error.message); + } +} + +// 测试登出功能 +async function testLogout() { + console.log('=== 测试登出功能 ===\n'); + + try { + // 设置代理 + proxyConfig.setEnvironmentVariables(); + + // 初始化后端 + const backend = new PixivBackend(); + await backend.init(); + + // 执行登出 + const logoutResult = backend.logout(); + + if (logoutResult.success) { + console.log('✅ 登出成功'); + + // 验证登出状态 + const loginStatus = backend.getLoginStatus(); + console.log('登出后状态:', loginStatus); + } else { + console.log('❌ 登出失败'); + } + + } catch (error) { + console.error('❌ 登出测试失败:', error.message); + } +} + +// 主函数 +async function main() { + console.log('请选择测试功能:'); + console.log('1. 测试完整登录流程'); + console.log('2. 测试重新登录'); + console.log('3. 测试登出'); + console.log('4. 运行所有测试'); + + const choice = await askQuestion('\n请输入选择 (1-4): '); + + switch (choice.trim()) { + case '1': + await testLogin(); + break; + case '2': + await testRelogin(); + break; + case '3': + await testLogout(); + break; + case '4': + console.log('\n=== 运行所有测试 ===\n'); + await testLogin(); + console.log('\n' + '='.repeat(50) + '\n'); + await testRelogin(); + console.log('\n' + '='.repeat(50) + '\n'); + await testLogout(); + break; + default: + console.log('❌ 无效选择'); + rl.close(); + } +} + +// 如果直接运行此脚本 +if (require.main === module) { + main().catch(console.error); +} + +module.exports = { + testLogin, + testRelogin, + testLogout +}; \ No newline at end of file diff --git a/backend/utils/response.js b/backend/utils/response.js new file mode 100644 index 0000000..b365a59 --- /dev/null +++ b/backend/utils/response.js @@ -0,0 +1,60 @@ +/** + * 统一API响应格式工具类 + */ +class ResponseUtil { + /** + * 成功响应 + */ + static success(data = null, message = 'Success') { + return { + success: true, + message, + data, + timestamp: new Date().toISOString() + }; + } + + /** + * 错误响应 + */ + static error(message = 'Error', code = null, details = null) { + return { + success: false, + message, + code, + details, + timestamp: new Date().toISOString() + }; + } + + /** + * 分页响应 + */ + static paginated(data, page, limit, total) { + return { + success: true, + data, + pagination: { + page: parseInt(page), + limit: parseInt(limit), + total, + pages: Math.ceil(total / limit) + }, + timestamp: new Date().toISOString() + }; + } + + /** + * 列表响应 + */ + static list(data, total = null) { + return { + success: true, + data, + total: total || (Array.isArray(data) ? data.length : 0), + timestamp: new Date().toISOString() + }; + } +} + +module.exports = ResponseUtil; \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000..1482389 --- /dev/null +++ b/package.json @@ -0,0 +1,41 @@ +{ + "name": "pixiv-backend", + "version": "1.0.0", + "description": "Pixiv 后端服务 - 支持 OAuth 2.0 登录和 API 调用", + "main": "backend/start.js", + "scripts": { + "start": "node backend/start.js", + "dev": "node backend/start.js", + "test": "node backend/test-login.js", + "set-proxy": "node backend/set-proxy.js" + }, + "dependencies": { + "appdata-path": "^1.0.0", + "axios": "^1.11.0", + "cors": "^2.8.5", + "crypto": "^1.0.1", + "express": "^5.1.0", + "fs-extra": "^11.3.1", + "js-base64": "^3.7.8", + "moment": "^2.30.1", + "morgan": "^1.10.1", + "proxy-agent": "^6.5.0", + "qs": "^6.14.0", + "uuid": "^11.1.0" + }, + "devDependencies": { + "readline-sync": "^1.4.10" + }, + "keywords": [ + "pixiv", + "oauth", + "api", + "backend", + "download" + ], + "author": "Your Name", + "license": "MIT", + "engines": { + "node": ">=16.0.0" + } +} \ No newline at end of file diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml new file mode 100644 index 0000000..d73db40 --- /dev/null +++ b/pnpm-lock.yaml @@ -0,0 +1,1047 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + appdata-path: + specifier: ^1.0.0 + version: 1.0.0 + axios: + specifier: ^1.11.0 + version: 1.11.0 + cors: + specifier: ^2.8.5 + version: 2.8.5 + crypto: + specifier: ^1.0.1 + version: 1.0.1 + express: + specifier: ^5.1.0 + version: 5.1.0 + fs-extra: + specifier: ^11.3.1 + version: 11.3.1 + js-base64: + specifier: ^3.7.8 + version: 3.7.8 + moment: + specifier: ^2.30.1 + version: 2.30.1 + morgan: + specifier: ^1.10.1 + version: 1.10.1 + proxy-agent: + specifier: ^6.5.0 + version: 6.5.0 + qs: + specifier: ^6.14.0 + version: 6.14.0 + uuid: + specifier: ^11.1.0 + version: 11.1.0 + devDependencies: + readline-sync: + specifier: ^1.4.10 + version: 1.4.10 + +packages: + + '@tootallnate/quickjs-emscripten@0.23.0': + resolution: {integrity: sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==} + + accepts@2.0.0: + resolution: {integrity: sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==} + engines: {node: '>= 0.6'} + + agent-base@7.1.4: + resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==} + engines: {node: '>= 14'} + + appdata-path@1.0.0: + resolution: {integrity: sha512-ZbH3ezXfnT/YE3NdqduIt4lBV+H0ybvA2Qx3K76gIjQvh8gROpDFdDLpx6B1QJtW7zxisCbpTlCLhKqoR8cDBw==} + + ast-types@0.13.4: + resolution: {integrity: sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==} + engines: {node: '>=4'} + + asynckit@0.4.0: + resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} + + axios@1.11.0: + resolution: {integrity: sha512-1Lx3WLFQWm3ooKDYZD1eXmoGO9fxYQjrycfHFC8P0sCfQVXyROp0p9PFWBehewBOdCwHc+f/b8I0fMto5eSfwA==} + + basic-auth@2.0.1: + resolution: {integrity: sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==} + engines: {node: '>= 0.8'} + + basic-ftp@5.0.5: + resolution: {integrity: sha512-4Bcg1P8xhUuqcii/S0Z9wiHIrQVPMermM1any+MX5GeGD7faD3/msQUDGLol9wOcz4/jbg/WJnGqoJF6LiBdtg==} + engines: {node: '>=10.0.0'} + + body-parser@2.2.0: + resolution: {integrity: sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==} + engines: {node: '>=18'} + + bytes@3.1.2: + resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} + engines: {node: '>= 0.8'} + + call-bind-apply-helpers@1.0.2: + resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} + engines: {node: '>= 0.4'} + + call-bound@1.0.4: + resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} + engines: {node: '>= 0.4'} + + combined-stream@1.0.8: + resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} + engines: {node: '>= 0.8'} + + content-disposition@1.0.0: + resolution: {integrity: sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==} + engines: {node: '>= 0.6'} + + content-type@1.0.5: + resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==} + engines: {node: '>= 0.6'} + + cookie-signature@1.2.2: + resolution: {integrity: sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==} + engines: {node: '>=6.6.0'} + + cookie@0.7.2: + resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==} + engines: {node: '>= 0.6'} + + cors@2.8.5: + resolution: {integrity: sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==} + engines: {node: '>= 0.10'} + + crypto@1.0.1: + resolution: {integrity: sha512-VxBKmeNcqQdiUQUW2Tzq0t377b54N2bMtXO/qiLa+6eRRmmC4qT3D4OnTGoT/U6O9aklQ/jTwbOtRMTTY8G0Ig==} + deprecated: This package is no longer supported. It's now a built-in Node module. If you've depended on crypto, you should switch to the one that's built-in. + + data-uri-to-buffer@6.0.2: + resolution: {integrity: sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw==} + engines: {node: '>= 14'} + + debug@2.6.9: + resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + debug@4.4.1: + resolution: {integrity: sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + degenerator@5.0.1: + resolution: {integrity: sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ==} + engines: {node: '>= 14'} + + delayed-stream@1.0.0: + resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} + engines: {node: '>=0.4.0'} + + depd@2.0.0: + resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} + engines: {node: '>= 0.8'} + + dunder-proto@1.0.1: + resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} + engines: {node: '>= 0.4'} + + ee-first@1.1.1: + resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} + + encodeurl@2.0.0: + resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==} + engines: {node: '>= 0.8'} + + es-define-property@1.0.1: + resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} + engines: {node: '>= 0.4'} + + es-errors@1.3.0: + resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} + engines: {node: '>= 0.4'} + + es-object-atoms@1.1.1: + resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} + engines: {node: '>= 0.4'} + + es-set-tostringtag@2.1.0: + resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} + engines: {node: '>= 0.4'} + + escape-html@1.0.3: + resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} + + escodegen@2.1.0: + resolution: {integrity: sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==} + engines: {node: '>=6.0'} + hasBin: true + + esprima@4.0.1: + resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} + engines: {node: '>=4'} + hasBin: true + + estraverse@5.3.0: + resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} + engines: {node: '>=4.0'} + + esutils@2.0.3: + resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} + engines: {node: '>=0.10.0'} + + etag@1.8.1: + resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} + engines: {node: '>= 0.6'} + + express@5.1.0: + resolution: {integrity: sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==} + engines: {node: '>= 18'} + + finalhandler@2.1.0: + resolution: {integrity: sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==} + engines: {node: '>= 0.8'} + + follow-redirects@1.15.11: + resolution: {integrity: sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==} + engines: {node: '>=4.0'} + peerDependencies: + debug: '*' + peerDependenciesMeta: + debug: + optional: true + + form-data@4.0.4: + resolution: {integrity: sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==} + engines: {node: '>= 6'} + + forwarded@0.2.0: + resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} + engines: {node: '>= 0.6'} + + fresh@2.0.0: + resolution: {integrity: sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==} + engines: {node: '>= 0.8'} + + fs-extra@11.3.1: + resolution: {integrity: sha512-eXvGGwZ5CL17ZSwHWd3bbgk7UUpF6IFHtP57NYYakPvHOs8GDgDe5KJI36jIJzDkJ6eJjuzRA8eBQb6SkKue0g==} + engines: {node: '>=14.14'} + + function-bind@1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + + get-intrinsic@1.3.0: + resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} + engines: {node: '>= 0.4'} + + get-proto@1.0.1: + resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} + engines: {node: '>= 0.4'} + + get-uri@6.0.5: + resolution: {integrity: sha512-b1O07XYq8eRuVzBNgJLstU6FYc1tS6wnMtF1I1D9lE8LxZSOGZ7LhxN54yPP6mGw5f2CkXY2BQUL9Fx41qvcIg==} + engines: {node: '>= 14'} + + gopd@1.2.0: + resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} + engines: {node: '>= 0.4'} + + graceful-fs@4.2.11: + resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + + has-symbols@1.1.0: + resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} + engines: {node: '>= 0.4'} + + has-tostringtag@1.0.2: + resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} + engines: {node: '>= 0.4'} + + hasown@2.0.2: + resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} + engines: {node: '>= 0.4'} + + http-errors@2.0.0: + resolution: {integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==} + engines: {node: '>= 0.8'} + + http-proxy-agent@7.0.2: + resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==} + engines: {node: '>= 14'} + + https-proxy-agent@7.0.6: + resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==} + engines: {node: '>= 14'} + + iconv-lite@0.6.3: + resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} + engines: {node: '>=0.10.0'} + + inherits@2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + + ip-address@10.0.1: + resolution: {integrity: sha512-NWv9YLW4PoW2B7xtzaS3NCot75m6nK7Icdv0o3lfMceJVRfSoQwqD4wEH5rLwoKJwUiZ/rfpiVBhnaF0FK4HoA==} + engines: {node: '>= 12'} + + ipaddr.js@1.9.1: + resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} + engines: {node: '>= 0.10'} + + is-promise@4.0.0: + resolution: {integrity: sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==} + + js-base64@3.7.8: + resolution: {integrity: sha512-hNngCeKxIUQiEUN3GPJOkz4wF/YvdUdbNL9hsBcMQTkKzboD7T/q3OYOuuPZLUE6dBxSGpwhk5mwuDud7JVAow==} + + jsonfile@6.2.0: + resolution: {integrity: sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==} + + lru-cache@7.18.3: + resolution: {integrity: sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==} + engines: {node: '>=12'} + + math-intrinsics@1.1.0: + resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} + engines: {node: '>= 0.4'} + + media-typer@1.1.0: + resolution: {integrity: sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==} + engines: {node: '>= 0.8'} + + merge-descriptors@2.0.0: + resolution: {integrity: sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==} + engines: {node: '>=18'} + + mime-db@1.52.0: + resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} + engines: {node: '>= 0.6'} + + mime-db@1.54.0: + resolution: {integrity: sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==} + engines: {node: '>= 0.6'} + + mime-types@2.1.35: + resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} + engines: {node: '>= 0.6'} + + mime-types@3.0.1: + resolution: {integrity: sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==} + engines: {node: '>= 0.6'} + + moment@2.30.1: + resolution: {integrity: sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==} + + morgan@1.10.1: + resolution: {integrity: sha512-223dMRJtI/l25dJKWpgij2cMtywuG/WiUKXdvwfbhGKBhy1puASqXwFzmWZ7+K73vUPoR7SS2Qz2cI/g9MKw0A==} + engines: {node: '>= 0.8.0'} + + ms@2.0.0: + resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + negotiator@1.0.0: + resolution: {integrity: sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==} + engines: {node: '>= 0.6'} + + netmask@2.0.2: + resolution: {integrity: sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg==} + engines: {node: '>= 0.4.0'} + + object-assign@4.1.1: + resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} + engines: {node: '>=0.10.0'} + + object-inspect@1.13.4: + resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} + engines: {node: '>= 0.4'} + + on-finished@2.3.0: + resolution: {integrity: sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==} + engines: {node: '>= 0.8'} + + on-finished@2.4.1: + resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==} + engines: {node: '>= 0.8'} + + on-headers@1.1.0: + resolution: {integrity: sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==} + engines: {node: '>= 0.8'} + + once@1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + + pac-proxy-agent@7.2.0: + resolution: {integrity: sha512-TEB8ESquiLMc0lV8vcd5Ql/JAKAoyzHFXaStwjkzpOpC5Yv+pIzLfHvjTSdf3vpa2bMiUQrg9i6276yn8666aA==} + engines: {node: '>= 14'} + + pac-resolver@7.0.1: + resolution: {integrity: sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg==} + engines: {node: '>= 14'} + + parseurl@1.3.3: + resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} + engines: {node: '>= 0.8'} + + path-to-regexp@8.2.0: + resolution: {integrity: sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ==} + engines: {node: '>=16'} + + proxy-addr@2.0.7: + resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} + engines: {node: '>= 0.10'} + + proxy-agent@6.5.0: + resolution: {integrity: sha512-TmatMXdr2KlRiA2CyDu8GqR8EjahTG3aY3nXjdzFyoZbmB8hrBsTyMezhULIXKnC0jpfjlmiZ3+EaCzoInSu/A==} + engines: {node: '>= 14'} + + proxy-from-env@1.1.0: + resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} + + qs@6.14.0: + resolution: {integrity: sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==} + engines: {node: '>=0.6'} + + range-parser@1.2.1: + resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==} + engines: {node: '>= 0.6'} + + raw-body@3.0.0: + resolution: {integrity: sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g==} + engines: {node: '>= 0.8'} + + readline-sync@1.4.10: + resolution: {integrity: sha512-gNva8/6UAe8QYepIQH/jQ2qn91Qj0B9sYjMBBs3QOB8F2CXcKgLxQaJRP76sWVRQt+QU+8fAkCbCvjjMFu7Ycw==} + engines: {node: '>= 0.8.0'} + + router@2.2.0: + resolution: {integrity: sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==} + engines: {node: '>= 18'} + + safe-buffer@5.1.2: + resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==} + + safe-buffer@5.2.1: + resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + + safer-buffer@2.1.2: + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + + send@1.2.0: + resolution: {integrity: sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==} + engines: {node: '>= 18'} + + serve-static@2.2.0: + resolution: {integrity: sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==} + engines: {node: '>= 18'} + + setprototypeof@1.2.0: + resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} + + side-channel-list@1.0.0: + resolution: {integrity: sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==} + engines: {node: '>= 0.4'} + + side-channel-map@1.0.1: + resolution: {integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==} + engines: {node: '>= 0.4'} + + side-channel-weakmap@1.0.2: + resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==} + engines: {node: '>= 0.4'} + + side-channel@1.1.0: + resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==} + engines: {node: '>= 0.4'} + + smart-buffer@4.2.0: + resolution: {integrity: sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==} + engines: {node: '>= 6.0.0', npm: '>= 3.0.0'} + + socks-proxy-agent@8.0.5: + resolution: {integrity: sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==} + engines: {node: '>= 14'} + + socks@2.8.7: + resolution: {integrity: sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A==} + engines: {node: '>= 10.0.0', npm: '>= 3.0.0'} + + source-map@0.6.1: + resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} + engines: {node: '>=0.10.0'} + + statuses@2.0.1: + resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==} + engines: {node: '>= 0.8'} + + statuses@2.0.2: + resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==} + engines: {node: '>= 0.8'} + + toidentifier@1.0.1: + resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} + engines: {node: '>=0.6'} + + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + + type-is@2.0.1: + resolution: {integrity: sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==} + engines: {node: '>= 0.6'} + + universalify@2.0.1: + resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} + engines: {node: '>= 10.0.0'} + + unpipe@1.0.0: + resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} + engines: {node: '>= 0.8'} + + uuid@11.1.0: + resolution: {integrity: sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==} + hasBin: true + + vary@1.1.2: + resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} + engines: {node: '>= 0.8'} + + wrappy@1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + +snapshots: + + '@tootallnate/quickjs-emscripten@0.23.0': {} + + accepts@2.0.0: + dependencies: + mime-types: 3.0.1 + negotiator: 1.0.0 + + agent-base@7.1.4: {} + + appdata-path@1.0.0: {} + + ast-types@0.13.4: + dependencies: + tslib: 2.8.1 + + asynckit@0.4.0: {} + + axios@1.11.0: + dependencies: + follow-redirects: 1.15.11 + form-data: 4.0.4 + proxy-from-env: 1.1.0 + transitivePeerDependencies: + - debug + + basic-auth@2.0.1: + dependencies: + safe-buffer: 5.1.2 + + basic-ftp@5.0.5: {} + + body-parser@2.2.0: + dependencies: + bytes: 3.1.2 + content-type: 1.0.5 + debug: 4.4.1 + http-errors: 2.0.0 + iconv-lite: 0.6.3 + on-finished: 2.4.1 + qs: 6.14.0 + raw-body: 3.0.0 + type-is: 2.0.1 + transitivePeerDependencies: + - supports-color + + bytes@3.1.2: {} + + call-bind-apply-helpers@1.0.2: + dependencies: + es-errors: 1.3.0 + function-bind: 1.1.2 + + call-bound@1.0.4: + dependencies: + call-bind-apply-helpers: 1.0.2 + get-intrinsic: 1.3.0 + + combined-stream@1.0.8: + dependencies: + delayed-stream: 1.0.0 + + content-disposition@1.0.0: + dependencies: + safe-buffer: 5.2.1 + + content-type@1.0.5: {} + + cookie-signature@1.2.2: {} + + cookie@0.7.2: {} + + cors@2.8.5: + dependencies: + object-assign: 4.1.1 + vary: 1.1.2 + + crypto@1.0.1: {} + + data-uri-to-buffer@6.0.2: {} + + debug@2.6.9: + dependencies: + ms: 2.0.0 + + debug@4.4.1: + dependencies: + ms: 2.1.3 + + degenerator@5.0.1: + dependencies: + ast-types: 0.13.4 + escodegen: 2.1.0 + esprima: 4.0.1 + + delayed-stream@1.0.0: {} + + depd@2.0.0: {} + + dunder-proto@1.0.1: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-errors: 1.3.0 + gopd: 1.2.0 + + ee-first@1.1.1: {} + + encodeurl@2.0.0: {} + + es-define-property@1.0.1: {} + + es-errors@1.3.0: {} + + es-object-atoms@1.1.1: + dependencies: + es-errors: 1.3.0 + + es-set-tostringtag@2.1.0: + dependencies: + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + has-tostringtag: 1.0.2 + hasown: 2.0.2 + + escape-html@1.0.3: {} + + escodegen@2.1.0: + dependencies: + esprima: 4.0.1 + estraverse: 5.3.0 + esutils: 2.0.3 + optionalDependencies: + source-map: 0.6.1 + + esprima@4.0.1: {} + + estraverse@5.3.0: {} + + esutils@2.0.3: {} + + etag@1.8.1: {} + + express@5.1.0: + dependencies: + accepts: 2.0.0 + body-parser: 2.2.0 + content-disposition: 1.0.0 + content-type: 1.0.5 + cookie: 0.7.2 + cookie-signature: 1.2.2 + debug: 4.4.1 + encodeurl: 2.0.0 + escape-html: 1.0.3 + etag: 1.8.1 + finalhandler: 2.1.0 + fresh: 2.0.0 + http-errors: 2.0.0 + merge-descriptors: 2.0.0 + mime-types: 3.0.1 + on-finished: 2.4.1 + once: 1.4.0 + parseurl: 1.3.3 + proxy-addr: 2.0.7 + qs: 6.14.0 + range-parser: 1.2.1 + router: 2.2.0 + send: 1.2.0 + serve-static: 2.2.0 + statuses: 2.0.2 + type-is: 2.0.1 + vary: 1.1.2 + transitivePeerDependencies: + - supports-color + + finalhandler@2.1.0: + dependencies: + debug: 4.4.1 + encodeurl: 2.0.0 + escape-html: 1.0.3 + on-finished: 2.4.1 + parseurl: 1.3.3 + statuses: 2.0.2 + transitivePeerDependencies: + - supports-color + + follow-redirects@1.15.11: {} + + form-data@4.0.4: + dependencies: + asynckit: 0.4.0 + combined-stream: 1.0.8 + es-set-tostringtag: 2.1.0 + hasown: 2.0.2 + mime-types: 2.1.35 + + forwarded@0.2.0: {} + + fresh@2.0.0: {} + + fs-extra@11.3.1: + dependencies: + graceful-fs: 4.2.11 + jsonfile: 6.2.0 + universalify: 2.0.1 + + function-bind@1.1.2: {} + + get-intrinsic@1.3.0: + 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 + + get-proto@1.0.1: + dependencies: + dunder-proto: 1.0.1 + es-object-atoms: 1.1.1 + + get-uri@6.0.5: + dependencies: + basic-ftp: 5.0.5 + data-uri-to-buffer: 6.0.2 + debug: 4.4.1 + transitivePeerDependencies: + - supports-color + + gopd@1.2.0: {} + + graceful-fs@4.2.11: {} + + has-symbols@1.1.0: {} + + has-tostringtag@1.0.2: + dependencies: + has-symbols: 1.1.0 + + hasown@2.0.2: + dependencies: + function-bind: 1.1.2 + + http-errors@2.0.0: + dependencies: + depd: 2.0.0 + inherits: 2.0.4 + setprototypeof: 1.2.0 + statuses: 2.0.1 + toidentifier: 1.0.1 + + http-proxy-agent@7.0.2: + dependencies: + agent-base: 7.1.4 + debug: 4.4.1 + transitivePeerDependencies: + - supports-color + + https-proxy-agent@7.0.6: + dependencies: + agent-base: 7.1.4 + debug: 4.4.1 + transitivePeerDependencies: + - supports-color + + iconv-lite@0.6.3: + dependencies: + safer-buffer: 2.1.2 + + inherits@2.0.4: {} + + ip-address@10.0.1: {} + + ipaddr.js@1.9.1: {} + + is-promise@4.0.0: {} + + js-base64@3.7.8: {} + + jsonfile@6.2.0: + dependencies: + universalify: 2.0.1 + optionalDependencies: + graceful-fs: 4.2.11 + + lru-cache@7.18.3: {} + + math-intrinsics@1.1.0: {} + + media-typer@1.1.0: {} + + merge-descriptors@2.0.0: {} + + mime-db@1.52.0: {} + + mime-db@1.54.0: {} + + mime-types@2.1.35: + dependencies: + mime-db: 1.52.0 + + mime-types@3.0.1: + dependencies: + mime-db: 1.54.0 + + moment@2.30.1: {} + + morgan@1.10.1: + dependencies: + basic-auth: 2.0.1 + debug: 2.6.9 + depd: 2.0.0 + on-finished: 2.3.0 + on-headers: 1.1.0 + transitivePeerDependencies: + - supports-color + + ms@2.0.0: {} + + ms@2.1.3: {} + + negotiator@1.0.0: {} + + netmask@2.0.2: {} + + object-assign@4.1.1: {} + + object-inspect@1.13.4: {} + + on-finished@2.3.0: + dependencies: + ee-first: 1.1.1 + + on-finished@2.4.1: + dependencies: + ee-first: 1.1.1 + + on-headers@1.1.0: {} + + once@1.4.0: + dependencies: + wrappy: 1.0.2 + + pac-proxy-agent@7.2.0: + dependencies: + '@tootallnate/quickjs-emscripten': 0.23.0 + agent-base: 7.1.4 + debug: 4.4.1 + get-uri: 6.0.5 + http-proxy-agent: 7.0.2 + https-proxy-agent: 7.0.6 + pac-resolver: 7.0.1 + socks-proxy-agent: 8.0.5 + transitivePeerDependencies: + - supports-color + + pac-resolver@7.0.1: + dependencies: + degenerator: 5.0.1 + netmask: 2.0.2 + + parseurl@1.3.3: {} + + path-to-regexp@8.2.0: {} + + proxy-addr@2.0.7: + dependencies: + forwarded: 0.2.0 + ipaddr.js: 1.9.1 + + proxy-agent@6.5.0: + dependencies: + agent-base: 7.1.4 + debug: 4.4.1 + http-proxy-agent: 7.0.2 + https-proxy-agent: 7.0.6 + lru-cache: 7.18.3 + pac-proxy-agent: 7.2.0 + proxy-from-env: 1.1.0 + socks-proxy-agent: 8.0.5 + transitivePeerDependencies: + - supports-color + + proxy-from-env@1.1.0: {} + + qs@6.14.0: + dependencies: + side-channel: 1.1.0 + + range-parser@1.2.1: {} + + raw-body@3.0.0: + dependencies: + bytes: 3.1.2 + http-errors: 2.0.0 + iconv-lite: 0.6.3 + unpipe: 1.0.0 + + readline-sync@1.4.10: {} + + router@2.2.0: + dependencies: + debug: 4.4.1 + depd: 2.0.0 + is-promise: 4.0.0 + parseurl: 1.3.3 + path-to-regexp: 8.2.0 + transitivePeerDependencies: + - supports-color + + safe-buffer@5.1.2: {} + + safe-buffer@5.2.1: {} + + safer-buffer@2.1.2: {} + + send@1.2.0: + dependencies: + debug: 4.4.1 + encodeurl: 2.0.0 + escape-html: 1.0.3 + etag: 1.8.1 + fresh: 2.0.0 + http-errors: 2.0.0 + mime-types: 3.0.1 + ms: 2.1.3 + on-finished: 2.4.1 + range-parser: 1.2.1 + statuses: 2.0.2 + transitivePeerDependencies: + - supports-color + + serve-static@2.2.0: + dependencies: + encodeurl: 2.0.0 + escape-html: 1.0.3 + parseurl: 1.3.3 + send: 1.2.0 + transitivePeerDependencies: + - supports-color + + setprototypeof@1.2.0: {} + + side-channel-list@1.0.0: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + + side-channel-map@1.0.1: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + + side-channel-weakmap@1.0.2: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + side-channel-map: 1.0.1 + + side-channel@1.1.0: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + side-channel-list: 1.0.0 + side-channel-map: 1.0.1 + side-channel-weakmap: 1.0.2 + + smart-buffer@4.2.0: {} + + socks-proxy-agent@8.0.5: + dependencies: + agent-base: 7.1.4 + debug: 4.4.1 + socks: 2.8.7 + transitivePeerDependencies: + - supports-color + + socks@2.8.7: + dependencies: + ip-address: 10.0.1 + smart-buffer: 4.2.0 + + source-map@0.6.1: + optional: true + + statuses@2.0.1: {} + + statuses@2.0.2: {} + + toidentifier@1.0.1: {} + + tslib@2.8.1: {} + + type-is@2.0.1: + dependencies: + content-type: 1.0.5 + media-typer: 1.1.0 + mime-types: 3.0.1 + + universalify@2.0.1: {} + + unpipe@1.0.0: {} + + uuid@11.1.0: {} + + vary@1.1.2: {} + + wrappy@1.0.2: {} diff --git a/ui/.editorconfig b/ui/.editorconfig new file mode 100644 index 0000000..3b510aa --- /dev/null +++ b/ui/.editorconfig @@ -0,0 +1,8 @@ +[*.{js,jsx,mjs,cjs,ts,tsx,mts,cts,vue,css,scss,sass,less,styl}] +charset = utf-8 +indent_size = 2 +indent_style = space +insert_final_newline = true +trim_trailing_whitespace = true +end_of_line = lf +max_line_length = 100 diff --git a/ui/.gitattributes b/ui/.gitattributes new file mode 100644 index 0000000..6313b56 --- /dev/null +++ b/ui/.gitattributes @@ -0,0 +1 @@ +* text=auto eol=lf diff --git a/ui/.gitignore b/ui/.gitignore new file mode 100644 index 0000000..8ee54e8 --- /dev/null +++ b/ui/.gitignore @@ -0,0 +1,30 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +.DS_Store +dist +dist-ssr +coverage +*.local + +/cypress/videos/ +/cypress/screenshots/ + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? + +*.tsbuildinfo diff --git a/ui/.vscode/extensions.json b/ui/.vscode/extensions.json new file mode 100644 index 0000000..5efa012 --- /dev/null +++ b/ui/.vscode/extensions.json @@ -0,0 +1,7 @@ +{ + "recommendations": [ + "Vue.volar", + "dbaeumer.vscode-eslint", + "EditorConfig.EditorConfig" + ] +} diff --git a/ui/README.md b/ui/README.md new file mode 100644 index 0000000..29feda4 --- /dev/null +++ b/ui/README.md @@ -0,0 +1,182 @@ +# Pixiv Manager Frontend + +一个优雅的 Pixiv 作品管理前端应用,基于 Vue 3 + TypeScript + Vite + Pinia 构建。 + +## 功能特性 + +- 🎨 **作品搜索与浏览** - 支持关键词搜索、标签筛选、排序等功能 +- 👨‍🎨 **作者管理** - 关注喜欢的作者,查看作品列表和统计信息 +- 📥 **下载管理** - 支持单个作品、批量作品、作者作品下载 +- 🔐 **用户认证** - 基于 Pixiv OAuth 2.0 的安全登录系统 +- 📱 **响应式设计** - 完美适配桌面端和移动端 +- ⚡ **现代化技术栈** - Vue 3 + TypeScript + Vite + Pinia + +## 技术栈 + +- **框架**: Vue 3 (Composition API) +- **语言**: TypeScript +- **构建工具**: Vite +- **状态管理**: Pinia +- **路由**: Vue Router 4 +- **HTTP 客户端**: Axios +- **样式**: CSS3 + 响应式设计 + +## 项目结构 + +``` +src/ +├── components/ # 组件目录 +│ ├── common/ # 通用组件 +│ │ ├── LoadingSpinner.vue +│ │ └── ErrorMessage.vue +│ └── artwork/ # 作品相关组件 +│ └── ArtworkCard.vue +├── views/ # 页面组件 +│ ├── HomeView.vue # 首页 +│ ├── LoginView.vue # 登录页 +│ ├── SearchView.vue # 搜索页 +│ ├── ArtworkView.vue # 作品详情页 +│ ├── ArtistView.vue # 作者详情页 +│ ├── DownloadsView.vue # 下载管理页 +│ └── ArtistsView.vue # 作者管理页 +├── services/ # API 服务层 +│ ├── api.ts # 基础 API 服务 +│ ├── auth.ts # 认证相关 API +│ ├── artwork.ts # 作品相关 API +│ ├── artist.ts # 作者相关 API +│ └── download.ts # 下载相关 API +├── stores/ # Pinia 状态管理 +│ └── auth.ts # 认证状态管理 +├── types/ # TypeScript 类型定义 +│ └── index.ts # 全局类型定义 +├── router/ # 路由配置 +│ └── index.ts # 路由定义 +├── assets/ # 静态资源 +│ └── main.css # 全局样式 +├── App.vue # 根组件 +└── main.ts # 应用入口 +``` + +## 开发指南 + +### 环境要求 + +- Node.js >= 16 +- pnpm >= 7 + +### 安装依赖 + +```bash +pnpm install +``` + +### 开发模式 + +```bash +pnpm dev +``` + +### 构建生产版本 + +```bash +pnpm build +``` + +### 代码检查 + +```bash +pnpm lint +``` + +## 设计原则 + +### 组件设计 +- **单一职责**: 每个组件只负责一个特定功能 +- **可复用性**: 组件设计时考虑复用性,避免过度耦合 +- **类型安全**: 全面使用 TypeScript 确保类型安全 + +### 状态管理 +- **集中管理**: 使用 Pinia 进行集中状态管理 +- **响应式**: 状态变化自动触发 UI 更新 +- **持久化**: 关键状态支持本地持久化 + +### API 设计 +- **服务层**: 所有 API 调用封装在服务层 +- **错误处理**: 统一的错误处理机制 +- **类型安全**: API 响应类型定义完整 + +### 样式设计 +- **响应式**: 支持多种屏幕尺寸 +- **一致性**: 统一的设计语言和组件库 +- **可维护性**: 模块化的 CSS 结构 + +## 主要功能模块 + +### 1. 认证模块 +- Pixiv OAuth 2.0 登录 +- 登录状态管理 +- 自动刷新 Token + +### 2. 作品搜索 +- 关键词搜索 +- 标签筛选 +- 排序选项 +- 分页加载 + +### 3. 作品详情 +- 作品信息展示 +- 多页作品支持 +- 下载功能 +- 作者信息 + +### 4. 作者管理 +- 作者信息展示 +- 作品列表 +- 关注/取消关注 +- 批量下载 + +### 5. 下载管理 +- 下载任务创建 +- 进度跟踪 +- 历史记录 +- 任务取消 + +## 部署说明 + +### 环境变量 + +创建 `.env` 文件: + +```env +VITE_API_BASE_URL=http://localhost:3000 +``` + +### 构建部署 + +```bash +# 构建生产版本 +pnpm build + +# 部署到静态服务器 +# dist/ 目录包含所有静态文件 +``` + +## 贡献指南 + +1. Fork 项目 +2. 创建功能分支 (`git checkout -b feature/AmazingFeature`) +3. 提交更改 (`git commit -m 'Add some AmazingFeature'`) +4. 推送到分支 (`git push origin feature/AmazingFeature`) +5. 打开 Pull Request + +## 许可证 + +本项目仅供学习和个人使用,请遵守 Pixiv 的服务条款。 + +## 更新日志 + +### v1.0.0 +- 初始版本发布 +- 基础功能实现 +- 响应式设计 +- TypeScript 支持 diff --git a/ui/env.d.ts b/ui/env.d.ts new file mode 100644 index 0000000..11f02fe --- /dev/null +++ b/ui/env.d.ts @@ -0,0 +1 @@ +/// diff --git a/ui/eslint.config.ts b/ui/eslint.config.ts new file mode 100644 index 0000000..edb1bbb --- /dev/null +++ b/ui/eslint.config.ts @@ -0,0 +1,20 @@ +import { globalIgnores } from 'eslint/config' +import { defineConfigWithVueTs, vueTsConfigs } from '@vue/eslint-config-typescript' +import pluginVue from 'eslint-plugin-vue' + +// To allow more languages other than `ts` in `.vue` files, uncomment the following lines: +// import { configureVueProject } from '@vue/eslint-config-typescript' +// configureVueProject({ scriptLangs: ['ts', 'tsx'] }) +// More info at https://github.com/vuejs/eslint-config-typescript/#advanced-setup + +export default defineConfigWithVueTs( + { + name: 'app/files-to-lint', + files: ['**/*.{ts,mts,tsx,vue}'], + }, + + globalIgnores(['**/dist/**', '**/dist-ssr/**', '**/coverage/**']), + + pluginVue.configs['flat/essential'], + vueTsConfigs.recommended, +) diff --git a/ui/index.html b/ui/index.html new file mode 100644 index 0000000..9e5fc8f --- /dev/null +++ b/ui/index.html @@ -0,0 +1,13 @@ + + + + + + + Vite App + + +
+ + + diff --git a/ui/package.json b/ui/package.json new file mode 100644 index 0000000..ee7b163 --- /dev/null +++ b/ui/package.json @@ -0,0 +1,38 @@ +{ + "name": "pixivdownload", + "version": "0.0.0", + "private": true, + "type": "module", + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "scripts": { + "dev": "vite", + "build": "run-p type-check \"build-only {@}\" --", + "preview": "vite preview", + "build-only": "vite build", + "type-check": "vue-tsc --build", + "lint": "eslint . --fix" + }, + "dependencies": { + "axios": "^1.11.0", + "pinia": "^3.0.3", + "vue": "^3.5.18", + "vue-router": "^4.5.1" + }, + "devDependencies": { + "@tsconfig/node22": "^22.0.2", + "@types/node": "^22.16.5", + "@vitejs/plugin-vue": "^6.0.1", + "@vue/eslint-config-typescript": "^14.6.0", + "@vue/tsconfig": "^0.7.0", + "eslint": "^9.31.0", + "eslint-plugin-vue": "~10.3.0", + "jiti": "^2.4.2", + "npm-run-all2": "^8.0.4", + "typescript": "~5.8.0", + "vite": "^7.0.6", + "vite-plugin-vue-devtools": "^8.0.0", + "vue-tsc": "^3.0.4" + } +} diff --git a/ui/pnpm-lock.yaml b/ui/pnpm-lock.yaml new file mode 100644 index 0000000..51dc392 --- /dev/null +++ b/ui/pnpm-lock.yaml @@ -0,0 +1,3296 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + axios: + specifier: ^1.11.0 + version: 1.11.0 + pinia: + specifier: ^3.0.3 + version: 3.0.3(typescript@5.8.3)(vue@3.5.18(typescript@5.8.3)) + vue: + specifier: ^3.5.18 + version: 3.5.18(typescript@5.8.3) + vue-router: + specifier: ^4.5.1 + version: 4.5.1(vue@3.5.18(typescript@5.8.3)) + devDependencies: + '@tsconfig/node22': + specifier: ^22.0.2 + version: 22.0.2 + '@types/node': + specifier: ^22.16.5 + version: 22.17.2 + '@vitejs/plugin-vue': + specifier: ^6.0.1 + version: 6.0.1(vite@7.1.3(@types/node@22.17.2)(jiti@2.5.1))(vue@3.5.18(typescript@5.8.3)) + '@vue/eslint-config-typescript': + specifier: ^14.6.0 + version: 14.6.0(eslint-plugin-vue@10.3.0(@typescript-eslint/parser@8.40.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.8.3))(eslint@9.33.0(jiti@2.5.1))(vue-eslint-parser@10.2.0(eslint@9.33.0(jiti@2.5.1))))(eslint@9.33.0(jiti@2.5.1))(typescript@5.8.3) + '@vue/tsconfig': + specifier: ^0.7.0 + version: 0.7.0(typescript@5.8.3)(vue@3.5.18(typescript@5.8.3)) + eslint: + specifier: ^9.31.0 + version: 9.33.0(jiti@2.5.1) + eslint-plugin-vue: + specifier: ~10.3.0 + version: 10.3.0(@typescript-eslint/parser@8.40.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.8.3))(eslint@9.33.0(jiti@2.5.1))(vue-eslint-parser@10.2.0(eslint@9.33.0(jiti@2.5.1))) + jiti: + specifier: ^2.4.2 + version: 2.5.1 + npm-run-all2: + specifier: ^8.0.4 + version: 8.0.4 + typescript: + specifier: ~5.8.0 + version: 5.8.3 + vite: + specifier: ^7.0.6 + version: 7.1.3(@types/node@22.17.2)(jiti@2.5.1) + vite-plugin-vue-devtools: + specifier: ^8.0.0 + version: 8.0.0(vite@7.1.3(@types/node@22.17.2)(jiti@2.5.1))(vue@3.5.18(typescript@5.8.3)) + vue-tsc: + specifier: ^3.0.4 + version: 3.0.6(typescript@5.8.3) + +packages: + + '@ampproject/remapping@2.3.0': + resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} + engines: {node: '>=6.0.0'} + + '@babel/code-frame@7.27.1': + resolution: {integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==} + engines: {node: '>=6.9.0'} + + '@babel/compat-data@7.28.0': + resolution: {integrity: sha512-60X7qkglvrap8mn1lh2ebxXdZYtUcpd7gsmy9kLaBJ4i/WdY8PqTSdxyA8qraikqKQK5C1KRBKXqznrVapyNaw==} + engines: {node: '>=6.9.0'} + + '@babel/core@7.28.3': + resolution: {integrity: sha512-yDBHV9kQNcr2/sUr9jghVyz9C3Y5G2zUM2H2lo+9mKv4sFgbA8s8Z9t8D1jiTkGoO/NoIfKMyKWr4s6CN23ZwQ==} + engines: {node: '>=6.9.0'} + + '@babel/generator@7.28.3': + resolution: {integrity: sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-annotate-as-pure@7.27.3': + resolution: {integrity: sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==} + engines: {node: '>=6.9.0'} + + '@babel/helper-compilation-targets@7.27.2': + resolution: {integrity: sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==} + engines: {node: '>=6.9.0'} + + '@babel/helper-create-class-features-plugin@7.28.3': + resolution: {integrity: sha512-V9f6ZFIYSLNEbuGA/92uOvYsGCJNsuA8ESZ4ldc09bWk/j8H8TKiPw8Mk1eG6olpnO0ALHJmYfZvF4MEE4gajg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-globals@7.28.0': + resolution: {integrity: sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-member-expression-to-functions@7.27.1': + resolution: {integrity: sha512-E5chM8eWjTp/aNoVpcbfM7mLxu9XGLWYise2eBKGQomAk/Mb4XoxyqXTZbuTohbsl8EKqdlMhnDI2CCLfcs9wA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-imports@7.27.1': + resolution: {integrity: sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-transforms@7.28.3': + resolution: {integrity: sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-optimise-call-expression@7.27.1': + resolution: {integrity: sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-plugin-utils@7.27.1': + resolution: {integrity: sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-replace-supers@7.27.1': + resolution: {integrity: sha512-7EHz6qDZc8RYS5ElPoShMheWvEgERonFCs7IAonWLLUTXW59DP14bCZt89/GKyreYn8g3S83m21FelHKbeDCKA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-skip-transparent-expression-wrappers@7.27.1': + resolution: {integrity: sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg==} + engines: {node: '>=6.9.0'} + + '@babel/helper-string-parser@7.27.1': + resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.27.1': + resolution: {integrity: sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-option@7.27.1': + resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==} + engines: {node: '>=6.9.0'} + + '@babel/helpers@7.28.3': + resolution: {integrity: sha512-PTNtvUQihsAsDHMOP5pfobP8C6CM4JWXmP8DrEIt46c3r2bf87Ua1zoqevsMo9g+tWDwgWrFP5EIxuBx5RudAw==} + engines: {node: '>=6.9.0'} + + '@babel/parser@7.28.3': + resolution: {integrity: sha512-7+Ey1mAgYqFAx2h0RuoxcQT5+MlG3GTV0TQrgr7/ZliKsm/MNDxVVutlWaziMq7wJNAz8MTqz55XLpWvva6StA==} + engines: {node: '>=6.0.0'} + hasBin: true + + '@babel/plugin-proposal-decorators@7.28.0': + resolution: {integrity: sha512-zOiZqvANjWDUaUS9xMxbMcK/Zccztbe/6ikvUXaG9nsPH3w6qh5UaPGAnirI/WhIbZ8m3OHU0ReyPrknG+ZKeg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-decorators@7.27.1': + resolution: {integrity: sha512-YMq8Z87Lhl8EGkmb0MwYkt36QnxC+fzCgrl66ereamPlYToRpIk5nUjKUY3QKLWq8mwUB1BgbeXcTJhZOCDg5A==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-import-attributes@7.27.1': + resolution: {integrity: sha512-oFT0FrKHgF53f4vOsZGi2Hh3I35PfSmVs4IBFLFj4dnafP+hIWDLg3VyKmUHfLoLHlyxY4C7DGtmHuJgn+IGww==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-import-meta@7.10.4': + resolution: {integrity: sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-jsx@7.27.1': + resolution: {integrity: sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-typescript@7.27.1': + resolution: {integrity: sha512-xfYCBMxveHrRMnAWl1ZlPXOZjzkN82THFvLhQhFXFt81Z5HnN+EtUkZhv/zcKpmT3fzmWZB0ywiBrbC3vogbwQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-typescript@7.28.0': + resolution: {integrity: sha512-4AEiDEBPIZvLQaWlc9liCavE0xRM0dNca41WtBeM3jgFptfUOSG9z0uteLhq6+3rq+WB6jIvUwKDTpXEHPJ2Vg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/template@7.27.2': + resolution: {integrity: sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==} + engines: {node: '>=6.9.0'} + + '@babel/traverse@7.28.3': + resolution: {integrity: sha512-7w4kZYHneL3A6NP2nxzHvT3HCZ7puDZZjFMqDpBPECub79sTtSO5CGXDkKrTQq8ksAwfD/XI2MRFX23njdDaIQ==} + engines: {node: '>=6.9.0'} + + '@babel/types@7.28.2': + resolution: {integrity: sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ==} + engines: {node: '>=6.9.0'} + + '@esbuild/aix-ppc64@0.25.9': + resolution: {integrity: sha512-OaGtL73Jck6pBKjNIe24BnFE6agGl+6KxDtTfHhy1HmhthfKouEcOhqpSL64K4/0WCtbKFLOdzD/44cJ4k9opA==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.25.9': + resolution: {integrity: sha512-IDrddSmpSv51ftWslJMvl3Q2ZT98fUSL2/rlUXuVqRXHCs5EUF1/f+jbjF5+NG9UffUDMCiTyh8iec7u8RlTLg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.25.9': + resolution: {integrity: sha512-5WNI1DaMtxQ7t7B6xa572XMXpHAaI/9Hnhk8lcxF4zVN4xstUgTlvuGDorBguKEnZO70qwEcLpfifMLoxiPqHQ==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.25.9': + resolution: {integrity: sha512-I853iMZ1hWZdNllhVZKm34f4wErd4lMyeV7BLzEExGEIZYsOzqDWDf+y082izYUE8gtJnYHdeDpN/6tUdwvfiw==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.25.9': + resolution: {integrity: sha512-XIpIDMAjOELi/9PB30vEbVMs3GV1v2zkkPnuyRRURbhqjyzIINwj+nbQATh4H9GxUgH1kFsEyQMxwiLFKUS6Rg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.25.9': + resolution: {integrity: sha512-jhHfBzjYTA1IQu8VyrjCX4ApJDnH+ez+IYVEoJHeqJm9VhG9Dh2BYaJritkYK3vMaXrf7Ogr/0MQ8/MeIefsPQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.25.9': + resolution: {integrity: sha512-z93DmbnY6fX9+KdD4Ue/H6sYs+bhFQJNCPZsi4XWJoYblUqT06MQUdBCpcSfuiN72AbqeBFu5LVQTjfXDE2A6Q==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.25.9': + resolution: {integrity: sha512-mrKX6H/vOyo5v71YfXWJxLVxgy1kyt1MQaD8wZJgJfG4gq4DpQGpgTB74e5yBeQdyMTbgxp0YtNj7NuHN0PoZg==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.25.9': + resolution: {integrity: sha512-BlB7bIcLT3G26urh5Dmse7fiLmLXnRlopw4s8DalgZ8ef79Jj4aUcYbk90g8iCa2467HX8SAIidbL7gsqXHdRw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.25.9': + resolution: {integrity: sha512-HBU2Xv78SMgaydBmdor38lg8YDnFKSARg1Q6AT0/y2ezUAKiZvc211RDFHlEZRFNRVhcMamiToo7bDx3VEOYQw==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.25.9': + resolution: {integrity: sha512-e7S3MOJPZGp2QW6AK6+Ly81rC7oOSerQ+P8L0ta4FhVi+/j/v2yZzx5CqqDaWjtPFfYz21Vi1S0auHrap3Ma3A==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.25.9': + resolution: {integrity: sha512-Sbe10Bnn0oUAB2AalYztvGcK+o6YFFA/9829PhOCUS9vkJElXGdphz0A3DbMdP8gmKkqPmPcMJmJOrI3VYB1JQ==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.25.9': + resolution: {integrity: sha512-YcM5br0mVyZw2jcQeLIkhWtKPeVfAerES5PvOzaDxVtIyZ2NUBZKNLjC5z3/fUlDgT6w89VsxP2qzNipOaaDyA==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.25.9': + resolution: {integrity: sha512-++0HQvasdo20JytyDpFvQtNrEsAgNG2CY1CLMwGXfFTKGBGQT3bOeLSYE2l1fYdvML5KUuwn9Z8L1EWe2tzs1w==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.25.9': + resolution: {integrity: sha512-uNIBa279Y3fkjV+2cUjx36xkx7eSjb8IvnL01eXUKXez/CBHNRw5ekCGMPM0BcmqBxBcdgUWuUXmVWwm4CH9kg==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.25.9': + resolution: {integrity: sha512-Mfiphvp3MjC/lctb+7D287Xw1DGzqJPb/J2aHHcHxflUo+8tmN/6d4k6I2yFR7BVo5/g7x2Monq4+Yew0EHRIA==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.25.9': + resolution: {integrity: sha512-iSwByxzRe48YVkmpbgoxVzn76BXjlYFXC7NvLYq+b+kDjyyk30J0JY47DIn8z1MO3K0oSl9fZoRmZPQI4Hklzg==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-arm64@0.25.9': + resolution: {integrity: sha512-9jNJl6FqaUG+COdQMjSCGW4QiMHH88xWbvZ+kRVblZsWrkXlABuGdFJ1E9L7HK+T0Yqd4akKNa/lO0+jDxQD4Q==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.25.9': + resolution: {integrity: sha512-RLLdkflmqRG8KanPGOU7Rpg829ZHu8nFy5Pqdi9U01VYtG9Y0zOG6Vr2z4/S+/3zIyOxiK6cCeYNWOFR9QP87g==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-arm64@0.25.9': + resolution: {integrity: sha512-YaFBlPGeDasft5IIM+CQAhJAqS3St3nJzDEgsgFixcfZeyGPCd6eJBWzke5piZuZ7CtL656eOSYKk4Ls2C0FRQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.25.9': + resolution: {integrity: sha512-1MkgTCuvMGWuqVtAvkpkXFmtL8XhWy+j4jaSO2wxfJtilVCi0ZE37b8uOdMItIHz4I6z1bWWtEX4CJwcKYLcuA==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openharmony-arm64@0.25.9': + resolution: {integrity: sha512-4Xd0xNiMVXKh6Fa7HEJQbrpP3m3DDn43jKxMjxLLRjWnRsfxjORYJlXPO4JNcXtOyfajXorRKY9NkOpTHptErg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + + '@esbuild/sunos-x64@0.25.9': + resolution: {integrity: sha512-WjH4s6hzo00nNezhp3wFIAfmGZ8U7KtrJNlFMRKxiI9mxEK1scOMAaa9i4crUtu+tBr+0IN6JCuAcSBJZfnphw==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.25.9': + resolution: {integrity: sha512-mGFrVJHmZiRqmP8xFOc6b84/7xa5y5YvR1x8djzXpJBSv/UsNK6aqec+6JDjConTgvvQefdGhFDAs2DLAds6gQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.25.9': + resolution: {integrity: sha512-b33gLVU2k11nVx1OhX3C8QQP6UHQK4ZtN56oFWvVXvz2VkDoe6fbG8TOgHFxEvqeqohmRnIHe5A1+HADk4OQww==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.25.9': + resolution: {integrity: sha512-PPOl1mi6lpLNQxnGoyAfschAodRFYXJ+9fs6WHXz7CSWKbOqiMZsubC+BQsVKuul+3vKLuwTHsS2c2y9EoKwxQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + + '@eslint-community/eslint-utils@4.7.0': + resolution: {integrity: sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + + '@eslint-community/regexpp@4.12.1': + resolution: {integrity: sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==} + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + + '@eslint/config-array@0.21.0': + resolution: {integrity: sha512-ENIdc4iLu0d93HeYirvKmrzshzofPw6VkZRKQGe9Nv46ZnWUzcF1xV01dcvEg/1wXUR61OmmlSfyeyO7EvjLxQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/config-helpers@0.3.1': + resolution: {integrity: sha512-xR93k9WhrDYpXHORXpxVL5oHj3Era7wo6k/Wd8/IsQNnZUTzkGS29lyn3nAT05v6ltUuTFVCCYDEGfy2Or/sPA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/core@0.15.2': + resolution: {integrity: sha512-78Md3/Rrxh83gCxoUc0EiciuOHsIITzLy53m3d9UyiW8y9Dj2D29FeETqyKA+BRK76tnTp6RXWb3pCay8Oyomg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/eslintrc@3.3.1': + resolution: {integrity: sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/js@9.33.0': + resolution: {integrity: sha512-5K1/mKhWaMfreBGJTwval43JJmkip0RmM+3+IuqupeSKNC/Th2Kc7ucaq5ovTSra/OOKB9c58CGSz3QMVbWt0A==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/object-schema@2.1.6': + resolution: {integrity: sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/plugin-kit@0.3.5': + resolution: {integrity: sha512-Z5kJ+wU3oA7MMIqVR9tyZRtjYPr4OC004Q4Rw7pgOKUOKkJfZ3O24nz3WYfGRpMDNmcOi3TwQOmgm7B7Tpii0w==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@humanfs/core@0.19.1': + resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==} + engines: {node: '>=18.18.0'} + + '@humanfs/node@0.16.6': + resolution: {integrity: sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==} + engines: {node: '>=18.18.0'} + + '@humanwhocodes/module-importer@1.0.1': + resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} + engines: {node: '>=12.22'} + + '@humanwhocodes/retry@0.3.1': + resolution: {integrity: sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==} + engines: {node: '>=18.18'} + + '@humanwhocodes/retry@0.4.3': + resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==} + engines: {node: '>=18.18'} + + '@jridgewell/gen-mapping@0.3.13': + resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + + '@jridgewell/sourcemap-codec@1.5.5': + resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} + + '@jridgewell/trace-mapping@0.3.30': + resolution: {integrity: sha512-GQ7Nw5G2lTu/BtHTKfXhKHok2WGetd4XYcVKGx00SjAk8GMwgJM3zr6zORiPGuOE+/vkc90KtTosSSvaCjKb2Q==} + + '@nodelib/fs.scandir@2.1.5': + resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} + engines: {node: '>= 8'} + + '@nodelib/fs.stat@2.0.5': + resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} + engines: {node: '>= 8'} + + '@nodelib/fs.walk@1.2.8': + resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} + engines: {node: '>= 8'} + + '@polka/url@1.0.0-next.29': + resolution: {integrity: sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==} + + '@rolldown/pluginutils@1.0.0-beta.29': + resolution: {integrity: sha512-NIJgOsMjbxAXvoGq/X0gD7VPMQ8j9g0BiDaNjVNVjvl+iKXxL3Jre0v31RmBYeLEmkbj2s02v8vFTbUXi5XS2Q==} + + '@rollup/rollup-android-arm-eabi@4.46.4': + resolution: {integrity: sha512-B2wfzCJ+ps/OBzRjeds7DlJumCU3rXMxJJS1vzURyj7+KBHGONm7c9q1TfdBl4vCuNMkDvARn3PBl2wZzuR5mw==} + cpu: [arm] + os: [android] + + '@rollup/rollup-android-arm64@4.46.4': + resolution: {integrity: sha512-FGJYXvYdn8Bs6lAlBZYT5n+4x0ciEp4cmttsvKAZc/c8/JiPaQK8u0c/86vKX8lA7OY/+37lIQSe0YoAImvBAA==} + cpu: [arm64] + os: [android] + + '@rollup/rollup-darwin-arm64@4.46.4': + resolution: {integrity: sha512-/9qwE/BM7ATw/W/OFEMTm3dmywbJyLQb4f4v5nmOjgYxPIGpw7HaxRi6LnD4Pjn/q7k55FGeHe1/OD02w63apA==} + cpu: [arm64] + os: [darwin] + + '@rollup/rollup-darwin-x64@4.46.4': + resolution: {integrity: sha512-QkWfNbeRuzFnv2d0aPlrzcA3Ebq2mE8kX/5Pl7VdRShbPBjSnom7dbT8E3Jmhxo2RL784hyqGvR5KHavCJQciw==} + cpu: [x64] + os: [darwin] + + '@rollup/rollup-freebsd-arm64@4.46.4': + resolution: {integrity: sha512-+ToyOMYnSfV8D+ckxO6NthPln/PDNp1P6INcNypfZ7muLmEvPKXqduUiD8DlJpMMT8LxHcE5W0dK9kXfJke9Zw==} + cpu: [arm64] + os: [freebsd] + + '@rollup/rollup-freebsd-x64@4.46.4': + resolution: {integrity: sha512-cGT6ey/W+sje6zywbLiqmkfkO210FgRz7tepWAzzEVgQU8Hn91JJmQWNqs55IuglG8sJdzk7XfNgmGRtcYlo1w==} + cpu: [x64] + os: [freebsd] + + '@rollup/rollup-linux-arm-gnueabihf@4.46.4': + resolution: {integrity: sha512-9fhTJyOb275w5RofPSl8lpr4jFowd+H4oQKJ9XTYzD1JWgxdZKE8bA6d4npuiMemkecQOcigX01FNZNCYnQBdA==} + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm-musleabihf@4.46.4': + resolution: {integrity: sha512-+6kCIM5Zjvz2HwPl/udgVs07tPMIp1VU2Y0c72ezjOvSvEfAIWsUgpcSDvnC7g9NrjYR6X9bZT92mZZ90TfvXw==} + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm64-gnu@4.46.4': + resolution: {integrity: sha512-SWuXdnsayCZL4lXoo6jn0yyAj7TTjWE4NwDVt9s7cmu6poMhtiras5c8h6Ih6Y0Zk6Z+8t/mLumvpdSPTWub2Q==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-arm64-musl@4.46.4': + resolution: {integrity: sha512-vDknMDqtMhrrroa5kyX6tuC0aRZZlQ+ipDfbXd2YGz5HeV2t8HOl/FDAd2ynhs7Ki5VooWiiZcCtxiZ4IjqZwQ==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-loongarch64-gnu@4.46.4': + resolution: {integrity: sha512-mCBkjRZWhvjtl/x+Bd4fQkWZT8canStKDxGrHlBiTnZmJnWygGcvBylzLVCZXka4dco5ymkWhZlLwKCGFF4ivw==} + cpu: [loong64] + os: [linux] + + '@rollup/rollup-linux-ppc64-gnu@4.46.4': + resolution: {integrity: sha512-YMdz2phOTFF+Z66dQfGf0gmeDSi5DJzY5bpZyeg9CPBkV9QDzJ1yFRlmi/j7WWRf3hYIWrOaJj5jsfwgc8GTHQ==} + cpu: [ppc64] + os: [linux] + + '@rollup/rollup-linux-riscv64-gnu@4.46.4': + resolution: {integrity: sha512-r0WKLSfFAK8ucG024v2yiLSJMedoWvk8yWqfNICX28NHDGeu3F/wBf8KG6mclghx4FsLePxJr/9N8rIj1PtCnw==} + cpu: [riscv64] + os: [linux] + + '@rollup/rollup-linux-riscv64-musl@4.46.4': + resolution: {integrity: sha512-IaizpPP2UQU3MNyPH1u0Xxbm73D+4OupL0bjo4Hm0496e2wg3zuvoAIhubkD1NGy9fXILEExPQy87mweujEatA==} + cpu: [riscv64] + os: [linux] + + '@rollup/rollup-linux-s390x-gnu@4.46.4': + resolution: {integrity: sha512-aCM29orANR0a8wk896p6UEgIfupReupnmISz6SUwMIwTGaTI8MuKdE0OD2LvEg8ondDyZdMvnaN3bW4nFbATPA==} + cpu: [s390x] + os: [linux] + + '@rollup/rollup-linux-x64-gnu@4.46.4': + resolution: {integrity: sha512-0Xj1vZE3cbr/wda8d/m+UeuSL+TDpuozzdD4QaSzu/xSOMK0Su5RhIkF7KVHFQsobemUNHPLEcYllL7ZTCP/Cg==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-linux-x64-musl@4.46.4': + resolution: {integrity: sha512-kM/orjpolfA5yxsx84kI6bnK47AAZuWxglGKcNmokw2yy9i5eHY5UAjcX45jemTJnfHAWo3/hOoRqEeeTdL5hw==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-win32-arm64-msvc@4.46.4': + resolution: {integrity: sha512-cNLH4psMEsWKILW0isbpQA2OvjXLbKvnkcJFmqAptPQbtLrobiapBJVj6RoIvg6UXVp5w0wnIfd/Q56cNpF+Ew==} + cpu: [arm64] + os: [win32] + + '@rollup/rollup-win32-ia32-msvc@4.46.4': + resolution: {integrity: sha512-OiEa5lRhiANpv4SfwYVgQ3opYWi/QmPDC5ve21m8G9pf6ZO+aX1g2EEF1/IFaM1xPSP7mK0msTRXlPs6mIagkg==} + cpu: [ia32] + os: [win32] + + '@rollup/rollup-win32-x64-msvc@4.46.4': + resolution: {integrity: sha512-IKL9mewGZ5UuuX4NQlwOmxPyqielvkAPUS2s1cl6yWjjQvyN3h5JTdVFGD5Jr5xMjRC8setOfGQDVgX8V+dkjg==} + cpu: [x64] + os: [win32] + + '@sec-ant/readable-stream@0.4.1': + resolution: {integrity: sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==} + + '@sindresorhus/merge-streams@4.0.0': + resolution: {integrity: sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==} + engines: {node: '>=18'} + + '@tsconfig/node22@22.0.2': + resolution: {integrity: sha512-Kmwj4u8sDRDrMYRoN9FDEcXD8UpBSaPQQ24Gz+Gamqfm7xxn+GBR7ge/Z7pK8OXNGyUzbSwJj+TH6B+DS/epyA==} + + '@types/estree@1.0.8': + resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} + + '@types/json-schema@7.0.15': + resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + + '@types/node@22.17.2': + resolution: {integrity: sha512-gL6z5N9Jm9mhY+U2KXZpteb+09zyffliRkZyZOHODGATyC5B1Jt/7TzuuiLkFsSUMLbS1OLmlj/E+/3KF4Q/4w==} + + '@typescript-eslint/eslint-plugin@8.40.0': + resolution: {integrity: sha512-w/EboPlBwnmOBtRbiOvzjD+wdiZdgFeo17lkltrtn7X37vagKKWJABvyfsJXTlHe6XBzugmYgd4A4nW+k8Mixw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + '@typescript-eslint/parser': ^8.40.0 + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/parser@8.40.0': + resolution: {integrity: sha512-jCNyAuXx8dr5KJMkecGmZ8KI61KBUhkCob+SD+C+I5+Y1FWI2Y3QmY4/cxMCC5WAsZqoEtEETVhUiUMIGCf6Bw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/project-service@8.40.0': + resolution: {integrity: sha512-/A89vz7Wf5DEXsGVvcGdYKbVM9F7DyFXj52lNYUDS1L9yJfqjW/fIp5PgMuEJL/KeqVTe2QSbXAGUZljDUpArw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/scope-manager@8.40.0': + resolution: {integrity: sha512-y9ObStCcdCiZKzwqsE8CcpyuVMwRouJbbSrNuThDpv16dFAj429IkM6LNb1dZ2m7hK5fHyzNcErZf7CEeKXR4w==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/tsconfig-utils@8.40.0': + resolution: {integrity: sha512-jtMytmUaG9d/9kqSl/W3E3xaWESo4hFDxAIHGVW/WKKtQhesnRIJSAJO6XckluuJ6KDB5woD1EiqknriCtAmcw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/type-utils@8.40.0': + resolution: {integrity: sha512-eE60cK4KzAc6ZrzlJnflXdrMqOBaugeukWICO2rB0KNvwdIMaEaYiywwHMzA1qFpTxrLhN9Lp4E/00EgWcD3Ow==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/types@8.40.0': + resolution: {integrity: sha512-ETdbFlgbAmXHyFPwqUIYrfc12ArvpBhEVgGAxVYSwli26dn8Ko+lIo4Su9vI9ykTZdJn+vJprs/0eZU0YMAEQg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/typescript-estree@8.40.0': + resolution: {integrity: sha512-k1z9+GJReVVOkc1WfVKs1vBrR5MIKKbdAjDTPvIK3L8De6KbFfPFt6BKpdkdk7rZS2GtC/m6yI5MYX+UsuvVYQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/utils@8.40.0': + resolution: {integrity: sha512-Cgzi2MXSZyAUOY+BFwGs17s7ad/7L+gKt6Y8rAVVWS+7o6wrjeFN4nVfTpbE25MNcxyJ+iYUXflbs2xR9h4UBg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/visitor-keys@8.40.0': + resolution: {integrity: sha512-8CZ47QwalyRjsypfwnbI3hKy5gJDPmrkLjkgMxhi0+DZZ2QNx2naS6/hWoVYUHU7LU2zleF68V9miaVZvhFfTA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@vitejs/plugin-vue@6.0.1': + resolution: {integrity: sha512-+MaE752hU0wfPFJEUAIxqw18+20euHHdxVtMvbFcOEpjEyfqXH/5DCoTHiVJ0J29EhTJdoTkjEv5YBKU9dnoTw==} + engines: {node: ^20.19.0 || >=22.12.0} + peerDependencies: + vite: ^5.0.0 || ^6.0.0 || ^7.0.0 + vue: ^3.2.25 + + '@volar/language-core@2.4.23': + resolution: {integrity: sha512-hEEd5ET/oSmBC6pi1j6NaNYRWoAiDhINbT8rmwtINugR39loROSlufGdYMF9TaKGfz+ViGs1Idi3mAhnuPcoGQ==} + + '@volar/source-map@2.4.23': + resolution: {integrity: sha512-Z1Uc8IB57Lm6k7q6KIDu/p+JWtf3xsXJqAX/5r18hYOTpJyBn0KXUR8oTJ4WFYOcDzWC9n3IflGgHowx6U6z9Q==} + + '@volar/typescript@2.4.23': + resolution: {integrity: sha512-lAB5zJghWxVPqfcStmAP1ZqQacMpe90UrP5RJ3arDyrhy4aCUQqmxPPLB2PWDKugvylmO41ljK7vZ+t6INMTag==} + + '@vue/babel-helper-vue-transform-on@1.5.0': + resolution: {integrity: sha512-0dAYkerNhhHutHZ34JtTl2czVQHUNWv6xEbkdF5W+Yrv5pCWsqjeORdOgbtW2I9gWlt+wBmVn+ttqN9ZxR5tzA==} + + '@vue/babel-plugin-jsx@1.5.0': + resolution: {integrity: sha512-mneBhw1oOqCd2247O0Yw/mRwC9jIGACAJUlawkmMBiNmL4dGA2eMzuNZVNqOUfYTa6vqmND4CtOPzmEEEqLKFw==} + peerDependencies: + '@babel/core': ^7.0.0-0 + peerDependenciesMeta: + '@babel/core': + optional: true + + '@vue/babel-plugin-resolve-type@1.5.0': + resolution: {integrity: sha512-Wm/60o+53JwJODm4Knz47dxJnLDJ9FnKnGZJbUUf8nQRAtt6P+undLUAVU3Ha33LxOJe6IPoifRQ6F/0RrU31w==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@vue/compiler-core@3.5.18': + resolution: {integrity: sha512-3slwjQrrV1TO8MoXgy3aynDQ7lslj5UqDxuHnrzHtpON5CBinhWjJETciPngpin/T3OuW3tXUf86tEurusnztw==} + + '@vue/compiler-dom@3.5.18': + resolution: {integrity: sha512-RMbU6NTU70++B1JyVJbNbeFkK+A+Q7y9XKE2EM4NLGm2WFR8x9MbAtWxPPLdm0wUkuZv9trpwfSlL6tjdIa1+A==} + + '@vue/compiler-sfc@3.5.18': + resolution: {integrity: sha512-5aBjvGqsWs+MoxswZPoTB9nSDb3dhd1x30xrrltKujlCxo48j8HGDNj3QPhF4VIS0VQDUrA1xUfp2hEa+FNyXA==} + + '@vue/compiler-ssr@3.5.18': + resolution: {integrity: sha512-xM16Ak7rSWHkM3m22NlmcdIM+K4BMyFARAfV9hYFl+SFuRzrZ3uGMNW05kA5pmeMa0X9X963Kgou7ufdbpOP9g==} + + '@vue/compiler-vue2@2.7.16': + resolution: {integrity: sha512-qYC3Psj9S/mfu9uVi5WvNZIzq+xnXMhOwbTFKKDD7b1lhpnn71jXSFdTQ+WsIEk0ONCd7VV2IMm7ONl6tbQ86A==} + + '@vue/devtools-api@6.6.4': + resolution: {integrity: sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==} + + '@vue/devtools-api@7.7.7': + resolution: {integrity: sha512-lwOnNBH2e7x1fIIbVT7yF5D+YWhqELm55/4ZKf45R9T8r9dE2AIOy8HKjfqzGsoTHFbWbr337O4E0A0QADnjBg==} + + '@vue/devtools-core@8.0.0': + resolution: {integrity: sha512-5bPtF0jAFnaGs4C/4+3vGRR5U+cf6Y8UWK0nJflutEDGepHxl5L9JRaPdHQYCUgrzUaf4cY4waNBEEGXrfcs3A==} + peerDependencies: + vue: ^3.0.0 + + '@vue/devtools-kit@7.7.7': + resolution: {integrity: sha512-wgoZtxcTta65cnZ1Q6MbAfePVFxfM+gq0saaeytoph7nEa7yMXoi6sCPy4ufO111B9msnw0VOWjPEFCXuAKRHA==} + + '@vue/devtools-kit@8.0.0': + resolution: {integrity: sha512-b11OeQODkE0bctdT0RhL684pEV2DPXJ80bjpywVCbFn1PxuL3bmMPDoJKjbMnnoWbrnUYXYzFfmMWBZAMhORkQ==} + + '@vue/devtools-shared@7.7.7': + resolution: {integrity: sha512-+udSj47aRl5aKb0memBvcUG9koarqnxNM5yjuREvqwK6T3ap4mn3Zqqc17QrBFTqSMjr3HK1cvStEZpMDpfdyw==} + + '@vue/devtools-shared@8.0.0': + resolution: {integrity: sha512-jrKnbjshQCiOAJanoeJjTU7WaCg0Dz2BUal6SaR6VM/P3hiFdX5Q6Pxl73ZMnrhCxNK9nAg5hvvRGqs+6dtU1g==} + + '@vue/eslint-config-typescript@14.6.0': + resolution: {integrity: sha512-UpiRY/7go4Yps4mYCjkvlIbVWmn9YvPGQDxTAlcKLphyaD77LjIu3plH4Y9zNT0GB4f3K5tMmhhtRhPOgrQ/bQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^9.10.0 + eslint-plugin-vue: ^9.28.0 || ^10.0.0 + typescript: '>=4.8.4' + peerDependenciesMeta: + typescript: + optional: true + + '@vue/language-core@3.0.6': + resolution: {integrity: sha512-e2RRzYWm+qGm8apUHW1wA5RQxzNhkqbbKdbKhiDUcmMrNAZGyM8aTiL3UrTqkaFI5s7wJRGGrp4u3jgusuBp2A==} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + '@vue/reactivity@3.5.18': + resolution: {integrity: sha512-x0vPO5Imw+3sChLM5Y+B6G1zPjwdOri9e8V21NnTnlEvkxatHEH5B5KEAJcjuzQ7BsjGrKtfzuQ5eQwXh8HXBg==} + + '@vue/runtime-core@3.5.18': + resolution: {integrity: sha512-DUpHa1HpeOQEt6+3nheUfqVXRog2kivkXHUhoqJiKR33SO4x+a5uNOMkV487WPerQkL0vUuRvq/7JhRgLW3S+w==} + + '@vue/runtime-dom@3.5.18': + resolution: {integrity: sha512-YwDj71iV05j4RnzZnZtGaXwPoUWeRsqinblgVJwR8XTXYZ9D5PbahHQgsbmzUvCWNF6x7siQ89HgnX5eWkr3mw==} + + '@vue/server-renderer@3.5.18': + resolution: {integrity: sha512-PvIHLUoWgSbDG7zLHqSqaCoZvHi6NNmfVFOqO+OnwvqMz/tqQr3FuGWS8ufluNddk7ZLBJYMrjcw1c6XzR12mA==} + peerDependencies: + vue: 3.5.18 + + '@vue/shared@3.5.18': + resolution: {integrity: sha512-cZy8Dq+uuIXbxCZpuLd2GJdeSO/lIzIspC2WtkqIpje5QyFbvLaI5wZtdUjLHjGZrlVX6GilejatWwVYYRc8tA==} + + '@vue/tsconfig@0.7.0': + resolution: {integrity: sha512-ku2uNz5MaZ9IerPPUyOHzyjhXoX2kVJaVf7hL315DC17vS6IiZRmmCPfggNbU16QTvM80+uYYy3eYJB59WCtvg==} + peerDependencies: + typescript: 5.x + vue: ^3.4.0 + peerDependenciesMeta: + typescript: + optional: true + vue: + optional: true + + acorn-jsx@5.3.2: + resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + + acorn@8.15.0: + resolution: {integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==} + engines: {node: '>=0.4.0'} + hasBin: true + + ajv@6.12.6: + resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} + + alien-signals@2.0.7: + resolution: {integrity: sha512-wE7y3jmYeb0+h6mr5BOovuqhFv22O/MV9j5p0ndJsa7z1zJNPGQ4ph5pQk/kTTCWRC3xsA4SmtwmkzQO+7NCNg==} + + ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + + ansi-styles@6.2.1: + resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} + engines: {node: '>=12'} + + ansis@4.1.0: + resolution: {integrity: sha512-BGcItUBWSMRgOCe+SVZJ+S7yTRG0eGt9cXAHev72yuGcY23hnLA7Bky5L/xLyPINoSN95geovfBkqoTlNZYa7w==} + engines: {node: '>=14'} + + argparse@2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + + asynckit@0.4.0: + resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} + + axios@1.11.0: + resolution: {integrity: sha512-1Lx3WLFQWm3ooKDYZD1eXmoGO9fxYQjrycfHFC8P0sCfQVXyROp0p9PFWBehewBOdCwHc+f/b8I0fMto5eSfwA==} + + balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + + birpc@2.5.0: + resolution: {integrity: sha512-VSWO/W6nNQdyP520F1mhf+Lc2f8pjGQOtoHHm7Ze8Go1kX7akpVIrtTa0fn+HB0QJEDVacl6aO08YE0PgXfdnQ==} + + boolbase@1.0.0: + resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==} + + brace-expansion@1.1.12: + resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==} + + brace-expansion@2.0.2: + resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==} + + braces@3.0.3: + resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} + engines: {node: '>=8'} + + browserslist@4.25.3: + resolution: {integrity: sha512-cDGv1kkDI4/0e5yON9yM5G/0A5u8sf5TnmdX5C9qHzI9PPu++sQ9zjm1k9NiOrf3riY4OkK0zSGqfvJyJsgCBQ==} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + hasBin: true + + bundle-name@4.1.0: + resolution: {integrity: sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==} + engines: {node: '>=18'} + + call-bind-apply-helpers@1.0.2: + resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} + engines: {node: '>= 0.4'} + + callsites@3.1.0: + resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} + engines: {node: '>=6'} + + caniuse-lite@1.0.30001735: + resolution: {integrity: sha512-EV/laoX7Wq2J9TQlyIXRxTJqIw4sxfXS4OYgudGxBYRuTv0q7AM6yMEpU/Vo1I94thg9U6EZ2NfZx9GJq83u7w==} + + chalk@4.1.2: + resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} + engines: {node: '>=10'} + + color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + + color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + + combined-stream@1.0.8: + resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} + engines: {node: '>= 0.8'} + + concat-map@0.0.1: + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + + convert-source-map@2.0.0: + resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + + copy-anything@3.0.5: + resolution: {integrity: sha512-yCEafptTtb4bk7GLEQoM8KVJpxAfdBJYaXyzQEgQQQgYrZiDp8SJmGKlYza6CYjEDNstAdNdKA3UuoULlEbS6w==} + engines: {node: '>=12.13'} + + cross-spawn@7.0.6: + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} + engines: {node: '>= 8'} + + cssesc@3.0.0: + resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} + engines: {node: '>=4'} + hasBin: true + + csstype@3.1.3: + resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} + + de-indent@1.0.2: + resolution: {integrity: sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==} + + debug@4.4.1: + resolution: {integrity: sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + deep-is@0.1.4: + resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + + default-browser-id@5.0.0: + resolution: {integrity: sha512-A6p/pu/6fyBcA1TRz/GqWYPViplrftcW2gZC9q79ngNCKAeR/X3gcEdXQHl4KNXV+3wgIJ1CPkJQ3IHM6lcsyA==} + engines: {node: '>=18'} + + default-browser@5.2.1: + resolution: {integrity: sha512-WY/3TUME0x3KPYdRRxEJJvXRHV4PyPoUsxtZa78lwItwRQRHhd2U9xOscaT/YTf8uCXIAjeJOFBVEh/7FtD8Xg==} + engines: {node: '>=18'} + + define-lazy-prop@3.0.0: + resolution: {integrity: sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==} + engines: {node: '>=12'} + + delayed-stream@1.0.0: + resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} + engines: {node: '>=0.4.0'} + + dunder-proto@1.0.1: + resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} + engines: {node: '>= 0.4'} + + electron-to-chromium@1.5.207: + resolution: {integrity: sha512-mryFrrL/GXDTmAtIVMVf+eIXM09BBPlO5IQ7lUyKmK8d+A4VpRGG+M3ofoVef6qyF8s60rJei8ymlJxjUA8Faw==} + + entities@4.5.0: + resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} + engines: {node: '>=0.12'} + + error-stack-parser-es@1.0.5: + resolution: {integrity: sha512-5qucVt2XcuGMcEGgWI7i+yZpmpByQ8J1lHhcL7PwqCwu9FPP3VUXzT4ltHe5i2z9dePwEHcDVOAfSnHsOlCXRA==} + + es-define-property@1.0.1: + resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} + engines: {node: '>= 0.4'} + + es-errors@1.3.0: + resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} + engines: {node: '>= 0.4'} + + es-object-atoms@1.1.1: + resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} + engines: {node: '>= 0.4'} + + es-set-tostringtag@2.1.0: + resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} + engines: {node: '>= 0.4'} + + esbuild@0.25.9: + resolution: {integrity: sha512-CRbODhYyQx3qp7ZEwzxOk4JBqmD/seJrzPa/cGjY1VtIn5E09Oi9/dB4JwctnfZ8Q8iT7rioVv5k/FNT/uf54g==} + engines: {node: '>=18'} + hasBin: true + + escalade@3.2.0: + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} + engines: {node: '>=6'} + + escape-string-regexp@4.0.0: + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} + engines: {node: '>=10'} + + eslint-plugin-vue@10.3.0: + resolution: {integrity: sha512-A0u9snqjCfYaPnqqOaH6MBLVWDUIN4trXn8J3x67uDcXvR7X6Ut8p16N+nYhMCQ9Y7edg2BIRGzfyZsY0IdqoQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + '@typescript-eslint/parser': ^7.0.0 || ^8.0.0 + eslint: ^8.57.0 || ^9.0.0 + vue-eslint-parser: ^10.0.0 + peerDependenciesMeta: + '@typescript-eslint/parser': + optional: true + + eslint-scope@8.4.0: + resolution: {integrity: sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + eslint-visitor-keys@3.4.3: + resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + eslint-visitor-keys@4.2.1: + resolution: {integrity: sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + eslint@9.33.0: + resolution: {integrity: sha512-TS9bTNIryDzStCpJN93aC5VRSW3uTx9sClUn4B87pwiCaJh220otoI0X8mJKr+VcPtniMdN8GKjlwgWGUv5ZKA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + hasBin: true + peerDependencies: + jiti: '*' + peerDependenciesMeta: + jiti: + optional: true + + espree@10.4.0: + resolution: {integrity: sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + esquery@1.6.0: + resolution: {integrity: sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==} + engines: {node: '>=0.10'} + + esrecurse@4.3.0: + resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} + engines: {node: '>=4.0'} + + estraverse@5.3.0: + resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} + engines: {node: '>=4.0'} + + estree-walker@2.0.2: + resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} + + esutils@2.0.3: + resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} + engines: {node: '>=0.10.0'} + + execa@9.6.0: + resolution: {integrity: sha512-jpWzZ1ZhwUmeWRhS7Qv3mhpOhLfwI+uAX4e5fOcXqwMR7EcJ0pj2kV1CVzHVMX/LphnKWD3LObjZCoJ71lKpHw==} + engines: {node: ^18.19.0 || >=20.5.0} + + fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + + fast-glob@3.3.3: + resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} + engines: {node: '>=8.6.0'} + + fast-json-stable-stringify@2.1.0: + resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} + + fast-levenshtein@2.0.6: + resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + + fastq@1.19.1: + resolution: {integrity: sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==} + + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + + figures@6.1.0: + resolution: {integrity: sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg==} + engines: {node: '>=18'} + + file-entry-cache@8.0.0: + resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} + engines: {node: '>=16.0.0'} + + fill-range@7.1.1: + resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} + engines: {node: '>=8'} + + find-up@5.0.0: + resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} + engines: {node: '>=10'} + + flat-cache@4.0.1: + resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==} + engines: {node: '>=16'} + + flatted@3.3.3: + resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==} + + follow-redirects@1.15.11: + resolution: {integrity: sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==} + engines: {node: '>=4.0'} + peerDependencies: + debug: '*' + peerDependenciesMeta: + debug: + optional: true + + form-data@4.0.4: + resolution: {integrity: sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==} + engines: {node: '>= 6'} + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + function-bind@1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + + gensync@1.0.0-beta.2: + resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} + engines: {node: '>=6.9.0'} + + get-intrinsic@1.3.0: + resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} + engines: {node: '>= 0.4'} + + get-proto@1.0.1: + resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} + engines: {node: '>= 0.4'} + + get-stream@9.0.1: + resolution: {integrity: sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA==} + engines: {node: '>=18'} + + glob-parent@5.1.2: + resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} + engines: {node: '>= 6'} + + glob-parent@6.0.2: + resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} + engines: {node: '>=10.13.0'} + + globals@14.0.0: + resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} + engines: {node: '>=18'} + + gopd@1.2.0: + resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} + engines: {node: '>= 0.4'} + + graphemer@1.4.0: + resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} + + has-flag@4.0.0: + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} + + has-symbols@1.1.0: + resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} + engines: {node: '>= 0.4'} + + has-tostringtag@1.0.2: + resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} + engines: {node: '>= 0.4'} + + hasown@2.0.2: + resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} + engines: {node: '>= 0.4'} + + he@1.2.0: + resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==} + hasBin: true + + hookable@5.5.3: + resolution: {integrity: sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==} + + human-signals@8.0.1: + resolution: {integrity: sha512-eKCa6bwnJhvxj14kZk5NCPc6Hb6BdsU9DZcOnmQKSnO1VKrfV0zCvtttPZUsBvjmNDn8rpcJfpwSYnHBjc95MQ==} + engines: {node: '>=18.18.0'} + + ignore@5.3.2: + resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} + engines: {node: '>= 4'} + + ignore@7.0.5: + resolution: {integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==} + engines: {node: '>= 4'} + + import-fresh@3.3.1: + resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} + engines: {node: '>=6'} + + imurmurhash@0.1.4: + resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} + engines: {node: '>=0.8.19'} + + is-docker@3.0.0: + resolution: {integrity: sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + hasBin: true + + is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + + is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + + is-inside-container@1.0.0: + resolution: {integrity: sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==} + engines: {node: '>=14.16'} + hasBin: true + + is-number@7.0.0: + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: '>=0.12.0'} + + is-plain-obj@4.1.0: + resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==} + engines: {node: '>=12'} + + is-stream@4.0.1: + resolution: {integrity: sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==} + engines: {node: '>=18'} + + is-unicode-supported@2.1.0: + resolution: {integrity: sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==} + engines: {node: '>=18'} + + is-what@4.1.16: + resolution: {integrity: sha512-ZhMwEosbFJkA0YhFnNDgTM4ZxDRsS6HqTo7qsZM08fehyRYIYa0yHu5R6mgo1n/8MgaPBXiPimPD77baVFYg+A==} + engines: {node: '>=12.13'} + + is-wsl@3.1.0: + resolution: {integrity: sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==} + engines: {node: '>=16'} + + isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + + isexe@3.1.1: + resolution: {integrity: sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==} + engines: {node: '>=16'} + + jiti@2.5.1: + resolution: {integrity: sha512-twQoecYPiVA5K/h6SxtORw/Bs3ar+mLUtoPSc7iMXzQzK8d7eJ/R09wmTwAjiamETn1cXYPGfNnu7DMoHgu12w==} + hasBin: true + + js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + + js-yaml@4.1.0: + resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} + hasBin: true + + jsesc@3.1.0: + resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} + engines: {node: '>=6'} + hasBin: true + + json-buffer@3.0.1: + resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} + + json-parse-even-better-errors@4.0.0: + resolution: {integrity: sha512-lR4MXjGNgkJc7tkQ97kb2nuEMnNCyU//XYVH0MKTGcXEiSudQ5MKGKen3C5QubYy0vmq+JGitUg92uuywGEwIA==} + engines: {node: ^18.17.0 || >=20.5.0} + + json-schema-traverse@0.4.1: + resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} + + json-stable-stringify-without-jsonify@1.0.1: + resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} + + json5@2.2.3: + resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} + engines: {node: '>=6'} + hasBin: true + + keyv@4.5.4: + resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} + + kolorist@1.8.0: + resolution: {integrity: sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==} + + levn@0.4.1: + resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} + engines: {node: '>= 0.8.0'} + + locate-path@6.0.0: + resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} + engines: {node: '>=10'} + + lodash.merge@4.6.2: + resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} + + lru-cache@5.1.1: + resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + + magic-string@0.30.17: + resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==} + + math-intrinsics@1.1.0: + resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} + engines: {node: '>= 0.4'} + + memorystream@0.3.1: + resolution: {integrity: sha512-S3UwM3yj5mtUSEfP41UZmt/0SCoVYUcU1rkXv+BQ5Ig8ndL4sPoJNBUJERafdPb5jjHJGuMgytgKvKIf58XNBw==} + engines: {node: '>= 0.10.0'} + + merge2@1.4.1: + resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} + engines: {node: '>= 8'} + + micromatch@4.0.8: + resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} + engines: {node: '>=8.6'} + + mime-db@1.52.0: + resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} + engines: {node: '>= 0.6'} + + mime-types@2.1.35: + resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} + engines: {node: '>= 0.6'} + + minimatch@3.1.2: + resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} + + minimatch@9.0.5: + resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} + engines: {node: '>=16 || 14 >=14.17'} + + mitt@3.0.1: + resolution: {integrity: sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==} + + mrmime@2.0.1: + resolution: {integrity: sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==} + engines: {node: '>=10'} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + muggle-string@0.4.1: + resolution: {integrity: sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==} + + nanoid@3.3.11: + resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + nanoid@5.1.5: + resolution: {integrity: sha512-Ir/+ZpE9fDsNH0hQ3C68uyThDXzYcim2EqcZ8zn8Chtt1iylPT9xXJB0kPCnqzgcEGikO9RxSrh63MsmVCU7Fw==} + engines: {node: ^18 || >=20} + hasBin: true + + natural-compare@1.4.0: + resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + + node-releases@2.0.19: + resolution: {integrity: sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==} + + npm-normalize-package-bin@4.0.0: + resolution: {integrity: sha512-TZKxPvItzai9kN9H/TkmCtx/ZN/hvr3vUycjlfmH0ootY9yFBzNOpiXAdIn1Iteqsvk4lQn6B5PTrt+n6h8k/w==} + engines: {node: ^18.17.0 || >=20.5.0} + + npm-run-all2@8.0.4: + resolution: {integrity: sha512-wdbB5My48XKp2ZfJUlhnLVihzeuA1hgBnqB2J9ahV77wLS+/YAJAlN8I+X3DIFIPZ3m5L7nplmlbhNiFDmXRDA==} + engines: {node: ^20.5.0 || >=22.0.0, npm: '>= 10'} + hasBin: true + + npm-run-path@6.0.0: + resolution: {integrity: sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA==} + engines: {node: '>=18'} + + nth-check@2.1.1: + resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==} + + ohash@2.0.11: + resolution: {integrity: sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==} + + open@10.2.0: + resolution: {integrity: sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA==} + engines: {node: '>=18'} + + optionator@0.9.4: + resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} + engines: {node: '>= 0.8.0'} + + p-limit@3.1.0: + resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} + engines: {node: '>=10'} + + p-locate@5.0.0: + resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} + engines: {node: '>=10'} + + parent-module@1.0.1: + resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} + engines: {node: '>=6'} + + parse-ms@4.0.0: + resolution: {integrity: sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw==} + engines: {node: '>=18'} + + path-browserify@1.0.1: + resolution: {integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==} + + path-exists@4.0.0: + resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} + engines: {node: '>=8'} + + path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + + path-key@4.0.0: + resolution: {integrity: sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==} + engines: {node: '>=12'} + + pathe@2.0.3: + resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} + + perfect-debounce@1.0.0: + resolution: {integrity: sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + picomatch@2.3.1: + resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} + engines: {node: '>=8.6'} + + picomatch@4.0.3: + resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} + engines: {node: '>=12'} + + pidtree@0.6.0: + resolution: {integrity: sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g==} + engines: {node: '>=0.10'} + hasBin: true + + pinia@3.0.3: + resolution: {integrity: sha512-ttXO/InUULUXkMHpTdp9Fj4hLpD/2AoJdmAbAeW2yu1iy1k+pkFekQXw5VpC0/5p51IOR/jDaDRfRWRnMMsGOA==} + peerDependencies: + typescript: '>=4.4.4' + vue: ^2.7.0 || ^3.5.11 + peerDependenciesMeta: + typescript: + optional: true + + postcss-selector-parser@6.1.2: + resolution: {integrity: sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==} + engines: {node: '>=4'} + + postcss@8.5.6: + resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==} + engines: {node: ^10 || ^12 || >=14} + + prelude-ls@1.2.1: + resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} + engines: {node: '>= 0.8.0'} + + pretty-ms@9.2.0: + resolution: {integrity: sha512-4yf0QO/sllf/1zbZWYnvWw3NxCQwLXKzIj0G849LSufP15BXKM0rbD2Z3wVnkMfjdn/CB0Dpp444gYAACdsplg==} + engines: {node: '>=18'} + + proxy-from-env@1.1.0: + resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} + + punycode@2.3.1: + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} + engines: {node: '>=6'} + + queue-microtask@1.2.3: + resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + + read-package-json-fast@4.0.0: + resolution: {integrity: sha512-qpt8EwugBWDw2cgE2W+/3oxC+KTez2uSVR8JU9Q36TXPAGCaozfQUs59v4j4GFpWTaw0i6hAZSvOmu1J0uOEUg==} + engines: {node: ^18.17.0 || >=20.5.0} + + resolve-from@4.0.0: + resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} + engines: {node: '>=4'} + + reusify@1.1.0: + resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} + engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + + rfdc@1.4.1: + resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==} + + rollup@4.46.4: + resolution: {integrity: sha512-YbxoxvoqNg9zAmw4+vzh1FkGAiZRK+LhnSrbSrSXMdZYsRPDWoshcSd/pldKRO6lWzv/e9TiJAVQyirYIeSIPQ==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true + + run-applescript@7.0.0: + resolution: {integrity: sha512-9by4Ij99JUr/MCFBUkDKLWK3G9HVXmabKz9U5MlIAIuvuzkiOicRYs8XJLxX+xahD+mLiiCYDqF9dKAgtzKP1A==} + engines: {node: '>=18'} + + run-parallel@1.2.0: + resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + + semver@6.3.1: + resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} + hasBin: true + + semver@7.7.2: + resolution: {integrity: sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==} + engines: {node: '>=10'} + hasBin: true + + shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + + shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + + shell-quote@1.8.3: + resolution: {integrity: sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==} + engines: {node: '>= 0.4'} + + signal-exit@4.1.0: + resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} + engines: {node: '>=14'} + + sirv@3.0.1: + resolution: {integrity: sha512-FoqMu0NCGBLCcAkS1qA+XJIQTR6/JHfQXl+uGteNCQ76T91DMUjPa9xfmeqMY3z80nLSg9yQmNjK0Px6RWsH/A==} + engines: {node: '>=18'} + + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + + speakingurl@14.0.1: + resolution: {integrity: sha512-1POYv7uv2gXoyGFpBCmpDVSNV74IfsWlDW216UPjbWufNf+bSU6GdbDsxdcxtfwb4xlI3yxzOTKClUosxARYrQ==} + engines: {node: '>=0.10.0'} + + strip-final-newline@4.0.0: + resolution: {integrity: sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw==} + engines: {node: '>=18'} + + strip-json-comments@3.1.1: + resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} + engines: {node: '>=8'} + + superjson@2.2.2: + resolution: {integrity: sha512-5JRxVqC8I8NuOUjzBbvVJAKNM8qoVuH0O77h4WInc/qC2q5IreqKxYwgkga3PfA22OayK2ikceb/B26dztPl+Q==} + engines: {node: '>=16'} + + supports-color@7.2.0: + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} + engines: {node: '>=8'} + + tinyglobby@0.2.14: + resolution: {integrity: sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==} + engines: {node: '>=12.0.0'} + + to-regex-range@5.0.1: + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} + engines: {node: '>=8.0'} + + totalist@3.0.1: + resolution: {integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==} + engines: {node: '>=6'} + + ts-api-utils@2.1.0: + resolution: {integrity: sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==} + engines: {node: '>=18.12'} + peerDependencies: + typescript: '>=4.8.4' + + type-check@0.4.0: + resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} + engines: {node: '>= 0.8.0'} + + typescript-eslint@8.40.0: + resolution: {integrity: sha512-Xvd2l+ZmFDPEt4oj1QEXzA4A2uUK6opvKu3eGN9aGjB8au02lIVcLyi375w94hHyejTOmzIU77L8ol2sRg9n7Q==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <6.0.0' + + typescript@5.8.3: + resolution: {integrity: sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==} + engines: {node: '>=14.17'} + hasBin: true + + undici-types@6.21.0: + resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} + + unicorn-magic@0.3.0: + resolution: {integrity: sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==} + engines: {node: '>=18'} + + unplugin-utils@0.2.5: + resolution: {integrity: sha512-gwXJnPRewT4rT7sBi/IvxKTjsms7jX7QIDLOClApuZwR49SXbrB1z2NLUZ+vDHyqCj/n58OzRRqaW+B8OZi8vg==} + engines: {node: '>=18.12.0'} + + update-browserslist-db@1.1.3: + resolution: {integrity: sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==} + hasBin: true + peerDependencies: + browserslist: '>= 4.21.0' + + uri-js@4.4.1: + resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + + util-deprecate@1.0.2: + resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + + vite-dev-rpc@1.1.0: + resolution: {integrity: sha512-pKXZlgoXGoE8sEKiKJSng4hI1sQ4wi5YT24FCrwrLt6opmkjlqPPVmiPWWJn8M8byMxRGzp1CrFuqQs4M/Z39A==} + peerDependencies: + vite: ^2.9.0 || ^3.0.0-0 || ^4.0.0-0 || ^5.0.0-0 || ^6.0.1 || ^7.0.0-0 + + vite-hot-client@2.1.0: + resolution: {integrity: sha512-7SpgZmU7R+dDnSmvXE1mfDtnHLHQSisdySVR7lO8ceAXvM0otZeuQQ6C8LrS5d/aYyP/QZ0hI0L+dIPrm4YlFQ==} + peerDependencies: + vite: ^2.6.0 || ^3.0.0 || ^4.0.0 || ^5.0.0-0 || ^6.0.0-0 || ^7.0.0-0 + + vite-plugin-inspect@11.3.2: + resolution: {integrity: sha512-nzwvyFQg58XSMAmKVLr2uekAxNYvAbz1lyPmCAFVIBncCgN9S/HPM+2UM9Q9cvc4JEbC5ZBgwLAdaE2onmQuKg==} + engines: {node: '>=14'} + peerDependencies: + '@nuxt/kit': '*' + vite: ^6.0.0 || ^7.0.0-0 + peerDependenciesMeta: + '@nuxt/kit': + optional: true + + vite-plugin-vue-devtools@8.0.0: + resolution: {integrity: sha512-9bWQig8UMu3nPbxX86NJv56aelpFYoBHxB5+pxuQz3pa3Tajc1ezRidj/0dnADA4/UHuVIfwIVYHOvMXYcPshg==} + engines: {node: '>=v14.21.3'} + peerDependencies: + vite: ^6.0.0 || ^7.0.0-0 + + vite-plugin-vue-inspector@5.3.2: + resolution: {integrity: sha512-YvEKooQcSiBTAs0DoYLfefNja9bLgkFM7NI2b07bE2SruuvX0MEa9cMaxjKVMkeCp5Nz9FRIdcN1rOdFVBeL6Q==} + peerDependencies: + vite: ^3.0.0-0 || ^4.0.0-0 || ^5.0.0-0 || ^6.0.0-0 || ^7.0.0-0 + + vite@7.1.3: + resolution: {integrity: sha512-OOUi5zjkDxYrKhTV3V7iKsoS37VUM7v40+HuwEmcrsf11Cdx9y3DIr2Px6liIcZFwt3XSRpQvFpL3WVy7ApkGw==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + peerDependencies: + '@types/node': ^20.19.0 || >=22.12.0 + jiti: '>=1.21.0' + less: ^4.0.0 + lightningcss: ^1.21.0 + sass: ^1.70.0 + sass-embedded: ^1.70.0 + stylus: '>=0.54.8' + sugarss: ^5.0.0 + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + '@types/node': + optional: true + jiti: + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + + vscode-uri@3.1.0: + resolution: {integrity: sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==} + + vue-eslint-parser@10.2.0: + resolution: {integrity: sha512-CydUvFOQKD928UzZhTp4pr2vWz1L+H99t7Pkln2QSPdvmURT0MoC4wUccfCnuEaihNsu9aYYyk+bep8rlfkUXw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + + vue-router@4.5.1: + resolution: {integrity: sha512-ogAF3P97NPm8fJsE4by9dwSYtDwXIY1nFY9T6DyQnGHd1E2Da94w9JIolpe42LJGIl0DwOHBi8TcRPlPGwbTtw==} + peerDependencies: + vue: ^3.2.0 + + vue-tsc@3.0.6: + resolution: {integrity: sha512-Tbs8Whd43R2e2nxez4WXPvvdjGbW24rOSgRhLOHXzWiT4pcP4G7KeWh0YCn18rF4bVwv7tggLLZ6MJnO6jXPBg==} + hasBin: true + peerDependencies: + typescript: '>=5.0.0' + + vue@3.5.18: + resolution: {integrity: sha512-7W4Y4ZbMiQ3SEo+m9lnoNpV9xG7QVMLa+/0RFwwiAVkeYoyGXqWE85jabU4pllJNUzqfLShJ5YLptewhCWUgNA==} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + + which@5.0.0: + resolution: {integrity: sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ==} + engines: {node: ^18.17.0 || >=20.5.0} + hasBin: true + + word-wrap@1.2.5: + resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} + engines: {node: '>=0.10.0'} + + wsl-utils@0.1.0: + resolution: {integrity: sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw==} + engines: {node: '>=18'} + + xml-name-validator@4.0.0: + resolution: {integrity: sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==} + engines: {node: '>=12'} + + yallist@3.1.1: + resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + + yocto-queue@0.1.0: + resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} + engines: {node: '>=10'} + + yoctocolors@2.1.2: + resolution: {integrity: sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug==} + engines: {node: '>=18'} + +snapshots: + + '@ampproject/remapping@2.3.0': + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.30 + + '@babel/code-frame@7.27.1': + dependencies: + '@babel/helper-validator-identifier': 7.27.1 + js-tokens: 4.0.0 + picocolors: 1.1.1 + + '@babel/compat-data@7.28.0': {} + + '@babel/core@7.28.3': + dependencies: + '@ampproject/remapping': 2.3.0 + '@babel/code-frame': 7.27.1 + '@babel/generator': 7.28.3 + '@babel/helper-compilation-targets': 7.27.2 + '@babel/helper-module-transforms': 7.28.3(@babel/core@7.28.3) + '@babel/helpers': 7.28.3 + '@babel/parser': 7.28.3 + '@babel/template': 7.27.2 + '@babel/traverse': 7.28.3 + '@babel/types': 7.28.2 + convert-source-map: 2.0.0 + debug: 4.4.1 + gensync: 1.0.0-beta.2 + json5: 2.2.3 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/generator@7.28.3': + dependencies: + '@babel/parser': 7.28.3 + '@babel/types': 7.28.2 + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.30 + jsesc: 3.1.0 + + '@babel/helper-annotate-as-pure@7.27.3': + dependencies: + '@babel/types': 7.28.2 + + '@babel/helper-compilation-targets@7.27.2': + dependencies: + '@babel/compat-data': 7.28.0 + '@babel/helper-validator-option': 7.27.1 + browserslist: 4.25.3 + lru-cache: 5.1.1 + semver: 6.3.1 + + '@babel/helper-create-class-features-plugin@7.28.3(@babel/core@7.28.3)': + dependencies: + '@babel/core': 7.28.3 + '@babel/helper-annotate-as-pure': 7.27.3 + '@babel/helper-member-expression-to-functions': 7.27.1 + '@babel/helper-optimise-call-expression': 7.27.1 + '@babel/helper-replace-supers': 7.27.1(@babel/core@7.28.3) + '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 + '@babel/traverse': 7.28.3 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/helper-globals@7.28.0': {} + + '@babel/helper-member-expression-to-functions@7.27.1': + dependencies: + '@babel/traverse': 7.28.3 + '@babel/types': 7.28.2 + transitivePeerDependencies: + - supports-color + + '@babel/helper-module-imports@7.27.1': + dependencies: + '@babel/traverse': 7.28.3 + '@babel/types': 7.28.2 + transitivePeerDependencies: + - supports-color + + '@babel/helper-module-transforms@7.28.3(@babel/core@7.28.3)': + dependencies: + '@babel/core': 7.28.3 + '@babel/helper-module-imports': 7.27.1 + '@babel/helper-validator-identifier': 7.27.1 + '@babel/traverse': 7.28.3 + transitivePeerDependencies: + - supports-color + + '@babel/helper-optimise-call-expression@7.27.1': + dependencies: + '@babel/types': 7.28.2 + + '@babel/helper-plugin-utils@7.27.1': {} + + '@babel/helper-replace-supers@7.27.1(@babel/core@7.28.3)': + dependencies: + '@babel/core': 7.28.3 + '@babel/helper-member-expression-to-functions': 7.27.1 + '@babel/helper-optimise-call-expression': 7.27.1 + '@babel/traverse': 7.28.3 + transitivePeerDependencies: + - supports-color + + '@babel/helper-skip-transparent-expression-wrappers@7.27.1': + dependencies: + '@babel/traverse': 7.28.3 + '@babel/types': 7.28.2 + transitivePeerDependencies: + - supports-color + + '@babel/helper-string-parser@7.27.1': {} + + '@babel/helper-validator-identifier@7.27.1': {} + + '@babel/helper-validator-option@7.27.1': {} + + '@babel/helpers@7.28.3': + dependencies: + '@babel/template': 7.27.2 + '@babel/types': 7.28.2 + + '@babel/parser@7.28.3': + dependencies: + '@babel/types': 7.28.2 + + '@babel/plugin-proposal-decorators@7.28.0(@babel/core@7.28.3)': + dependencies: + '@babel/core': 7.28.3 + '@babel/helper-create-class-features-plugin': 7.28.3(@babel/core@7.28.3) + '@babel/helper-plugin-utils': 7.27.1 + '@babel/plugin-syntax-decorators': 7.27.1(@babel/core@7.28.3) + transitivePeerDependencies: + - supports-color + + '@babel/plugin-syntax-decorators@7.27.1(@babel/core@7.28.3)': + dependencies: + '@babel/core': 7.28.3 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-syntax-import-attributes@7.27.1(@babel/core@7.28.3)': + dependencies: + '@babel/core': 7.28.3 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.28.3)': + dependencies: + '@babel/core': 7.28.3 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-syntax-jsx@7.27.1(@babel/core@7.28.3)': + dependencies: + '@babel/core': 7.28.3 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-syntax-typescript@7.27.1(@babel/core@7.28.3)': + dependencies: + '@babel/core': 7.28.3 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-transform-typescript@7.28.0(@babel/core@7.28.3)': + dependencies: + '@babel/core': 7.28.3 + '@babel/helper-annotate-as-pure': 7.27.3 + '@babel/helper-create-class-features-plugin': 7.28.3(@babel/core@7.28.3) + '@babel/helper-plugin-utils': 7.27.1 + '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 + '@babel/plugin-syntax-typescript': 7.27.1(@babel/core@7.28.3) + transitivePeerDependencies: + - supports-color + + '@babel/template@7.27.2': + dependencies: + '@babel/code-frame': 7.27.1 + '@babel/parser': 7.28.3 + '@babel/types': 7.28.2 + + '@babel/traverse@7.28.3': + dependencies: + '@babel/code-frame': 7.27.1 + '@babel/generator': 7.28.3 + '@babel/helper-globals': 7.28.0 + '@babel/parser': 7.28.3 + '@babel/template': 7.27.2 + '@babel/types': 7.28.2 + debug: 4.4.1 + transitivePeerDependencies: + - supports-color + + '@babel/types@7.28.2': + dependencies: + '@babel/helper-string-parser': 7.27.1 + '@babel/helper-validator-identifier': 7.27.1 + + '@esbuild/aix-ppc64@0.25.9': + optional: true + + '@esbuild/android-arm64@0.25.9': + optional: true + + '@esbuild/android-arm@0.25.9': + optional: true + + '@esbuild/android-x64@0.25.9': + optional: true + + '@esbuild/darwin-arm64@0.25.9': + optional: true + + '@esbuild/darwin-x64@0.25.9': + optional: true + + '@esbuild/freebsd-arm64@0.25.9': + optional: true + + '@esbuild/freebsd-x64@0.25.9': + optional: true + + '@esbuild/linux-arm64@0.25.9': + optional: true + + '@esbuild/linux-arm@0.25.9': + optional: true + + '@esbuild/linux-ia32@0.25.9': + optional: true + + '@esbuild/linux-loong64@0.25.9': + optional: true + + '@esbuild/linux-mips64el@0.25.9': + optional: true + + '@esbuild/linux-ppc64@0.25.9': + optional: true + + '@esbuild/linux-riscv64@0.25.9': + optional: true + + '@esbuild/linux-s390x@0.25.9': + optional: true + + '@esbuild/linux-x64@0.25.9': + optional: true + + '@esbuild/netbsd-arm64@0.25.9': + optional: true + + '@esbuild/netbsd-x64@0.25.9': + optional: true + + '@esbuild/openbsd-arm64@0.25.9': + optional: true + + '@esbuild/openbsd-x64@0.25.9': + optional: true + + '@esbuild/openharmony-arm64@0.25.9': + optional: true + + '@esbuild/sunos-x64@0.25.9': + optional: true + + '@esbuild/win32-arm64@0.25.9': + optional: true + + '@esbuild/win32-ia32@0.25.9': + optional: true + + '@esbuild/win32-x64@0.25.9': + optional: true + + '@eslint-community/eslint-utils@4.7.0(eslint@9.33.0(jiti@2.5.1))': + dependencies: + eslint: 9.33.0(jiti@2.5.1) + eslint-visitor-keys: 3.4.3 + + '@eslint-community/regexpp@4.12.1': {} + + '@eslint/config-array@0.21.0': + dependencies: + '@eslint/object-schema': 2.1.6 + debug: 4.4.1 + minimatch: 3.1.2 + transitivePeerDependencies: + - supports-color + + '@eslint/config-helpers@0.3.1': {} + + '@eslint/core@0.15.2': + dependencies: + '@types/json-schema': 7.0.15 + + '@eslint/eslintrc@3.3.1': + dependencies: + ajv: 6.12.6 + debug: 4.4.1 + espree: 10.4.0 + globals: 14.0.0 + ignore: 5.3.2 + import-fresh: 3.3.1 + js-yaml: 4.1.0 + minimatch: 3.1.2 + strip-json-comments: 3.1.1 + transitivePeerDependencies: + - supports-color + + '@eslint/js@9.33.0': {} + + '@eslint/object-schema@2.1.6': {} + + '@eslint/plugin-kit@0.3.5': + dependencies: + '@eslint/core': 0.15.2 + levn: 0.4.1 + + '@humanfs/core@0.19.1': {} + + '@humanfs/node@0.16.6': + dependencies: + '@humanfs/core': 0.19.1 + '@humanwhocodes/retry': 0.3.1 + + '@humanwhocodes/module-importer@1.0.1': {} + + '@humanwhocodes/retry@0.3.1': {} + + '@humanwhocodes/retry@0.4.3': {} + + '@jridgewell/gen-mapping@0.3.13': + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + '@jridgewell/trace-mapping': 0.3.30 + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/sourcemap-codec@1.5.5': {} + + '@jridgewell/trace-mapping@0.3.30': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.5 + + '@nodelib/fs.scandir@2.1.5': + dependencies: + '@nodelib/fs.stat': 2.0.5 + run-parallel: 1.2.0 + + '@nodelib/fs.stat@2.0.5': {} + + '@nodelib/fs.walk@1.2.8': + dependencies: + '@nodelib/fs.scandir': 2.1.5 + fastq: 1.19.1 + + '@polka/url@1.0.0-next.29': {} + + '@rolldown/pluginutils@1.0.0-beta.29': {} + + '@rollup/rollup-android-arm-eabi@4.46.4': + optional: true + + '@rollup/rollup-android-arm64@4.46.4': + optional: true + + '@rollup/rollup-darwin-arm64@4.46.4': + optional: true + + '@rollup/rollup-darwin-x64@4.46.4': + optional: true + + '@rollup/rollup-freebsd-arm64@4.46.4': + optional: true + + '@rollup/rollup-freebsd-x64@4.46.4': + optional: true + + '@rollup/rollup-linux-arm-gnueabihf@4.46.4': + optional: true + + '@rollup/rollup-linux-arm-musleabihf@4.46.4': + optional: true + + '@rollup/rollup-linux-arm64-gnu@4.46.4': + optional: true + + '@rollup/rollup-linux-arm64-musl@4.46.4': + optional: true + + '@rollup/rollup-linux-loongarch64-gnu@4.46.4': + optional: true + + '@rollup/rollup-linux-ppc64-gnu@4.46.4': + optional: true + + '@rollup/rollup-linux-riscv64-gnu@4.46.4': + optional: true + + '@rollup/rollup-linux-riscv64-musl@4.46.4': + optional: true + + '@rollup/rollup-linux-s390x-gnu@4.46.4': + optional: true + + '@rollup/rollup-linux-x64-gnu@4.46.4': + optional: true + + '@rollup/rollup-linux-x64-musl@4.46.4': + optional: true + + '@rollup/rollup-win32-arm64-msvc@4.46.4': + optional: true + + '@rollup/rollup-win32-ia32-msvc@4.46.4': + optional: true + + '@rollup/rollup-win32-x64-msvc@4.46.4': + optional: true + + '@sec-ant/readable-stream@0.4.1': {} + + '@sindresorhus/merge-streams@4.0.0': {} + + '@tsconfig/node22@22.0.2': {} + + '@types/estree@1.0.8': {} + + '@types/json-schema@7.0.15': {} + + '@types/node@22.17.2': + dependencies: + undici-types: 6.21.0 + + '@typescript-eslint/eslint-plugin@8.40.0(@typescript-eslint/parser@8.40.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.8.3))(eslint@9.33.0(jiti@2.5.1))(typescript@5.8.3)': + dependencies: + '@eslint-community/regexpp': 4.12.1 + '@typescript-eslint/parser': 8.40.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.8.3) + '@typescript-eslint/scope-manager': 8.40.0 + '@typescript-eslint/type-utils': 8.40.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.8.3) + '@typescript-eslint/utils': 8.40.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.8.3) + '@typescript-eslint/visitor-keys': 8.40.0 + eslint: 9.33.0(jiti@2.5.1) + graphemer: 1.4.0 + ignore: 7.0.5 + natural-compare: 1.4.0 + ts-api-utils: 2.1.0(typescript@5.8.3) + typescript: 5.8.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/parser@8.40.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.8.3)': + dependencies: + '@typescript-eslint/scope-manager': 8.40.0 + '@typescript-eslint/types': 8.40.0 + '@typescript-eslint/typescript-estree': 8.40.0(typescript@5.8.3) + '@typescript-eslint/visitor-keys': 8.40.0 + debug: 4.4.1 + eslint: 9.33.0(jiti@2.5.1) + typescript: 5.8.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/project-service@8.40.0(typescript@5.8.3)': + dependencies: + '@typescript-eslint/tsconfig-utils': 8.40.0(typescript@5.8.3) + '@typescript-eslint/types': 8.40.0 + debug: 4.4.1 + typescript: 5.8.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/scope-manager@8.40.0': + dependencies: + '@typescript-eslint/types': 8.40.0 + '@typescript-eslint/visitor-keys': 8.40.0 + + '@typescript-eslint/tsconfig-utils@8.40.0(typescript@5.8.3)': + dependencies: + typescript: 5.8.3 + + '@typescript-eslint/type-utils@8.40.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.8.3)': + dependencies: + '@typescript-eslint/types': 8.40.0 + '@typescript-eslint/typescript-estree': 8.40.0(typescript@5.8.3) + '@typescript-eslint/utils': 8.40.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.8.3) + debug: 4.4.1 + eslint: 9.33.0(jiti@2.5.1) + ts-api-utils: 2.1.0(typescript@5.8.3) + typescript: 5.8.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/types@8.40.0': {} + + '@typescript-eslint/typescript-estree@8.40.0(typescript@5.8.3)': + dependencies: + '@typescript-eslint/project-service': 8.40.0(typescript@5.8.3) + '@typescript-eslint/tsconfig-utils': 8.40.0(typescript@5.8.3) + '@typescript-eslint/types': 8.40.0 + '@typescript-eslint/visitor-keys': 8.40.0 + debug: 4.4.1 + fast-glob: 3.3.3 + is-glob: 4.0.3 + minimatch: 9.0.5 + semver: 7.7.2 + ts-api-utils: 2.1.0(typescript@5.8.3) + typescript: 5.8.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/utils@8.40.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.8.3)': + dependencies: + '@eslint-community/eslint-utils': 4.7.0(eslint@9.33.0(jiti@2.5.1)) + '@typescript-eslint/scope-manager': 8.40.0 + '@typescript-eslint/types': 8.40.0 + '@typescript-eslint/typescript-estree': 8.40.0(typescript@5.8.3) + eslint: 9.33.0(jiti@2.5.1) + typescript: 5.8.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/visitor-keys@8.40.0': + dependencies: + '@typescript-eslint/types': 8.40.0 + eslint-visitor-keys: 4.2.1 + + '@vitejs/plugin-vue@6.0.1(vite@7.1.3(@types/node@22.17.2)(jiti@2.5.1))(vue@3.5.18(typescript@5.8.3))': + dependencies: + '@rolldown/pluginutils': 1.0.0-beta.29 + vite: 7.1.3(@types/node@22.17.2)(jiti@2.5.1) + vue: 3.5.18(typescript@5.8.3) + + '@volar/language-core@2.4.23': + dependencies: + '@volar/source-map': 2.4.23 + + '@volar/source-map@2.4.23': {} + + '@volar/typescript@2.4.23': + dependencies: + '@volar/language-core': 2.4.23 + path-browserify: 1.0.1 + vscode-uri: 3.1.0 + + '@vue/babel-helper-vue-transform-on@1.5.0': {} + + '@vue/babel-plugin-jsx@1.5.0(@babel/core@7.28.3)': + dependencies: + '@babel/helper-module-imports': 7.27.1 + '@babel/helper-plugin-utils': 7.27.1 + '@babel/plugin-syntax-jsx': 7.27.1(@babel/core@7.28.3) + '@babel/template': 7.27.2 + '@babel/traverse': 7.28.3 + '@babel/types': 7.28.2 + '@vue/babel-helper-vue-transform-on': 1.5.0 + '@vue/babel-plugin-resolve-type': 1.5.0(@babel/core@7.28.3) + '@vue/shared': 3.5.18 + optionalDependencies: + '@babel/core': 7.28.3 + transitivePeerDependencies: + - supports-color + + '@vue/babel-plugin-resolve-type@1.5.0(@babel/core@7.28.3)': + dependencies: + '@babel/code-frame': 7.27.1 + '@babel/core': 7.28.3 + '@babel/helper-module-imports': 7.27.1 + '@babel/helper-plugin-utils': 7.27.1 + '@babel/parser': 7.28.3 + '@vue/compiler-sfc': 3.5.18 + transitivePeerDependencies: + - supports-color + + '@vue/compiler-core@3.5.18': + dependencies: + '@babel/parser': 7.28.3 + '@vue/shared': 3.5.18 + entities: 4.5.0 + estree-walker: 2.0.2 + source-map-js: 1.2.1 + + '@vue/compiler-dom@3.5.18': + dependencies: + '@vue/compiler-core': 3.5.18 + '@vue/shared': 3.5.18 + + '@vue/compiler-sfc@3.5.18': + dependencies: + '@babel/parser': 7.28.3 + '@vue/compiler-core': 3.5.18 + '@vue/compiler-dom': 3.5.18 + '@vue/compiler-ssr': 3.5.18 + '@vue/shared': 3.5.18 + estree-walker: 2.0.2 + magic-string: 0.30.17 + postcss: 8.5.6 + source-map-js: 1.2.1 + + '@vue/compiler-ssr@3.5.18': + dependencies: + '@vue/compiler-dom': 3.5.18 + '@vue/shared': 3.5.18 + + '@vue/compiler-vue2@2.7.16': + dependencies: + de-indent: 1.0.2 + he: 1.2.0 + + '@vue/devtools-api@6.6.4': {} + + '@vue/devtools-api@7.7.7': + dependencies: + '@vue/devtools-kit': 7.7.7 + + '@vue/devtools-core@8.0.0(vite@7.1.3(@types/node@22.17.2)(jiti@2.5.1))(vue@3.5.18(typescript@5.8.3))': + dependencies: + '@vue/devtools-kit': 8.0.0 + '@vue/devtools-shared': 8.0.0 + mitt: 3.0.1 + nanoid: 5.1.5 + pathe: 2.0.3 + vite-hot-client: 2.1.0(vite@7.1.3(@types/node@22.17.2)(jiti@2.5.1)) + vue: 3.5.18(typescript@5.8.3) + transitivePeerDependencies: + - vite + + '@vue/devtools-kit@7.7.7': + dependencies: + '@vue/devtools-shared': 7.7.7 + birpc: 2.5.0 + hookable: 5.5.3 + mitt: 3.0.1 + perfect-debounce: 1.0.0 + speakingurl: 14.0.1 + superjson: 2.2.2 + + '@vue/devtools-kit@8.0.0': + dependencies: + '@vue/devtools-shared': 8.0.0 + birpc: 2.5.0 + hookable: 5.5.3 + mitt: 3.0.1 + perfect-debounce: 1.0.0 + speakingurl: 14.0.1 + superjson: 2.2.2 + + '@vue/devtools-shared@7.7.7': + dependencies: + rfdc: 1.4.1 + + '@vue/devtools-shared@8.0.0': + dependencies: + rfdc: 1.4.1 + + '@vue/eslint-config-typescript@14.6.0(eslint-plugin-vue@10.3.0(@typescript-eslint/parser@8.40.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.8.3))(eslint@9.33.0(jiti@2.5.1))(vue-eslint-parser@10.2.0(eslint@9.33.0(jiti@2.5.1))))(eslint@9.33.0(jiti@2.5.1))(typescript@5.8.3)': + dependencies: + '@typescript-eslint/utils': 8.40.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.8.3) + eslint: 9.33.0(jiti@2.5.1) + eslint-plugin-vue: 10.3.0(@typescript-eslint/parser@8.40.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.8.3))(eslint@9.33.0(jiti@2.5.1))(vue-eslint-parser@10.2.0(eslint@9.33.0(jiti@2.5.1))) + fast-glob: 3.3.3 + typescript-eslint: 8.40.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.8.3) + vue-eslint-parser: 10.2.0(eslint@9.33.0(jiti@2.5.1)) + optionalDependencies: + typescript: 5.8.3 + transitivePeerDependencies: + - supports-color + + '@vue/language-core@3.0.6(typescript@5.8.3)': + dependencies: + '@volar/language-core': 2.4.23 + '@vue/compiler-dom': 3.5.18 + '@vue/compiler-vue2': 2.7.16 + '@vue/shared': 3.5.18 + alien-signals: 2.0.7 + muggle-string: 0.4.1 + path-browserify: 1.0.1 + picomatch: 4.0.3 + optionalDependencies: + typescript: 5.8.3 + + '@vue/reactivity@3.5.18': + dependencies: + '@vue/shared': 3.5.18 + + '@vue/runtime-core@3.5.18': + dependencies: + '@vue/reactivity': 3.5.18 + '@vue/shared': 3.5.18 + + '@vue/runtime-dom@3.5.18': + dependencies: + '@vue/reactivity': 3.5.18 + '@vue/runtime-core': 3.5.18 + '@vue/shared': 3.5.18 + csstype: 3.1.3 + + '@vue/server-renderer@3.5.18(vue@3.5.18(typescript@5.8.3))': + dependencies: + '@vue/compiler-ssr': 3.5.18 + '@vue/shared': 3.5.18 + vue: 3.5.18(typescript@5.8.3) + + '@vue/shared@3.5.18': {} + + '@vue/tsconfig@0.7.0(typescript@5.8.3)(vue@3.5.18(typescript@5.8.3))': + optionalDependencies: + typescript: 5.8.3 + vue: 3.5.18(typescript@5.8.3) + + acorn-jsx@5.3.2(acorn@8.15.0): + dependencies: + acorn: 8.15.0 + + acorn@8.15.0: {} + + ajv@6.12.6: + dependencies: + fast-deep-equal: 3.1.3 + fast-json-stable-stringify: 2.1.0 + json-schema-traverse: 0.4.1 + uri-js: 4.4.1 + + alien-signals@2.0.7: {} + + ansi-styles@4.3.0: + dependencies: + color-convert: 2.0.1 + + ansi-styles@6.2.1: {} + + ansis@4.1.0: {} + + argparse@2.0.1: {} + + asynckit@0.4.0: {} + + axios@1.11.0: + dependencies: + follow-redirects: 1.15.11 + form-data: 4.0.4 + proxy-from-env: 1.1.0 + transitivePeerDependencies: + - debug + + balanced-match@1.0.2: {} + + birpc@2.5.0: {} + + boolbase@1.0.0: {} + + brace-expansion@1.1.12: + dependencies: + balanced-match: 1.0.2 + concat-map: 0.0.1 + + brace-expansion@2.0.2: + dependencies: + balanced-match: 1.0.2 + + braces@3.0.3: + dependencies: + fill-range: 7.1.1 + + browserslist@4.25.3: + dependencies: + caniuse-lite: 1.0.30001735 + electron-to-chromium: 1.5.207 + node-releases: 2.0.19 + update-browserslist-db: 1.1.3(browserslist@4.25.3) + + bundle-name@4.1.0: + dependencies: + run-applescript: 7.0.0 + + call-bind-apply-helpers@1.0.2: + dependencies: + es-errors: 1.3.0 + function-bind: 1.1.2 + + callsites@3.1.0: {} + + caniuse-lite@1.0.30001735: {} + + chalk@4.1.2: + dependencies: + ansi-styles: 4.3.0 + supports-color: 7.2.0 + + color-convert@2.0.1: + dependencies: + color-name: 1.1.4 + + color-name@1.1.4: {} + + combined-stream@1.0.8: + dependencies: + delayed-stream: 1.0.0 + + concat-map@0.0.1: {} + + convert-source-map@2.0.0: {} + + copy-anything@3.0.5: + dependencies: + is-what: 4.1.16 + + cross-spawn@7.0.6: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + + cssesc@3.0.0: {} + + csstype@3.1.3: {} + + de-indent@1.0.2: {} + + debug@4.4.1: + dependencies: + ms: 2.1.3 + + deep-is@0.1.4: {} + + default-browser-id@5.0.0: {} + + default-browser@5.2.1: + dependencies: + bundle-name: 4.1.0 + default-browser-id: 5.0.0 + + define-lazy-prop@3.0.0: {} + + delayed-stream@1.0.0: {} + + dunder-proto@1.0.1: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-errors: 1.3.0 + gopd: 1.2.0 + + electron-to-chromium@1.5.207: {} + + entities@4.5.0: {} + + error-stack-parser-es@1.0.5: {} + + es-define-property@1.0.1: {} + + es-errors@1.3.0: {} + + es-object-atoms@1.1.1: + dependencies: + es-errors: 1.3.0 + + es-set-tostringtag@2.1.0: + dependencies: + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + has-tostringtag: 1.0.2 + hasown: 2.0.2 + + esbuild@0.25.9: + optionalDependencies: + '@esbuild/aix-ppc64': 0.25.9 + '@esbuild/android-arm': 0.25.9 + '@esbuild/android-arm64': 0.25.9 + '@esbuild/android-x64': 0.25.9 + '@esbuild/darwin-arm64': 0.25.9 + '@esbuild/darwin-x64': 0.25.9 + '@esbuild/freebsd-arm64': 0.25.9 + '@esbuild/freebsd-x64': 0.25.9 + '@esbuild/linux-arm': 0.25.9 + '@esbuild/linux-arm64': 0.25.9 + '@esbuild/linux-ia32': 0.25.9 + '@esbuild/linux-loong64': 0.25.9 + '@esbuild/linux-mips64el': 0.25.9 + '@esbuild/linux-ppc64': 0.25.9 + '@esbuild/linux-riscv64': 0.25.9 + '@esbuild/linux-s390x': 0.25.9 + '@esbuild/linux-x64': 0.25.9 + '@esbuild/netbsd-arm64': 0.25.9 + '@esbuild/netbsd-x64': 0.25.9 + '@esbuild/openbsd-arm64': 0.25.9 + '@esbuild/openbsd-x64': 0.25.9 + '@esbuild/openharmony-arm64': 0.25.9 + '@esbuild/sunos-x64': 0.25.9 + '@esbuild/win32-arm64': 0.25.9 + '@esbuild/win32-ia32': 0.25.9 + '@esbuild/win32-x64': 0.25.9 + + escalade@3.2.0: {} + + escape-string-regexp@4.0.0: {} + + eslint-plugin-vue@10.3.0(@typescript-eslint/parser@8.40.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.8.3))(eslint@9.33.0(jiti@2.5.1))(vue-eslint-parser@10.2.0(eslint@9.33.0(jiti@2.5.1))): + dependencies: + '@eslint-community/eslint-utils': 4.7.0(eslint@9.33.0(jiti@2.5.1)) + eslint: 9.33.0(jiti@2.5.1) + natural-compare: 1.4.0 + nth-check: 2.1.1 + postcss-selector-parser: 6.1.2 + semver: 7.7.2 + vue-eslint-parser: 10.2.0(eslint@9.33.0(jiti@2.5.1)) + xml-name-validator: 4.0.0 + optionalDependencies: + '@typescript-eslint/parser': 8.40.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.8.3) + + eslint-scope@8.4.0: + dependencies: + esrecurse: 4.3.0 + estraverse: 5.3.0 + + eslint-visitor-keys@3.4.3: {} + + eslint-visitor-keys@4.2.1: {} + + eslint@9.33.0(jiti@2.5.1): + dependencies: + '@eslint-community/eslint-utils': 4.7.0(eslint@9.33.0(jiti@2.5.1)) + '@eslint-community/regexpp': 4.12.1 + '@eslint/config-array': 0.21.0 + '@eslint/config-helpers': 0.3.1 + '@eslint/core': 0.15.2 + '@eslint/eslintrc': 3.3.1 + '@eslint/js': 9.33.0 + '@eslint/plugin-kit': 0.3.5 + '@humanfs/node': 0.16.6 + '@humanwhocodes/module-importer': 1.0.1 + '@humanwhocodes/retry': 0.4.3 + '@types/estree': 1.0.8 + '@types/json-schema': 7.0.15 + ajv: 6.12.6 + chalk: 4.1.2 + cross-spawn: 7.0.6 + debug: 4.4.1 + escape-string-regexp: 4.0.0 + eslint-scope: 8.4.0 + eslint-visitor-keys: 4.2.1 + espree: 10.4.0 + esquery: 1.6.0 + esutils: 2.0.3 + fast-deep-equal: 3.1.3 + file-entry-cache: 8.0.0 + find-up: 5.0.0 + glob-parent: 6.0.2 + ignore: 5.3.2 + imurmurhash: 0.1.4 + is-glob: 4.0.3 + json-stable-stringify-without-jsonify: 1.0.1 + lodash.merge: 4.6.2 + minimatch: 3.1.2 + natural-compare: 1.4.0 + optionator: 0.9.4 + optionalDependencies: + jiti: 2.5.1 + transitivePeerDependencies: + - supports-color + + espree@10.4.0: + dependencies: + acorn: 8.15.0 + acorn-jsx: 5.3.2(acorn@8.15.0) + eslint-visitor-keys: 4.2.1 + + esquery@1.6.0: + dependencies: + estraverse: 5.3.0 + + esrecurse@4.3.0: + dependencies: + estraverse: 5.3.0 + + estraverse@5.3.0: {} + + estree-walker@2.0.2: {} + + esutils@2.0.3: {} + + execa@9.6.0: + dependencies: + '@sindresorhus/merge-streams': 4.0.0 + cross-spawn: 7.0.6 + figures: 6.1.0 + get-stream: 9.0.1 + human-signals: 8.0.1 + is-plain-obj: 4.1.0 + is-stream: 4.0.1 + npm-run-path: 6.0.0 + pretty-ms: 9.2.0 + signal-exit: 4.1.0 + strip-final-newline: 4.0.0 + yoctocolors: 2.1.2 + + fast-deep-equal@3.1.3: {} + + fast-glob@3.3.3: + dependencies: + '@nodelib/fs.stat': 2.0.5 + '@nodelib/fs.walk': 1.2.8 + glob-parent: 5.1.2 + merge2: 1.4.1 + micromatch: 4.0.8 + + fast-json-stable-stringify@2.1.0: {} + + fast-levenshtein@2.0.6: {} + + fastq@1.19.1: + dependencies: + reusify: 1.1.0 + + fdir@6.5.0(picomatch@4.0.3): + optionalDependencies: + picomatch: 4.0.3 + + figures@6.1.0: + dependencies: + is-unicode-supported: 2.1.0 + + file-entry-cache@8.0.0: + dependencies: + flat-cache: 4.0.1 + + fill-range@7.1.1: + dependencies: + to-regex-range: 5.0.1 + + find-up@5.0.0: + dependencies: + locate-path: 6.0.0 + path-exists: 4.0.0 + + flat-cache@4.0.1: + dependencies: + flatted: 3.3.3 + keyv: 4.5.4 + + flatted@3.3.3: {} + + follow-redirects@1.15.11: {} + + form-data@4.0.4: + dependencies: + asynckit: 0.4.0 + combined-stream: 1.0.8 + es-set-tostringtag: 2.1.0 + hasown: 2.0.2 + mime-types: 2.1.35 + + fsevents@2.3.3: + optional: true + + function-bind@1.1.2: {} + + gensync@1.0.0-beta.2: {} + + get-intrinsic@1.3.0: + 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 + + get-proto@1.0.1: + dependencies: + dunder-proto: 1.0.1 + es-object-atoms: 1.1.1 + + get-stream@9.0.1: + dependencies: + '@sec-ant/readable-stream': 0.4.1 + is-stream: 4.0.1 + + glob-parent@5.1.2: + dependencies: + is-glob: 4.0.3 + + glob-parent@6.0.2: + dependencies: + is-glob: 4.0.3 + + globals@14.0.0: {} + + gopd@1.2.0: {} + + graphemer@1.4.0: {} + + has-flag@4.0.0: {} + + has-symbols@1.1.0: {} + + has-tostringtag@1.0.2: + dependencies: + has-symbols: 1.1.0 + + hasown@2.0.2: + dependencies: + function-bind: 1.1.2 + + he@1.2.0: {} + + hookable@5.5.3: {} + + human-signals@8.0.1: {} + + ignore@5.3.2: {} + + ignore@7.0.5: {} + + import-fresh@3.3.1: + dependencies: + parent-module: 1.0.1 + resolve-from: 4.0.0 + + imurmurhash@0.1.4: {} + + is-docker@3.0.0: {} + + is-extglob@2.1.1: {} + + is-glob@4.0.3: + dependencies: + is-extglob: 2.1.1 + + is-inside-container@1.0.0: + dependencies: + is-docker: 3.0.0 + + is-number@7.0.0: {} + + is-plain-obj@4.1.0: {} + + is-stream@4.0.1: {} + + is-unicode-supported@2.1.0: {} + + is-what@4.1.16: {} + + is-wsl@3.1.0: + dependencies: + is-inside-container: 1.0.0 + + isexe@2.0.0: {} + + isexe@3.1.1: {} + + jiti@2.5.1: {} + + js-tokens@4.0.0: {} + + js-yaml@4.1.0: + dependencies: + argparse: 2.0.1 + + jsesc@3.1.0: {} + + json-buffer@3.0.1: {} + + json-parse-even-better-errors@4.0.0: {} + + json-schema-traverse@0.4.1: {} + + json-stable-stringify-without-jsonify@1.0.1: {} + + json5@2.2.3: {} + + keyv@4.5.4: + dependencies: + json-buffer: 3.0.1 + + kolorist@1.8.0: {} + + levn@0.4.1: + dependencies: + prelude-ls: 1.2.1 + type-check: 0.4.0 + + locate-path@6.0.0: + dependencies: + p-locate: 5.0.0 + + lodash.merge@4.6.2: {} + + lru-cache@5.1.1: + dependencies: + yallist: 3.1.1 + + magic-string@0.30.17: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + + math-intrinsics@1.1.0: {} + + memorystream@0.3.1: {} + + merge2@1.4.1: {} + + micromatch@4.0.8: + dependencies: + braces: 3.0.3 + picomatch: 2.3.1 + + mime-db@1.52.0: {} + + mime-types@2.1.35: + dependencies: + mime-db: 1.52.0 + + minimatch@3.1.2: + dependencies: + brace-expansion: 1.1.12 + + minimatch@9.0.5: + dependencies: + brace-expansion: 2.0.2 + + mitt@3.0.1: {} + + mrmime@2.0.1: {} + + ms@2.1.3: {} + + muggle-string@0.4.1: {} + + nanoid@3.3.11: {} + + nanoid@5.1.5: {} + + natural-compare@1.4.0: {} + + node-releases@2.0.19: {} + + npm-normalize-package-bin@4.0.0: {} + + npm-run-all2@8.0.4: + dependencies: + ansi-styles: 6.2.1 + cross-spawn: 7.0.6 + memorystream: 0.3.1 + picomatch: 4.0.3 + pidtree: 0.6.0 + read-package-json-fast: 4.0.0 + shell-quote: 1.8.3 + which: 5.0.0 + + npm-run-path@6.0.0: + dependencies: + path-key: 4.0.0 + unicorn-magic: 0.3.0 + + nth-check@2.1.1: + dependencies: + boolbase: 1.0.0 + + ohash@2.0.11: {} + + open@10.2.0: + dependencies: + default-browser: 5.2.1 + define-lazy-prop: 3.0.0 + is-inside-container: 1.0.0 + wsl-utils: 0.1.0 + + optionator@0.9.4: + dependencies: + deep-is: 0.1.4 + fast-levenshtein: 2.0.6 + levn: 0.4.1 + prelude-ls: 1.2.1 + type-check: 0.4.0 + word-wrap: 1.2.5 + + p-limit@3.1.0: + dependencies: + yocto-queue: 0.1.0 + + p-locate@5.0.0: + dependencies: + p-limit: 3.1.0 + + parent-module@1.0.1: + dependencies: + callsites: 3.1.0 + + parse-ms@4.0.0: {} + + path-browserify@1.0.1: {} + + path-exists@4.0.0: {} + + path-key@3.1.1: {} + + path-key@4.0.0: {} + + pathe@2.0.3: {} + + perfect-debounce@1.0.0: {} + + picocolors@1.1.1: {} + + picomatch@2.3.1: {} + + picomatch@4.0.3: {} + + pidtree@0.6.0: {} + + pinia@3.0.3(typescript@5.8.3)(vue@3.5.18(typescript@5.8.3)): + dependencies: + '@vue/devtools-api': 7.7.7 + vue: 3.5.18(typescript@5.8.3) + optionalDependencies: + typescript: 5.8.3 + + postcss-selector-parser@6.1.2: + dependencies: + cssesc: 3.0.0 + util-deprecate: 1.0.2 + + postcss@8.5.6: + dependencies: + nanoid: 3.3.11 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + prelude-ls@1.2.1: {} + + pretty-ms@9.2.0: + dependencies: + parse-ms: 4.0.0 + + proxy-from-env@1.1.0: {} + + punycode@2.3.1: {} + + queue-microtask@1.2.3: {} + + read-package-json-fast@4.0.0: + dependencies: + json-parse-even-better-errors: 4.0.0 + npm-normalize-package-bin: 4.0.0 + + resolve-from@4.0.0: {} + + reusify@1.1.0: {} + + rfdc@1.4.1: {} + + rollup@4.46.4: + dependencies: + '@types/estree': 1.0.8 + optionalDependencies: + '@rollup/rollup-android-arm-eabi': 4.46.4 + '@rollup/rollup-android-arm64': 4.46.4 + '@rollup/rollup-darwin-arm64': 4.46.4 + '@rollup/rollup-darwin-x64': 4.46.4 + '@rollup/rollup-freebsd-arm64': 4.46.4 + '@rollup/rollup-freebsd-x64': 4.46.4 + '@rollup/rollup-linux-arm-gnueabihf': 4.46.4 + '@rollup/rollup-linux-arm-musleabihf': 4.46.4 + '@rollup/rollup-linux-arm64-gnu': 4.46.4 + '@rollup/rollup-linux-arm64-musl': 4.46.4 + '@rollup/rollup-linux-loongarch64-gnu': 4.46.4 + '@rollup/rollup-linux-ppc64-gnu': 4.46.4 + '@rollup/rollup-linux-riscv64-gnu': 4.46.4 + '@rollup/rollup-linux-riscv64-musl': 4.46.4 + '@rollup/rollup-linux-s390x-gnu': 4.46.4 + '@rollup/rollup-linux-x64-gnu': 4.46.4 + '@rollup/rollup-linux-x64-musl': 4.46.4 + '@rollup/rollup-win32-arm64-msvc': 4.46.4 + '@rollup/rollup-win32-ia32-msvc': 4.46.4 + '@rollup/rollup-win32-x64-msvc': 4.46.4 + fsevents: 2.3.3 + + run-applescript@7.0.0: {} + + run-parallel@1.2.0: + dependencies: + queue-microtask: 1.2.3 + + semver@6.3.1: {} + + semver@7.7.2: {} + + shebang-command@2.0.0: + dependencies: + shebang-regex: 3.0.0 + + shebang-regex@3.0.0: {} + + shell-quote@1.8.3: {} + + signal-exit@4.1.0: {} + + sirv@3.0.1: + dependencies: + '@polka/url': 1.0.0-next.29 + mrmime: 2.0.1 + totalist: 3.0.1 + + source-map-js@1.2.1: {} + + speakingurl@14.0.1: {} + + strip-final-newline@4.0.0: {} + + strip-json-comments@3.1.1: {} + + superjson@2.2.2: + dependencies: + copy-anything: 3.0.5 + + supports-color@7.2.0: + dependencies: + has-flag: 4.0.0 + + tinyglobby@0.2.14: + dependencies: + fdir: 6.5.0(picomatch@4.0.3) + picomatch: 4.0.3 + + to-regex-range@5.0.1: + dependencies: + is-number: 7.0.0 + + totalist@3.0.1: {} + + ts-api-utils@2.1.0(typescript@5.8.3): + dependencies: + typescript: 5.8.3 + + type-check@0.4.0: + dependencies: + prelude-ls: 1.2.1 + + typescript-eslint@8.40.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.8.3): + dependencies: + '@typescript-eslint/eslint-plugin': 8.40.0(@typescript-eslint/parser@8.40.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.8.3))(eslint@9.33.0(jiti@2.5.1))(typescript@5.8.3) + '@typescript-eslint/parser': 8.40.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.8.3) + '@typescript-eslint/typescript-estree': 8.40.0(typescript@5.8.3) + '@typescript-eslint/utils': 8.40.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.8.3) + eslint: 9.33.0(jiti@2.5.1) + typescript: 5.8.3 + transitivePeerDependencies: + - supports-color + + typescript@5.8.3: {} + + undici-types@6.21.0: {} + + unicorn-magic@0.3.0: {} + + unplugin-utils@0.2.5: + dependencies: + pathe: 2.0.3 + picomatch: 4.0.3 + + update-browserslist-db@1.1.3(browserslist@4.25.3): + dependencies: + browserslist: 4.25.3 + escalade: 3.2.0 + picocolors: 1.1.1 + + uri-js@4.4.1: + dependencies: + punycode: 2.3.1 + + util-deprecate@1.0.2: {} + + vite-dev-rpc@1.1.0(vite@7.1.3(@types/node@22.17.2)(jiti@2.5.1)): + dependencies: + birpc: 2.5.0 + vite: 7.1.3(@types/node@22.17.2)(jiti@2.5.1) + vite-hot-client: 2.1.0(vite@7.1.3(@types/node@22.17.2)(jiti@2.5.1)) + + vite-hot-client@2.1.0(vite@7.1.3(@types/node@22.17.2)(jiti@2.5.1)): + dependencies: + vite: 7.1.3(@types/node@22.17.2)(jiti@2.5.1) + + vite-plugin-inspect@11.3.2(vite@7.1.3(@types/node@22.17.2)(jiti@2.5.1)): + dependencies: + ansis: 4.1.0 + debug: 4.4.1 + error-stack-parser-es: 1.0.5 + ohash: 2.0.11 + open: 10.2.0 + perfect-debounce: 1.0.0 + sirv: 3.0.1 + unplugin-utils: 0.2.5 + vite: 7.1.3(@types/node@22.17.2)(jiti@2.5.1) + vite-dev-rpc: 1.1.0(vite@7.1.3(@types/node@22.17.2)(jiti@2.5.1)) + transitivePeerDependencies: + - supports-color + + vite-plugin-vue-devtools@8.0.0(vite@7.1.3(@types/node@22.17.2)(jiti@2.5.1))(vue@3.5.18(typescript@5.8.3)): + dependencies: + '@vue/devtools-core': 8.0.0(vite@7.1.3(@types/node@22.17.2)(jiti@2.5.1))(vue@3.5.18(typescript@5.8.3)) + '@vue/devtools-kit': 8.0.0 + '@vue/devtools-shared': 8.0.0 + execa: 9.6.0 + sirv: 3.0.1 + vite: 7.1.3(@types/node@22.17.2)(jiti@2.5.1) + vite-plugin-inspect: 11.3.2(vite@7.1.3(@types/node@22.17.2)(jiti@2.5.1)) + vite-plugin-vue-inspector: 5.3.2(vite@7.1.3(@types/node@22.17.2)(jiti@2.5.1)) + transitivePeerDependencies: + - '@nuxt/kit' + - supports-color + - vue + + vite-plugin-vue-inspector@5.3.2(vite@7.1.3(@types/node@22.17.2)(jiti@2.5.1)): + dependencies: + '@babel/core': 7.28.3 + '@babel/plugin-proposal-decorators': 7.28.0(@babel/core@7.28.3) + '@babel/plugin-syntax-import-attributes': 7.27.1(@babel/core@7.28.3) + '@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.28.3) + '@babel/plugin-transform-typescript': 7.28.0(@babel/core@7.28.3) + '@vue/babel-plugin-jsx': 1.5.0(@babel/core@7.28.3) + '@vue/compiler-dom': 3.5.18 + kolorist: 1.8.0 + magic-string: 0.30.17 + vite: 7.1.3(@types/node@22.17.2)(jiti@2.5.1) + transitivePeerDependencies: + - supports-color + + vite@7.1.3(@types/node@22.17.2)(jiti@2.5.1): + dependencies: + esbuild: 0.25.9 + fdir: 6.5.0(picomatch@4.0.3) + picomatch: 4.0.3 + postcss: 8.5.6 + rollup: 4.46.4 + tinyglobby: 0.2.14 + optionalDependencies: + '@types/node': 22.17.2 + fsevents: 2.3.3 + jiti: 2.5.1 + + vscode-uri@3.1.0: {} + + vue-eslint-parser@10.2.0(eslint@9.33.0(jiti@2.5.1)): + dependencies: + debug: 4.4.1 + eslint: 9.33.0(jiti@2.5.1) + eslint-scope: 8.4.0 + eslint-visitor-keys: 4.2.1 + espree: 10.4.0 + esquery: 1.6.0 + semver: 7.7.2 + transitivePeerDependencies: + - supports-color + + vue-router@4.5.1(vue@3.5.18(typescript@5.8.3)): + dependencies: + '@vue/devtools-api': 6.6.4 + vue: 3.5.18(typescript@5.8.3) + + vue-tsc@3.0.6(typescript@5.8.3): + dependencies: + '@volar/typescript': 2.4.23 + '@vue/language-core': 3.0.6(typescript@5.8.3) + typescript: 5.8.3 + + vue@3.5.18(typescript@5.8.3): + dependencies: + '@vue/compiler-dom': 3.5.18 + '@vue/compiler-sfc': 3.5.18 + '@vue/runtime-dom': 3.5.18 + '@vue/server-renderer': 3.5.18(vue@3.5.18(typescript@5.8.3)) + '@vue/shared': 3.5.18 + optionalDependencies: + typescript: 5.8.3 + + which@2.0.2: + dependencies: + isexe: 2.0.0 + + which@5.0.0: + dependencies: + isexe: 3.1.1 + + word-wrap@1.2.5: {} + + wsl-utils@0.1.0: + dependencies: + is-wsl: 3.1.0 + + xml-name-validator@4.0.0: {} + + yallist@3.1.1: {} + + yocto-queue@0.1.0: {} + + yoctocolors@2.1.2: {} diff --git a/ui/public/favicon.ico b/ui/public/favicon.ico new file mode 100644 index 0000000..df36fcf Binary files /dev/null and b/ui/public/favicon.ico differ diff --git a/ui/src/App.vue b/ui/src/App.vue new file mode 100644 index 0000000..4a2c269 --- /dev/null +++ b/ui/src/App.vue @@ -0,0 +1,225 @@ + + + + + diff --git a/ui/src/assets/logo.svg b/ui/src/assets/logo.svg new file mode 100644 index 0000000..7565660 --- /dev/null +++ b/ui/src/assets/logo.svg @@ -0,0 +1 @@ + diff --git a/ui/src/assets/main.css b/ui/src/assets/main.css new file mode 100644 index 0000000..91b3a2c --- /dev/null +++ b/ui/src/assets/main.css @@ -0,0 +1,281 @@ +/* 全局样式重置 */ +* { + box-sizing: border-box; +} + +html, body { + margin: 0; + padding: 0; + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', + 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', + sans-serif; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + line-height: 1.6; + color: #1f2937; +} + +/* 全局按钮样式 */ +.btn { + display: inline-flex; + align-items: center; + justify-content: center; + padding: 0.75rem 1.5rem; + border-radius: 0.5rem; + font-weight: 600; + text-decoration: none; + transition: all 0.2s; + border: none; + cursor: pointer; + font-size: 1rem; +} + +.btn:disabled { + opacity: 0.6; + cursor: not-allowed; +} + +.btn-primary { + background: #3b82f6; + color: white; +} + +.btn-primary:hover:not(:disabled) { + background: #2563eb; + transform: translateY(-1px); +} + +.btn-secondary { + background: #f3f4f6; + color: #374151; + border: 1px solid #d1d5db; +} + +.btn-secondary:hover:not(:disabled) { + background: #e5e7eb; +} + +.btn-danger { + background: #ef4444; + color: white; +} + +.btn-danger:hover:not(:disabled) { + background: #dc2626; +} + +.btn-text { + background: none; + color: #6b7280; + padding: 0.5rem 1rem; +} + +.btn-text:hover { + color: #374151; + background: #f3f4f6; +} + +/* 全局容器样式 */ +.container { + max-width: 1200px; + margin: 0 auto; + padding: 0 2rem; +} + +/* 响应式设计 */ +@media (max-width: 768px) { + .container { + padding: 0 1rem; + } +} + +/* 滚动条样式 */ +::-webkit-scrollbar { + width: 8px; + height: 8px; +} + +::-webkit-scrollbar-track { + background: #f1f1f1; + border-radius: 4px; +} + +::-webkit-scrollbar-thumb { + background: #c1c1c1; + border-radius: 4px; +} + +::-webkit-scrollbar-thumb:hover { + background: #a8a8a8; +} + +/* 工具类 */ +.text-center { + text-align: center; +} + +.text-left { + text-align: left; +} + +.text-right { + text-align: right; +} + +.mt-1 { margin-top: 0.25rem; } +.mt-2 { margin-top: 0.5rem; } +.mt-3 { margin-top: 0.75rem; } +.mt-4 { margin-top: 1rem; } +.mt-5 { margin-top: 1.25rem; } +.mt-6 { margin-top: 1.5rem; } + +.mb-1 { margin-bottom: 0.25rem; } +.mb-2 { margin-bottom: 0.5rem; } +.mb-3 { margin-bottom: 0.75rem; } +.mb-4 { margin-bottom: 1rem; } +.mb-5 { margin-bottom: 1.25rem; } +.mb-6 { margin-bottom: 1.5rem; } + +.ml-1 { margin-left: 0.25rem; } +.ml-2 { margin-left: 0.5rem; } +.ml-3 { margin-left: 0.75rem; } +.ml-4 { margin-left: 1rem; } + +.mr-1 { margin-right: 0.25rem; } +.mr-2 { margin-right: 0.5rem; } +.mr-3 { margin-right: 0.75rem; } +.mr-4 { margin-right: 1rem; } + +.p-1 { padding: 0.25rem; } +.p-2 { padding: 0.5rem; } +.p-3 { padding: 0.75rem; } +.p-4 { padding: 1rem; } +.p-5 { padding: 1.25rem; } +.p-6 { padding: 1.5rem; } + +.flex { + display: flex; +} + +.flex-col { + flex-direction: column; +} + +.items-center { + align-items: center; +} + +.justify-center { + justify-content: center; +} + +.justify-between { + justify-content: space-between; +} + +.gap-1 { gap: 0.25rem; } +.gap-2 { gap: 0.5rem; } +.gap-3 { gap: 0.75rem; } +.gap-4 { gap: 1rem; } +.gap-5 { gap: 1.25rem; } +.gap-6 { gap: 1.5rem; } + +.w-full { + width: 100%; +} + +.h-full { + height: 100%; +} + +.rounded { + border-radius: 0.25rem; +} + +.rounded-lg { + border-radius: 0.5rem; +} + +.rounded-xl { + border-radius: 0.75rem; +} + +.shadow { + box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06); +} + +.shadow-lg { + box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05); +} + +.bg-white { + background-color: white; +} + +.bg-gray-50 { + background-color: #f9fafb; +} + +.bg-gray-100 { + background-color: #f3f4f6; +} + +.text-gray-500 { + color: #6b7280; +} + +.text-gray-600 { + color: #4b5563; +} + +.text-gray-700 { + color: #374151; +} + +.text-gray-900 { + color: #111827; +} + +.text-blue-600 { + color: #2563eb; +} + +.text-red-600 { + color: #dc2626; +} + +.text-green-600 { + color: #059669; +} + +.text-sm { + font-size: 0.875rem; +} + +.text-base { + font-size: 1rem; +} + +.text-lg { + font-size: 1.125rem; +} + +.text-xl { + font-size: 1.25rem; +} + +.text-2xl { + font-size: 1.5rem; +} + +.font-medium { + font-weight: 500; +} + +.font-semibold { + font-weight: 600; +} + +.font-bold { + font-weight: 700; +} diff --git a/ui/src/components/TheWelcome.vue b/ui/src/components/TheWelcome.vue new file mode 100644 index 0000000..6092dff --- /dev/null +++ b/ui/src/components/TheWelcome.vue @@ -0,0 +1,94 @@ + + + diff --git a/ui/src/components/WelcomeItem.vue b/ui/src/components/WelcomeItem.vue new file mode 100644 index 0000000..6d7086a --- /dev/null +++ b/ui/src/components/WelcomeItem.vue @@ -0,0 +1,87 @@ + + + diff --git a/ui/src/components/artwork/ArtworkCard.vue b/ui/src/components/artwork/ArtworkCard.vue new file mode 100644 index 0000000..8e2bad7 --- /dev/null +++ b/ui/src/components/artwork/ArtworkCard.vue @@ -0,0 +1,227 @@ + + + + + \ No newline at end of file diff --git a/ui/src/components/common/ErrorMessage.vue b/ui/src/components/common/ErrorMessage.vue new file mode 100644 index 0000000..a14ae91 --- /dev/null +++ b/ui/src/components/common/ErrorMessage.vue @@ -0,0 +1,110 @@ + + + + + \ No newline at end of file diff --git a/ui/src/components/common/LoadingSpinner.vue b/ui/src/components/common/LoadingSpinner.vue new file mode 100644 index 0000000..e0e91c5 --- /dev/null +++ b/ui/src/components/common/LoadingSpinner.vue @@ -0,0 +1,63 @@ + + + + + \ No newline at end of file diff --git a/ui/src/components/icons/IconCommunity.vue b/ui/src/components/icons/IconCommunity.vue new file mode 100644 index 0000000..2dc8b05 --- /dev/null +++ b/ui/src/components/icons/IconCommunity.vue @@ -0,0 +1,7 @@ + diff --git a/ui/src/components/icons/IconDocumentation.vue b/ui/src/components/icons/IconDocumentation.vue new file mode 100644 index 0000000..6d4791c --- /dev/null +++ b/ui/src/components/icons/IconDocumentation.vue @@ -0,0 +1,7 @@ + diff --git a/ui/src/components/icons/IconEcosystem.vue b/ui/src/components/icons/IconEcosystem.vue new file mode 100644 index 0000000..c3a4f07 --- /dev/null +++ b/ui/src/components/icons/IconEcosystem.vue @@ -0,0 +1,7 @@ + diff --git a/ui/src/components/icons/IconSupport.vue b/ui/src/components/icons/IconSupport.vue new file mode 100644 index 0000000..7452834 --- /dev/null +++ b/ui/src/components/icons/IconSupport.vue @@ -0,0 +1,7 @@ + diff --git a/ui/src/components/icons/IconTooling.vue b/ui/src/components/icons/IconTooling.vue new file mode 100644 index 0000000..660598d --- /dev/null +++ b/ui/src/components/icons/IconTooling.vue @@ -0,0 +1,19 @@ + + diff --git a/ui/src/main.ts b/ui/src/main.ts new file mode 100644 index 0000000..5dcad83 --- /dev/null +++ b/ui/src/main.ts @@ -0,0 +1,14 @@ +import './assets/main.css' + +import { createApp } from 'vue' +import { createPinia } from 'pinia' + +import App from './App.vue' +import router from './router' + +const app = createApp(App) + +app.use(createPinia()) +app.use(router) + +app.mount('#app') diff --git a/ui/src/router/index.ts b/ui/src/router/index.ts new file mode 100644 index 0000000..5d1cfcd --- /dev/null +++ b/ui/src/router/index.ts @@ -0,0 +1,63 @@ +import { createRouter, createWebHistory } from 'vue-router' +import HomeView from '../views/HomeView.vue' +import LoginView from '../views/LoginView.vue' + +const router = createRouter({ + history: createWebHistory(import.meta.env.BASE_URL), + routes: [ + { + path: '/', + name: 'home', + component: HomeView + }, + { + path: '/login', + name: 'login', + component: LoginView + }, + { + path: '/search', + name: 'search', + component: () => import('../views/SearchView.vue'), + meta: { requiresAuth: true } + }, + { + path: '/artwork/:id', + name: 'artwork', + component: () => import('../views/ArtworkView.vue'), + meta: { requiresAuth: true } + }, + { + path: '/artist/:id', + name: 'artist', + component: () => import('../views/ArtistView.vue'), + meta: { requiresAuth: true } + }, + { + path: '/downloads', + name: 'downloads', + component: () => import('../views/DownloadsView.vue'), + meta: { requiresAuth: true } + }, + { + path: '/artists', + name: 'artists', + component: () => import('../views/ArtistsView.vue'), + meta: { requiresAuth: true } + } + ] +}) + +// 路由守卫 +router.beforeEach(async (to, from, next) => { + // 检查是否需要认证 + if (to.meta.requiresAuth) { + // 这里可以添加认证检查逻辑 + // 暂时直接放行,后续可以集成认证状态检查 + next() + } else { + next() + } +}) + +export default router diff --git a/ui/src/services/api.ts b/ui/src/services/api.ts new file mode 100644 index 0000000..85a3d61 --- /dev/null +++ b/ui/src/services/api.ts @@ -0,0 +1,91 @@ +import axios, { type AxiosInstance, type AxiosRequestConfig, type AxiosResponse } from 'axios'; +import type { ApiResponse } from '@/types'; + +// API配置 +const API_BASE_URL = import.meta.env.VITE_API_BASE_URL || 'http://localhost:3000'; + +class ApiService { + private client: AxiosInstance; + + constructor() { + this.client = axios.create({ + baseURL: API_BASE_URL, + timeout: 30000, + headers: { + 'Content-Type': 'application/json', + }, + }); + + // 请求拦截器 + this.client.interceptors.request.use( + (config) => { + // 可以在这里添加认证token等 + return config; + }, + (error) => { + return Promise.reject(error); + } + ); + + // 响应拦截器 + this.client.interceptors.response.use( + (response: AxiosResponse) => { + return response; + }, + (error) => { + // 统一错误处理 + if (error.response) { + const { status, data } = error.response; + console.error(`API Error ${status}:`, data); + } else if (error.request) { + console.error('Network Error:', error.request); + } else { + console.error('Request Error:', error.message); + } + return Promise.reject(error); + } + ); + } + + /** + * GET请求 + */ + async get(url: string, config?: AxiosRequestConfig): Promise> { + const response = await this.client.get>(url, config); + return response.data; + } + + /** + * POST请求 + */ + async post(url: string, data?: any, config?: AxiosRequestConfig): Promise> { + const response = await this.client.post>(url, data, config); + return response.data; + } + + /** + * PUT请求 + */ + async put(url: string, data?: any, config?: AxiosRequestConfig): Promise> { + const response = await this.client.put>(url, data, config); + return response.data; + } + + /** + * DELETE请求 + */ + async delete(url: string, config?: AxiosRequestConfig): Promise> { + const response = await this.client.delete>(url, config); + return response.data; + } + + /** + * 健康检查 + */ + async healthCheck(): Promise { + return this.get('/health'); + } +} + +export const apiService = new ApiService(); +export default apiService; \ No newline at end of file diff --git a/ui/src/services/artist.ts b/ui/src/services/artist.ts new file mode 100644 index 0000000..9a68435 --- /dev/null +++ b/ui/src/services/artist.ts @@ -0,0 +1,81 @@ +import apiService from './api'; +import type { ApiResponse, Artist, User } from '@/types'; + +export interface ArtistArtworksOptions { + type?: 'art' | 'manga' | 'novel'; + filter?: 'for_ios' | 'for_android'; + offset?: number; + limit?: number; +} + +export interface ArtistFollowingOptions { + restrict?: 'public' | 'private'; + offset?: number; + limit?: number; +} + +export interface ArtistFollowersOptions { + offset?: number; + limit?: number; +} + +class ArtistService { + /** + * 获取作者信息 + */ + async getArtistInfo(id: number): Promise> { + return apiService.get(`/api/artist/${id}`); + } + + /** + * 获取作者作品列表 + */ + async getArtistArtworks(id: number, options: ArtistArtworksOptions = {}): Promise> { + const params = new URLSearchParams(); + if (options.type) params.append('type', options.type); + if (options.filter) params.append('filter', options.filter); + if (options.offset !== undefined) params.append('offset', options.offset.toString()); + if (options.limit !== undefined) params.append('limit', options.limit.toString()); + + const query = params.toString(); + const url = query ? `/api/artist/${id}/artworks?${query}` : `/api/artist/${id}/artworks`; + return apiService.get<{ artworks: any[]; next_url?: string; total: number }>(url); + } + + /** + * 获取作者关注列表 + */ + async getArtistFollowing(id: number, options: ArtistFollowingOptions = {}): Promise> { + const params = new URLSearchParams(); + if (options.restrict) params.append('restrict', options.restrict); + if (options.offset !== undefined) params.append('offset', options.offset.toString()); + if (options.limit !== undefined) params.append('limit', options.limit.toString()); + + const query = params.toString(); + const url = query ? `/api/artist/${id}/following?${query}` : `/api/artist/${id}/following`; + return apiService.get<{ users: User[]; next_url?: string; total: number }>(url); + } + + /** + * 获取作者粉丝列表 + */ + async getArtistFollowers(id: number, options: ArtistFollowersOptions = {}): Promise> { + const params = new URLSearchParams(); + if (options.offset !== undefined) params.append('offset', options.offset.toString()); + if (options.limit !== undefined) params.append('limit', options.limit.toString()); + + const query = params.toString(); + const url = query ? `/api/artist/${id}/followers?${query}` : `/api/artist/${id}/followers`; + return apiService.get<{ users: User[]; next_url?: string; total: number }>(url); + } + + /** + * 关注/取消关注作者 + */ + async followArtist(id: number, action: 'follow' | 'unfollow'): Promise { + return apiService.post(`/api/artist/${id}/follow`, { action }); + } +} + +export const artistService = new ArtistService(); +export default artistService; \ No newline at end of file diff --git a/ui/src/services/artwork.ts b/ui/src/services/artwork.ts new file mode 100644 index 0000000..d757cb8 --- /dev/null +++ b/ui/src/services/artwork.ts @@ -0,0 +1,71 @@ +import apiService from './api'; +import type { ApiResponse, Artwork, SearchParams, PaginatedResponse } from '@/types'; + +export interface ArtworkDetailOptions { + include_user?: boolean; + include_series?: boolean; +} + +export interface ArtworkImagesResponse { + artwork_id: number; + total_pages: number; + images: Array<{ + page: number; + original: string; + large: string; + medium: string; + square_medium: string; + }>; + selected_size: string; +} + +class ArtworkService { + /** + * 获取作品详情 + */ + async getArtworkDetail(id: number, options: ArtworkDetailOptions = {}): Promise> { + const params = new URLSearchParams(); + if (options.include_user !== undefined) { + params.append('include_user', options.include_user.toString()); + } + if (options.include_series !== undefined) { + params.append('include_series', options.include_series.toString()); + } + + const query = params.toString(); + const url = query ? `/api/artwork/${id}?${query}` : `/api/artwork/${id}`; + return apiService.get(url); + } + + /** + * 获取作品预览信息 + */ + async getArtworkPreview(id: number): Promise> { + return apiService.get(`/api/artwork/${id}/preview`); + } + + /** + * 获取作品图片URL + */ + async getArtworkImages(id: number, size: string = 'medium'): Promise> { + return apiService.get(`/api/artwork/${id}/images?size=${size}`); + } + + /** + * 搜索作品 + */ + async searchArtworks(params: SearchParams): Promise> { + const queryParams = new URLSearchParams(); + queryParams.append('keyword', params.keyword); + if (params.type) queryParams.append('type', params.type); + if (params.sort) queryParams.append('sort', params.sort); + if (params.duration) queryParams.append('duration', params.duration); + if (params.offset !== undefined) queryParams.append('offset', params.offset.toString()); + if (params.limit !== undefined) queryParams.append('limit', params.limit.toString()); + + return apiService.get<{ artworks: Artwork[]; next_url?: string; total: number }>(`/api/artwork/search?${queryParams.toString()}`); + } +} + +export const artworkService = new ArtworkService(); +export default artworkService; \ No newline at end of file diff --git a/ui/src/services/auth.ts b/ui/src/services/auth.ts new file mode 100644 index 0000000..6ca16dc --- /dev/null +++ b/ui/src/services/auth.ts @@ -0,0 +1,59 @@ +import apiService from './api'; +import type { ApiResponse, LoginStatus } from '@/types'; + +export interface LoginUrlResponse { + login_url: string; + code_verifier: string; +} + +export interface LoginCallbackRequest { + code: string; +} + +export interface LoginCallbackResponse { + user: { + id: number; + name: string; + account: string; + }; +} + +class AuthService { + /** + * 获取登录状态 + */ + async getLoginStatus(): Promise> { + return apiService.get('/api/auth/status'); + } + + /** + * 获取登录URL + */ + async getLoginUrl(): Promise> { + return apiService.get('/api/auth/login-url'); + } + + /** + * 处理登录回调 + */ + async handleLoginCallback(code: string): Promise> { + return apiService.post('/api/auth/callback', { code }); + } + + /** + * 重新登录 + */ + async relogin(): Promise { + return apiService.post('/api/auth/relogin'); + } + + /** + * 登出 + */ + async logout(): Promise { + return apiService.post('/api/auth/logout'); + } +} + +export const authService = new AuthService(); +export default authService; \ No newline at end of file diff --git a/ui/src/services/download.ts b/ui/src/services/download.ts new file mode 100644 index 0000000..6b6c649 --- /dev/null +++ b/ui/src/services/download.ts @@ -0,0 +1,66 @@ +import apiService from './api'; +import type { ApiResponse, DownloadTask, DownloadParams } from '@/types'; + +export interface DownloadArtworkRequest extends DownloadParams { + size?: 'original' | 'large' | 'medium' | 'square_medium'; + quality?: 'high' | 'medium' | 'low'; + format?: 'auto' | 'jpg' | 'png'; +} + +export interface DownloadMultipleRequest extends DownloadParams { + artworkIds: number[]; + concurrent?: number; +} + +export interface DownloadArtistRequest extends DownloadParams { + type?: 'art' | 'manga' | 'novel'; + filter?: 'for_ios' | 'for_android'; + limit?: number; +} + +class DownloadService { + /** + * 下载单个作品 + */ + async downloadArtwork(id: number, params: DownloadArtworkRequest = {}): Promise> { + return apiService.post(`/api/download/artwork/${id}`, params); + } + + /** + * 批量下载作品 + */ + async downloadMultipleArtworks(params: DownloadMultipleRequest): Promise> { + return apiService.post('/api/download/artworks', params); + } + + /** + * 下载作者作品 + */ + async downloadArtistArtworks(id: number, params: DownloadArtistRequest = {}): Promise> { + return apiService.post(`/api/download/artist/${id}`, params); + } + + /** + * 获取下载进度 + */ + async getDownloadProgress(taskId: string): Promise> { + return apiService.get(`/api/download/progress/${taskId}`); + } + + /** + * 取消下载任务 + */ + async cancelDownload(taskId: string): Promise { + return apiService.delete(`/api/download/cancel/${taskId}`); + } + + /** + * 获取下载历史 + */ + async getDownloadHistory(offset: number = 0, limit: number = 20): Promise> { + return apiService.get<{ tasks: DownloadTask[]; total: number; offset: number; limit: number }>(`/api/download/history?offset=${offset}&limit=${limit}`); + } +} + +export const downloadService = new DownloadService(); +export default downloadService; \ No newline at end of file diff --git a/ui/src/stores/auth.ts b/ui/src/stores/auth.ts new file mode 100644 index 0000000..4889783 --- /dev/null +++ b/ui/src/stores/auth.ts @@ -0,0 +1,139 @@ +import { defineStore } from 'pinia'; +import { ref, computed } from 'vue'; +import authService from '@/services/auth'; +import type { LoginStatus } from '@/types'; + +export const useAuthStore = defineStore('auth', () => { + // 状态 + const loginStatus = ref({ + isLoggedIn: false + }); + const loading = ref(false); + const error = ref(null); + + // 计算属性 + const isLoggedIn = computed(() => loginStatus.value.isLoggedIn); + const username = computed(() => loginStatus.value.username); + const userId = computed(() => loginStatus.value.user_id); + + // 获取登录状态 + const fetchLoginStatus = async () => { + try { + loading.value = true; + error.value = null; + const response = await authService.getLoginStatus(); + if (response.success && response.data) { + loginStatus.value = response.data; + } + } catch (err) { + error.value = err instanceof Error ? err.message : '获取登录状态失败'; + console.error('获取登录状态失败:', err); + } finally { + loading.value = false; + } + }; + + // 获取登录URL + const getLoginUrl = async () => { + try { + loading.value = true; + error.value = null; + const response = await authService.getLoginUrl(); + if (response.success && response.data) { + return response.data.login_url; + } + throw new Error(response.error || '获取登录URL失败'); + } catch (err) { + error.value = err instanceof Error ? err.message : '获取登录URL失败'; + console.error('获取登录URL失败:', err); + throw err; + } finally { + loading.value = false; + } + }; + + // 处理登录回调 + const handleLoginCallback = async (code: string) => { + try { + loading.value = true; + error.value = null; + const response = await authService.handleLoginCallback(code); + if (response.success) { + await fetchLoginStatus(); // 重新获取登录状态 + return response.data; + } + throw new Error(response.error || '登录失败'); + } catch (err) { + error.value = err instanceof Error ? err.message : '登录失败'; + console.error('登录失败:', err); + throw err; + } finally { + loading.value = false; + } + }; + + // 重新登录 + const relogin = async () => { + try { + loading.value = true; + error.value = null; + const response = await authService.relogin(); + if (response.success) { + await fetchLoginStatus(); // 重新获取登录状态 + return true; + } + throw new Error(response.error || '重新登录失败'); + } catch (err) { + error.value = err instanceof Error ? err.message : '重新登录失败'; + console.error('重新登录失败:', err); + throw err; + } finally { + loading.value = false; + } + }; + + // 登出 + const logout = async () => { + try { + loading.value = true; + error.value = null; + const response = await authService.logout(); + if (response.success) { + loginStatus.value = { isLoggedIn: false }; + return true; + } + throw new Error(response.error || '登出失败'); + } catch (err) { + error.value = err instanceof Error ? err.message : '登出失败'; + console.error('登出失败:', err); + throw err; + } finally { + loading.value = false; + } + }; + + // 清除错误 + const clearError = () => { + error.value = null; + }; + + return { + // 状态 + loginStatus, + loading, + error, + + // 计算属性 + isLoggedIn, + username, + userId, + + // 方法 + fetchLoginStatus, + getLoginUrl, + handleLoginCallback, + relogin, + logout, + clearError + }; +}); \ No newline at end of file diff --git a/ui/src/stores/counter.ts b/ui/src/stores/counter.ts new file mode 100644 index 0000000..b6757ba --- /dev/null +++ b/ui/src/stores/counter.ts @@ -0,0 +1,12 @@ +import { ref, computed } from 'vue' +import { defineStore } from 'pinia' + +export const useCounterStore = defineStore('counter', () => { + const count = ref(0) + const doubleCount = computed(() => count.value * 2) + function increment() { + count.value++ + } + + return { count, doubleCount, increment } +}) diff --git a/ui/src/types/index.ts b/ui/src/types/index.ts new file mode 100644 index 0000000..d67f4a3 --- /dev/null +++ b/ui/src/types/index.ts @@ -0,0 +1,132 @@ +// 基础响应类型 +export interface ApiResponse { + success: boolean; + message?: string; + data?: T; + error?: string; + code?: string; + timestamp?: string; +} + +// 分页响应类型 +export interface PaginatedResponse extends ApiResponse { + pagination?: { + page: number; + limit: number; + total: number; + pages: number; + }; +} + +// 用户信息 +export interface User { + id: number; + name: string; + account: string; + profile_image_urls: { + medium: string; + }; + is_followed: boolean; +} + +// 作品信息 +export interface Artwork { + id: number; + title: string; + description: string; + user: User; + image_urls: { + square_medium: string; + medium: string; + large: string; + original: string; + }; + tags: Array<{ + name: string; + translated_name?: string; + }>; + create_date: string; + update_date: string; + type: string; + width: number; + height: number; + page_count: number; + is_bookmarked: boolean; + total_bookmarks: number; + total_view: number; + is_muted: boolean; + meta_single_page?: { + original_image_url?: string; + large_image_url?: string; + }; + meta_pages?: Array<{ + image_urls: { + square_medium: string; + medium: string; + large: string; + original: string; + }; + }>; +} + +// 作者信息 +export interface Artist { + id: number; + name: string; + account: string; + profile_image_urls: { + medium: string; + }; + comment: string; + is_followed: boolean; + total_illusts: number; + total_manga: number; + total_novels: number; + total_bookmarked_illust: number; + total_following: number; + total_followers: number; +} + +// 登录状态 +export interface LoginStatus { + isLoggedIn: boolean; + username?: string; + user_id?: number; +} + +// 下载任务 +export interface DownloadTask { + id: string; + type: 'artwork' | 'batch' | 'artist'; + status: 'downloading' | 'completed' | 'failed' | 'partial' | 'cancelled'; + progress: number; + total: number; + completed: number; + failed: number; + start_time: string; + end_time?: string; + files?: Array<{ + path: string; + url: string; + size: string; + }>; + results?: any[]; +} + +// 搜索参数 +export interface SearchParams { + keyword: string; + type?: 'all' | 'art' | 'manga' | 'novel'; + sort?: 'date_desc' | 'date_asc' | 'popular_desc'; + duration?: 'all' | 'within_last_day' | 'within_last_week' | 'within_last_month'; + offset?: number; + limit?: number; +} + +// 下载参数 +export interface DownloadParams { + size?: 'original' | 'large' | 'medium' | 'square_medium'; + quality?: 'high' | 'medium' | 'low'; + format?: 'auto' | 'jpg' | 'png'; + concurrent?: number; +} \ No newline at end of file diff --git a/ui/src/views/AboutView.vue b/ui/src/views/AboutView.vue new file mode 100644 index 0000000..756ad2a --- /dev/null +++ b/ui/src/views/AboutView.vue @@ -0,0 +1,15 @@ + + + diff --git a/ui/src/views/ArtistView.vue b/ui/src/views/ArtistView.vue new file mode 100644 index 0000000..6aae2a6 --- /dev/null +++ b/ui/src/views/ArtistView.vue @@ -0,0 +1,490 @@ + + + + + \ No newline at end of file diff --git a/ui/src/views/ArtistsView.vue b/ui/src/views/ArtistsView.vue new file mode 100644 index 0000000..c818f43 --- /dev/null +++ b/ui/src/views/ArtistsView.vue @@ -0,0 +1,588 @@ + + + + + \ No newline at end of file diff --git a/ui/src/views/ArtworkView.vue b/ui/src/views/ArtworkView.vue new file mode 100644 index 0000000..08b7c0d --- /dev/null +++ b/ui/src/views/ArtworkView.vue @@ -0,0 +1,554 @@ + + + + + \ No newline at end of file diff --git a/ui/src/views/DownloadsView.vue b/ui/src/views/DownloadsView.vue new file mode 100644 index 0000000..c62d96f --- /dev/null +++ b/ui/src/views/DownloadsView.vue @@ -0,0 +1,607 @@ + + + + + \ No newline at end of file diff --git a/ui/src/views/HomeView.vue b/ui/src/views/HomeView.vue new file mode 100644 index 0000000..947450e --- /dev/null +++ b/ui/src/views/HomeView.vue @@ -0,0 +1,365 @@ + + + + + diff --git a/ui/src/views/LoginView.vue b/ui/src/views/LoginView.vue new file mode 100644 index 0000000..cfe105e --- /dev/null +++ b/ui/src/views/LoginView.vue @@ -0,0 +1,337 @@ + + + + + \ No newline at end of file diff --git a/ui/src/views/SearchView.vue b/ui/src/views/SearchView.vue new file mode 100644 index 0000000..2f1aaa5 --- /dev/null +++ b/ui/src/views/SearchView.vue @@ -0,0 +1,408 @@ + + + + + \ No newline at end of file diff --git a/ui/tsconfig.app.json b/ui/tsconfig.app.json new file mode 100644 index 0000000..913b8f2 --- /dev/null +++ b/ui/tsconfig.app.json @@ -0,0 +1,12 @@ +{ + "extends": "@vue/tsconfig/tsconfig.dom.json", + "include": ["env.d.ts", "src/**/*", "src/**/*.vue"], + "exclude": ["src/**/__tests__/*"], + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", + + "paths": { + "@/*": ["./src/*"] + } + } +} diff --git a/ui/tsconfig.json b/ui/tsconfig.json new file mode 100644 index 0000000..66b5e57 --- /dev/null +++ b/ui/tsconfig.json @@ -0,0 +1,11 @@ +{ + "files": [], + "references": [ + { + "path": "./tsconfig.node.json" + }, + { + "path": "./tsconfig.app.json" + } + ] +} diff --git a/ui/tsconfig.node.json b/ui/tsconfig.node.json new file mode 100644 index 0000000..a83dfc9 --- /dev/null +++ b/ui/tsconfig.node.json @@ -0,0 +1,19 @@ +{ + "extends": "@tsconfig/node22/tsconfig.json", + "include": [ + "vite.config.*", + "vitest.config.*", + "cypress.config.*", + "nightwatch.conf.*", + "playwright.config.*", + "eslint.config.*" + ], + "compilerOptions": { + "noEmit": true, + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", + + "module": "ESNext", + "moduleResolution": "Bundler", + "types": ["node"] + } +} diff --git a/ui/vite.config.ts b/ui/vite.config.ts new file mode 100644 index 0000000..4217010 --- /dev/null +++ b/ui/vite.config.ts @@ -0,0 +1,18 @@ +import { fileURLToPath, URL } from 'node:url' + +import { defineConfig } from 'vite' +import vue from '@vitejs/plugin-vue' +import vueDevTools from 'vite-plugin-vue-devtools' + +// https://vite.dev/config/ +export default defineConfig({ + plugins: [ + vue(), + vueDevTools(), + ], + resolve: { + alias: { + '@': fileURLToPath(new URL('./src', import.meta.url)) + }, + }, +})