diff --git a/packages/napcat-webui-backend/package.json b/packages/napcat-webui-backend/package.json index b3b140b7..b5d731f0 100644 --- a/packages/napcat-webui-backend/package.json +++ b/packages/napcat-webui-backend/package.json @@ -27,6 +27,7 @@ "multer": "^2.0.1", "napcat-common": "workspace:*", "napcat-pty": "workspace:*", + "unzipper": "^0.10.14", "ws": "^8.18.3" }, "devDependencies": { @@ -34,6 +35,7 @@ "@types/express": "^5.0.0", "@types/multer": "^1.4.12", "@types/node": "^22.0.1", + "@types/unzipper": "^0.10.9", "@types/ws": "^8.5.12" }, "engines": { diff --git a/packages/napcat-webui-backend/src/api/BackupConfig.ts b/packages/napcat-webui-backend/src/api/BackupConfig.ts new file mode 100644 index 00000000..13b4e9e6 --- /dev/null +++ b/packages/napcat-webui-backend/src/api/BackupConfig.ts @@ -0,0 +1,178 @@ +import { RequestHandler } from 'express'; +import { existsSync, createReadStream, mkdirSync, rmSync, cpSync, readdirSync } from 'node:fs'; +import { join, normalize } from 'node:path'; +import os from 'node:os'; +import { webUiPathWrapper } from '@/napcat-webui-backend/index'; +import { WebUiDataRuntime } from '@/napcat-webui-backend/src/helper/Data'; +import { sendError, sendSuccess } from '@/napcat-webui-backend/src/utils/response'; +import compressing from 'compressing'; +import unzipper from 'unzipper'; + +// 使用compressing库进行压缩 +export const BackupExportConfigHandler: RequestHandler = async (_req, res) => { + const isLogin = WebUiDataRuntime.getQQLoginStatus(); + if (!isLogin) { + return sendError(res, 'Not Login'); + } + + try { + const configPath = webUiPathWrapper.configPath; + + if (!existsSync(configPath)) { + return sendError(res, '配置目录不存在'); + } + + const formatDate = (date: Date) => { + return date.toISOString().replace(/[:.]/g, '-'); + }; + const zipFileName = `config_backup_${formatDate(new Date())}.zip`; + + // 设置响应头 + res.setHeader('Content-Type', 'application/zip'); + res.setHeader('Content-Disposition', `attachment; filename="${zipFileName}"`); + + // 使用compressing的Stream API[1](@ref) + const stream = new compressing.zip.Stream(); + + // 添加目录下的所有内容到压缩流 + const entries = readdirSync(configPath, { withFileTypes: true }); + for (const entry of entries) { + const entryPath = join(configPath, entry.name); + stream.addEntry(entryPath, { relativePath: entry.name }); + } + + // 管道传输到响应 + stream.pipe(res); + + // 处理流错误 + stream.on('error', (err) => { + console.error('压缩流错误:', err); + if (!res.headersSent) { + sendError(res, '流式压缩失败'); + } + }); + + } catch (error) { + const msg = (error as Error).message; + console.error('导出配置失败:', error); + if (!res.headersSent) { + return sendError(res, `导出配置失败: ${msg}`); + } + } +}; + +// 导入配置,将上传的zip文件解压到工作目录下的tmp目录,然后覆盖到config文件夹 +export const BackupImportConfigHandler: RequestHandler = async (req, res) => { + // 检查是否有文件上传 + if (!req.file) { + return sendError(res, '请选择要导入的配置文件'); + } + + try { + const configPath = webUiPathWrapper.configPath; + const tmpPath = join(os.tmpdir(), 'napcat-upload', 'tmp'); + const backupRootPath = join(os.tmpdir(), 'napcat-upload', 'backup'); + let extractPath = join(tmpPath, 'imported_config'); + const uploadedFilePath = req.file.path; + + // 确保临时目录和备份目录存在 + mkdirSync(tmpPath, { recursive: true }); + mkdirSync(backupRootPath, { recursive: true }); + mkdirSync(extractPath, { recursive: true }); + + // 解压上传的zip文件 + await createReadStream(uploadedFilePath) + .pipe(unzipper.Extract({ path: extractPath })) + .promise(); + + // 检查解压后的文件 + let extractedFiles = readdirSync(extractPath); + if (extractedFiles.length === 0) { + rmSync(extractPath, { recursive: true, force: true }); + rmSync(uploadedFilePath, { force: true }); + return sendError(res, '配置文件为空或格式不正确'); + } + + // 检查是否有嵌套的config目录 + if (extractedFiles.length === 1) { + const nestedDirName = extractedFiles[0]; + if (nestedDirName) { + const nestedPath = join(extractPath, nestedDirName); + if (existsSync(nestedPath) && !existsSync(join(nestedPath, '.git'))) { + const nestedFiles = readdirSync(nestedPath); + if (nestedFiles.length > 0) { + // 如果只有一个目录,且目录不为空且不是.git目录,使用该目录作为解压路径 + extractPath = nestedPath; + extractedFiles = nestedFiles; + } + } + } + } + + // 备份当前配置到专门的backup文件夹 + const formatDate = (date: Date) => { + return date.toISOString().replace(/[:.]/g, '-'); + }; + const backupPath = join(backupRootPath, `config_${formatDate(new Date())}`); + if (existsSync(configPath)) { + mkdirSync(backupPath, { recursive: true }); + // 递归复制所有文件和文件夹 + const copyRecursive = (src: string, dest: string) => { + const entries = readdirSync(src, { withFileTypes: true }); + for (const entry of entries) { + const srcPath = join(src, entry.name); + const destPath = join(dest, entry.name); + + // 防止路径穿越攻击 + const normalizedDestPath = normalize(destPath); + const normalizedDestDir = normalize(dest); + if (!normalizedDestPath.startsWith(normalizedDestDir)) { + continue; + } + + if (entry.isDirectory()) { + mkdirSync(destPath, { recursive: true }); + copyRecursive(srcPath, destPath); + } else { + cpSync(srcPath, destPath); + } + } + }; + copyRecursive(configPath, backupPath); + } + + // 覆盖配置文件和文件夹 + const copyRecursive = (src: string, dest: string) => { + const entries = readdirSync(src, { withFileTypes: true }); + for (const entry of entries) { + const srcPath = join(src, entry.name); + const destPath = join(dest, entry.name); + + // 防止路径穿越攻击 + const normalizedDestPath = normalize(destPath); + const normalizedDestDir = normalize(dest); + if (!normalizedDestPath.startsWith(normalizedDestDir)) { + continue; + } + + if (entry.isDirectory()) { + mkdirSync(destPath, { recursive: true }); + copyRecursive(srcPath, destPath); + } else { + cpSync(srcPath, destPath); + } + } + }; + copyRecursive(extractPath, configPath); + + return sendSuccess(res, { + message: '配置导入成功,重启后生效~', + backupPath: backupPath + }); + + } catch (error) { + console.error('导入配置失败:', error); + const msg = (error as Error).message; + return sendError(res, `导入配置失败: ${msg}`); + } +}; diff --git a/packages/napcat-webui-backend/src/helper/config.ts b/packages/napcat-webui-backend/src/helper/config.ts index 42f486a5..01e36ed7 100644 --- a/packages/napcat-webui-backend/src/helper/config.ts +++ b/packages/napcat-webui-backend/src/helper/config.ts @@ -32,6 +32,8 @@ const WebUiConfigSchema = Type.Object({ ipBlacklist: Type.Array(Type.String(), { default: [] }), // 是否启用 X-Forwarded-For 获取真实IP enableXForwardedFor: Type.Boolean({ default: false }), + // 上传文件大小限制(MB) + uploadSizeLimit: Type.Number({ default: 50 }), }); export type WebUiConfigType = Static; diff --git a/packages/napcat-webui-backend/src/router/OB11Config.ts b/packages/napcat-webui-backend/src/router/OB11Config.ts index 99f96681..ed5d5c59 100644 --- a/packages/napcat-webui-backend/src/router/OB11Config.ts +++ b/packages/napcat-webui-backend/src/router/OB11Config.ts @@ -1,11 +1,47 @@ import { Router } from 'express'; +import multer from 'multer'; +import { join } from 'node:path'; +import os from 'node:os'; +import { WebUiConfig } from '@/napcat-webui-backend/index'; import { OB11GetConfigHandler, OB11SetConfigHandler } from '@/napcat-webui-backend/src/api/OB11Config'; +import { BackupExportConfigHandler, BackupImportConfigHandler } from '@/napcat-webui-backend/src/api/BackupConfig'; const router: Router = Router(); + +// 延迟初始化multer配置 +const getUpload = () => { + // 使用系统临时目录作为基础路径,方便多个napcat用户统一读取使用 + const tmpPath = join(os.tmpdir(), 'napcat-upload'); + // 获取上传大小限制,默认50MB,最大200MB + let uploadSizeLimit = 50; + try { + // 使用同步方式获取配置 + uploadSizeLimit = WebUiConfig?.WebUiConfigData?.uploadSizeLimit || 50; + } catch (error) { + // 如果获取失败,使用默认值 + console.warn('获取上传大小限制失败:', error); + } + // 确保不超过最大限制 + uploadSizeLimit = Math.min(uploadSizeLimit, 200); + return multer({ + dest: tmpPath, + limits: { + fileSize: uploadSizeLimit * 1024 * 1024 // 转换为字节 + } + }); +}; + // router:读取配置 router.post('/GetConfig', OB11GetConfigHandler); // router:写入配置 router.post('/SetConfig', OB11SetConfigHandler); +// router:导出配置 +router.get('/ExportConfig', BackupExportConfigHandler); +// router:导入配置 +router.post('/ImportConfig', (req, res, next) => { + const upload = getUpload(); + upload.single('configFile')(req, res, next); +}, BackupImportConfigHandler); export { router as OB11ConfigRouter }; diff --git a/packages/napcat-webui-frontend/src/pages/dashboard/config/backup.tsx b/packages/napcat-webui-frontend/src/pages/dashboard/config/backup.tsx new file mode 100644 index 00000000..50cd417f --- /dev/null +++ b/packages/napcat-webui-frontend/src/pages/dashboard/config/backup.tsx @@ -0,0 +1,129 @@ +import { Button } from '@heroui/button'; +import toast from 'react-hot-toast'; +import { LuDownload, LuUpload } from 'react-icons/lu'; +import { requestServerWithFetch } from '@/utils/request'; + +// 导入配置 +const handleImportConfig = async (event: React.ChangeEvent) => { + const file = event.target.files?.[0]; + if (!file) return; + + // 检查文件类型 + if (!file.name.endsWith('.zip')) { + toast.error('请选择zip格式的配置文件'); + return; + } + + try { + const formData = new FormData(); + formData.append('configFile', file); + + const response = await requestServerWithFetch('/OB11Config/ImportConfig', { + method: 'POST', + body: formData, + }); + + if (!response.ok) { + const errorData = await response.json(); + throw new Error(errorData.message || '导入配置失败'); + } + + const result = await response.json(); + // 检查是否成功导入 + if (result.code === 0) { + toast.success(result.data?.message || '配置导入成功。'); + } else { + toast.error(`配置导入失败: ${result.data?.message || '未知错误'}`); + } + + } catch (error) { + const msg = (error as Error).message; + toast.error(`导入配置失败: ${msg}`); + } finally { + // 重置文件输入 + event.target.value = ''; + } +}; + +// 导出配置 +const handleExportConfig = async () => { + try { + const response = await requestServerWithFetch('/OB11Config/ExportConfig', { + method: 'GET', + }); + + if (!response.ok) { + throw new Error('导出配置失败'); + } + + // 创建下载链接 + const blob = await response.blob(); + const url = window.URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + const fileName = response.headers.get('Content-Disposition')?.split('=')[1]?.replace(/"/g, '') || 'config_backup.zip'; + a.download = fileName; + document.body.appendChild(a); + a.click(); + window.URL.revokeObjectURL(url); + document.body.removeChild(a); + + toast.success('配置导出成功'); + } catch (error) { + const msg = (error as Error).message; + + toast.error(`导出配置失败: ${msg}`); + } +}; + +const BackupConfigCard: React.FC = () => { + return ( +
+
+

备份与恢复

+

+ 您可以通过导入/导出配置文件来备份和恢复NapCat的所有设置 +

+ +
+ + +
+ +
+
+

+ 导入配置会覆盖当前所有设置,请谨慎操作。导入前建议先导出当前配置作为备份。 +

+
+
+
+
+ ); +}; + +export default BackupConfigCard; \ No newline at end of file diff --git a/packages/napcat-webui-frontend/src/pages/dashboard/config/index.tsx b/packages/napcat-webui-frontend/src/pages/dashboard/config/index.tsx index 711f58d8..1274b879 100644 --- a/packages/napcat-webui-frontend/src/pages/dashboard/config/index.tsx +++ b/packages/napcat-webui-frontend/src/pages/dashboard/config/index.tsx @@ -13,6 +13,7 @@ import ServerConfigCard from './server'; import SSLConfigCard from './ssl'; import ThemeConfigCard from './theme'; import WebUIConfigCard from './webui'; +import BackupConfigCard from './backup'; export interface ConfigPageProps { children?: React.ReactNode; @@ -108,6 +109,11 @@ export default function ConfigPage () { + + + + + ); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3afc2260..2f83b988 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -432,6 +432,9 @@ importers: napcat-pty: specifier: workspace:* version: link:../napcat-pty + unzipper: + specifier: ^0.10.14 + version: 0.10.14 ws: specifier: ^8.18.3 version: 8.18.3 @@ -448,6 +451,9 @@ importers: '@types/node': specifier: ^22.0.1 version: 22.19.1 + '@types/unzipper': + specifier: ^0.10.9 + version: 0.10.11 '@types/ws': specifier: ^8.5.12 version: 8.18.1 @@ -1894,89 +1900,105 @@ packages: resolution: {integrity: sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==} cpu: [arm64] os: [linux] + libc: [glibc] '@img/sharp-libvips-linux-arm@1.2.4': resolution: {integrity: sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==} cpu: [arm] os: [linux] + libc: [glibc] '@img/sharp-libvips-linux-ppc64@1.2.4': resolution: {integrity: sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==} cpu: [ppc64] os: [linux] + libc: [glibc] '@img/sharp-libvips-linux-riscv64@1.2.4': resolution: {integrity: sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==} cpu: [riscv64] os: [linux] + libc: [glibc] '@img/sharp-libvips-linux-s390x@1.2.4': resolution: {integrity: sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==} cpu: [s390x] os: [linux] + libc: [glibc] '@img/sharp-libvips-linux-x64@1.2.4': resolution: {integrity: sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==} cpu: [x64] os: [linux] + libc: [glibc] '@img/sharp-libvips-linuxmusl-arm64@1.2.4': resolution: {integrity: sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==} cpu: [arm64] os: [linux] + libc: [musl] '@img/sharp-libvips-linuxmusl-x64@1.2.4': resolution: {integrity: sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==} cpu: [x64] os: [linux] + libc: [musl] '@img/sharp-linux-arm64@0.34.5': resolution: {integrity: sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm64] os: [linux] + libc: [glibc] '@img/sharp-linux-arm@0.34.5': resolution: {integrity: sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm] os: [linux] + libc: [glibc] '@img/sharp-linux-ppc64@0.34.5': resolution: {integrity: sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [ppc64] os: [linux] + libc: [glibc] '@img/sharp-linux-riscv64@0.34.5': resolution: {integrity: sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [riscv64] os: [linux] + libc: [glibc] '@img/sharp-linux-s390x@0.34.5': resolution: {integrity: sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [s390x] os: [linux] + libc: [glibc] '@img/sharp-linux-x64@0.34.5': resolution: {integrity: sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [x64] os: [linux] + libc: [glibc] '@img/sharp-linuxmusl-arm64@0.34.5': resolution: {integrity: sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm64] os: [linux] + libc: [musl] '@img/sharp-linuxmusl-x64@0.34.5': resolution: {integrity: sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [x64] os: [linux] + libc: [musl] '@img/sharp-wasm32@0.34.5': resolution: {integrity: sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==} @@ -2727,56 +2749,67 @@ packages: resolution: {integrity: sha512-EPlb95nUsz6Dd9Qy13fI5kUPXNSljaG9FiJ4YUGU1O/Q77i5DYFW5KR8g1OzTcdZUqQQ1KdDqsTohdFVwCwjqg==} cpu: [arm] os: [linux] + libc: [glibc] '@rollup/rollup-linux-arm-musleabihf@4.53.2': resolution: {integrity: sha512-BOmnVW+khAUX+YZvNfa0tGTEMVVEerOxN0pDk2E6N6DsEIa2Ctj48FOMfNDdrwinocKaC7YXUZ1pHlKpnkja/Q==} cpu: [arm] os: [linux] + libc: [musl] '@rollup/rollup-linux-arm64-gnu@4.53.2': resolution: {integrity: sha512-Xt2byDZ+6OVNuREgBXr4+CZDJtrVso5woFtpKdGPhpTPHcNG7D8YXeQzpNbFRxzTVqJf7kvPMCub/pcGUWgBjA==} cpu: [arm64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-arm64-musl@4.53.2': resolution: {integrity: sha512-+LdZSldy/I9N8+klim/Y1HsKbJ3BbInHav5qE9Iy77dtHC/pibw1SR/fXlWyAk0ThnpRKoODwnAuSjqxFRDHUQ==} cpu: [arm64] os: [linux] + libc: [musl] '@rollup/rollup-linux-loong64-gnu@4.53.2': resolution: {integrity: sha512-8ms8sjmyc1jWJS6WdNSA23rEfdjWB30LH8Wqj0Cqvv7qSHnvw6kgMMXRdop6hkmGPlyYBdRPkjJnj3KCUHV/uQ==} cpu: [loong64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-ppc64-gnu@4.53.2': resolution: {integrity: sha512-3HRQLUQbpBDMmzoxPJYd3W6vrVHOo2cVW8RUo87Xz0JPJcBLBr5kZ1pGcQAhdZgX9VV7NbGNipah1omKKe23/g==} cpu: [ppc64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-riscv64-gnu@4.53.2': resolution: {integrity: sha512-fMjKi+ojnmIvhk34gZP94vjogXNNUKMEYs+EDaB/5TG/wUkoeua7p7VCHnE6T2Tx+iaghAqQX8teQzcvrYpaQA==} cpu: [riscv64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-riscv64-musl@4.53.2': resolution: {integrity: sha512-XuGFGU+VwUUV5kLvoAdi0Wz5Xbh2SrjIxCtZj6Wq8MDp4bflb/+ThZsVxokM7n0pcbkEr2h5/pzqzDYI7cCgLQ==} cpu: [riscv64] os: [linux] + libc: [musl] '@rollup/rollup-linux-s390x-gnu@4.53.2': resolution: {integrity: sha512-w6yjZF0P+NGzWR3AXWX9zc0DNEGdtvykB03uhonSHMRa+oWA6novflo2WaJr6JZakG2ucsyb+rvhrKac6NIy+w==} cpu: [s390x] os: [linux] + libc: [glibc] '@rollup/rollup-linux-x64-gnu@4.53.2': resolution: {integrity: sha512-yo8d6tdfdeBArzC7T/PnHd7OypfI9cbuZzPnzLJIyKYFhAQ8SvlkKtKBMbXDxe1h03Rcr7u++nFS7tqXz87Gtw==} cpu: [x64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-x64-musl@4.53.2': resolution: {integrity: sha512-ah59c1YkCxKExPP8O9PwOvs+XRLKwh/mV+3YdKqQ5AMQ0r4M4ZDuOrpWkUaqO7fzAHdINzV9tEVu8vNw48z0lA==} cpu: [x64] os: [linux] + libc: [musl] '@rollup/rollup-openharmony-arm64@4.53.2': resolution: {integrity: sha512-4VEd19Wmhr+Zy7hbUsFZ6YXEiP48hE//KPLCSVNY5RMGX2/7HZ+QkN55a3atM1C/BZCGIgqN+xrVgtdak2S9+A==} @@ -2851,24 +2884,28 @@ packages: engines: {node: '>=10'} cpu: [arm64] os: [linux] + libc: [glibc] '@swc/core-linux-arm64-musl@1.15.1': resolution: {integrity: sha512-fKzP9mRQGbhc5QhJPIsqKNNX/jyWrZgBxmo3Nz1SPaepfCUc7RFmtcJQI5q8xAun3XabXjh90wqcY/OVyg2+Kg==} engines: {node: '>=10'} cpu: [arm64] os: [linux] + libc: [musl] '@swc/core-linux-x64-gnu@1.15.1': resolution: {integrity: sha512-ZLjMi138uTJxb+1wzo4cB8mIbJbAsSLWRNeHc1g1pMvkERPWOGlem+LEYkkzaFzCNv1J8aKcL653Vtw8INHQeg==} engines: {node: '>=10'} cpu: [x64] os: [linux] + libc: [glibc] '@swc/core-linux-x64-musl@1.15.1': resolution: {integrity: sha512-jvSI1IdsIYey5kOITzyajjofXOOySVitmLxb45OPUjoNojql4sDojvlW5zoHXXFePdA6qAX4Y6KbzAOV3T3ctA==} engines: {node: '>=10'} cpu: [x64] os: [linux] + libc: [musl] '@swc/core-win32-arm64-msvc@1.15.1': resolution: {integrity: sha512-X/FcDtNrDdY9r4FcXHt9QxUqC/2FbQdvZobCKHlHe8vTSKhUHOilWl5EBtkFVfsEs4D5/yAri9e3bJbwyBhhBw==} @@ -3097,6 +3134,9 @@ packages: '@types/unist@3.0.3': resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==} + '@types/unzipper@0.10.11': + resolution: {integrity: sha512-D25im2zjyMCcgL9ag6N46+wbtJBnXIr7SI4zHf9eJD2Dw2tEB5e+p5MYkrxKIVRscs5QV0EhtU9rgXSPx90oJg==} + '@types/use-sync-external-store@0.0.6': resolution: {integrity: sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==} @@ -3233,41 +3273,49 @@ packages: resolution: {integrity: sha512-34gw7PjDGB9JgePJEmhEqBhWvCiiWCuXsL9hYphDF7crW7UgI05gyBAi6MF58uGcMOiOqSJ2ybEeCvHcq0BCmQ==} cpu: [arm64] os: [linux] + libc: [glibc] '@unrs/resolver-binding-linux-arm64-musl@1.11.1': resolution: {integrity: sha512-RyMIx6Uf53hhOtJDIamSbTskA99sPHS96wxVE/bJtePJJtpdKGXO1wY90oRdXuYOGOTuqjT8ACccMc4K6QmT3w==} cpu: [arm64] os: [linux] + libc: [musl] '@unrs/resolver-binding-linux-ppc64-gnu@1.11.1': resolution: {integrity: sha512-D8Vae74A4/a+mZH0FbOkFJL9DSK2R6TFPC9M+jCWYia/q2einCubX10pecpDiTmkJVUH+y8K3BZClycD8nCShA==} cpu: [ppc64] os: [linux] + libc: [glibc] '@unrs/resolver-binding-linux-riscv64-gnu@1.11.1': resolution: {integrity: sha512-frxL4OrzOWVVsOc96+V3aqTIQl1O2TjgExV4EKgRY09AJ9leZpEg8Ak9phadbuX0BA4k8U5qtvMSQQGGmaJqcQ==} cpu: [riscv64] os: [linux] + libc: [glibc] '@unrs/resolver-binding-linux-riscv64-musl@1.11.1': resolution: {integrity: sha512-mJ5vuDaIZ+l/acv01sHoXfpnyrNKOk/3aDoEdLO/Xtn9HuZlDD6jKxHlkN8ZhWyLJsRBxfv9GYM2utQ1SChKew==} cpu: [riscv64] os: [linux] + libc: [musl] '@unrs/resolver-binding-linux-s390x-gnu@1.11.1': resolution: {integrity: sha512-kELo8ebBVtb9sA7rMe1Cph4QHreByhaZ2QEADd9NzIQsYNQpt9UkM9iqr2lhGr5afh885d/cB5QeTXSbZHTYPg==} cpu: [s390x] os: [linux] + libc: [glibc] '@unrs/resolver-binding-linux-x64-gnu@1.11.1': resolution: {integrity: sha512-C3ZAHugKgovV5YvAMsxhq0gtXuwESUKc5MhEtjBpLoHPLYM+iuwSj3lflFwK3DPm68660rZ7G8BMcwSro7hD5w==} cpu: [x64] os: [linux] + libc: [glibc] '@unrs/resolver-binding-linux-x64-musl@1.11.1': resolution: {integrity: sha512-rV0YSoyhK2nZ4vEswT/QwqzqQXw5I6CjoaYMOX0TqBlWhojUf8P94mvI7nuJTeaCkkds3QE4+zS8Ko+GdXuZtA==} cpu: [x64] os: [linux] + libc: [musl] '@unrs/resolver-binding-wasm32-wasi@1.11.1': resolution: {integrity: sha512-5u4RkfxJm+Ng7IWgkzi3qrFOvLvQYnPBmjmZQ8+szTK/b31fQCnleNl1GgEt7nIsZRIf5PLhPwT0WM+q45x/UQ==} @@ -3559,10 +3607,17 @@ packages: resolution: {integrity: sha512-2CXFpkjVnY2FT+B6GrSYxzYf65BJWEqz5tIRHCvNsZZ2F3CmsCB37h8SpYgKG7y9C4YAeTipIPWG7EmFmhAeXA==} hasBin: true + big-integer@1.6.52: + resolution: {integrity: sha512-QxD8cf2eVqJOOz63z6JIN9BzvVs/dlySa5HGSBH5xtR8dPteIRQnBxxKqkNTiT6jbDTF6jAfrd4oMcND9RGbQg==} + engines: {node: '>=0.6'} + binary-extensions@2.3.0: resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} engines: {node: '>=8'} + binary@0.3.0: + resolution: {integrity: sha512-D4H1y5KYwpJgK8wk1Cue5LLPgmwHKYSChkbspQg5JtVuR5ulGckxfR62H3AE9UDkdMC8yyXlqYihuz3Aqg2XZg==} + bindings@1.5.0: resolution: {integrity: sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==} @@ -3578,6 +3633,9 @@ packages: blf-debug-appender@1.0.5: resolution: {integrity: sha512-ftNcGiVW2nxzG0WJ8Hcs58CbygPl2vMuLDDSxsfhGZmVl2NFGmZOuXhDDeJifvWmP0FHzP/L8dblxn65eQjgEA==} + bluebird@3.4.7: + resolution: {integrity: sha512-iD3898SR7sWVRHbiQv+sHUtHnMvC1o3nW5rAcqnq3uOn07DSAppZYUkIGslDz6gXC7HfunPe7YVBgoEJASPcHA==} + body-parser@2.2.0: resolution: {integrity: sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==} engines: {node: '>=18'} @@ -3616,6 +3674,10 @@ packages: buffer-from@1.1.2: resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} + buffer-indexof-polyfill@1.0.2: + resolution: {integrity: sha512-I7wzHwA3t1/lwXQh+A5PbNvJxgfo5r3xulgpYDB5zckTu/Z9oUK9biouBKQUjEqzaz3HnAT6TYoovmE+GqSf7A==} + engines: {node: '>=0.10'} + buffer-to-vinyl@1.1.0: resolution: {integrity: sha512-t6B4HXJ3YdJ/lXKhK3nlGW1aAvpQH2FMyHh25SmfdYkQAU3/R2MYo4VrY1DlQuZd8zLNOqWPxZFJILRuTkqaEQ==} engines: {node: '>=0.10.0'} @@ -3623,6 +3685,10 @@ packages: buffer@5.7.1: resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} + buffers@0.1.1: + resolution: {integrity: sha512-9q/rDEGSb/Qsvv2qvzIzdluL5k7AaJOTrw23z9reQthrbF7is4CtlT0DXyO1oei2DCp4uojjzQ7igaSHp1kAEQ==} + engines: {node: '>=0.2.0'} + bufferstreams@3.0.0: resolution: {integrity: sha512-Qg0ggJUWJq90vtg4lDsGN9CDWvzBMQxhiEkSOD/sJfYt6BLect3eV1/S6K7SCSKJ34n60rf6U5eUPmQENVE4UA==} engines: {node: '>=8.12.0'} @@ -3677,6 +3743,9 @@ packages: resolution: {integrity: sha512-p4Z49OGG5W/WBCPSS/dH3jQ73kD6tiMmUM+bckNK6Jr5JHMG3k9bg/BvKR8lKmtVBKmOiuVaV2ws8s9oSbwysg==} engines: {node: '>=18'} + chainsaw@0.1.0: + resolution: {integrity: sha512-75kWfWt6MEKNC8xYXIdRpDehRYY/tNSgwKaJq+dbbDcxORuVrrQ+SEHoWsniVn9XPYfP4gmdWIeDk/4YNp1rNQ==} + chalk@4.1.2: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} engines: {node: '>=10'} @@ -4009,6 +4078,9 @@ packages: resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} engines: {node: '>= 0.4'} + duplexer2@0.1.4: + resolution: {integrity: sha512-asLFVfWWtJ90ZyOUHMqk7/S2w2guQKxUI2itj3d92ADHhxUSbCMGi1f1cBcJ7xM1To+pE/Khbwo1yuNbMEPKeA==} + duplexer@0.1.2: resolution: {integrity: sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==} @@ -4531,6 +4603,11 @@ packages: engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} os: [darwin] + fstream@1.0.12: + resolution: {integrity: sha512-WvJ193OHa0GHPEL+AycEJgxvBEwyfRkN1vhjca23OaPVMCaLCXTd5qAu82AjTcgP1UJmytkOKb63Ypde7raDIg==} + engines: {node: '>=0.6'} + deprecated: This package is no longer supported. + function-bind@1.1.2: resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} @@ -5091,6 +5168,9 @@ packages: lines-and-columns@1.2.4: resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} + listenercount@1.0.1: + resolution: {integrity: sha512-3mk/Zag0+IJxeDrxSgaDPy4zZ3w05PRZeJNnlWhzFz5OkX49J4krc+A8X2d2M69vGMBEX0uyl8M+W+8gH+kBqQ==} + locate-path@6.0.0: resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} engines: {node: '>=10'} @@ -6096,6 +6176,11 @@ packages: resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + rimraf@2.7.1: + resolution: {integrity: sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==} + deprecated: Rimraf versions prior to v4 are no longer supported + hasBin: true + rimraf@3.0.2: resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} deprecated: Rimraf versions prior to v4 are no longer supported @@ -6183,6 +6268,9 @@ packages: resolution: {integrity: sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==} engines: {node: '>= 0.4'} + setimmediate@1.0.5: + resolution: {integrity: sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==} + setprototypeof@1.2.0: resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} @@ -6530,6 +6618,9 @@ packages: resolution: {integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==} engines: {node: '>=6'} + traverse@0.3.9: + resolution: {integrity: sha512-iawgk0hLP3SxGKDfnDJf8wTz4p2qImnyihM5Hh/sGvQ3K37dPi/w8sRhdNIxYA1TwFwc5mDhIJq+O0RsvXBKdQ==} + trim-lines@3.0.1: resolution: {integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==} @@ -6701,6 +6792,9 @@ packages: unrs-resolver@1.11.1: resolution: {integrity: sha512-bSjt9pjaEBnNiGgc9rUiHGKv5l4/TGzDmYw3RhnkJGtLhbnnA/5qJj7x3dNDCRx/PJxu774LlH8lCOlB4hEfKg==} + unzipper@0.10.14: + resolution: {integrity: sha512-ti4wZj+0bQTiX2KmKWuwj7lhV+2n//uXEotUmGuQqrbVZSEGFMbI68+c6JCQ8aAmUWYvtHEz2A8K6wXvueR/6g==} + update-browserslist-db@1.1.4: resolution: {integrity: sha512-q0SPT4xyU84saUX+tomz1WLkxUbuaJnR1xWt17M7fJtEJigJeWUNGUqrauFXsHnqev9y9JTRGwk13tFBuKby4A==} hasBin: true @@ -9949,6 +10043,10 @@ snapshots: '@types/unist@3.0.3': {} + '@types/unzipper@0.10.11': + dependencies: + '@types/node': 22.19.1 + '@types/use-sync-external-store@0.0.6': {} '@types/ws@8.18.1': @@ -10456,8 +10554,15 @@ snapshots: baseline-browser-mapping@2.8.27: {} + big-integer@1.6.52: {} + binary-extensions@2.3.0: {} + binary@0.3.0: + dependencies: + buffers: 0.1.1 + chainsaw: 0.1.0 + bindings@1.5.0: dependencies: file-uri-to-path: 1.0.0 @@ -10488,6 +10593,8 @@ snapshots: transitivePeerDependencies: - supports-color + bluebird@3.4.7: {} + body-parser@2.2.0: dependencies: bytes: 3.1.2 @@ -10538,6 +10645,8 @@ snapshots: buffer-from@1.1.2: {} + buffer-indexof-polyfill@1.0.2: {} + buffer-to-vinyl@1.1.0: dependencies: file-type: 3.9.0 @@ -10550,6 +10659,8 @@ snapshots: base64-js: 1.5.1 ieee754: 1.2.1 + buffers@0.1.1: {} + bufferstreams@3.0.0: dependencies: readable-stream: 3.6.2 @@ -10619,6 +10730,10 @@ snapshots: chai@6.2.1: {} + chainsaw@0.1.0: + dependencies: + traverse: 0.3.9 + chalk@4.1.2: dependencies: ansi-styles: 4.3.0 @@ -10918,6 +11033,10 @@ snapshots: es-errors: 1.3.0 gopd: 1.2.0 + duplexer2@0.1.4: + dependencies: + readable-stream: 2.3.8 + duplexer@0.1.2: {} duplexify@3.7.1: @@ -11644,6 +11763,13 @@ snapshots: fsevents@2.3.3: optional: true + fstream@1.0.12: + dependencies: + graceful-fs: 4.2.11 + inherits: 2.0.4 + mkdirp: 0.5.6 + rimraf: 2.7.1 + function-bind@1.1.2: {} function.prototype.name@1.1.8: @@ -12225,6 +12351,8 @@ snapshots: lines-and-columns@1.2.4: {} + listenercount@1.0.1: {} + locate-path@6.0.0: dependencies: p-locate: 5.0.0 @@ -13489,6 +13617,10 @@ snapshots: reusify@1.1.0: {} + rimraf@2.7.1: + dependencies: + glob: 7.2.3 + rimraf@3.0.2: dependencies: glob: 7.2.3 @@ -13625,6 +13757,8 @@ snapshots: es-errors: 1.3.0 es-object-atoms: 1.1.1 + setimmediate@1.0.5: {} + setprototypeof@1.2.0: {} sharp@0.34.5: @@ -14063,6 +14197,8 @@ snapshots: totalist@3.0.1: {} + traverse@0.3.9: {} + trim-lines@3.0.1: {} trim-newlines@4.1.1: {} @@ -14284,6 +14420,19 @@ snapshots: '@unrs/resolver-binding-win32-ia32-msvc': 1.11.1 '@unrs/resolver-binding-win32-x64-msvc': 1.11.1 + unzipper@0.10.14: + dependencies: + big-integer: 1.6.52 + binary: 0.3.0 + bluebird: 3.4.7 + buffer-indexof-polyfill: 1.0.2 + duplexer2: 0.1.4 + fstream: 1.0.12 + graceful-fs: 4.2.11 + listenercount: 1.0.1 + readable-stream: 2.3.8 + setimmediate: 1.0.5 + update-browserslist-db@1.1.4(browserslist@4.28.0): dependencies: browserslist: 4.28.0