commit 2caac3c31f31652dceba12f41eea6f2aa5362983 Author: fuenmao <980740792@qq.com> Date: Mon Oct 19 11:09:09 2026 +0800 Initial commit: Receipt Studio desktop application diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..08c12af --- /dev/null +++ b/.gitattributes @@ -0,0 +1,4 @@ +* text=auto eol=lf +*.cmd text eol=crlf +*.png binary +*.ico binary diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..332a007 --- /dev/null +++ b/.gitignore @@ -0,0 +1,39 @@ +# Dependencies and generated output +/node_modules/ +/dist/ +/release/ +/coverage/ +*.tsbuildinfo + +# Local receipts, stamps, preferences, and test output +/.local-data/ +/.qa/ +*.sqlite +*.sqlite-* +*.db +*.db-* + +# Local credentials and environment configuration +.env +.env.* +!.env.example +!.env.*.example +*.pem +*.key +*.p12 +*.pfx +/.aws/ + +# Local editor and assistant settings +/.idea/ +/.vscode/ +/.codex/ +/.agents/ + +# Logs, caches, and operating system files +*.log +*.dmp +.cache/ +.DS_Store +Thumbs.db +Desktop.ini diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000..1223b5c --- /dev/null +++ b/.npmrc @@ -0,0 +1,2 @@ +fund=false +audit=false diff --git a/README.md b/README.md new file mode 100644 index 0000000..7e62a4e --- /dev/null +++ b/README.md @@ -0,0 +1,64 @@ +# 收据工坊 + +面向 Windows 10 x64 的本地收据工具。安装后无需登录或联网,无需安装 Excel。 + +## 使用 + +1. 打开“偏好设置”,选择导出目录和印章文件夹,填写默认出纳、复核人。 +2. 在“印章管理”选择本批印章;也可以明确选择不使用印章。 +3. 在工作台下载 Excel 模板,每行填写一条收费项目,再导入;在“按分组核对收据”中按学校、年级、收费项目整组勾选,核对张数、金额和待修改条数。 +4. 核对错误行、选中数据与右侧预览,选择 PDF、PNG、A4 打印版后生成。A4 预览按已选分组显示对应页,不跨组混排。 +5. 补打请到“生成记录”,按学校、年级、收费项目,或输入姓名、任意收据编号查找,预览匹配的原收据。重新导出沿用原编号、数据和印章,导出该批全部收据。 +6. 生成记录支持逐批删除,确认后移除批次数据;已导出的文件保留,流水号继续递增。删除后不能在列表中补打该批次。 + +生成记录默认每页 5 条,可切换为每页 10、20 或 50 条,并记住上次选择。列表底部显示总条数、当前范围和页码,可使用首页、上一页、页码、下一页和末页翻页。改变筛选条件或每页条数会回到第一页;删除末页最后一条记录后自动回到有效页。分页覆盖全部生成记录。 + +A4 打印版默认上下两张,奇数张时最后半页留空,也可选每页一张。单张 PDF/PNG 页面为 190 × 140 mm;收据主体为 180 × 120 mm。打印 A4 文件时使用 A4、实际大小或 100% 比例。首批请在实际打印机上试打一页。 + +每次导出自动建立独立批次文件夹,生成成功后自动打开输出目录,这两项行为无需设置。批次保留原有命名,内部按“学校 → 年级 → 收费项目”分层。单张 PDF、PNG 以及该组的 A4 打印版放在同一个项目文件夹;每组独立排版,组内保持 Excel 顺序,每页两张时奇数张的最后半页留空。生成清单保留在批次根目录,记录文件的相对路径。历史记录缺少学校或年级时,使用“未分类学校”“未分类年级”目录。文件名在“偏好设置 → 文件与常用选择 → 导出的文件叫什么名字”中调整,修改后立即显示文件名示例。 + +```text +原有批次文件夹/ + 巴南中学/ + 高2028届/ + 秋季学期服务费/ + SJ00000001_巴南中学 高2028届 张三.pdf + SJ00000001_巴南中学 高2028届 张三.png + A4打印版_每页2张.pdf + 生成清单.json +``` + +Excel 必填列:收款日期、学校、年级、姓名、收费项目、金额。收据上的客户名称自动按“学校 年级 姓名”拼接,三列去除首尾空白后用一个空格分隔。选填列:出纳、复核。金额为正数,最多两位小数,上限 9,999,999.99 元。单次最多 5000 条,仅支持 .xlsx。印章支持 PNG/JPG/WebP,每张不超过 8 MB,列表最多加载 100 张;建议透明 PNG。 + +## 编号与本地数据 + +默认编号 SJ00000001,按前缀独立递增。预览不占号,正式生成前先持久保存编号;失败、取消和重启后可从生成记录按原编号重试。重复导入相同文件会提示;只有明确继续才会生成新编号。 + +设置文件和 SQLite 记录保存在 Windows 用户数据目录。开发数据在 .local-data/app,自动测试数据在 .qa/profile,生产数据与二者隔离。升级及默认卸载保留用户数据。 + +多台电脑离线使用时,给每台设置不同编号前缀。 + +## 开发 + +本地调试无需安装程序,双击项目根目录的“启动本地调试.cmd”即可。入口优先使用这台电脑已有的兼容 Node 运行环境,不改变系统默认 Node 版本。 + +请在启动后自动弹出的“收据工坊”独立桌面窗口中调试。桌面窗口按 F12 打开调试工具;界面修改会自动更新。浏览器直接打开 127.0.0.1:5173 只会加载前端资源,完整的导入、印章和导出功能需要桌面程序。修改 src/main、src/core 或 src/shared 中影响主进程的代码后,关闭窗口并重新启动。开发设置和编号保存在 .local-data/app。 + +常规命令需要 Node.js 24 LTS(最低 22.13)、npm,以及 Windows 10 x64。 + +- npm install +- npm run dev:桌面开发环境 +- npm test:核心行为测试 +- npm run build:类型检查和构建 +- npm run test:desktop:真实 Electron 集成验证(隐藏窗口,产物在 .qa) +- npm run test:loading:加载时机、持续旋转及取消选择验证(隔离测试数据) +- npm run test:history:记录分页、每页条数记忆、筛选和删除末页验证(隔离测试数据) +- npm run test:print:较大印章批量打印、错误提示和原编号重试验证(独立测试数据) +- npm run package:生成 Windows x64 安装程序 +- npm run package:dir:生成免安装应用目录 + +依赖版本由 package-lock.json 锁定。应用、字体回退、PDF 渲染资源均使用本地资源。开发依赖下载需要网络,已安装的应用运行不依赖网络。 + +## 扩展 + +详细模块边界与扩展方法见 [架构说明](docs/ARCHITECTURE.md)。 diff --git a/build/icon.ico b/build/icon.ico new file mode 100644 index 0000000..b319f79 Binary files /dev/null and b/build/icon.ico differ diff --git a/build/icon.png b/build/icon.png new file mode 100644 index 0000000..9c0e876 Binary files /dev/null and b/build/icon.png differ diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md new file mode 100644 index 0000000..57b0a36 --- /dev/null +++ b/docs/ARCHITECTURE.md @@ -0,0 +1,101 @@ +# 架构与扩展 + +应用采用“桌面界面 → 白名单 IPC → 应用服务 → 业务规则与存储”的结构。Renderer 不直接访问文件系统或数据库;主进程不接受界面传来的任意 HTML、SQL 或脚本。 + +## 模块边界 + +| 位置 | 职责 | +| --- | --- | +| src/shared | 接口类型、设置契约、输入校验;不依赖 Electron | +| src/shared/receipt-groups.ts | 共用分组层级、分组摘要、勾选统计及 A4 预览选页 | +| src/shared/file-names.ts | 文件名规则、字符处理;设置示例和正式导出共用 | +| src/core/money.ts | 整数分、金额上限、人民币大写、金额格 | +| src/core/date.ts | 日期与 Excel 日期制 | +| src/core/templates.ts | 模板接口、注册器、打印页面组合;与桌面环境解耦 | +| src/main/workbooks.ts | WorkbookReader 接口及 .xlsx 适配器 | +| src/main/ledger.ts | 持久编号、批次快照及删除记录 | +| src/main/settings-store.ts | 版本化设置及原子写入 | +| src/main/confirmations.ts | 主进程确认请求、回复匹配与取消 | +| src/renderer/components/ConfirmDialog.vue | 基于 Reka UI 的统一确认弹窗 | +| src/renderer/components/ActionButton.vue | 操作触发、禁用及行内目标匹配 | +| src/renderer/components/LoadingRegion.vue | 整块区域遮罩、转圈、区域错误及取消入口 | +| src/renderer/components/ReceiptGroupTree.vue | 可折叠的整组勾选、部分选中状态及按需显示 | +| src/renderer/loading.ts | 操作任务与板块加载状态映射 | +| src/renderer/confirmations.ts | 界面确认状态及焦点恢复 | +| src/main/seals.ts | 本地图片校验、缓存、快照 | +| src/main/pdf-engine.ts | PdfEngine 接口及 Electron 渲染适配器 | +| src/main/print-documents.ts | 内存打印页面、随机本地地址及内容释放 | +| src/main/exporter.ts | 导出编排、批量分块、取消、清单与进度 | +| src/main/export-layout.ts | 学校/年级/收费项目分组、目录名称及冲突处理 | +| src/main/index.ts | 桌面生命周期、原生对话框、IPC 接线 | +| src/renderer | Vue 页面、交互状态、收据预览及 PDF 转 PNG | + +界面划分为工作台、印章、记录、设置。收据 HTML 不直接从 Vue 界面截图,预览与导出都使用同一模板注册器,防止两套排版逐渐分离。 + +## 操作反馈 + +操作反馈位于对应板块。LoadingRegion 通过 as 保留原表单、面板和网格结构,显示整块浅色遮罩与居中转圈。指示器按板块与视口的交集定位,长页面滚动后仍可见。加载时 region-content 使用 inert 阻止鼠标与键盘操作,完成后恢复内容;页面顶部不再显示独立成功或错误提示条。 + +usePanelState 将设置、印章、生成记录、导入数据、预览与导出六个板块映射到任务。actions.task 在启动时绑定所属板块,防止同类操作重入;操作立即执行,保存和手动刷新等反馈从首帧绘制后计时,至少可见 500 毫秒;较慢的操作显示到真实完成。loadingTasks 与 busy 分开:选择/打开文件夹、切换印章及后台同步不会产生人为等待或整块遮罩。主进程通过 onProcessing 在导入、模板保存和删除的选择/确认步骤结束后通知界面开始转圈,取消选择直接结束。错误写入 panelErrors 并显示在对应区域,可关闭;启动错误显示在启动区域。 + +ActionButton 负责事件和禁用状态,图标与文字保持稳定。导出遮罩使用 loading-actions 插槽提供取消按钮,该插槽位于 inert 内容之外。取消请求发出后显示“正在取消”,直至主进程报告结束。预览版本号保证旧请求不能关闭新请求的加载反馈。必要的进度转圈使用独立的 region-spinner 动画,在 prefers-reduced-motion 下仍旋转;其余装饰过渡遵循系统偏好。scripts/loading-test.mjs 在可见的测试窗口逐帧检查旋转角度和持续时间,使用隔离配置及文件夹验证选择器、取消和处理阶段。 + +## 确认弹窗 + +使用 Reka UI AlertDialog,并封装统一的标题、说明、详情、提示与按钮。弹窗提供焦点约束、默认聚焦取消和 Esc 取消,业务内容通过 Vue 文本绑定呈现。删除、重复生成和退出确认共用同一组件;离开未保存设置使用同一界面服务。 + +主进程通过 ConfirmationService 发送带随机 id 的请求,等待白名单 IPC 回复。仅匹配当前请求的回复有效,同时只允许一个主进程请求。页面导航、进程异常或窗口关闭会取消等待中的确认。操作执行仍保留主进程的数据校验与任务状态检查。新增主进程确认调用 confirmations.ask,新增纯界面确认调用 askConfirmation。 + +Reka UI 按需打包到本地前端,运行不依赖网络;许可声明随构建输出到 renderer/licenses/reka-ui.txt。 + +## 新增收据模板 + +实现 ReceiptTemplate 的 id、name、css、render,并在 TemplateRegistry 构造时注册。id 是持久标识,发布后保持稳定;已有模板发生版式不兼容变化时使用新 id,保留旧实现以支持历史重打。设置页自动从注册器读取可用模板。 + +模板使用物理毫米布局,单张页面与 A4 页面由 buildDocument 统一组合。render 中所有业务文字经过 escapeHtml,图片仅接受已校验本地文件转换的数据 URL。 + +## 新增导入来源 + +实现 WorkbookReader.read,将新来源映射为 ImportDraft、ImportRow 和 ReceiptInput。收据编号由 Ledger 分配,导入适配器不创建编号。导入层保留 school、grade、studentName 三个来源字段,并生成以单个空格分隔的 customer 展示文字。来源字段对旧记录为可选,历史客户名称继续按快照读取,避免升级后影响补打。CSV、旧 .xls 或其他来源都可以增加适配器,再在入口按扩展名选择。 + +## 新增导出格式 + +ExportCoordinator 只依赖 PdfEngine 接口。可以替换渲染器而不改变编号与导入。新增格式在 ExportOptions 中明确声明,输入经共享校验器校验;禁止将任意文件路径和脚本通过 IPC 暴露给网页。 + +打印 HTML 通过随机的 receipt://print/ 地址在内存中提供,避免把整页及重复印章编码进地址而触发长度限制。每次 PDF 导出完成或失败后释放页面内容;不写临时 HTML 文件。错误提示与历史摘要会省略内嵌页面内容并限制长度。 + +PNG 使用已生成 PDF 的第一页渲染,保持字体和版式一致。A4 批量打印每次最多渲染 40 张收据再合并 PDF;分块大小为偶数,以免每个分块引入额外空白半页。 + +导出目录保留现有批次外层,内部由 groupReceipts 按 school、grade、item 三个来源字段逐层规划。目录层级定义集中在 shared/receipt-groups.ts 的 receiptGroupLevels 中,导出路径、界面核对和历史分类共用这一规则。缺失的历史字段使用未分类目录,不拆解或修改旧客户名称。目录名过滤 Windows 非法字符与保留名,截断或替换后的名称加稳定摘要,并处理大小写冲突,避免不同分组合并。 + +单张 PDF/PNG 按原收据顺序写入所属目录,每组 A4 单独生成,组内仍按原顺序每 40 张分块。单组失败后继续其余分组,仅打印时按分组累计成功/失败张数。生成清单保留在批次根目录,receipts.directory、receipts.files 和 printFiles 使用相对路径;重新导出从原快照重新规划目录,沿用原编号并保留同名文件追加后缀的行为。 + +## 分组核对与历史查找 + +importGroupTree 按三级分组统计有效行、待修改行、选中张数和金额;选择学校或年级时只切换有效行。界面使用原生 details 和复选框,支持部分选中状态;多于 20 个有效分组时减少初始展开,同级每次显示 50 项,关闭节点的子树按需创建。勾选直接更新原有 selected 集合。 + +A4 预览使用 printPreviewRows 在已选同组收据中定位当前行所在页,并按全局已选顺序展示待分配编号。预览查询不修改流水号。设置页面的实时文件名示例和正式导出使用相同的 receiptFileStem,保持前缀、命名方式和字符替换一致。 + +记录列表通过 listBatchPage 传递经校验的学校、年级、项目、文本、页码和每页条数,返回当前页、真实总条数及有效页码。无筛选时使用数据库 COUNT/LIMIT/OFFSET;有筛选时逐批匹配同一张收据,统计所有匹配批次并仅构建当前页摘要,末页删除后自动钳制到有效页。学校、姓名和任意编号查找仍覆盖全库。原 listBatches 继续为已有调用提供最多 200 条摘要,bootstrap 提供全库分类选项和真实总数。 + +界面默认每页 5 条,可选 5/10/20/50;historyPageSize 在设置校验器中为旧配置补默认值,修改后持久保存。筛选或每页条数变化时回到第一页,刷新保留当前页;使用请求版本号保持最新结果。显式翻页沿用整块加载反馈,自动筛选合并短时间内的输入变化。过滤结果仍对应完整批次,重新导出维持原有批次语义。原收据预览可指定匹配编号,旧批次输出目录经过全库归属检查。 + +## 编号一致性 + +应用使用单实例锁;主进程生成任务互斥。Ledger 内部串行事务分配流水,先原子持久保存数据库,再写导出文件。编号不因取消或失败而回收。原批次重试不再次 reserve。 + +批次保存原数据、默认姓名生效后的内容、模板 id、设置及印章图片快照。更换印章或移动图片不改变历史批次。导出位置与导出选项可以变化,正式编号和内容保持原值。 + +批次记录支持删除。主进程在确认前后检查生成任务状态,Ledger 在事务中拒绝删除 reserved/running 批次,仅移除批次快照,保留 receipt_numbers 和 sequences。已导出的文件不会由此功能删除。删除后清理当前草稿对该批次的引用。 + +数据损坏时停止启动和分配编号,保留原文件,不静默创建空数据库。 + +## 设置与升级 + +设置包含 schemaVersion。增加可选字段时为共享校验器和 defaultSettings 同时补默认值,保证旧配置可加载。更改数据库结构需在 Ledger.open 中增加显式事务迁移,再写兼容性测试。不要通过删除用户数据解决升级问题。 + +设置页面保留文件、打印、印章和编号四个板块,单张文件命名方式直接显示在文件板块。独立批次目录与成功后打开目录为固定启用行为,无界面开关;SettingsStore 在读取和保存时将旧配置中的 createBatchFolder、openAfterExport 归一为 true,其余偏好保持原值。Exporter 每次都创建批次目录,包括从旧快照重新导出。备份和恢复的界面、IPC、预加载接口、状态操作及数据库合并代码已移除。 + +## 验证 + +核心测试覆盖金额边界、日期、工作簿错误、模板转义、持久编号、旧配置兼容、分块排版、取消和重试。桌面测试实际点击界面并检查 PDF 页数、A4 尺寸、PNG 分辨率、编号重启、设置持久化及自动打开目录。实际打印机的可打印边距需在交付时试打确认。 diff --git a/docs/VERIFICATION.md b/docs/VERIFICATION.md new file mode 100644 index 0000000..47c012b --- /dev/null +++ b/docs/VERIFICATION.md @@ -0,0 +1,112 @@ +# 版本验证记录 + +版本:1.0.1。验证日期:2026-10-16。 + +环境:Windows 10 专业版 x64,版本 10.0.19045。开发运行时 Node.js 24.19.0。 + +- 类型检查与生产构建通过。 +- 新版 Excel 模板使用学校、年级、姓名三列;各列去除首尾空白后,以一个空格拼接收据客户名称。必填校验、列顺序调整和来源字段持久化均通过验证;旧记录仍可补打。 +- 31 项核心测试通过:金额精度和越界、金额大写、Excel 日期制、错误行、重复行、模板转义、编号持久化、旧备份合并、分块打印、取消重试、不覆盖同名文件、PNG 物理密度。 +- 实际 Electron 界面验证通过:保存偏好、绑定和选择本地印章、导出模板、导入含错误行的工作簿、独立 PDF 和 PNG、A4 一页两张、奇数张留空、原编号补打、备份与重启。 +- 三张收据输出为三份独立 PDF、三份 300 DPI PNG,以及两页 A4 打印 PDF。 +- 打包后的独立程序验证通过:不依赖项目 node_modules,启动、Excel 导入、盖章、PDF、PNG 均通过;PNG 分辨率标记为 11811 像素/米,约为 300 DPI。 + +安装包:release/ReceiptStudio-1.0.1-win-x64-setup.exe。 +免安装目录:release/win-unpacked(整个目录一起使用)。 + +尚未进行实体打印机试打。交付后请由财务使用 A4、实际大小或 100% 比例试打一页,核对印章与裁切位置。 + +当前安装包未配置代码签名证书。 + +## 本地调试版:批量打印修复(2026-10-16) + +- 复现旧版 ERR_INVALID_URL:较大印章重复嵌入批量页面,data 地址中的内容达到约 11.7 百万个字符,打印加载失败。 +- 打印页面改用内存中的短本地地址,导出成功或失败后释放内容。类型检查、构建和 31 项测试通过。 +- 实际 Electron 验证:41 张带印章收据,跨 40 张分块边界,输出 21 页 A4;原编号重试输出 41 份单张 PDF、41 份 PNG 和 1 份 A4 PDF,下一编号保持 REG00000042。 +- 模拟页面加载失败,错误提示保留错误码、隐藏页面内容,失败不显示绿色成功提示;随后原编号重试成功。 +- 验证产物:.qa/print-regression/result.json。使用独立测试目录,未改写用户设置、记录及编号。 +- 本次修复更新源码与本地构建,未重新生成安装包。重新启动“启动本地调试.cmd”后生效。 + +## 本地调试版:记录表与批次删除(2026-10-16) + +- 生成记录表头由 10px 调整为 12px,提高字重与文字对比度。 +- 每条批次增加删除按钮和确认对话框。删除批次快照后,导出文件、编号归属与流水保留;生成中的批次禁止删除。 +- 备份合并保留已删除批次的编号归属;删除前备份可恢复原批次,并拒绝其他批次占用同一编号。 +- 类型检查、构建和 35 项测试通过。实际 Electron 验证删除取消、删除确认、文件保留、重启后删除生效及流水继续;1920 与 1120 像素窗口均检查表头和操作布局。 +- 验证产物:.qa/history-delete-check/result.json。使用独立测试数据,未删除用户现有批次。 +- 新增桌面接口和预加载桥接,需关闭程序并重新启动本地调试后使用删除功能。本次未重新生成安装包。 + +## 本地调试版:统一确认弹窗(2026-10-16) + +- 引入开源 Reka UI 2.11.0,统一删除、重复生成、恢复备份、未保存设置和生成中退出的确认样式;组件随应用本地打包。 +- 删除弹窗采用居中卡片、图标、批次详情、保留文件说明和明确操作按钮。取消默认聚焦,Tab 焦点保持在弹窗内,Esc 取消。 +- 主进程请求使用随机 id 匹配回复,取消不批准操作,界面重载或关闭时撤销等待中的确认,防止遗留请求。 +- 类型检查、构建和 39 项测试通过。实际 Electron 验证重复生成取消/继续、删除取消/确认、恢复备份取消/确认、未保存设置留存/放弃、界面重载后再次确认、生成中关闭窗口后继续生成。 +- 文件和编号保持验证通过,未触发业务原生提示框,无界面脚本异常。验证记录:.qa/dialogs-check/result.json;外观截图:.qa/dialogs-check/delete-dialog.png。 +- 使用独立测试配置,用户现有记录未改动。更新后需要重新启动本地调试,安装包未重新生成。 + +## 本地调试版:操作与刷新反馈(2026-10-16) + +- 保存设置、列表刷新、Excel 导入与模板保存、印章切换、目录选择、备份恢复、批次查看/删除/重新导出均使用统一操作反馈。 +- 操作时显示旋转图标与处理中说明,禁止重复点击;成功后短暂打勾。快速操作的反馈保持可见,相同成功提示再次触发入场动画。取消和失败不显示成功。 +- 手动刷新保留原列表并淡化,完成后说明记录/印章数量;后台同步独立处理。预览显示加载覆盖层,导出取消反馈持续至任务结束。支持减少动画的系统偏好。 +- 类型检查、构建与 39 项测试通过。实际 Electron 验证保存转圈/禁用/持久化/完成打勾/重复保存提示、印章刷新计数与切换反馈、刷新失败无成功状态、生成记录刷新、取消导入无成功提示;无界面脚本异常。 +- 完整桌面操作验证通过:模板导入、错误行、选择印章、单张 PDF、300 DPI PNG、A4 排版、原编号重新导出、备份与重启。 +- 验证记录:.qa/feedback-check/result.json、.qa/result.json。使用独立测试目录,用户记录未改动。本次只更新源码与本地构建。 + +## 本地调试版:改为板块整体加载(2026-10-16) + +- 根据使用反馈,加载动画改为整个对应板块的浅色遮罩与居中转圈,处理完成后恢复;移除页面顶部的独立成功、错误提示条及按钮成功打勾反馈。 +- 设置区域、印章区域、生成记录、收据数据、预览和导出分别关联自己的加载状态;加载内容使用 inert 阻止重复操作,错误留在对应板块内。 +- 长页面滚动后,转圈指示器保持在可见区域;导出遮罩保留可操作的取消入口,取消后等实际任务结束才恢复。 +- 类型检查与构建通过。实际 Electron 验证整块遮罩尺寸、内容不可交互、滚动后指示器可见、取消导入恢复、导出遮罩中可取消、刷新失败显示在板块内;未出现界面脚本异常。 +- 完整桌面导入/设置/PDF/PNG/A4/原编号补打/备份/重启验证通过。1920、1440、1120 像素窗口及 A4 预览的列表铺满与底部分页布局验证通过。 +- 验证记录:.qa/regions-check/result.json、.qa/result.json、.qa/layout-check/result.json。使用独立测试配置,用户记录未改动;未重新生成安装包。 + +## 本地调试版:确保转圈与合理的加载时机(2026-10-16) + +- 使用独立的持续旋转动画,修复系统减少动态效果时进度图标停止的问题;保存、手动刷新等操作从首帧绘制后至少显示 500 毫秒。加载仍覆盖对应板块,不增加顶部提示条。 +- 选择/打开文件夹、绑定印章目录及切换印章不增加人为加载等待。预览自动更新保留短延迟以避免闪烁,工作台手动刷新直接显示完整半秒反馈。 +- 增加主进程处理阶段通知:导入、保存模板、备份、恢复和删除在选好文件或确认之后才显示加载,取消选择或确认直接结束。 +- 新增 test:loading 实际窗口逐帧验证。普通和减少动态效果两种设置下旋转角度持续改变,保存、三处刷新入口、导入、模板、备份恢复和确认删除实测约 520~534 毫秒;取消文件夹选择约 4 毫秒,未产生遮罩。 +- 构建、类型检查、39 项单元测试和完整桌面导入/PDF/PNG/A4/原编号重导出/备份/重启验证通过。删除确认后保留已分配编号;无界面脚本异常。 +- 验证记录:.qa/loading/result.json、.qa/result.json。全部使用独立测试配置,用户记录未改动。需要重新启动本地调试加载新的处理阶段接口;未重新生成安装包。 + +## 本地调试版:按学校、年级、收费项目分层导出(2026-10-19) + +- 保留原有最外层批次目录命名,内部按“学校 → 年级 → 收费项目”创建目录;单张 PDF、PNG 和对应分组的 A4 打印版保存在项目目录,生成清单保留在批次根目录。 +- 分组打印独立排版,组内保持 Excel 顺序,非连续行可归入同组;每组仍按 40 张分块,奇数张只在本组最后一页留空半页。打印分组失败后继续其他分组,并累计正确的成功/失败张数。 +- 目录名处理非法字符、保留名、超长名称和大小写冲突。旧记录缺少学校或年级时使用未分类目录,不修改原客户名称。重新导出沿用原编号,保留同名文件追加后缀的规则。 +- 43 项测试通过,覆盖三层文件位置、混合分组、清单相对路径、旧记录、目录冲突、取消重试、分组失败与 81 张分块排版。类型检查和构建通过。 +- 实际 Electron 桌面验证导入、设置、单张 PDF、300 DPI PNG、三个独立 A4 分组、原编号分层重新导出、备份与重启通过。较大印章 41 张同组收据正确生成 21 页 A4,失败后原编号重试生成 42 个 PDF、41 个 PNG,通过错误摘要检查。 +- 验证记录:.qa/result.json、.qa/print-regression/result.json。测试使用隔离配置与目录,用户记录未改动。重启本地调试后生效;未重新生成安装包。 + +## 本地调试版:简化设置与移除备份(2026-10-19) + +- 移除备份、恢复备份和“更多偏好与数据备份”折叠区域,同时移除预加载接口、IPC 处理、界面操作和数据库备份合并代码。单张文件命名设置移到“文件与常用选择”板块。 +- 独立批次目录和成功后自动打开输出目录改为固定启用,不再显示开关。旧配置曾保存为关闭时,读取后自动启用;其余偏好保留。下次保存设置时持久化这两项启用状态,旧记录和编号不变。 +- 从含旧关闭状态的历史快照重新导出也创建新的独立批次目录;原导出文件保留。 +- 类型检查、构建和 41 项测试通过,包含旧配置读取/保存兼容及取消后原编号独立目录重试。已移除不再适用的备份测试。 +- 实际 Electron 验证设置页面入口和开关已移除、单张命名设置直接可见、备份 API 不再暴露、旧配置自动启用、新生成与重新导出均自动打开正确批次目录、重启后设置和编号连续。完整 PDF/PNG/A4/分层导出验证通过,设置页面截图已检查。 +- 加载动画实际窗口验证通过,保存/刷新仍连续旋转半秒,选择文件夹不显示遮罩,删除确认和取消行为正常。 +- 验证记录:.qa/result.json、.qa/loading/result.json;截图:.qa/02-settings.png。测试使用隔离目录,用户记录及已有备份文件未改动;未重新生成安装包。重启本地调试后生效。 + +## 本地调试版:命名示例、分组核对与记录查找(2026-10-19) + +- 文件命名设置改为“导出的文件叫什么名字”,三种规则用日常表述展示;按当前前缀、选中数据和格式实时给出示例,正式导出与示例共用命名和字符处理函数。 +- 导入后展示学校、年级、收费项目三级核对树。支持整组勾选、取消和部分选中状态,张数与金额实时更新;错误行单独计数且不可生成。多组数据按需展开,同级每次显示 50 项,减少初次渲染量。 +- A4 预览按已选同组收据定位当前行所在页,保持实际导出的分组、空白半页和编号顺序,不推进流水号。 +- 生成记录增加分类摘要及学校/年级/项目筛选,文本查找支持文件名、姓名和任意收据编号。组合条件匹配同一收据,最多返回 200 个匹配批次,覆盖更早记录;匹配信息显示姓名、编号和张数,预览直接显示匹配的原收据。重新导出维持完整批次语义,界面有明确说明。 +- 类型检查、构建与 45 项测试通过,覆盖非连续行分组、错误行排除、A4 预览选页、组合条件、批次中间编号、姓名及 200 批以前的旧记录和目录归属;历史查找和预览不分配新编号。 +- 实际 Electron 验证三种命名示例、分组勾选及父级部分选中、错误组禁用、分组 A4 预览、组合筛选无误命中、中间编号及姓名查找、匹配原收据预览、清空筛选、60 所学校按需加载及整组取消。完整 PDF/PNG/A4、原编号重导出和重启验证通过,无界面脚本异常。 +- 加载动画验证通过,保存与刷新继续显示半秒持续旋转,选择文件夹、导入取消及删除确认保持原交互。 +- 验证记录:.qa/result.json、.qa/loading/result.json;页面截图:.qa/02-settings.png、.qa/03-import-preview.png、.qa/05-history-filter.png。使用隔离测试配置,用户现有记录未改动;未重新生成安装包。重启本地调试后生效。 + +## 本地调试版:生成记录分页(2026-10-19) + +- 生成记录默认每页 5 条,可选 5/10/20/50,列表底部显示真实总条数、当前条目范围、首页/上一页/页码/下一页/末页。每页条数保存在偏好设置中,切换板块或重启后继续使用上次选择。 +- 新增 listBatchPage 查询当前页及总数,分页覆盖全部历史记录;保留原 listBatches 的调用兼容。无筛选采用数据库 COUNT/LIMIT/OFFSET,有筛选按全部匹配批次计数,避免先截取 200 条再分页。侧边栏同步显示真实总数。 +- 筛选条件或每页条数改变后回到第一页;刷新保留当前页,删除末页记录后自动回到有效页。显式翻页沿用板块整体加载反馈,保留查询版本检查。 +- 类型检查、构建及 46 项测试通过,覆盖默认条数、分页不重复、每页 10/50 条、筛选总数、空结果、页码越界、删除末页、201 条记录的最后一页以及旧配置默认值与条数保存。 +- 新增隔离桌面验证 test:history,使用 13 个测试批次实际验证五条分页、范围和边界按钮、翻页、条数切换、筛选回首页、清空筛选、跨板块/重启记忆、逐条删除末页自动回退,流水号保持不变;截图已检查,无界面脚本异常。完整桌面导入/分组/PDF/PNG/A4/原编号补打/重启验证通过。 +- 验证记录:.qa/history-pagination/result.json、.qa/result.json;截图:.qa/history-pagination/pagination.png。测试使用隔离配置,用户记录未改动;未重新生成安装包。重启本地调试后生效。 diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..a605a96 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,6550 @@ +{ + "name": "receipt-studio", + "version": "1.0.1", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "receipt-studio", + "version": "1.0.1", + "license": "UNLICENSED", + "dependencies": { + "@lucide/vue": "^1.58.0", + "exceljs": "^4.4.0", + "pdf-lib": "^1.17.1", + "pdfjs-dist": "^5.4.149", + "reka-ui": "^2.11.0", + "sql.js": "^1.13.0", + "vue": "^3.5.22", + "zod": "^4.1.12" + }, + "devDependencies": { + "@playwright/test": "^1.56.0", + "@types/node": "^22.18.10", + "@types/sql.js": "^1.4.9", + "@vitejs/plugin-vue": "^6.0.1", + "electron": "^42.0.0", + "electron-builder": "^26.0.12", + "esbuild": "^0.25.11", + "pngjs": "^7.0.0", + "typescript": "^5.9.3", + "vite": "^7.1.10", + "vitest": "^3.2.4", + "vue-tsc": "^3.1.1" + }, + "engines": { + "node": ">=22.13.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.9", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.9.tgz", + "integrity": "sha512-CjXrNHTnvqBVqHgdBysY3vk2T8tpJHb5/RMeHJBTyVa9xgugCB0CJTx/3oO8RV2QRQP391RWpB7D6hLjm8V9uA==", + "dependencies": { + "@babel/types": "^7.29.8" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.8.tgz", + "integrity": "sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==", + "dependencies": { + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@electron-internal/extract-zip": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@electron-internal/extract-zip/-/extract-zip-1.0.5.tgz", + "integrity": "sha512-+bqFCP98pLI0Tt0XQo1TmlXtwjWchISndDOxCkEcIuUgXWpBnLyRI+2DU+mesvnMMX6L1XDqYNA0lXNDHd/yiA==", + "dev": true, + "engines": { + "node": ">=22.12.0" + } + }, + "node_modules/@electron/asar": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/@electron/asar/-/asar-3.4.1.tgz", + "integrity": "sha512-i4/rNPRS84t0vSRa2HorerGRXWyF4vThfHesw0dmcWHp+cspK743UanA0suA5Q5y8kzY2y6YKrvbIUn69BCAiA==", + "dev": true, + "dependencies": { + "commander": "^5.0.0", + "glob": "^7.1.6", + "minimatch": "^3.0.4" + }, + "bin": { + "asar": "bin/asar.js" + }, + "engines": { + "node": ">=10.12.0" + } + }, + "node_modules/@electron/asar/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "node_modules/@electron/asar/node_modules/brace-expansion": { + "version": "1.1.21", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.21.tgz", + "integrity": "sha512-9zeA+KLZNNzglF2TPKRQEDyx6Yby7daAkuy8MiPzpXPsYDWi/DRM8jmwUDxokQjYqBpv5DgPiwD4h4ZZSy1Ujw==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@electron/asar/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@electron/fuses": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@electron/fuses/-/fuses-1.8.0.tgz", + "integrity": "sha512-zx0EIq78WlY/lBb1uXlziZmDZI4ubcCXIMJ4uGjXzZW0nS19TjSPeXPAjzzTmKQlJUZm0SbmZhPKP7tuQ1SsEw==", + "dev": true, + "dependencies": { + "chalk": "^4.1.1", + "fs-extra": "^9.0.1", + "minimist": "^1.2.5" + }, + "bin": { + "electron-fuses": "dist/bin.js" + } + }, + "node_modules/@electron/fuses/node_modules/fs-extra": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "dev": true, + "dependencies": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@electron/get": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@electron/get/-/get-5.1.0.tgz", + "integrity": "sha512-3kSBtG8ObcTVfXanm5vVJ6UnBLEVmVsRk1M+vGqCuMBV+XLCbJYuWQful+yIy0GQDsSlK0kHEriEHn7SPk4EnA==", + "dev": true, + "dependencies": { + "debug": "^4.1.1", + "env-paths": "^3.0.0", + "graceful-fs": "^4.2.11", + "progress": "^2.0.3", + "semver": "^7.6.3", + "sumchecker": "^3.0.1" + }, + "engines": { + "node": ">=22.12.0" + }, + "optionalDependencies": { + "undici": "^7.24.4" + } + }, + "node_modules/@electron/notarize": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@electron/notarize/-/notarize-2.5.0.tgz", + "integrity": "sha512-jNT8nwH1f9X5GEITXaQ8IF/KdskvIkOFfB2CvwumsveVidzpSc+mvhhTMdAGSYF3O+Nq49lJ7y+ssODRXu06+A==", + "dev": true, + "dependencies": { + "debug": "^4.1.1", + "fs-extra": "^9.0.1", + "promise-retry": "^2.0.1" + }, + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/@electron/notarize/node_modules/fs-extra": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "dev": true, + "dependencies": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@electron/osx-sign": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@electron/osx-sign/-/osx-sign-1.3.3.tgz", + "integrity": "sha512-KZ8mhXvWv2rIEgMbWZ4y33bDHyUKMXnx4M0sTyPNK/vcB81ImdeY9Ggdqy0SWbMDgmbqyQ+phgejh6V3R2QuSg==", + "dev": true, + "dependencies": { + "compare-version": "^0.1.2", + "debug": "^4.3.4", + "fs-extra": "^10.0.0", + "isbinaryfile": "^4.0.8", + "minimist": "^1.2.6", + "plist": "^3.0.5" + }, + "bin": { + "electron-osx-flat": "bin/electron-osx-flat.js", + "electron-osx-sign": "bin/electron-osx-sign.js" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/@electron/osx-sign/node_modules/isbinaryfile": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-4.0.10.tgz", + "integrity": "sha512-iHrqe5shvBUcFbmZq9zOQHBoeOhZJu6RQGrDpBgenUm/Am+F3JM2MgQj+rK3Z601fzrL5gLZWtAPH2OBaSVcyw==", + "dev": true, + "engines": { + "node": ">= 8.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/gjtorikian/" + } + }, + "node_modules/@electron/rebuild": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@electron/rebuild/-/rebuild-4.2.1.tgz", + "integrity": "sha512-IS4pG3BysRrCLJ8xTAfvg+IQDbiUoAzdJy2SfjcYKnLDy5DpCdjnPdhS5Zf5UMfrkB24xu0KC+TqcOxg+eG+6w==", + "dev": true, + "dependencies": { + "@malept/cross-spawn-promise": "^2.0.0", + "debug": "^4.1.1", + "node-abi": "^4.2.0", + "node-api-version": "^0.2.1", + "node-gyp": "^12.2.0", + "read-binary-file-arch": "^1.0.6" + }, + "bin": { + "electron-rebuild": "lib/cli.js" + }, + "engines": { + "node": ">=22.12.0" + } + }, + "node_modules/@electron/universal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@electron/universal/-/universal-2.0.3.tgz", + "integrity": "sha512-Wn9sPYIVFRFl5HmwMJkARCCf7rqK/EurkfQ/rJZ14mHP3iYTjZSIOSVonEAnhWeAXwtw7zOekGRlc6yTtZ0t+g==", + "dev": true, + "dependencies": { + "@electron/asar": "^3.3.1", + "@malept/cross-spawn-promise": "^2.0.0", + "debug": "^4.3.1", + "dir-compare": "^4.2.0", + "fs-extra": "^11.1.1", + "minimatch": "^9.0.3", + "plist": "^3.1.0" + }, + "engines": { + "node": ">=16.4" + } + }, + "node_modules/@electron/universal/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "node_modules/@electron/universal/node_modules/brace-expansion": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.7.tgz", + "integrity": "sha512-uZbew1NqdmPDTMJ8ah1y+b+9QEJrfkXFk3RcTQw3X0jW/xRUvFKsg1CfQdSYGdTbXZWExtU3J3ccxtnfw1Fi0g==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@electron/universal/node_modules/fs-extra": { + "version": "11.4.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.4.1.tgz", + "integrity": "sha512-KYAb4c9BJQI6QqGKthV68OHe0badztdXJWKo0WtBA9IuCFPTKvE5ZdUBglP833aMjhaSPNO4A5j/EkzZtGlKjA==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/@electron/universal/node_modules/minimatch": { + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.2" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@electron/windows-sign": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@electron/windows-sign/-/windows-sign-1.2.2.tgz", + "integrity": "sha512-dfZeox66AvdPtb2lD8OsIIQh12Tp0GNCRUDfBHIKGpbmopZto2/A8nSpYYLoedPIHpqkeblZ/k8OV0Gy7PYuyQ==", + "dev": true, + "optional": true, + "peer": true, + "dependencies": { + "cross-dirname": "^0.1.0", + "debug": "^4.3.4", + "fs-extra": "^11.1.1", + "minimist": "^1.2.8", + "postject": "^1.0.0-alpha.6" + }, + "bin": { + "electron-windows-sign": "bin/electron-windows-sign.js" + }, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/@electron/windows-sign/node_modules/fs-extra": { + "version": "11.4.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.4.1.tgz", + "integrity": "sha512-KYAb4c9BJQI6QqGKthV68OHe0badztdXJWKo0WtBA9IuCFPTKvE5ZdUBglP833aMjhaSPNO4A5j/EkzZtGlKjA==", + "dev": true, + "optional": true, + "peer": true, + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", + "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@fast-csv/format": { + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/@fast-csv/format/-/format-4.3.5.tgz", + "integrity": "sha512-8iRn6QF3I8Ak78lNAa+Gdl5MJJBM5vRHivFtMRUWINdevNo00K7OXxS2PshawLKTejVwieIlPmK5YlLu6w4u8A==", + "dependencies": { + "@types/node": "^14.0.1", + "lodash.escaperegexp": "^4.1.2", + "lodash.isboolean": "^3.0.3", + "lodash.isequal": "^4.5.0", + "lodash.isfunction": "^3.0.9", + "lodash.isnil": "^4.0.0" + } + }, + "node_modules/@fast-csv/format/node_modules/@types/node": { + "version": "14.18.63", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.63.tgz", + "integrity": "sha512-fAtCfv4jJg+ExtXhvCkCqUKZ+4ok/JQk01qDKhL5BDDoS3AxKXhV5/MAVUZyQnSEd2GT92fkgZl0pz0Q0AzcIQ==" + }, + "node_modules/@fast-csv/parse": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/@fast-csv/parse/-/parse-4.3.6.tgz", + "integrity": "sha512-uRsLYksqpbDmWaSmzvJcuApSEe38+6NQZBUsuAyMZKqHxH0g1wcJgsKUvN3WC8tewaqFjBMMGrkHmC+T7k8LvA==", + "dependencies": { + "@types/node": "^14.0.1", + "lodash.escaperegexp": "^4.1.2", + "lodash.groupby": "^4.6.0", + "lodash.isfunction": "^3.0.9", + "lodash.isnil": "^4.0.0", + "lodash.isundefined": "^3.0.1", + "lodash.uniq": "^4.5.0" + } + }, + "node_modules/@fast-csv/parse/node_modules/@types/node": { + "version": "14.18.63", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.63.tgz", + "integrity": "sha512-fAtCfv4jJg+ExtXhvCkCqUKZ+4ok/JQk01qDKhL5BDDoS3AxKXhV5/MAVUZyQnSEd2GT92fkgZl0pz0Q0AzcIQ==" + }, + "node_modules/@floating-ui/core": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.8.0.tgz", + "integrity": "sha512-0CIZ5itps/8x7BG8dEIhs53BvCUH2PCoogtakwRTut+Arm58sJooJ0AuZhLw2HJYIR5cMLNPBSS728sPho2khQ==", + "dependencies": { + "@floating-ui/utils": "^0.2.12" + } + }, + "node_modules/@floating-ui/dom": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.8.0.tgz", + "integrity": "sha512-yXSrzeHZBTZadLOlfyhCkJHNeLJnHRnRInwdZ40L7ZiaAtrBwoYlsDrX3v5zB1Utk7CLfzcOVnVVWoXEky7Ceg==", + "dependencies": { + "@floating-ui/core": "^1.8.0", + "@floating-ui/utils": "^0.2.12" + } + }, + "node_modules/@floating-ui/utils": { + "version": "0.2.12", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.12.tgz", + "integrity": "sha512-HpCo8tmWzLVad5s2d19EhAz5zqrrQ6s69qd6moPMQvkOuSwDT1YgRfWSVuc4ennqrgv3OHppiOGMQ7oC13yIww==" + }, + "node_modules/@floating-ui/vue": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@floating-ui/vue/-/vue-2.0.1.tgz", + "integrity": "sha512-043dyEKD9TDSfz080YH2vobubgg+NIo/9Fw4RAl7sEMpD8Am1DNrrSTl/GpMBjyGHoD/FbJY2QLawY+HSQsEIA==", + "dependencies": { + "@floating-ui/dom": "^1.8.0", + "@floating-ui/utils": "^0.2.12" + }, + "peerDependencies": { + "vue": ">=3.3.0" + } + }, + "node_modules/@internationalized/date": { + "version": "3.12.4", + "resolved": "https://registry.npmjs.org/@internationalized/date/-/date-3.12.4.tgz", + "integrity": "sha512-M1dEn4c1U1HsSlaVR8upZtSqvXrTkHDfv18H01uCSJyjVLDxnBR38v/fMxecmlwXKR4i9HeZcmgQAPE6A+aGJQ==", + "dependencies": { + "@swc/helpers": "^0.5.0" + } + }, + "node_modules/@internationalized/number": { + "version": "3.6.9", + "resolved": "https://registry.npmjs.org/@internationalized/number/-/number-3.6.9.tgz", + "integrity": "sha512-GgWy2caO/+hV9sT/oCTWGHvbjmJMPT9kkXzoKRLnkEq7adghhR7UprhvY9sXrKfrp3NcDfhF6QQHXR1KfQbB8g==", + "dependencies": { + "@swc/helpers": "^0.5.0" + } + }, + "node_modules/@isaacs/fs-minipass": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", + "integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==", + "dev": true, + "dependencies": { + "minipass": "^7.0.4" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.6.0.tgz", + "integrity": "sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==" + }, + "node_modules/@lucide/vue": { + "version": "1.58.0", + "resolved": "https://registry.npmjs.org/@lucide/vue/-/vue-1.58.0.tgz", + "integrity": "sha512-HW7yrGw9OUC1TzldLep1d4p1Uc1OCRWLJwzKHm9C3V9Z4/9GADN84NDlyeTzgrcD8yxXs8/SVh8CpKFaA+OPRg==", + "peerDependencies": { + "vue": ">=3.0.1" + } + }, + "node_modules/@malept/cross-spawn-promise": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@malept/cross-spawn-promise/-/cross-spawn-promise-2.0.0.tgz", + "integrity": "sha512-1DpKU0Z5ThltBwjNySMC14g0CkbyhCaz9FkhxqNsZI6uAPJXFS8cMXlBKo26FJ8ZuW6S9GCMcR9IO5k2X5/9Fg==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/malept" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/subscription/pkg/npm-.malept-cross-spawn-promise?utm_medium=referral&utm_source=npm_fund" + } + ], + "dependencies": { + "cross-spawn": "^7.0.1" + }, + "engines": { + "node": ">= 12.13.0" + } + }, + "node_modules/@malept/flatpak-bundler": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@malept/flatpak-bundler/-/flatpak-bundler-0.4.0.tgz", + "integrity": "sha512-9QOtNffcOF/c1seMCDnjckb3R9WHcG34tky+FHpNKKCW0wc/scYLwMtO+ptyGUfMW0/b/n4qRiALlaFHc9Oj7Q==", + "dev": true, + "dependencies": { + "debug": "^4.1.1", + "fs-extra": "^9.0.0", + "lodash": "^4.17.15", + "tmp-promise": "^3.0.2" + }, + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/@malept/flatpak-bundler/node_modules/fs-extra": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "dev": true, + "dependencies": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@napi-rs/canvas": { + "version": "0.1.100", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas/-/canvas-0.1.100.tgz", + "integrity": "sha512-xglYA6q3XO5P3BNJYxVZ1IV7DLVjp1Py6nwag88YntrS+3vKHyYcMqXVS4ZztJmwz2uGvz1FWhI/4LgbR5uQDA==", + "optional": true, + "workspaces": [ + "e2e/*" + ], + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "optionalDependencies": { + "@napi-rs/canvas-android-arm64": "0.1.100", + "@napi-rs/canvas-darwin-arm64": "0.1.100", + "@napi-rs/canvas-darwin-x64": "0.1.100", + "@napi-rs/canvas-linux-arm-gnueabihf": "0.1.100", + "@napi-rs/canvas-linux-arm64-gnu": "0.1.100", + "@napi-rs/canvas-linux-arm64-musl": "0.1.100", + "@napi-rs/canvas-linux-riscv64-gnu": "0.1.100", + "@napi-rs/canvas-linux-x64-gnu": "0.1.100", + "@napi-rs/canvas-linux-x64-musl": "0.1.100", + "@napi-rs/canvas-win32-arm64-msvc": "0.1.100", + "@napi-rs/canvas-win32-x64-msvc": "0.1.100" + } + }, + "node_modules/@napi-rs/canvas-win32-x64-msvc": { + "version": "0.1.100", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-win32-x64-msvc/-/canvas-win32-x64-msvc-0.1.100.tgz", + "integrity": "sha512-MyT1j3mHC2+Lu4pBi9mKyMJhtP6U7k7EldY7sj/uS5gJA65gTXt8MefJQXLJo5d/vZbuWmfxzkEUNc/urV3pHA==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + } + }, + "node_modules/@noble/hashes": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-2.4.0.tgz", + "integrity": "sha512-X5XaVWZIBCT7HHZGm5I7ZQXDwLG+bGXuSrMQAW+7Zvl87h1kmc1ZB1VSRJcpUfoUrGQp4Fkoxm5kZ+Ms+aW+eA==", + "dev": true, + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@pdf-lib/standard-fonts": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@pdf-lib/standard-fonts/-/standard-fonts-1.0.0.tgz", + "integrity": "sha512-hU30BK9IUN/su0Mn9VdlVKsWBS6GyhVfqjwl1FjZN4TxP6cCw0jP2w7V3Hf5uX7M0AZJ16vey9yE0ny7Sa59ZA==", + "dependencies": { + "pako": "^1.0.6" + } + }, + "node_modules/@pdf-lib/upng": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@pdf-lib/upng/-/upng-1.0.1.tgz", + "integrity": "sha512-dQK2FUMQtowVP00mtIksrlZhdFXQZPC+taih1q4CvPZ5vqdxR/LKBaFg0oAfzd1GlHZXXSPdQfzQnt+ViGvEIQ==", + "dependencies": { + "pako": "^1.0.10" + } + }, + "node_modules/@peculiar/asn1-schema": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-schema/-/asn1-schema-2.11.0.tgz", + "integrity": "sha512-8xh4avo53/vXO6UEv9yaoJ83hY581geaLTygf61NPIXP5AeIZCPjJhpTaYU23IZkG+NpLN/C9FYzWp7hphIT9A==", + "dev": true, + "dependencies": { + "@peculiar/utils": "^2.0.2", + "asn1js": "^3.0.10", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@peculiar/json-schema": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/@peculiar/json-schema/-/json-schema-1.1.12.tgz", + "integrity": "sha512-coUfuoMeIB7B8/NMekxaDzLhaYmp0HZNPEjYRm9goRou8UZIC3z21s0sL9AWoCw4EG876QyO3kYrc61WNF9B/w==", + "dev": true, + "dependencies": { + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@peculiar/utils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@peculiar/utils/-/utils-2.0.3.tgz", + "integrity": "sha512-+oL3HPFRIZ1St2K50lWCXiioIgSoxzz7R1J3uF6neO2yl1sgmpgY6XXJH4BdpoDkMWznQTeYF6oWNDZLCdQ4eQ==", + "dev": true, + "dependencies": { + "tslib": "^2.8.1" + } + }, + "node_modules/@peculiar/webcrypto": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/@peculiar/webcrypto/-/webcrypto-1.7.1.tgz", + "integrity": "sha512-ODOov0sGMJMf3jPonOkgGqPknTsu+DdQ7kD++gz8aI+aFMOMHFbWAA2taqXXVTdP+OTOQR/znGvSpmkeI0WTYQ==", + "dev": true, + "dependencies": { + "@peculiar/asn1-schema": "^2.7.0", + "@peculiar/json-schema": "^1.1.12", + "@peculiar/utils": "^2.0.2", + "tslib": "^2.8.1", + "webcrypto-core": "^1.9.2" + }, + "engines": { + "node": ">=14.18.0" + } + }, + "node_modules/@playwright/test": { + "version": "1.64.1", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.64.1.tgz", + "integrity": "sha512-IaYGml/nSOiZWROfPtuIL7neuu9qDOZ3TNvNljUIrhDMxiGTgPjzj/g9p9RxpS2tSepPDRY+UwAaEeiKtUvIAg==", + "dev": true, + "dependencies": { + "playwright": "1.64.1" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz", + "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==", + "dev": true + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.64.7", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.64.7.tgz", + "integrity": "sha512-u9bp2yXCI3/BUjUxY6uWknAY32pfvvDCPysyUTySotx0dj3ydlFHrRQYfn/b/kZgnfjyEwySMtI7Ga054uiysQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.64.7", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.64.7.tgz", + "integrity": "sha512-EhX6N3XPMEnVU5cRfNgzPOKcSucsbu3nNNWuoAaEuKIqmddBKHN5b9d4rPe65T1I+FaOT176JKiZNsUBp3rqbA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@sindresorhus/is": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz", + "integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/is?sponsor=1" + } + }, + "node_modules/@swc/helpers": { + "version": "0.5.23", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.23.tgz", + "integrity": "sha512-5lSsMOTXURePglDfvuAQUqkGek9Hg2kksOYay2m0+XR++b2NWYL/4sWyuvVBIs8oKnJaxkdi9whaL/sqN13afw==", + "dependencies": { + "tslib": "^2.8.0" + } + }, + "node_modules/@szmarczak/http-timer": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.6.tgz", + "integrity": "sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==", + "dev": true, + "dependencies": { + "defer-to-connect": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@tanstack/virtual-core": { + "version": "3.19.5", + "resolved": "https://registry.npmjs.org/@tanstack/virtual-core/-/virtual-core-3.19.5.tgz", + "integrity": "sha512-X/wq6DtSK/XClznMM+I4JE63yyIwmyPk4Ts83bD0VKaIbwHNdRsO5QipCPX+3OovqYyr0puQNpbmp8+SPhspNg==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/vue-virtual": { + "version": "3.13.46", + "resolved": "https://registry.npmjs.org/@tanstack/vue-virtual/-/vue-virtual-3.13.46.tgz", + "integrity": "sha512-gUv08KbJAobyp9UyZGZylVfFGjwjrouHyAJtOEF7kPwxjoucuxKon1xCWA2Pepin1Or6FzuLi0H2nyMjkHiI7g==", + "dependencies": { + "@tanstack/virtual-core": "3.19.5" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "vue": "^2.7.0 || ^3.0.0" + } + }, + "node_modules/@types/cacheable-request": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.3.tgz", + "integrity": "sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw==", + "dev": true, + "dependencies": { + "@types/http-cache-semantics": "*", + "@types/keyv": "^3.1.4", + "@types/node": "*", + "@types/responselike": "^1.0.0" + } + }, + "node_modules/@types/chai": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", + "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", + "dev": true, + "dependencies": { + "@types/deep-eql": "*", + "assertion-error": "^2.0.1" + } + }, + "node_modules/@types/debug": { + "version": "4.1.13", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.13.tgz", + "integrity": "sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw==", + "dev": true, + "dependencies": { + "@types/ms": "*" + } + }, + "node_modules/@types/deep-eql": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", + "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", + "dev": true + }, + "node_modules/@types/emscripten": { + "version": "1.41.6", + "resolved": "https://registry.npmjs.org/@types/emscripten/-/emscripten-1.41.6.tgz", + "integrity": "sha512-uN+9i8bFT5CUcZfyIEYDrSueACEyKGbUs5kC/72DGlZZoinh84sJfVV0i8UOJD1asdzkvLPBRrKs41kZ8MdEXg==", + "dev": true + }, + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "dev": true + }, + "node_modules/@types/fs-extra": { + "version": "9.0.13", + "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-9.0.13.tgz", + "integrity": "sha512-nEnwB++1u5lVDM2UI4c1+5R+FYaKfaAzS4OococimjVm3nQw3TuzH5UNsocrcTBbhnerblyHj4A49qXbIiZdpA==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/http-cache-semantics": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz", + "integrity": "sha512-L3LgimLHXtGkWikKnsPg0/VFx9OGZaC+eN1u4r+OB1XRqH3meBIAVC2zr1WdMH+RHmnRkqliQAOHNJ/E0j/e0Q==", + "dev": true + }, + "node_modules/@types/keyv": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.4.tgz", + "integrity": "sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/ms": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", + "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", + "dev": true + }, + "node_modules/@types/node": { + "version": "22.20.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.20.5.tgz", + "integrity": "sha512-U2+DNr+wSjpsTS/wZGYHq7GcwfuSmKiKvoPvK22zwTlRhU91yOniN4qRR5KhIjvif7ysw/dz/hKmfDH0Ris4aA==", + "dev": true, + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@types/responselike": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.3.tgz", + "integrity": "sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/sql.js": { + "version": "1.4.11", + "resolved": "https://registry.npmjs.org/@types/sql.js/-/sql.js-1.4.11.tgz", + "integrity": "sha512-QXIx38p2ZThJaK9vP5ZdqdlRe1FG9I8SmCZOS7FHfB/2qPAjZwkL7/vlfPg6N/oWHuuOaGg/P/IRwfP2W0kWVQ==", + "dev": true, + "dependencies": { + "@types/emscripten": "*", + "@types/node": "*" + } + }, + "node_modules/@types/web-bluetooth": { + "version": "0.0.21", + "resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.21.tgz", + "integrity": "sha512-oIQLCGWtcFZy2JW77j9k8nHzAOpqMHLQejDA48XXMWH6tjCQHz5RCFz1bzsmROyL6PUm+LLnUiI4BCn221inxA==" + }, + "node_modules/@vitejs/plugin-vue": { + "version": "6.0.10", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-6.0.10.tgz", + "integrity": "sha512-b80aUQh3n+qEzzZm5oQp3rT2QImeCx58K6eFl/RkJ3d65KW0kTuv5lNsLRVAjbW9YRUpo/wzmvzMgV34bTAItA==", + "dev": true, + "dependencies": { + "@rolldown/pluginutils": "^1.0.1" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "peerDependencies": { + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0", + "vue": "^3.2.25 || ^3.6.0-rc.10" + } + }, + "node_modules/@vitest/expect": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.2.7.tgz", + "integrity": "sha512-E8eBXaKibuvH2pSZErOjdVb5vF4PbKYcrnluBTYxEk1l/VhhwZg1kZQsdtjq+CsF5CFydf2Rdkz7jDHKSisi3w==", + "dev": true, + "dependencies": { + "@types/chai": "^5.2.2", + "@vitest/spy": "3.2.7", + "@vitest/utils": "3.2.7", + "chai": "^5.2.0", + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/mocker": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.2.7.tgz", + "integrity": "sha512-Trr0hYO9CM3Wj6ksWHRhK9IZpIY6wTMO5u/MqXurMxT57sWBaOPEtP3Oq60ihZuh5JsiagKfz95OcxdEP6dBrA==", + "dev": true, + "dependencies": { + "@vitest/spy": "3.2.7", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.17" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@vitest/pretty-format": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.2.7.tgz", + "integrity": "sha512-KUHlwqVu0sRlhCdyPdQ/wBoTfRahjUky1MubOmYw9fWfIZy1gNoHpuaaQBPAaMaVYdQYHJLurzj8ECCj5OwTqA==", + "dev": true, + "dependencies": { + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.2.7.tgz", + "integrity": "sha512-sB9y4ovltoQP+WaUPwmSxO9WIg9Ig694Di5PalVPsYHklAdE027mehpWF2SQSVq+k6sFgaivbTjTJwZLSHbedA==", + "dev": true, + "dependencies": { + "@vitest/utils": "3.2.7", + "pathe": "^2.0.3", + "strip-literal": "^3.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.2.7.tgz", + "integrity": "sha512-7C+MwShwtBSI5Buwoyg3s/iY1eHL9PKAf+O1wVh/TdnjXUtkoL/9YQtre90i4MtNXM6edP1wJ2zOBpfCyhIS7g==", + "dev": true, + "dependencies": { + "@vitest/pretty-format": "3.2.7", + "magic-string": "^0.30.17", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.2.7.tgz", + "integrity": "sha512-Q2eQGI6d2L/hBtZ0qNuKcAGid68XK6cv1xsoaIma6PaJhHPoqcEJhYpXZ/5myCMqkNgtP6UKuBhbc0nHKnrkuQ==", + "dev": true, + "dependencies": { + "tinyspy": "^4.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.2.7.tgz", + "integrity": "sha512-x6BDOd7dyo3PFLY3I9/HJ25X/6OurhGXk2/B9gOZNPF7XDVjeBK4k01lQE5uvDpbuheErh91qYuE1E2OEjK3Rw==", + "dev": true, + "dependencies": { + "@vitest/pretty-format": "3.2.7", + "loupe": "^3.1.4", + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@volar/language-core": { + "version": "2.4.28", + "resolved": "https://registry.npmjs.org/@volar/language-core/-/language-core-2.4.28.tgz", + "integrity": "sha512-w4qhIJ8ZSitgLAkVay6AbcnC7gP3glYM3fYwKV3srj8m494E3xtrCv6E+bWviiK/8hs6e6t1ij1s2Endql7vzQ==", + "dev": true, + "dependencies": { + "@volar/source-map": "2.4.28" + } + }, + "node_modules/@volar/source-map": { + "version": "2.4.28", + "resolved": "https://registry.npmjs.org/@volar/source-map/-/source-map-2.4.28.tgz", + "integrity": "sha512-yX2BDBqJkRXfKw8my8VarTyjv48QwxdJtvRgUpNE5erCsgEUdI2DsLbpa+rOQVAJYshY99szEcRDmyHbF10ggQ==", + "dev": true + }, + "node_modules/@volar/typescript": { + "version": "2.4.28", + "resolved": "https://registry.npmjs.org/@volar/typescript/-/typescript-2.4.28.tgz", + "integrity": "sha512-Ja6yvWrbis2QtN4ClAKreeUZPVYMARDYZl9LMEv1iQ1QdepB6wn0jTRxA9MftYmYa4DQ4k/DaSZpFPUfxl8giw==", + "dev": true, + "dependencies": { + "@volar/language-core": "2.4.28", + "path-browserify": "^1.0.1", + "vscode-uri": "^3.0.8" + } + }, + "node_modules/@vue/compiler-core": { + "version": "3.5.44", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.44.tgz", + "integrity": "sha512-k34nCytbK8PQ1gR2mGROYOw6Pnv/mumqIdrZ3x2tbyXZDWm7ViKGLTfdB+DRrWqEQsLgGJvn0WN9jBif5zCTLQ==", + "dependencies": { + "@babel/parser": "^7.29.9", + "@vue/shared": "3.5.44", + "entities": "^7.0.1", + "estree-walker": "^2.0.2", + "source-map-js": "^1.2.2" + } + }, + "node_modules/@vue/compiler-core/node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==" + }, + "node_modules/@vue/compiler-dom": { + "version": "3.5.44", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.44.tgz", + "integrity": "sha512-W5AoBFWdw1lQAKiHowkfgFKFab82oSRT6sGsPfw/q1GRjsy/ZJFLHGhJyBRIFN9APLTUduDH9U2JAiPOIyKuYg==", + "dependencies": { + "@vue/compiler-core": "3.5.44", + "@vue/shared": "3.5.44" + } + }, + "node_modules/@vue/compiler-sfc": { + "version": "3.5.44", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.44.tgz", + "integrity": "sha512-dzHmQ4jXTpdr5FjIQUeykVakN/uzqFbUo9/C5ikBFg3a3PeM6uRxeczx1ZbXBML0/zJstEh3ygYipzR5Z7b2Pw==", + "dependencies": { + "@babel/parser": "^7.29.9", + "@vue/compiler-core": "3.5.44", + "@vue/compiler-dom": "3.5.44", + "@vue/compiler-ssr": "3.5.44", + "@vue/shared": "3.5.44", + "estree-walker": "^2.0.2", + "magic-string": "^0.30.21", + "postcss": "^8.5.29", + "source-map-js": "^1.2.2" + } + }, + "node_modules/@vue/compiler-sfc/node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==" + }, + "node_modules/@vue/compiler-ssr": { + "version": "3.5.44", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.44.tgz", + "integrity": "sha512-xr5gb1JVHlfXJwSlRz1FeP6uxoazdzHuMTVLE7up+4Vrqxk219CvxLPxD9NXBt47yvZW7+p4DXfyp4O1fkByIA==", + "dependencies": { + "@vue/compiler-dom": "3.5.44", + "@vue/shared": "3.5.44" + } + }, + "node_modules/@vue/language-core": { + "version": "3.3.13", + "resolved": "https://registry.npmjs.org/@vue/language-core/-/language-core-3.3.13.tgz", + "integrity": "sha512-axA0Xemn5LuUKuKWob0/kVq7xQ2mJJM6VqA9Exvt35nM9yRC31goWVve/G/Zb2ZCLsXC8FHYjjfXlnshknSCew==", + "dev": true, + "dependencies": { + "@volar/language-core": "2.4.28", + "@vue/compiler-dom": "^3.5.0", + "@vue/shared": "^3.5.0", + "alien-signals": "^3.2.1", + "muggle-string": "^0.4.1", + "path-browserify": "^1.0.1", + "picomatch": "^4.0.4" + } + }, + "node_modules/@vue/reactivity": { + "version": "3.5.44", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.44.tgz", + "integrity": "sha512-u+QJdKeBBEADkPdevsL+6E12QlYVfBD8Zcw5qjDQKtamI6l14KyqazJMobhCKelhWN5zpyKUiEDoTyHE93cqWw==", + "dependencies": { + "@vue/shared": "3.5.44" + } + }, + "node_modules/@vue/runtime-core": { + "version": "3.5.44", + "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.44.tgz", + "integrity": "sha512-uEa/ndwjhrrhnOfb2p+sZy6zeoamJOISqaLSJGk/8R6nABT1ZldpzcJoPzKj+zHVomVBPcmQYyC2r4LtvDV6CQ==", + "dependencies": { + "@vue/reactivity": "3.5.44", + "@vue/shared": "3.5.44" + } + }, + "node_modules/@vue/runtime-dom": { + "version": "3.5.44", + "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.44.tgz", + "integrity": "sha512-3fc4ARcXVhVBaJ3ujWiNZJARD6pkyRcwG+ZYhMzjKtKUp7MAY0HLwr7r3Smep3Li6ZaWT6ewdmx8XqdaD5oNZg==", + "dependencies": { + "@vue/reactivity": "3.5.44", + "@vue/runtime-core": "3.5.44", + "@vue/shared": "3.5.44", + "csstype": "^3.2.3" + } + }, + "node_modules/@vue/server-renderer": { + "version": "3.5.44", + "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.44.tgz", + "integrity": "sha512-R4S1UoH4h5Frrauv8Z5XE5TxV9oBiUSgWIC4TyVK1FK+RocRnOLwFA5Xcmo2011RlgJp+AbZCnw6afrhlgRuUQ==", + "dependencies": { + "@vue/compiler-ssr": "3.5.44", + "@vue/runtime-dom": "3.5.44", + "@vue/shared": "3.5.44" + } + }, + "node_modules/@vue/shared": { + "version": "3.5.44", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.44.tgz", + "integrity": "sha512-H82XLH0DjmSOdbEwO15x3QTo1f5PWEtv237eLpYKc0mqLu+B8hnq16lFWmuBmNurkeiG3wr+oM1FhOll/YVFRg==" + }, + "node_modules/@vueuse/core": { + "version": "14.4.0", + "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-14.4.0.tgz", + "integrity": "sha512-X4WHz1HlCzCBoYXesUkifzzWBAcZgXG8Fi5iNPQg/epdzOB3gu8Fawj3hvuwYR1nGcXGnvxwYYcUC/71++svtQ==", + "dependencies": { + "@types/web-bluetooth": "^0.0.21", + "@vueuse/metadata": "14.4.0", + "@vueuse/shared": "14.4.0" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "vue": "^3.5.0" + } + }, + "node_modules/@vueuse/metadata": { + "version": "14.4.0", + "resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-14.4.0.tgz", + "integrity": "sha512-swx/255R6JyHZFJhx845iz5CRWDZdCfvkZOpACWc5+c5WHcG24mv8gUT1WIdFQaHt6dq79rvILd9QnCWiyVm9g==", + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@vueuse/shared": { + "version": "14.4.0", + "resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-14.4.0.tgz", + "integrity": "sha512-JRgY90Sz8DDtPMsaDflvPMp9xYk69JZAmbuDvAquUVXKr2gEjqtzGNTTthLfckH0BzBqvnu31gb4a8TGLRe79g==", + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "vue": "^3.5.0" + } + }, + "node_modules/@xmldom/xmldom": { + "version": "0.8.15", + "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.15.tgz", + "integrity": "sha512-/5NV/vDALVFDXgLmfsy9TRCBlKwO2LNBFzpzvb9iIj+jR+eSc6DLYYvVOdivT/jm7MtU6TebYuRmzEOI7w40UA==", + "dev": true, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/abbrev": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-4.0.0.tgz", + "integrity": "sha512-a1wflyaL0tHtJSmLSOVybYhy22vRih4eduhhrkcjgrWGnRfrZtovJ2FRjxuTtkkj47O/baf0R86QU5OuYpz8fA==", + "dev": true, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "dev": true, + "engines": { + "node": ">= 14" + } + }, + "node_modules/ajv": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", + "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/alien-signals": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/alien-signals/-/alien-signals-3.2.1.tgz", + "integrity": "sha512-I8FjmltrfnDFoZedi5CG8DghVYNhzb/Ijluz7tCSJH0xpd0484Kowhbb1XDYOxfJpU1p5wnM2X54dA+IfGyD1g==", + "dev": true + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/app-builder-lib": { + "version": "26.15.3", + "resolved": "https://registry.npmjs.org/app-builder-lib/-/app-builder-lib-26.15.3.tgz", + "integrity": "sha512-2VnyWkqsP5v5XbBhL3tD5Syx8iNPBYsoU7kY4S2fz7wg8Rj/nztWKCUzGKaFRTv0Xwf3/H058CR1Kvtd/3lRow==", + "dev": true, + "dependencies": { + "@electron/asar": "3.4.1", + "@electron/fuses": "^1.8.0", + "@electron/get": "^3.0.0", + "@electron/notarize": "2.5.0", + "@electron/osx-sign": "1.3.3", + "@electron/rebuild": "^4.0.4", + "@electron/universal": "2.0.3", + "@malept/flatpak-bundler": "^0.4.0", + "@noble/hashes": "^2.2.0", + "@peculiar/webcrypto": "^1.7.1", + "@types/fs-extra": "9.0.13", + "ajv": "^8.18.0", + "asn1js": "^3.0.10", + "async-exit-hook": "^2.0.1", + "builder-util": "26.15.3", + "builder-util-runtime": "9.7.0", + "chromium-pickle-js": "^0.2.0", + "ci-info": "4.3.1", + "debug": "^4.3.4", + "dotenv": "^16.4.5", + "dotenv-expand": "^11.0.6", + "ejs": "^3.1.8", + "electron-publish": "26.15.3", + "fs-extra": "^10.1.0", + "hosted-git-info": "^4.1.0", + "isbinaryfile": "^5.0.0", + "jiti": "^2.4.2", + "js-yaml": "^4.1.0", + "json5": "^2.2.3", + "lazy-val": "^1.0.5", + "minimatch": "^10.2.5", + "pkijs": "^3.4.0", + "plist": "3.1.0", + "proper-lockfile": "^4.1.2", + "resedit": "^1.7.0", + "semver": "~7.7.3", + "tar": "^7.5.7", + "temp-file": "^3.4.0", + "tiny-async-pool": "1.3.0", + "unzipper": "^0.12.3", + "which": "^5.0.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "dmg-builder": "26.15.3", + "electron-builder-squirrel-windows": "26.15.3" + } + }, + "node_modules/app-builder-lib/node_modules/@electron/get": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@electron/get/-/get-3.1.0.tgz", + "integrity": "sha512-F+nKc0xW+kVbBRhFzaMgPy3KwmuNTYX1fx6+FxxoSnNgwYX6LD7AKBTWkU0MQ6IBoe7dz069CNkR673sPAgkCQ==", + "dev": true, + "dependencies": { + "debug": "^4.1.1", + "env-paths": "^2.2.0", + "fs-extra": "^8.1.0", + "got": "^11.8.5", + "progress": "^2.0.3", + "semver": "^6.2.0", + "sumchecker": "^3.0.1" + }, + "engines": { + "node": ">=14" + }, + "optionalDependencies": { + "global-agent": "^3.0.0" + } + }, + "node_modules/app-builder-lib/node_modules/@electron/get/node_modules/fs-extra": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + }, + "engines": { + "node": ">=6 <7 || >=8" + } + }, + "node_modules/app-builder-lib/node_modules/@electron/get/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/app-builder-lib/node_modules/ci-info": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.3.1.tgz", + "integrity": "sha512-Wdy2Igu8OcBpI2pZePZ5oWjPC38tmDVx5WKUXKwlLYkA0ozo85sLsLvkBbBn/sZaSCMFOGZJ14fvW9t5/d7kdA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "engines": { + "node": ">=8" + } + }, + "node_modules/app-builder-lib/node_modules/env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/app-builder-lib/node_modules/jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", + "dev": true, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/app-builder-lib/node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/app-builder-lib/node_modules/universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "dev": true, + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/archiver": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/archiver/-/archiver-5.3.2.tgz", + "integrity": "sha512-+25nxyyznAXF7Nef3y0EbBeqmGZgeN/BxHX29Rs39djAfaFalmQ89SE6CWyDCHzGL0yt/ycBtNOmGTW0FyGWNw==", + "dependencies": { + "archiver-utils": "^2.1.0", + "async": "^3.2.4", + "buffer-crc32": "^0.2.1", + "readable-stream": "^3.6.0", + "readdir-glob": "^1.1.2", + "tar-stream": "^2.2.0", + "zip-stream": "^4.1.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/archiver-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-2.1.0.tgz", + "integrity": "sha512-bEL/yUb/fNNiNTuUz979Z0Yg5L+LzLxGJz8x79lYmR54fmTIb6ob/hNQgkQnIUDWIFjZVQwl9Xs356I6BAMHfw==", + "dependencies": { + "glob": "^7.1.4", + "graceful-fs": "^4.2.0", + "lazystream": "^1.0.0", + "lodash.defaults": "^4.2.0", + "lodash.difference": "^4.5.0", + "lodash.flatten": "^4.4.0", + "lodash.isplainobject": "^4.0.6", + "lodash.union": "^4.6.0", + "normalize-path": "^3.0.0", + "readable-stream": "^2.0.0" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/archiver-utils/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/archiver-utils/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "node_modules/archiver-utils/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/aria-hidden": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.2.6.tgz", + "integrity": "sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==", + "dependencies": { + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/array-buffer-byte-length": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz", + "integrity": "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==", + "dependencies": { + "call-bound": "^1.0.3", + "is-array-buffer": "^3.0.5" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/arraybuffer.prototype.slice": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz", + "integrity": "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==", + "dependencies": { + "array-buffer-byte-length": "^1.0.1", + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "is-array-buffer": "^3.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/asn1js": { + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/asn1js/-/asn1js-3.0.10.tgz", + "integrity": "sha512-S2s3aOytiKdFRdulw2qPE51MzjzVOisppcVv7jVFR+Kw0kxwvFrDcYA0h7Ndqbmj0HkMIXYWaoj7fli8kgx1eg==", + "dev": true, + "dependencies": { + "pvtsutils": "^1.3.6", + "pvutils": "^1.1.5", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/async": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==" + }, + "node_modules/async-exit-hook": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/async-exit-hook/-/async-exit-hook-2.0.1.tgz", + "integrity": "sha512-NW2cX8m1Q7KPA7a5M2ULQeZ2wR5qI5PAbw5L0UOMxdioVk9PMZ0h1TmyZEkPYrCvYjDlFICusOu1dlEKAAeXBw==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/async-function": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/async-function/-/async-function-1.0.0.tgz", + "integrity": "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "dev": true + }, + "node_modules/at-least-node": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", + "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==", + "dev": true, + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/available-typed-arrays": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/aws4": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.13.2.tgz", + "integrity": "sha512-lHe62zvbTB5eEABUVi/AwVh0ZKY9rMMDhmm+eeyuuUQbQ3+J+fONVQOZyj+DdrvD4BY33uYniyRJ4UJIaSKAfw==", + "dev": true + }, + "node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/big-integer": { + "version": "1.6.52", + "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.52.tgz", + "integrity": "sha512-QxD8cf2eVqJOOz63z6JIN9BzvVs/dlySa5HGSBH5xtR8dPteIRQnBxxKqkNTiT6jbDTF6jAfrd4oMcND9RGbQg==", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/binary": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/binary/-/binary-0.3.0.tgz", + "integrity": "sha512-D4H1y5KYwpJgK8wk1Cue5LLPgmwHKYSChkbspQg5JtVuR5ulGckxfR62H3AE9UDkdMC8yyXlqYihuz3Aqg2XZg==", + "dependencies": { + "buffers": "~0.1.1", + "chainsaw": "~0.1.0" + }, + "engines": { + "node": "*" + } + }, + "node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/bluebird": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", + "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", + "dev": true + }, + "node_modules/boolean": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/boolean/-/boolean-3.2.0.tgz", + "integrity": "sha512-d0II/GO9uf9lfUHH2BQsjxzRJZBdsjgsBiW4BvhWk/3qoKwQFjIDVN19PfX8F2D/r9PCMTtLWjYVCFrpeYUzsw==", + "deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.", + "dev": true, + "optional": true + }, + "node_modules/brace-expansion": { + "version": "5.0.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.12.tgz", + "integrity": "sha512-YovQ3rzhaLMIrDjNDMkNS01tea93qhEhG5xy8f6+R0l+dw3Ki+5sCoIoI942iuLZTHWogWktgwVDhU09iNEimQ==", + "dev": true, + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", + "engines": { + "node": "*" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true + }, + "node_modules/buffer-indexof-polyfill": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/buffer-indexof-polyfill/-/buffer-indexof-polyfill-1.0.2.tgz", + "integrity": "sha512-I7wzHwA3t1/lwXQh+A5PbNvJxgfo5r3xulgpYDB5zckTu/Z9oUK9biouBKQUjEqzaz3HnAT6TYoovmE+GqSf7A==", + "engines": { + "node": ">=0.10" + } + }, + "node_modules/buffers": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/buffers/-/buffers-0.1.1.tgz", + "integrity": "sha512-9q/rDEGSb/Qsvv2qvzIzdluL5k7AaJOTrw23z9reQthrbF7is4CtlT0DXyO1oei2DCp4uojjzQ7igaSHp1kAEQ==", + "engines": { + "node": ">=0.2.0" + } + }, + "node_modules/builder-util": { + "version": "26.15.3", + "resolved": "https://registry.npmjs.org/builder-util/-/builder-util-26.15.3.tgz", + "integrity": "sha512-q2hn7Mbo2nFNkVekPiHFx6Nfo3hURmES3tfBn+k5Pqxl2RkmP3QGqZUhH/q9Pch/4G05NRhPjDlVj1O8q4Txvw==", + "dev": true, + "dependencies": { + "@types/debug": "^4.1.6", + "builder-util-runtime": "9.7.0", + "chalk": "^4.1.2", + "cross-spawn": "^7.0.6", + "debug": "^4.3.4", + "fs-extra": "^10.1.0", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.0", + "js-yaml": "^4.1.0", + "sanitize-filename": "^1.6.3", + "source-map-support": "^0.5.19", + "stat-mode": "^1.0.0", + "temp-file": "^3.4.0", + "tiny-async-pool": "1.3.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/builder-util-runtime": { + "version": "9.7.0", + "resolved": "https://registry.npmjs.org/builder-util-runtime/-/builder-util-runtime-9.7.0.tgz", + "integrity": "sha512-g/kR520giAFYkSXTzcmF3kqQq7wi8F6N6SzeDgZrqTBN+VHdmgWOyTdD1yD7AATDId/yXLvuP34CxW46/BwCdw==", + "dev": true, + "dependencies": { + "debug": "^4.3.4", + "sax": "^1.2.4" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/bytestreamjs": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/bytestreamjs/-/bytestreamjs-2.0.1.tgz", + "integrity": "sha512-U1Z/ob71V/bXfVABvNr/Kumf5VyeQRBEm6Txb0PQ6S7V5GpBM3w4Cbqz/xPDicR5tN0uvDifng8C+5qECeGwyQ==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/cac": { + "version": "6.7.14", + "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", + "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/cacheable-lookup": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz", + "integrity": "sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==", + "dev": true, + "engines": { + "node": ">=10.6.0" + } + }, + "node_modules/cacheable-request": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-7.0.4.tgz", + "integrity": "sha512-v+p6ongsrp0yTGbJXjgxPow2+DL93DASP4kXCDKb8/bwRtt9OEF3whggkkDkGNzgcWy2XaF4a8nZglC7uElscg==", + "dev": true, + "dependencies": { + "clone-response": "^1.0.2", + "get-stream": "^5.1.0", + "http-cache-semantics": "^4.0.0", + "keyv": "^4.0.0", + "lowercase-keys": "^2.0.0", + "normalize-url": "^6.0.1", + "responselike": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/call-bind": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.9.tgz", + "integrity": "sha512-a/hy+pNsFUTR+Iz8TCJvXudKVLAnz/DyeSUo10I5yvFDQJBFU2s9uqQpoSrJlroHUKoKqzg+epxyP9lqFdzfBQ==", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "get-intrinsic": "^1.3.0", + "set-function-length": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/chai": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/chai/-/chai-5.3.3.tgz", + "integrity": "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==", + "dev": true, + "dependencies": { + "assertion-error": "^2.0.1", + "check-error": "^2.1.1", + "deep-eql": "^5.0.1", + "loupe": "^3.1.0", + "pathval": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/chainsaw": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/chainsaw/-/chainsaw-0.1.0.tgz", + "integrity": "sha512-75kWfWt6MEKNC8xYXIdRpDehRYY/tNSgwKaJq+dbbDcxORuVrrQ+SEHoWsniVn9XPYfP4gmdWIeDk/4YNp1rNQ==", + "dependencies": { + "traverse": ">=0.3.0 <0.4" + }, + "engines": { + "node": "*" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/check-error": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.3.tgz", + "integrity": "sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==", + "dev": true, + "engines": { + "node": ">= 16" + } + }, + "node_modules/chownr": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", + "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", + "dev": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/chromium-pickle-js": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/chromium-pickle-js/-/chromium-pickle-js-0.2.0.tgz", + "integrity": "sha512-1R5Fho+jBq0DDydt+/vHWj5KJNJCKdARKOCwZUen84I5BreWoLqRLANH1U87eJy1tiASPtMnGqJJq0ZsLoRPOw==", + "dev": true + }, + "node_modules/ci-info": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.4.0.tgz", + "integrity": "sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/clone-response": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.3.tgz", + "integrity": "sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA==", + "dev": true, + "dependencies": { + "mimic-response": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/commander": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-5.1.0.tgz", + "integrity": "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/compare-version": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/compare-version/-/compare-version-0.1.2.tgz", + "integrity": "sha512-pJDh5/4wrEnXX/VWRZvruAGHkzKdr46z11OlTPN+VrATlWWhSKewNCJ1futCO5C7eJB3nPMFZA1LeYtcFboZ2A==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/compress-commons": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-4.1.2.tgz", + "integrity": "sha512-D3uMHtGc/fcO1Gt1/L7i1e33VOvD4A9hfQLP+6ewd+BvG/gQ84Yh4oftEhAdjSMgBgwGL+jsppT7JYNpo6MHHg==", + "dependencies": { + "buffer-crc32": "^0.2.13", + "crc32-stream": "^4.0.2", + "normalize-path": "^3.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" + }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" + }, + "node_modules/crc-32": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz", + "integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==", + "bin": { + "crc32": "bin/crc32.njs" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/crc32-stream": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-4.0.3.tgz", + "integrity": "sha512-NT7w2JVU7DFroFdYkeq8cywxrgjPHWkdX1wjpRQXPX5Asews3tA+Ght6lddQO5Mkumffp3X7GEqku3epj2toIw==", + "dependencies": { + "crc-32": "^1.2.0", + "readable-stream": "^3.4.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/cross-dirname": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/cross-dirname/-/cross-dirname-0.1.0.tgz", + "integrity": "sha512-+R08/oI0nl3vfPcqftZRpytksBXDzOUveBq/NBVx0sUp1axwzPQrKinNx5yd5sxPu8j1wIy8AfnVQ+5eFdha6Q==", + "dev": true, + "optional": true, + "peer": true + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/cross-spawn/node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "node_modules/cross-spawn/node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==" + }, + "node_modules/data-view-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz", + "integrity": "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/data-view-byte-length": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.2.tgz", + "integrity": "sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/inspect-js" + } + }, + "node_modules/data-view-byte-offset": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.1.tgz", + "integrity": "sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/dayjs": { + "version": "1.11.23", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.23.tgz", + "integrity": "sha512-QDTCU0M0MxR3hQfnlDJfwekQiaanm1ubOD231u73WBckQ/fsamwRLiE2GBz6D3a/xF1NgfiDLJjXBa1hYOYTtQ==" + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "dev": true, + "dependencies": { + "mimic-response": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/decompress-response/node_modules/mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/deep-eql": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", + "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/defer-to-connect": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz", + "integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/defu": { + "version": "6.1.7", + "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.7.tgz", + "integrity": "sha512-7z22QmUWiQ/2d0KkdYmANbRUVABpZ9SNYyH5vx6PZ+nE5bcC0l7uFvEfHlyld/HcGBFTL536ClDt3DEcSlEJAQ==" + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/detect-node": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz", + "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==", + "dev": true, + "optional": true + }, + "node_modules/dir-compare": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/dir-compare/-/dir-compare-4.2.0.tgz", + "integrity": "sha512-2xMCmOoMrdQIPHdsTawECdNPwlVFB9zGcz3kuhmBO6U3oU+UQjsue0i8ayLKpgBcm+hcXPMVSGUN9d+pvJ6+VQ==", + "dev": true, + "dependencies": { + "minimatch": "^3.0.5", + "p-limit": "^3.1.0 " + } + }, + "node_modules/dir-compare/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "node_modules/dir-compare/node_modules/brace-expansion": { + "version": "1.1.21", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.21.tgz", + "integrity": "sha512-9zeA+KLZNNzglF2TPKRQEDyx6Yby7daAkuy8MiPzpXPsYDWi/DRM8jmwUDxokQjYqBpv5DgPiwD4h4ZZSy1Ujw==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/dir-compare/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/dmg-builder": { + "version": "26.15.3", + "resolved": "https://registry.npmjs.org/dmg-builder/-/dmg-builder-26.15.3.tgz", + "integrity": "sha512-O3zJUFUYHJKgzPqioHxfxzBzlSC1eXCSr79gMSBKBP5AgjjpmrydMsMLotEg9fAJF36vdUncb+4ndRNxoPdlSQ==", + "dev": true, + "dependencies": { + "app-builder-lib": "26.15.3", + "builder-util": "26.15.3", + "fs-extra": "^10.1.0", + "js-yaml": "^4.1.0" + } + }, + "node_modules/dotenv": { + "version": "16.6.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", + "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/dotenv-expand": { + "version": "11.0.7", + "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-11.0.7.tgz", + "integrity": "sha512-zIHwmZPRshsCdpMDyVsqGmgyP0yT8GAgXUnkdAoJisxvf33k7yO6OuoKmcTGuXPWSsm8Oh88nZicRLA9Y0rUeA==", + "dev": true, + "dependencies": { + "dotenv": "^16.4.5" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/duplexer2": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz", + "integrity": "sha512-asLFVfWWtJ90ZyOUHMqk7/S2w2guQKxUI2itj3d92ADHhxUSbCMGi1f1cBcJ7xM1To+pE/Khbwo1yuNbMEPKeA==", + "dependencies": { + "readable-stream": "^2.0.2" + } + }, + "node_modules/duplexer2/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/duplexer2/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "node_modules/duplexer2/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/ejs": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz", + "integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==", + "dev": true, + "dependencies": { + "jake": "^10.8.5" + }, + "bin": { + "ejs": "bin/cli.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/electron": { + "version": "42.11.15", + "resolved": "https://registry.npmjs.org/electron/-/electron-42.11.15.tgz", + "integrity": "sha512-pZOe/Qga4o3t6q9WdL6hV0hYjjlcagJsSCFp3Zc4rxyqTYsIQr6macePGoQefXkdKFnsA4VvwqHjYxTUSiE3qA==", + "dev": true, + "dependencies": { + "@electron-internal/extract-zip": "^1.0.1", + "@electron/get": "^5.0.0", + "@types/node": "^24.9.0" + }, + "bin": { + "electron": "cli.js", + "install-electron": "install.js" + }, + "engines": { + "node": ">= 22.12.0" + } + }, + "node_modules/electron-builder": { + "version": "26.15.3", + "resolved": "https://registry.npmjs.org/electron-builder/-/electron-builder-26.15.3.tgz", + "integrity": "sha512-a1KM5heqS3gQCZzizXEI8RjJy3QVogULPdeSknt76uLDpBIW/HDGsMg/XgP0riP6PI9COsRvFITKKGDqA8fJxA==", + "dev": true, + "dependencies": { + "app-builder-lib": "26.15.3", + "builder-util": "26.15.3", + "builder-util-runtime": "9.7.0", + "chalk": "^4.1.2", + "ci-info": "^4.2.0", + "dmg-builder": "26.15.3", + "fs-extra": "^10.1.0", + "lazy-val": "^1.0.5", + "simple-update-notifier": "2.0.0", + "yargs": "^17.6.2" + }, + "bin": { + "electron-builder": "cli.js", + "install-app-deps": "install-app-deps.js" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/electron-builder-squirrel-windows": { + "version": "26.15.3", + "resolved": "https://registry.npmjs.org/electron-builder-squirrel-windows/-/electron-builder-squirrel-windows-26.15.3.tgz", + "integrity": "sha512-Jc19XPV9y9+2bAdZPkXuVNGNIEFBq9poHC61l8Kv6FdK7DRG3+Ic0rerC0DXOaeHNz8yW0fg/JnF8GQROOF5MA==", + "dev": true, + "peer": true, + "dependencies": { + "app-builder-lib": "26.15.3", + "builder-util": "26.15.3", + "electron-winstaller": "5.4.0" + } + }, + "node_modules/electron-publish": { + "version": "26.15.3", + "resolved": "https://registry.npmjs.org/electron-publish/-/electron-publish-26.15.3.tgz", + "integrity": "sha512-g/2bn8YTavY4cuS5F+jOS7zmZbXXBV8KZ8yHKfJjFPoKtzBqrpCdNPxBd3tqdBwP7BVd0lGzf7Bk2s0KesWZ4Q==", + "dev": true, + "dependencies": { + "@types/fs-extra": "^9.0.11", + "aws4": "^1.13.2", + "builder-util": "26.15.3", + "builder-util-runtime": "9.7.0", + "chalk": "^4.1.2", + "form-data": "^4.0.5", + "fs-extra": "^10.1.0", + "lazy-val": "^1.0.5", + "mime": "^2.5.2" + } + }, + "node_modules/electron-winstaller": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/electron-winstaller/-/electron-winstaller-5.4.0.tgz", + "integrity": "sha512-bO3y10YikuUwUuDUQRM4KfwNkKhnpVO7IPdbsrejwN9/AABJzzTQ4GeHwyzNSrVO+tEH3/Np255a3sVZpZDjvg==", + "dev": true, + "hasInstallScript": true, + "peer": true, + "dependencies": { + "@electron/asar": "^3.2.1", + "debug": "^4.1.1", + "fs-extra": "^7.0.1", + "lodash": "^4.17.21", + "temp": "^0.9.0" + }, + "engines": { + "node": ">=8.0.0" + }, + "optionalDependencies": { + "@electron/windows-sign": "^1.1.2" + } + }, + "node_modules/electron-winstaller/node_modules/fs-extra": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz", + "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", + "dev": true, + "peer": true, + "dependencies": { + "graceful-fs": "^4.1.2", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + }, + "engines": { + "node": ">=6 <7 || >=8" + } + }, + "node_modules/electron-winstaller/node_modules/jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", + "dev": true, + "peer": true, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/electron-winstaller/node_modules/universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "dev": true, + "peer": true, + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/electron/node_modules/@types/node": { + "version": "24.19.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.19.2.tgz", + "integrity": "sha512-93d49wCWJkCyN87MxLpNN6KXPCwFw7bVUg79mGUV1OS40JGWwVCr8n61DXCSqz7CV6/VfPkb7xec7uP2M+QHWQ==", + "dev": true, + "dependencies": { + "undici-types": ">=7.24.0 <7.24.7" + } + }, + "node_modules/electron/node_modules/undici-types": { + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.24.6.tgz", + "integrity": "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==", + "dev": true + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/end-of-stream": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", + "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/entities": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz", + "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/env-paths": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-3.0.0.tgz", + "integrity": "sha512-dtJUTepzMW3Lm/NPxRf3wP4642UWhjL2sQxc+ym2YMj1m/H2zDNQOlezafzkHwn6sMstjHTwG6iQQsctDW/b1A==", + "dev": true, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/err-code": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz", + "integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==", + "dev": true + }, + "node_modules/es-abstract": { + "version": "1.24.2", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.2.tgz", + "integrity": "sha512-2FpH9Q5i2RRwyEP1AylXe6nYLR5OhaJTZwmlcP0dL/+JCbgg7yyEo/sEK6HeGZRf3dFpWwThaRHVApXSkW3xeg==", + "dependencies": { + "array-buffer-byte-length": "^1.0.2", + "arraybuffer.prototype.slice": "^1.0.4", + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "data-view-buffer": "^1.0.2", + "data-view-byte-length": "^1.0.2", + "data-view-byte-offset": "^1.0.1", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "es-set-tostringtag": "^2.1.0", + "es-to-primitive": "^1.3.0", + "function.prototype.name": "^1.1.8", + "get-intrinsic": "^1.3.0", + "get-proto": "^1.0.1", + "get-symbol-description": "^1.1.0", + "globalthis": "^1.0.4", + "gopd": "^1.2.0", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "internal-slot": "^1.1.0", + "is-array-buffer": "^3.0.5", + "is-callable": "^1.2.7", + "is-data-view": "^1.0.2", + "is-negative-zero": "^2.0.3", + "is-regex": "^1.2.1", + "is-set": "^2.0.3", + "is-shared-array-buffer": "^1.0.4", + "is-string": "^1.1.1", + "is-typed-array": "^1.1.15", + "is-weakref": "^1.1.1", + "math-intrinsics": "^1.1.0", + "object-inspect": "^1.13.4", + "object-keys": "^1.1.1", + "object.assign": "^4.1.7", + "own-keys": "^1.0.1", + "regexp.prototype.flags": "^1.5.4", + "safe-array-concat": "^1.1.3", + "safe-push-apply": "^1.0.0", + "safe-regex-test": "^1.1.0", + "set-proto": "^1.0.0", + "stop-iteration-iterator": "^1.1.0", + "string.prototype.trim": "^1.2.10", + "string.prototype.trimend": "^1.0.9", + "string.prototype.trimstart": "^1.0.8", + "typed-array-buffer": "^1.0.3", + "typed-array-byte-length": "^1.0.3", + "typed-array-byte-offset": "^1.0.4", + "typed-array-length": "^1.0.7", + "unbox-primitive": "^1.1.0", + "which-typed-array": "^1.1.19" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-abstract-get": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-abstract-get/-/es-abstract-get-1.0.0.tgz", + "integrity": "sha512-6PMWXpdhshVvFp+FoWYs1EvG1Nj0tvk0dZM+XcK0xMEM1czRVcP6ohqPWHy6qPagSpC8j4+p89WXlT+xXJs/fg==", + "dependencies": { + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.2", + "is-callable": "^1.2.7", + "object-inspect": "^1.13.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-module-lexer": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", + "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", + "dev": true + }, + "node_modules/es-object-atoms": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz", + "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-to-primitive": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.3.4.tgz", + "integrity": "sha512-yPDz7wqpg1/mmHLmS3tcfTfbw5f1eryXvyghYBffGdERwe+mV7ZcWzTR8LR17Kvqt3qfPurjlonmnq3MKXIOXw==", + "dependencies": { + "es-abstract-get": "^1.0.0", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "is-callable": "^1.2.7", + "is-date-object": "^1.1.0", + "is-symbol": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es6-error": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz", + "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==", + "dev": true, + "optional": true + }, + "node_modules/esbuild": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", + "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", + "dev": true, + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.12", + "@esbuild/android-arm": "0.25.12", + "@esbuild/android-arm64": "0.25.12", + "@esbuild/android-x64": "0.25.12", + "@esbuild/darwin-arm64": "0.25.12", + "@esbuild/darwin-x64": "0.25.12", + "@esbuild/freebsd-arm64": "0.25.12", + "@esbuild/freebsd-x64": "0.25.12", + "@esbuild/linux-arm": "0.25.12", + "@esbuild/linux-arm64": "0.25.12", + "@esbuild/linux-ia32": "0.25.12", + "@esbuild/linux-loong64": "0.25.12", + "@esbuild/linux-mips64el": "0.25.12", + "@esbuild/linux-ppc64": "0.25.12", + "@esbuild/linux-riscv64": "0.25.12", + "@esbuild/linux-s390x": "0.25.12", + "@esbuild/linux-x64": "0.25.12", + "@esbuild/netbsd-arm64": "0.25.12", + "@esbuild/netbsd-x64": "0.25.12", + "@esbuild/openbsd-arm64": "0.25.12", + "@esbuild/openbsd-x64": "0.25.12", + "@esbuild/openharmony-arm64": "0.25.12", + "@esbuild/sunos-x64": "0.25.12", + "@esbuild/win32-arm64": "0.25.12", + "@esbuild/win32-ia32": "0.25.12", + "@esbuild/win32-x64": "0.25.12" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "optional": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/exceljs": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/exceljs/-/exceljs-4.4.0.tgz", + "integrity": "sha512-XctvKaEMaj1Ii9oDOqbW/6e1gXknSY4g/aLCDicOXqBE4M0nRWkUu0PTp++UPNzoFY12BNHMfs/VadKIS6llvg==", + "dependencies": { + "archiver": "^5.0.0", + "dayjs": "^1.8.34", + "fast-csv": "^4.3.1", + "jszip": "^3.10.1", + "readable-stream": "^3.6.0", + "saxes": "^5.0.1", + "tmp": "^0.2.0", + "unzipper": "^0.10.11", + "uuid": "^8.3.0" + }, + "engines": { + "node": ">=8.3.0" + } + }, + "node_modules/exceljs/node_modules/bluebird": { + "version": "3.4.7", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.4.7.tgz", + "integrity": "sha512-iD3898SR7sWVRHbiQv+sHUtHnMvC1o3nW5rAcqnq3uOn07DSAppZYUkIGslDz6gXC7HfunPe7YVBgoEJASPcHA==" + }, + "node_modules/exceljs/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "node_modules/exceljs/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/exceljs/node_modules/unzipper": { + "version": "0.10.14", + "resolved": "https://registry.npmjs.org/unzipper/-/unzipper-0.10.14.tgz", + "integrity": "sha512-ti4wZj+0bQTiX2KmKWuwj7lhV+2n//uXEotUmGuQqrbVZSEGFMbI68+c6JCQ8aAmUWYvtHEz2A8K6wXvueR/6g==", + "dependencies": { + "big-integer": "^1.6.17", + "binary": "~0.3.0", + "bluebird": "~3.4.1", + "buffer-indexof-polyfill": "~1.0.0", + "duplexer2": "~0.1.4", + "fstream": "^1.0.12", + "graceful-fs": "^4.2.2", + "listenercount": "~1.0.1", + "readable-stream": "~2.3.6", + "setimmediate": "~1.0.4" + } + }, + "node_modules/exceljs/node_modules/unzipper/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/expect-type": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.4.0.tgz", + "integrity": "sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA==", + "dev": true, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/exponential-backoff": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/exponential-backoff/-/exponential-backoff-3.1.3.tgz", + "integrity": "sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA==", + "dev": true + }, + "node_modules/fast-csv": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/fast-csv/-/fast-csv-4.3.6.tgz", + "integrity": "sha512-2RNSpuwwsJGP0frGsOmTb9oUF+VkFSM4SyLTDgwf2ciHWTarN0lQTC+F2f/t5J9QjW+c65VFIAAu85GsvMIusw==", + "dependencies": { + "@fast-csv/format": "4.3.5", + "@fast-csv/parse": "4.3.6" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "node_modules/fast-uri": { + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.8.tgz", + "integrity": "sha512-GZMtZUTNRpOVIECoXwLNZS5xUGE+mVNbTB8h/7Rwh2TFWcBQiPzTgyZi05BF9UMZKkLJv8XBRJTlU7zg8+ZfMg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ] + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/filelist": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.6.tgz", + "integrity": "sha512-5giy2PkLYY1cP39p17Ech+2xlpTRL9HLspOfEgm0L6CwBXBTgsK5ou0JtzYuepxkaQ/tvhCFIJ5uXo0OrM2DxA==", + "dev": true, + "dependencies": { + "minimatch": "^5.0.1" + } + }, + "node_modules/filelist/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "node_modules/filelist/node_modules/brace-expansion": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.7.tgz", + "integrity": "sha512-uZbew1NqdmPDTMJ8ah1y+b+9QEJrfkXFk3RcTQw3X0jW/xRUvFKsg1CfQdSYGdTbXZWExtU3J3ccxtnfw1Fi0g==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/filelist/node_modules/minimatch": { + "version": "5.1.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.9.tgz", + "integrity": "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/for-each": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", + "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", + "dependencies": { + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/form-data": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.6.tgz", + "integrity": "sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==", + "dev": true, + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.4", + "mime-types": "^2.1.35" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==" + }, + "node_modules/fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" + }, + "node_modules/fstream": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.12.tgz", + "integrity": "sha512-WvJ193OHa0GHPEL+AycEJgxvBEwyfRkN1vhjca23OaPVMCaLCXTd5qAu82AjTcgP1UJmytkOKb63Ypde7raDIg==", + "deprecated": "This package is no longer supported.", + "dependencies": { + "graceful-fs": "^4.1.2", + "inherits": "~2.0.0", + "mkdirp": ">=0.5 0", + "rimraf": "2" + }, + "engines": { + "node": ">=0.6" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/function.prototype.name": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.2.0.tgz", + "integrity": "sha512-jObKIik1P2QjPHP5nz5BaOtUlfgS0fWo8IUByNXkM+o+02sJOi94em77GwJKQSJ3gfPHdgzLNrHc1uokV4P/ew==", + "dependencies": { + "call-bind": "^1.0.9", + "call-bound": "^1.0.4", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.2", + "hasown": "^2.0.4", + "is-callable": "^1.2.7", + "is-document.all": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/generator-function": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/generator-function/-/generator-function-2.0.1.tgz", + "integrity": "sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "dev": true, + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-symbol-description": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.1.0.tgz", + "integrity": "sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + }, + "node_modules/glob/node_modules/brace-expansion": { + "version": "1.1.21", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.21.tgz", + "integrity": "sha512-9zeA+KLZNNzglF2TPKRQEDyx6Yby7daAkuy8MiPzpXPsYDWi/DRM8jmwUDxokQjYqBpv5DgPiwD4h4ZZSy1Ujw==", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/glob/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/global-agent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/global-agent/-/global-agent-3.0.0.tgz", + "integrity": "sha512-PT6XReJ+D07JvGoxQMkT6qji/jVNfX/h364XHZOWeRzy64sSFr+xJ5OX7LI3b4MPQzdL4H8Y8M0xzPpsVMwA8Q==", + "dev": true, + "optional": true, + "dependencies": { + "boolean": "^3.0.1", + "es6-error": "^4.1.1", + "matcher": "^3.0.0", + "roarr": "^2.15.3", + "semver": "^7.3.2", + "serialize-error": "^7.0.1" + }, + "engines": { + "node": ">=10.0" + } + }, + "node_modules/globalthis": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", + "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", + "dependencies": { + "define-properties": "^1.2.1", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/got": { + "version": "11.8.6", + "resolved": "https://registry.npmjs.org/got/-/got-11.8.6.tgz", + "integrity": "sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g==", + "dev": true, + "dependencies": { + "@sindresorhus/is": "^4.0.0", + "@szmarczak/http-timer": "^4.0.5", + "@types/cacheable-request": "^6.0.1", + "@types/responselike": "^1.0.0", + "cacheable-lookup": "^5.0.3", + "cacheable-request": "^7.0.2", + "decompress-response": "^6.0.0", + "http2-wrapper": "^1.0.0-beta.5.2", + "lowercase-keys": "^2.0.0", + "p-cancelable": "^2.0.0", + "responselike": "^2.0.0" + }, + "engines": { + "node": ">=10.19.0" + }, + "funding": { + "url": "https://github.com/sindresorhus/got?sponsor=1" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==" + }, + "node_modules/has-bigints": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz", + "integrity": "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.2.0.tgz", + "integrity": "sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==", + "dependencies": { + "dunder-proto": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", + "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hosted-git-info": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz", + "integrity": "sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/http-cache-semantics": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.3.0.tgz", + "integrity": "sha512-M5t5LlJpS1UHMjvwRQVdFHvPISGeLAxNcrWuJkeGh0KxsqCHZ1O3NXZU/8x7cD0BDcGW8kapxMKTvwlqrNkHkA==", + "dev": true + }, + "node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "dev": true, + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/http2-wrapper": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-1.0.3.tgz", + "integrity": "sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==", + "dev": true, + "dependencies": { + "quick-lru": "^5.1.1", + "resolve-alpn": "^1.0.0" + }, + "engines": { + "node": ">=10.19.0" + } + }, + "node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "dev": true, + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/immediate": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", + "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==" + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "node_modules/internal-slot": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz", + "integrity": "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==", + "dependencies": { + "es-errors": "^1.3.0", + "hasown": "^2.0.2", + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/is-array-buffer": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz", + "integrity": "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-async-function": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.1.1.tgz", + "integrity": "sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==", + "dependencies": { + "async-function": "^1.0.0", + "call-bound": "^1.0.3", + "get-proto": "^1.0.1", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-bigint": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.1.0.tgz", + "integrity": "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==", + "dependencies": { + "has-bigints": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-boolean-object": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.2.2.tgz", + "integrity": "sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-data-view": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.2.tgz", + "integrity": "sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==", + "dependencies": { + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-date-object": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.1.0.tgz", + "integrity": "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==", + "dependencies": { + "call-bound": "^1.0.2", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-document.all": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-document.all/-/is-document.all-1.0.0.tgz", + "integrity": "sha512-+XSoyS05OdBbhFuELhgTCpFNHkpBOJqtsZfUFFpe5QTw+9Sjbh8zitxhQkYAo6wV7e1Vb8cAPvpCk9jGam/82g==", + "dependencies": { + "call-bound": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-finalizationregistry": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.1.1.tgz", + "integrity": "sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-generator-function": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.2.tgz", + "integrity": "sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==", + "dependencies": { + "call-bound": "^1.0.4", + "generator-function": "^2.0.0", + "get-proto": "^1.0.1", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-map": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", + "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-negative-zero": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz", + "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-number-object": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.1.1.tgz", + "integrity": "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-regex": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", + "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", + "dependencies": { + "call-bound": "^1.0.2", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-set": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz", + "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-shared-array-buffer": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.4.tgz", + "integrity": "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-string": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.1.1.tgz", + "integrity": "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-symbol": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.1.1.tgz", + "integrity": "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==", + "dependencies": { + "call-bound": "^1.0.2", + "has-symbols": "^1.1.0", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typed-array": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", + "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", + "dependencies": { + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakmap": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", + "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakref": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.1.1.tgz", + "integrity": "sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakset": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.4.tgz", + "integrity": "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==", + "dependencies": { + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" + }, + "node_modules/isbinaryfile": { + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-5.0.7.tgz", + "integrity": "sha512-gnWD14Jh3FzS3CPhF0AxNOJ8CxqeblPTADzI38r0wt8ZyQl5edpy75myt08EG2oKvpyiqSqsx+Wkz9vtkbTqYQ==", + "dev": true, + "engines": { + "node": ">= 18.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/gjtorikian/" + } + }, + "node_modules/isexe": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.5.tgz", + "integrity": "sha512-6B3tLtFqtQS4ekarvLVMZ+X+VlvQekbe4taUkf/rhVO3d/h0M2rfARm/pXLcPEsjjMsFgrFgSrhQIxcSVrBz8w==", + "dev": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/jake": { + "version": "10.9.4", + "resolved": "https://registry.npmjs.org/jake/-/jake-10.9.4.tgz", + "integrity": "sha512-wpHYzhxiVQL+IV05BLE2Xn34zW1S223hvjtqk0+gsPrwd/8JNLXJgZZM/iPFsYc1xyphF+6M6EvdE5E9MBGkDA==", + "dev": true, + "dependencies": { + "async": "^3.2.6", + "filelist": "^1.0.4", + "picocolors": "^1.1.1" + }, + "bin": { + "jake": "bin/cli.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/jiti": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.7.0.tgz", + "integrity": "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==", + "dev": true, + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "node_modules/js-tokens": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.1.tgz", + "integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==", + "dev": true + }, + "node_modules/js-yaml": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.2.tgz", + "integrity": "sha512-SFNOvSJ+Dgf/9An904Yx+CgSlIPCkIpao4qo51lpee25TIRejdH3rhR4EZMGoNx3/TP3O+wzWuiTFl4sqbltzA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/nodeca" + } + ], + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true + }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true + }, + "node_modules/json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", + "dev": true, + "optional": true + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsonfile": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz", + "integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==", + "dev": true, + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/jszip": { + "version": "3.10.2", + "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.2.tgz", + "integrity": "sha512-3l+rb15IOWtUhU0H5MFqES/T6Kh7abYwjosBey/vD6hDt8zoEffkSC5Ws5SGtgVw3gBx2NEbhTeSW1+kWkpyTQ==", + "dependencies": { + "lie": "~3.3.0", + "pako": "~1.0.2", + "readable-stream": "~2.3.6", + "setimmediate": "^1.0.5" + } + }, + "node_modules/jszip/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/jszip/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "node_modules/jszip/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/lazy-val": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/lazy-val/-/lazy-val-1.0.5.tgz", + "integrity": "sha512-0/BnGCCfyUMkBpeDgWihanIAF9JmZhHBgUhEqzvf+adhNGLoP6TaiI5oF8oyb3I45P+PcnrqihSf01M0l0G5+Q==", + "dev": true + }, + "node_modules/lazystream": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/lazystream/-/lazystream-1.0.1.tgz", + "integrity": "sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==", + "dependencies": { + "readable-stream": "^2.0.5" + }, + "engines": { + "node": ">= 0.6.3" + } + }, + "node_modules/lazystream/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/lazystream/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "node_modules/lazystream/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/lie": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", + "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", + "dependencies": { + "immediate": "~3.0.5" + } + }, + "node_modules/listenercount": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/listenercount/-/listenercount-1.0.1.tgz", + "integrity": "sha512-3mk/Zag0+IJxeDrxSgaDPy4zZ3w05PRZeJNnlWhzFz5OkX49J4krc+A8X2d2M69vGMBEX0uyl8M+W+8gH+kBqQ==" + }, + "node_modules/lodash": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", + "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", + "dev": true + }, + "node_modules/lodash.defaults": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", + "integrity": "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==" + }, + "node_modules/lodash.difference": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.difference/-/lodash.difference-4.5.0.tgz", + "integrity": "sha512-dS2j+W26TQ7taQBGN8Lbbq04ssV3emRw4NY58WErlTO29pIqS0HmoT5aJ9+TUQ1N3G+JOZSji4eugsWwGp9yPA==" + }, + "node_modules/lodash.escaperegexp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.escaperegexp/-/lodash.escaperegexp-4.1.2.tgz", + "integrity": "sha512-TM9YBvyC84ZxE3rgfefxUWiQKLilstD6k7PTGt6wfbtXF8ixIJLOL3VYyV/z+ZiPLsVxAsKAFVwWlWeb2Y8Yyw==" + }, + "node_modules/lodash.flatten": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz", + "integrity": "sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g==" + }, + "node_modules/lodash.groupby": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.groupby/-/lodash.groupby-4.6.0.tgz", + "integrity": "sha512-5dcWxm23+VAoz+awKmBaiBvzox8+RqMgFhi7UvX9DHZr2HdxHXM/Wrf8cfKpsW37RNrvtPn6hSwNqurSILbmJw==" + }, + "node_modules/lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==" + }, + "node_modules/lodash.isequal": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", + "integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==", + "deprecated": "This package is deprecated. Use require('node:util').isDeepStrictEqual instead." + }, + "node_modules/lodash.isfunction": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/lodash.isfunction/-/lodash.isfunction-3.0.9.tgz", + "integrity": "sha512-AirXNj15uRIMMPihnkInB4i3NHeb4iBtNg9WRWuK2o31S+ePwwNmDPaTL3o7dTJ+VXNZim7rFs4rxN4YU1oUJw==" + }, + "node_modules/lodash.isnil": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/lodash.isnil/-/lodash.isnil-4.0.0.tgz", + "integrity": "sha512-up2Mzq3545mwVnMhTDMdfoG1OurpA/s5t88JmQX809eH3C8491iu2sfKhTfhQtKY78oPNhiaHJUpT/dUDAAtng==" + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==" + }, + "node_modules/lodash.isundefined": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/lodash.isundefined/-/lodash.isundefined-3.0.1.tgz", + "integrity": "sha512-MXB1is3s899/cD8jheYYE2V9qTHwKvt+npCwpD+1Sxm3Q3cECXCiYHjeHWXNwr6Q0SOBPrYUDxendrO6goVTEA==" + }, + "node_modules/lodash.union": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.union/-/lodash.union-4.6.0.tgz", + "integrity": "sha512-c4pB2CdGrGdjMKYLA+XiRDO7Y0PRQbm/Gzg8qMj+QH+pFVAoTp5sBpO0odL3FjoPCGjK96p6qsP+yQoiLoOBcw==" + }, + "node_modules/lodash.uniq": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", + "integrity": "sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==" + }, + "node_modules/loupe": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.2.1.tgz", + "integrity": "sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==", + "dev": true + }, + "node_modules/lowercase-keys": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", + "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/matcher": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/matcher/-/matcher-3.0.0.tgz", + "integrity": "sha512-OkeDaAZ/bQCxeFAozM55PKcKU0yJMPGifLwV4Qgjitu+5MoAfSQN4lsLJeXZ1b8w0x+/Emda6MZgXS1jvsapng==", + "dev": true, + "optional": true, + "dependencies": { + "escape-string-regexp": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/mime": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", + "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", + "dev": true, + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-response": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", + "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/minimatch": { + "version": "10.2.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.6.tgz", + "integrity": "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==", + "dev": true, + "dependencies": { + "brace-expansion": "^5.0.8" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/minipass": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", + "dev": true, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/minizlib": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.1.0.tgz", + "integrity": "sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==", + "dev": true, + "dependencies": { + "minipass": "^7.1.2" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "dependencies": { + "minimist": "^1.2.6" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, + "node_modules/muggle-string": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/muggle-string/-/muggle-string-0.4.1.tgz", + "integrity": "sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==", + "dev": true + }, + "node_modules/nanoid": { + "version": "3.3.20", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.20.tgz", + "integrity": "sha512-uKdg2G3GNCKQn9byYOpxbGqrT2fGO5KRt5J/8b3pok8rT6qxGWF6hxMyJiEYtAf+FVyYuD9hRaDqX5uPFYJ4ZQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/node-abi": { + "version": "4.38.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-4.38.0.tgz", + "integrity": "sha512-suSdznUPfabdaeLiNERA0LHod14RhcEn8wMPUZEQ6bKvajUCZ+aizY4pIvlLU+gImeLKp2bFDHhOc9yonISsXw==", + "dev": true, + "dependencies": { + "semver": "^7.6.3" + }, + "engines": { + "node": ">=22.12.0" + } + }, + "node_modules/node-api-version": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/node-api-version/-/node-api-version-0.2.1.tgz", + "integrity": "sha512-2xP/IGGMmmSQpI1+O/k72jF/ykvZ89JeuKX3TLJAYPDVLUalrshrLHkeVcCCZqG/eEa635cr8IBYzgnDvM2O8Q==", + "dev": true, + "dependencies": { + "semver": "^7.3.5" + } + }, + "node_modules/node-gyp": { + "version": "12.4.0", + "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-12.4.0.tgz", + "integrity": "sha512-OMcPNvqTCFUnNaBlmdgq+lfNqY7gTiSmNRDjY3uAXRyudeKZEZxu3CLtjMQrx4zZxCX2b/mpNqTtwuCJgXhHkw==", + "dev": true, + "dependencies": { + "env-paths": "^2.2.0", + "exponential-backoff": "^3.1.1", + "graceful-fs": "^4.2.6", + "nopt": "^9.0.0", + "proc-log": "^6.0.0", + "semver": "^7.3.5", + "tar": "^7.5.4", + "tinyglobby": "^0.2.12", + "undici": "^6.25.0", + "which": "^6.0.0" + }, + "bin": { + "node-gyp": "bin/node-gyp.js" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/node-gyp/node_modules/env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/node-gyp/node_modules/isexe": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-4.0.0.tgz", + "integrity": "sha512-FFUtZMpoZ8RqHS3XeXEmHWLA4thH+ZxCv2lOiPIn1Xc7CxrqhWzNSDzD+/chS/zbYezmiwWLdQC09JdQKmthOw==", + "dev": true, + "engines": { + "node": ">=20" + } + }, + "node_modules/node-gyp/node_modules/undici": { + "version": "6.29.1", + "resolved": "https://registry.npmjs.org/undici/-/undici-6.29.1.tgz", + "integrity": "sha512-EvRlUt/QqAEvXdQeRMYUfwOQsfzpDztykrG2owGEE/rBOBoYv9uF39ZzsNDh4lTFHhTr2XOZjKkYgueD12ZmGA==", + "dev": true, + "engines": { + "node": ">=18.17" + } + }, + "node_modules/node-gyp/node_modules/which": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/which/-/which-6.0.1.tgz", + "integrity": "sha512-oGLe46MIrCRqX7ytPUf66EAYvdeMIZYn3WaocqqKZAxrBpkqHfL/qvTyJ/bTk5+AqHCjXmrv3CEWgy368zhRUg==", + "dev": true, + "dependencies": { + "isexe": "^4.0.0" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/node-int64": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", + "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", + "dev": true + }, + "node_modules/nopt": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-9.0.0.tgz", + "integrity": "sha512-Zhq3a+yFKrYwSBluL4H9XP3m3y5uvQkB/09CwDruCiRmR/UJYnn9W4R48ry0uGC70aeTPKLynBtscP9efFFcPw==", + "dev": true, + "dependencies": { + "abbrev": "^4.0.0" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/normalize-url": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz", + "integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz", + "integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0", + "has-symbols": "^1.1.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/ohash": { + "version": "2.0.12", + "resolved": "https://registry.npmjs.org/ohash/-/ohash-2.0.12.tgz", + "integrity": "sha512-65S/5gk9YSsaRjcyf7Nfa6h/d3E8/1gslpXfI4W7Dxn/oap8IKRuNT5VXkLQ1YFKIEg4apRY4Pj6aiwFzrDdmw==" + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/own-keys": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/own-keys/-/own-keys-1.0.2.tgz", + "integrity": "sha512-19YVAg7T+WTrxggPukVq7DjTv6+PJ867TmhCvBsYwmbFCsZd344rq2Ld1p0wo8f8Qrrhgp82c6FJRqdXWtSEhg==", + "dependencies": { + "call-bound": "^1.0.4", + "get-intrinsic": "^1.3.0", + "object-keys": "^1.1.1", + "safe-push-apply": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/p-cancelable": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.1.tgz", + "integrity": "sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==" + }, + "node_modules/path-browserify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz", + "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==", + "dev": true + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true + }, + "node_modules/pathval": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.1.tgz", + "integrity": "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==", + "dev": true, + "engines": { + "node": ">= 14.16" + } + }, + "node_modules/pdf-lib": { + "version": "1.17.1", + "resolved": "https://registry.npmjs.org/pdf-lib/-/pdf-lib-1.17.1.tgz", + "integrity": "sha512-V/mpyJAoTsN4cnP31vc0wfNA1+p20evqqnap0KLoRUN0Yk/p3wN52DOEsL4oBFcLdb76hlpKPtzJIgo67j/XLw==", + "dependencies": { + "@pdf-lib/standard-fonts": "^1.0.0", + "@pdf-lib/upng": "^1.0.1", + "pako": "^1.0.11", + "tslib": "^1.11.1" + } + }, + "node_modules/pdf-lib/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + }, + "node_modules/pdfjs-dist": { + "version": "5.7.284", + "resolved": "https://registry.npmjs.org/pdfjs-dist/-/pdfjs-dist-5.7.284.tgz", + "integrity": "sha512-h4EdYQczmGhbOlqc3PPZwxevn7ApdWPbovAuWXOB/DjIyigSnwfy2oze7c6mRcSr9XgLp3eN3EeL4DyySTPMFw==", + "engines": { + "node": ">=22.13.0 || >=24" + }, + "optionalDependencies": { + "@napi-rs/canvas": "^0.1.100" + } + }, + "node_modules/pe-library": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/pe-library/-/pe-library-0.4.1.tgz", + "integrity": "sha512-eRWB5LBz7PpDu4PUlwT0PhnQfTQJlDDdPa35urV4Osrm0t0AqQFGn+UIkU3klZvwJ8KPO3VbBFsXquA6p6kqZw==", + "dev": true, + "engines": { + "node": ">=12", + "npm": ">=6" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/jet2jet" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==" + }, + "node_modules/picomatch": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz", + "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pkijs": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/pkijs/-/pkijs-3.4.1.tgz", + "integrity": "sha512-Oo/NZcSWccq8KyoG7gLE9fnltgHns+pNCjCAp/WmjsUySi+sX7y4z4Xqu4fVb42CDHzRPl33fjzT15V1wvcyhA==", + "dev": true, + "workspaces": [ + "website" + ], + "dependencies": { + "@noble/hashes": "1.8.0", + "asn1js": "^3.0.6", + "bytestreamjs": "^2.0.1", + "pvtsutils": "^1.3.6", + "pvutils": "^1.1.3", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/pkijs/node_modules/@noble/hashes": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", + "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", + "dev": true, + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/playwright": { + "version": "1.64.1", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.64.1.tgz", + "integrity": "sha512-HCrVhHr5QQuwYoswEUGTb7gGp522yflbv/ST8nF4sFWGuLjQCt17Q/EubpGGSNKYn1tsTCPZxqHhHCfVURu7UA==", + "dev": true, + "dependencies": { + "playwright-core": "1.64.1" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/playwright-core": { + "version": "1.64.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.64.1.tgz", + "integrity": "sha512-BC4s8y6SEVA6Q1rSxZTto8hI1KUI3xXlHGZgvGqP4IqPdI/a6FX9H29IDNz7vKo9l21b6LfYosScPL6M3xxyDQ==", + "dev": true, + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/plist": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/plist/-/plist-3.1.0.tgz", + "integrity": "sha512-uysumyrvkUX0rX/dEVqt8gC3sTBzd4zoWfLeS29nb53imdaXVvLINYXTI2GNqzaMuvacNx4uJQ8+b3zXR0pkgQ==", + "dev": true, + "dependencies": { + "@xmldom/xmldom": "^0.8.8", + "base64-js": "^1.5.1", + "xmlbuilder": "^15.1.1" + }, + "engines": { + "node": ">=10.4.0" + } + }, + "node_modules/pngjs": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-7.0.0.tgz", + "integrity": "sha512-LKWqWJRhstyYo9pGvgor/ivk2w94eSjE3RGVuzLGlr3NmD8bf7RcYGze1mNdEHRP6TRP6rMuDHk5t44hnTRyow==", + "dev": true, + "engines": { + "node": ">=14.19.0" + } + }, + "node_modules/possible-typed-array-names": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", + "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/postcss": { + "version": "8.5.30", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.30.tgz", + "integrity": "sha512-zkd+e9S1AQHhmaBKye9zlFHWjOwVgqXl1q3ECYy8561K7qxR2cREf/STLskulBFd78k6AOFeA+ff1TtEzk9bAA==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "nanoid": "^3.3.20", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.2" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postject": { + "version": "1.0.0-alpha.6", + "resolved": "https://registry.npmjs.org/postject/-/postject-1.0.0-alpha.6.tgz", + "integrity": "sha512-b9Eb8h2eVqNE8edvKdwqkrY6O7kAwmI8kcnBv1NScolYJbo59XUF0noFq+lxbC1yN20bmC0WBEbDC5H/7ASb0A==", + "dev": true, + "optional": true, + "peer": true, + "dependencies": { + "commander": "^9.4.0" + }, + "bin": { + "postject": "dist/cli.js" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/postject/node_modules/commander": { + "version": "9.5.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz", + "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==", + "dev": true, + "optional": true, + "peer": true, + "engines": { + "node": "^12.20.0 || >=14" + } + }, + "node_modules/proc-log": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-6.1.0.tgz", + "integrity": "sha512-iG+GYldRf2BQ0UDUAd6JQ/RwzaQy6mXmsk/IzlYyal4A4SNFw54MeH4/tLkF4I5WoWG9SQwuqWzS99jaFQHBuQ==", + "dev": true, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" + }, + "node_modules/progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/promise-retry": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz", + "integrity": "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==", + "dev": true, + "dependencies": { + "err-code": "^2.0.2", + "retry": "^0.12.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/proper-lockfile": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/proper-lockfile/-/proper-lockfile-4.1.2.tgz", + "integrity": "sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.2.4", + "retry": "^0.12.0", + "signal-exit": "^3.0.2" + } + }, + "node_modules/pump": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.4.tgz", + "integrity": "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==", + "dev": true, + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/pvtsutils": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/pvtsutils/-/pvtsutils-1.3.6.tgz", + "integrity": "sha512-PLgQXQ6H2FWCaeRak8vvk1GW462lMxB5s3Jm673N82zI4vqtVUPuZdffdZbPDFRoU8kAhItWFtPCWiPpp4/EDg==", + "dev": true, + "dependencies": { + "tslib": "^2.8.1" + } + }, + "node_modules/pvutils": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pvutils/-/pvutils-1.2.0.tgz", + "integrity": "sha512-BbubeCEyTuQjVMakvJQ/Sxbc93F2pwmbsxONT/ZRrwU7Ua38d8unYTwXpTVLAKJ4BDuH9IGztCjQcd/N/39Dvg==", + "dev": true, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/quick-lru": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", + "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/read-binary-file-arch": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/read-binary-file-arch/-/read-binary-file-arch-1.0.6.tgz", + "integrity": "sha512-BNg9EN3DD3GsDXX7Aa8O4p92sryjkmzYYgmgTAc6CA4uGLEDzFfxOxugu21akOxpcXHiEgsYkC6nPsQvLLLmEg==", + "dev": true, + "dependencies": { + "debug": "^4.3.4" + }, + "bin": { + "read-binary-file-arch": "cli.js" + } + }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/readdir-glob": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/readdir-glob/-/readdir-glob-1.1.3.tgz", + "integrity": "sha512-v05I2k7xN8zXvPD9N+z/uhXPaj0sUFCe2rcWZIpBsqxfP7xXFQ0tipAd/wjj1YxWyWtUS5IDJpOG82JKt2EAVA==", + "dependencies": { + "minimatch": "^5.1.0" + } + }, + "node_modules/readdir-glob/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + }, + "node_modules/readdir-glob/node_modules/brace-expansion": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.7.tgz", + "integrity": "sha512-uZbew1NqdmPDTMJ8ah1y+b+9QEJrfkXFk3RcTQw3X0jW/xRUvFKsg1CfQdSYGdTbXZWExtU3J3ccxtnfw1Fi0g==", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/readdir-glob/node_modules/minimatch": { + "version": "5.1.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.9.tgz", + "integrity": "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/reflect.getprototypeof": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", + "integrity": "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.9", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.7", + "get-proto": "^1.0.1", + "which-builtin-type": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/regexp.prototype.flags": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz", + "integrity": "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-errors": "^1.3.0", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "set-function-name": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/reka-ui": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/reka-ui/-/reka-ui-2.11.0.tgz", + "integrity": "sha512-ilkpm4bIAol4jwzm4tpm3WVcffyGOpqksNZL1k1qO9QmIwAA+q8jroxjkUGLEn/GddfdExFfvofWfQiKZszUeQ==", + "dependencies": { + "@floating-ui/vue": "^2.0.1", + "@internationalized/date": "^3.5.0", + "@internationalized/number": "^3.5.0", + "@tanstack/vue-virtual": "^3.12.0", + "@vueuse/core": "^14.1.0", + "@vueuse/shared": "^14.1.0", + "aria-hidden": "^1.2.4", + "defu": "^6.1.5", + "ohash": "^2.0.11" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/zernonia" + }, + "peerDependencies": { + "vue": ">= 3.4.0" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resedit": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/resedit/-/resedit-1.7.2.tgz", + "integrity": "sha512-vHjcY2MlAITJhC0eRD/Vv8Vlgmu9Sd3LX9zZvtGzU5ZImdTN3+d6e/4mnTyV8vEbyf1sgNIrWxhWlrys52OkEA==", + "dev": true, + "dependencies": { + "pe-library": "^0.4.1" + }, + "engines": { + "node": ">=12", + "npm": ">=6" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/jet2jet" + } + }, + "node_modules/resolve-alpn": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.1.tgz", + "integrity": "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==", + "dev": true + }, + "node_modules/responselike": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/responselike/-/responselike-2.0.1.tgz", + "integrity": "sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw==", + "dev": true, + "dependencies": { + "lowercase-keys": "^2.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/rimraf": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", + "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + } + }, + "node_modules/roarr": { + "version": "2.15.4", + "resolved": "https://registry.npmjs.org/roarr/-/roarr-2.15.4.tgz", + "integrity": "sha512-CHhPh+UNHD2GTXNYhPWLnU8ONHdI+5DI+4EYIAOaiD63rHeYlZvyh8P+in5999TTSFgUYuKUAjzRI4mdh/p+2A==", + "dev": true, + "optional": true, + "dependencies": { + "boolean": "^3.0.1", + "detect-node": "^2.0.4", + "globalthis": "^1.0.1", + "json-stringify-safe": "^5.0.1", + "semver-compare": "^1.0.0", + "sprintf-js": "^1.1.2" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/rollup": { + "version": "4.64.7", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.64.7.tgz", + "integrity": "sha512-aCiJONoZp0lNGJ6sYqspLkh6UXaWEey/7Ko2TZyXQ4kgzAhDUxAgbt/oulXKmD+zFHXw1k+RF6ZZWOrjp7DZnQ==", + "dev": true, + "dependencies": { + "@types/estree": "1.0.9" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@napi-rs/lzma-linux-x64-gnu": "1.5.1", + "@rollup/rollup-android-arm-eabi": "4.64.7", + "@rollup/rollup-android-arm64": "4.64.7", + "@rollup/rollup-darwin-arm64": "4.64.7", + "@rollup/rollup-darwin-x64": "4.64.7", + "@rollup/rollup-freebsd-arm64": "4.64.7", + "@rollup/rollup-freebsd-x64": "4.64.7", + "@rollup/rollup-linux-arm-gnueabihf": "4.64.7", + "@rollup/rollup-linux-arm-musleabihf": "4.64.7", + "@rollup/rollup-linux-arm64-gnu": "4.64.7", + "@rollup/rollup-linux-arm64-musl": "4.64.7", + "@rollup/rollup-linux-loong64-gnu": "4.64.7", + "@rollup/rollup-linux-loong64-musl": "4.64.7", + "@rollup/rollup-linux-ppc64-gnu": "4.64.7", + "@rollup/rollup-linux-ppc64-musl": "4.64.7", + "@rollup/rollup-linux-riscv64-gnu": "4.64.7", + "@rollup/rollup-linux-riscv64-musl": "4.64.7", + "@rollup/rollup-linux-s390x-gnu": "4.64.7", + "@rollup/rollup-linux-x64-gnu": "4.64.7", + "@rollup/rollup-linux-x64-musl": "4.64.7", + "@rollup/rollup-openbsd-x64": "4.64.7", + "@rollup/rollup-openharmony-arm64": "4.64.7", + "@rollup/rollup-win32-arm64-msvc": "4.64.7", + "@rollup/rollup-win32-ia32-msvc": "4.64.7", + "@rollup/rollup-win32-x64-gnu": "4.64.7", + "@rollup/rollup-win32-x64-msvc": "4.64.7", + "fsevents": "~2.3.2" + } + }, + "node_modules/safe-array-concat": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.4.tgz", + "integrity": "sha512-wtZlHyOje6OZTGqAoaDKxFkgRtkF9CnHAVnCHKfuj200wAgL+bSJhdsCD2l0Qx/2ekEXjPWcyKkfGb5CPboslg==", + "dependencies": { + "call-bind": "^1.0.9", + "call-bound": "^1.0.4", + "get-intrinsic": "^1.3.0", + "has-symbols": "^1.1.0", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">=0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-array-concat/node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==" + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/safe-push-apply": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/safe-push-apply/-/safe-push-apply-1.0.0.tgz", + "integrity": "sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==", + "dependencies": { + "es-errors": "^1.3.0", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-push-apply/node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==" + }, + "node_modules/safe-regex-test": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", + "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-regex": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/sanitize-filename": { + "version": "1.6.4", + "resolved": "https://registry.npmjs.org/sanitize-filename/-/sanitize-filename-1.6.4.tgz", + "integrity": "sha512-9ZyI08PsvdQl2r/bBIGubpVdR3RR9sY6RDiWFPreA21C/EFlQhmgo20UZlNjZMMZNubusLhAQozkA0Od5J21Eg==", + "dev": true, + "dependencies": { + "truncate-utf8-bytes": "^1.0.0" + } + }, + "node_modules/sax": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.6.1.tgz", + "integrity": "sha512-42tBVwLWnaQvW5zc4HbZrTuWccECCZfBi92FDuwtqxasH+JbPB3/FOKb1m222K42R4WxuxzzMsTswfzgtSu64Q==", + "dev": true, + "engines": { + "node": ">=11.0.0" + } + }, + "node_modules/saxes": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-5.0.1.tgz", + "integrity": "sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw==", + "dependencies": { + "xmlchars": "^2.2.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/semver-compare": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/semver-compare/-/semver-compare-1.0.0.tgz", + "integrity": "sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow==", + "dev": true, + "optional": true + }, + "node_modules/serialize-error": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-7.0.1.tgz", + "integrity": "sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw==", + "dev": true, + "optional": true, + "dependencies": { + "type-fest": "^0.13.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-function-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", + "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-proto": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/set-proto/-/set-proto-1.0.0.tgz", + "integrity": "sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/setimmediate": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", + "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==" + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/side-channel": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.1.tgz", + "integrity": "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.4", + "side-channel-list": "^1.0.1", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz", + "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true + }, + "node_modules/simple-update-notifier": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", + "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==", + "dev": true, + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-js": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.2.tgz", + "integrity": "sha512-KGj/8Y43x35aZVDtt+J4mK1hoLGHULMYfSkODJNQjNDC3oW1PqPoxMwo0pLUsWM/UEGzON/NxeHywEfNXNP3Vw==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/sprintf-js": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz", + "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==", + "dev": true, + "optional": true + }, + "node_modules/sql.js": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/sql.js/-/sql.js-1.15.0.tgz", + "integrity": "sha512-54VT5h9/VGlLaQan5pX/Zx8k6Vbykwl5hRUtl4h4+VYnaZCTraUs4P1i2WLz21O6oPVp6Gvg+d1OyPo01ZG5ig==" + }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true + }, + "node_modules/stat-mode": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/stat-mode/-/stat-mode-1.0.0.tgz", + "integrity": "sha512-jH9EhtKIjuXZ2cWxmXS8ZP80XyC3iasQxMDV8jzhNJpfDb7VbQLVW4Wvsxz9QZvzV+G4YoSfBUVKDOyxLzi/sg==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/std-env": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz", + "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==", + "dev": true + }, + "node_modules/stop-iteration-iterator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz", + "integrity": "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==", + "dependencies": { + "es-errors": "^1.3.0", + "internal-slot": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string.prototype.trim": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.11.tgz", + "integrity": "sha512-PwvK7BU+CMTJGYQCTZb5RWXIML92lftJLhQz1tBzgKiqGxJaMlBAa48POXaNAC2s4y8jr3EFqrkF9+44neS46w==", + "dependencies": { + "call-bind": "^1.0.9", + "call-bound": "^1.0.4", + "define-data-property": "^1.1.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.24.2", + "es-object-atoms": "^1.1.2", + "has-property-descriptors": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimend": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.10.tgz", + "integrity": "sha512-2+3aDAOmPTmuFwjDnmJG2ctEkQKVki7vOSqaxkv42Mowj1V6PnvuwFCRrR5lChUux1TBskPjfkeTOhqczDMxTw==", + "dependencies": { + "call-bind": "^1.0.9", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimstart": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz", + "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-literal": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-3.1.0.tgz", + "integrity": "sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg==", + "dev": true, + "dependencies": { + "js-tokens": "^9.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/sumchecker": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/sumchecker/-/sumchecker-3.0.1.tgz", + "integrity": "sha512-MvjXzkz/BOfyVDkG0oFOtBxHX2u3gKbMHIF/dXblZsgD3BWOFLmHovIpZY7BykJdAjcqRCBi1WYBNdEC9yI7vg==", + "dev": true, + "dependencies": { + "debug": "^4.1.0" + }, + "engines": { + "node": ">= 8.0" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tar": { + "version": "7.5.22", + "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.22.tgz", + "integrity": "sha512-MFO/QzvtAOmJbkhOaCTvbGcFN9L9b+JunIsDwaKljSOdcLMea3NJ1k9Usz/rjdfSXTq4dfzfeS7W4p4YOAAHeA==", + "dev": true, + "dependencies": { + "@isaacs/fs-minipass": "^4.0.0", + "chownr": "^3.0.0", + "minipass": "^7.1.2", + "minizlib": "^3.1.0", + "yallist": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "dependencies": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/tar/node_modules/yallist": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", + "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", + "dev": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/temp": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/temp/-/temp-0.9.4.tgz", + "integrity": "sha512-yYrrsWnrXMcdsnu/7YMYAofM1ktpL5By7vZhf15CrXijWWrEYZks5AXBudalfSWJLlnen/QUJUB5aoB0kqZUGA==", + "dev": true, + "peer": true, + "dependencies": { + "mkdirp": "^0.5.1", + "rimraf": "~2.6.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/temp-file": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/temp-file/-/temp-file-3.4.0.tgz", + "integrity": "sha512-C5tjlC/HCtVUOi3KWVokd4vHVViOmGjtLwIh4MuzPo/nMYTV/p1urt3RnMz2IWXDdKEGJH3k5+KPxtqRsUYGtg==", + "dev": true, + "dependencies": { + "async-exit-hook": "^2.0.1", + "fs-extra": "^10.0.0" + } + }, + "node_modules/tiny-async-pool": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/tiny-async-pool/-/tiny-async-pool-1.3.0.tgz", + "integrity": "sha512-01EAw5EDrcVrdgyCLgoSPvqznC0sVxDSVeiOz09FUpjh71G79VCqneOr+xvt7T1r76CF6ZZfPjHorN2+d+3mqA==", + "dev": true, + "dependencies": { + "semver": "^5.5.0" + } + }, + "node_modules/tiny-async-pool/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "dev": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true + }, + "node_modules/tinyexec": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz", + "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==", + "dev": true + }, + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "dev": true, + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinypool": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.1.1.tgz", + "integrity": "sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==", + "dev": true, + "engines": { + "node": "^18.0.0 || >=20.0.0" + } + }, + "node_modules/tinyrainbow": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-2.0.0.tgz", + "integrity": "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==", + "dev": true, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tinyspy": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-4.0.6.tgz", + "integrity": "sha512-u8KszXvGfU68hVcZpRHKG28T0krMuv2G5nDhiHaMLen/gIuFEgIJhaJuO69qjnXg5paSrbPMFfx3brNuN8eVSg==", + "dev": true, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tmp": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.7.tgz", + "integrity": "sha512-e0votIpp4Uo2AJYSzVHV6xCcawuiez3DzqDAbrTc3YxBkplN6e+dM13ZeIcZnDg/QpSuU2zfZ3rzwY8ukEnaXw==", + "engines": { + "node": ">=14.14" + } + }, + "node_modules/tmp-promise": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/tmp-promise/-/tmp-promise-3.0.3.tgz", + "integrity": "sha512-RwM7MoPojPxsOBYnyd2hy0bxtIlVrihNs9pj5SUvY8Zz1sQcQG2tG1hSr8PDxfgEB8RNKDhqbIlroIarSNDNsQ==", + "dev": true, + "dependencies": { + "tmp": "^0.2.0" + } + }, + "node_modules/traverse": { + "version": "0.3.10", + "resolved": "https://registry.npmjs.org/traverse/-/traverse-0.3.10.tgz", + "integrity": "sha512-pd6fq5qmiFuPA3TpdqBCqJXlOrSdCSt0l9IZDg83Vbf4x3m/X18prEwMMVM5LXp9vGL9NxjeiDGBrTasqyngXQ==", + "dependencies": { + "define-data-property": "^1.1.4", + "es-to-primitive": "^1.3.4", + "gopd": "^1.2.0", + "typedarray.prototype.slice": "^1.0.6", + "which-typed-array": "^1.1.24" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/truncate-utf8-bytes": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/truncate-utf8-bytes/-/truncate-utf8-bytes-1.0.2.tgz", + "integrity": "sha512-95Pu1QXQvruGEhv62XCMO3Mm90GscOCClvrIUwCM0PYOXK3kaF3l3sIHxx71ThJfcbM2O5Au6SO3AWCSEfW4mQ==", + "dev": true, + "dependencies": { + "utf8-byte-length": "^1.0.1" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + }, + "node_modules/type-fest": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.13.1.tgz", + "integrity": "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==", + "dev": true, + "optional": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typed-array-buffer": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", + "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/typed-array-byte-length": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.3.tgz", + "integrity": "sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==", + "dependencies": { + "call-bind": "^1.0.8", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-byte-offset": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.5.tgz", + "integrity": "sha512-0FHJvLPqZ7KJzp17O13jfsAjsqazgrxBu2zEK95PmUz8lv2+GjRuxUInCr2Rk9Dms3ihN21zJ929ZO43yJ95QQ==", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.9", + "for-each": "^0.3.5", + "gopd": "^1.2.0", + "is-typed-array": "^1.1.15", + "reflect.getprototypeof": "^1.0.10" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-length": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.8.tgz", + "integrity": "sha512-phPGCwqr2+Qo0fwniCE8e4pKnGu/yFb5nD5Y8bf0EEeiI5GklnACYA9GFy/DrAeRrKHXvHn+1SUsOWgJp6RO+g==", + "dependencies": { + "call-bind": "^1.0.9", + "for-each": "^0.3.5", + "gopd": "^1.2.0", + "is-typed-array": "^1.1.15", + "possible-typed-array-names": "^1.1.0", + "reflect.getprototypeof": "^1.0.10" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typedarray.prototype.slice": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/typedarray.prototype.slice/-/typedarray.prototype.slice-1.0.6.tgz", + "integrity": "sha512-r+7YvGfM5TkTiDQ5EqWi/Z4L4KP3jUI+YCmTtWg1tmTHUw6xladLJqzR2heMaHBIM0TTFLZmn/EkIaEHYs/bIA==", + "dependencies": { + "call-bind": "^1.0.9", + "define-properties": "^1.2.1", + "es-abstract": "^1.24.2", + "es-errors": "^1.3.0", + "get-proto": "^1.0.1", + "math-intrinsics": "^1.1.0", + "typed-array-buffer": "^1.0.3", + "typed-array-byte-offset": "^1.0.5" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "devOptional": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/unbox-primitive": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz", + "integrity": "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==", + "dependencies": { + "call-bound": "^1.0.3", + "has-bigints": "^1.0.2", + "has-symbols": "^1.1.0", + "which-boxed-primitive": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/undici": { + "version": "7.30.1", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.30.1.tgz", + "integrity": "sha512-03KrMbjIFDWQR5RNPrSw3bd1r12JpyFQl9/yxjUbJtqkjmllqC5lFjZh6ujeuJCKr8EOQvRW872Oy/az+nLYxg==", + "dev": true, + "optional": true, + "engines": { + "node": ">=20.18.1" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true + }, + "node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/unzipper": { + "version": "0.12.5", + "resolved": "https://registry.npmjs.org/unzipper/-/unzipper-0.12.5.tgz", + "integrity": "sha512-tXYOi9R57Uj/2Z25SOs5RRSzq886MBQj2gY8dPL+xl/kv6s6SvByoKfAtvfVeEuhntWDgjd2o9p2lb4TVPAz0A==", + "dev": true, + "dependencies": { + "bluebird": "~3.7.2", + "duplexer2": "~0.1.4", + "fs-extra": "11.3.1", + "graceful-fs": "^4.2.2", + "node-int64": "^0.4.0" + } + }, + "node_modules/unzipper/node_modules/fs-extra": { + "version": "11.3.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.1.tgz", + "integrity": "sha512-eXvGGwZ5CL17ZSwHWd3bbgk7UUpF6IFHtP57NYYakPvHOs8GDgDe5KJI36jIJzDkJ6eJjuzRA8eBQb6SkKue0g==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/utf8-byte-length": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/utf8-byte-length/-/utf8-byte-length-1.0.5.tgz", + "integrity": "sha512-Xn0w3MtiQ6zoz2vFyUVruaCL53O/DwUvkEeOvj+uulMm0BkUGYWmBYVyElqZaSLhY6ZD0ulfU3aBra2aVT4xfA==", + "dev": true + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" + }, + "node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "deprecated": "uuid@10 and below is no longer supported. For ESM codebases, update to uuid@latest. For CommonJS codebases, use uuid@11 (but be aware this version will likely be deprecated in 2028).", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/vite": { + "version": "7.3.7", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.7.tgz", + "integrity": "sha512-AHEzTORMbi7/7aXp7w7KZ5+DCUdSeLsJz0grY8xHpc9DHdYb0vQu6DqJfm2tOw3gDExNgPgnJTXxM6syYFHaCA==", + "dev": true, + "dependencies": { + "esbuild": "^0.27.0 || ^0.28.0", + "fdir": "^6.5.0", + "picomatch": "^4.0.3", + "postcss": "^8.5.6", + "rollup": "^4.43.0", + "tinyglobby": "^0.2.15" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "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 + } + } + }, + "node_modules/vite-node": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.2.4.tgz", + "integrity": "sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==", + "dev": true, + "dependencies": { + "cac": "^6.7.14", + "debug": "^4.4.1", + "es-module-lexer": "^1.7.0", + "pathe": "^2.0.3", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" + }, + "bin": { + "vite-node": "vite-node.mjs" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/vite/node_modules/@esbuild/win32-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.2.tgz", + "integrity": "sha512-5ebpxr3nWMzrL/rnUI755Jkuee0bHL/Gq0WTF9lvcpv73wAp5eu8MfBUgWK9bhWvZjj7yX8etf/8tI8Ney695g==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/esbuild": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.2.tgz", + "integrity": "sha512-HKVLS8dvII+xoKW9kmqxbRKrnWEXfJJr/FZhhJmiqIB0e053QNYFqOBouTMO/k5sID4MvCiUCvv8b9M4h32wIA==", + "dev": true, + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.28.2", + "@esbuild/android-arm": "0.28.2", + "@esbuild/android-arm64": "0.28.2", + "@esbuild/android-x64": "0.28.2", + "@esbuild/darwin-arm64": "0.28.2", + "@esbuild/darwin-x64": "0.28.2", + "@esbuild/freebsd-arm64": "0.28.2", + "@esbuild/freebsd-x64": "0.28.2", + "@esbuild/linux-arm": "0.28.2", + "@esbuild/linux-arm64": "0.28.2", + "@esbuild/linux-ia32": "0.28.2", + "@esbuild/linux-loong64": "0.28.2", + "@esbuild/linux-mips64el": "0.28.2", + "@esbuild/linux-ppc64": "0.28.2", + "@esbuild/linux-riscv64": "0.28.2", + "@esbuild/linux-s390x": "0.28.2", + "@esbuild/linux-x64": "0.28.2", + "@esbuild/netbsd-arm64": "0.28.2", + "@esbuild/netbsd-x64": "0.28.2", + "@esbuild/openbsd-arm64": "0.28.2", + "@esbuild/openbsd-x64": "0.28.2", + "@esbuild/openharmony-arm64": "0.28.2", + "@esbuild/sunos-x64": "0.28.2", + "@esbuild/win32-arm64": "0.28.2", + "@esbuild/win32-ia32": "0.28.2", + "@esbuild/win32-x64": "0.28.2" + } + }, + "node_modules/vitest": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.2.7.tgz", + "integrity": "sha512-KrxIJ62Fd89gfysR4WotlgZABiz2dqFPgqGzX7s+CwsqLFomRH7777ZcrOD6+WVAh7khPQP41A+BKbpcJFrdEg==", + "dev": true, + "dependencies": { + "@types/chai": "^5.2.2", + "@vitest/expect": "3.2.7", + "@vitest/mocker": "3.2.7", + "@vitest/pretty-format": "^3.2.7", + "@vitest/runner": "3.2.7", + "@vitest/snapshot": "3.2.7", + "@vitest/spy": "3.2.7", + "@vitest/utils": "3.2.7", + "chai": "^5.2.0", + "debug": "^4.4.1", + "expect-type": "^1.2.1", + "magic-string": "^0.30.17", + "pathe": "^2.0.3", + "picomatch": "^4.0.2", + "std-env": "^3.9.0", + "tinybench": "^2.9.0", + "tinyexec": "^0.3.2", + "tinyglobby": "^0.2.14", + "tinypool": "^1.1.1", + "tinyrainbow": "^2.0.0", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0", + "vite-node": "3.2.4", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@types/debug": "^4.1.12", + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "@vitest/browser": "3.2.7", + "@vitest/ui": "3.2.7", + "happy-dom": "*", + "jsdom": "*" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@types/debug": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + } + } + }, + "node_modules/vscode-uri": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.2.0.tgz", + "integrity": "sha512-m2gXo3bn0G1kT9InzMf07fTbqMbGtyckj3bH5ktLO+1Ssv+yiATZ4dhwaQv9UZWxJh6E9IFGnQyjgWVDWVBDrg==", + "dev": true + }, + "node_modules/vue": { + "version": "3.5.44", + "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.44.tgz", + "integrity": "sha512-dvvmaOCG7lAMkJTPqPwWeGMsPS6DIsFsfZDUgBp9V6mHf9b3+/wyVwYR4Es+WV6uJC0FKRxp81Obcq0ZwUcGFQ==", + "dependencies": { + "@vue/compiler-dom": "3.5.44", + "@vue/compiler-sfc": "3.5.44", + "@vue/runtime-dom": "3.5.44", + "@vue/server-renderer": "3.5.44", + "@vue/shared": "3.5.44" + }, + "peerDependencies": { + "typescript": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/vue-tsc": { + "version": "3.3.13", + "resolved": "https://registry.npmjs.org/vue-tsc/-/vue-tsc-3.3.13.tgz", + "integrity": "sha512-jE4/bXdOMSHVxEGgQCqNllJy+9N3Gx6tfSAsg0yhFK/MAN6eXqISihupnDEKSCt12VVnjN2gI3AqLYcqoh5x/A==", + "dev": true, + "dependencies": { + "@volar/typescript": "2.4.28", + "@vue/language-core": "3.3.13" + }, + "bin": { + "vue-tsc": "bin/vue-tsc.js" + }, + "peerDependencies": { + "typescript": ">=5.0.0" + } + }, + "node_modules/webcrypto-core": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/webcrypto-core/-/webcrypto-core-1.9.2.tgz", + "integrity": "sha512-gsXecm82UQNlTBURJGuqOWy1Ww08S3kZUcr3aOJS02Pk0xLtkfeUAVC0u0xhgdonFme80edSJUIJyuvL/7250Q==", + "dev": true, + "dependencies": { + "@peculiar/asn1-schema": "^2.7.0", + "@peculiar/json-schema": "^1.1.12", + "@peculiar/utils": "^2.0.2", + "asn1js": "^3.0.10", + "tslib": "^2.8.1" + } + }, + "node_modules/which": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-5.0.0.tgz", + "integrity": "sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ==", + "dev": true, + "dependencies": { + "isexe": "^3.1.1" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/which-boxed-primitive": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz", + "integrity": "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==", + "dependencies": { + "is-bigint": "^1.1.0", + "is-boolean-object": "^1.2.1", + "is-number-object": "^1.1.1", + "is-string": "^1.1.1", + "is-symbol": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-builtin-type": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.2.1.tgz", + "integrity": "sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==", + "dependencies": { + "call-bound": "^1.0.2", + "function.prototype.name": "^1.1.6", + "has-tostringtag": "^1.0.2", + "is-async-function": "^2.0.0", + "is-date-object": "^1.1.0", + "is-finalizationregistry": "^1.1.0", + "is-generator-function": "^1.0.10", + "is-regex": "^1.2.1", + "is-weakref": "^1.0.2", + "isarray": "^2.0.5", + "which-boxed-primitive": "^1.1.0", + "which-collection": "^1.0.2", + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-builtin-type/node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==" + }, + "node_modules/which-collection": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz", + "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==", + "dependencies": { + "is-map": "^2.0.3", + "is-set": "^2.0.3", + "is-weakmap": "^2.0.2", + "is-weakset": "^2.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-typed-array": { + "version": "1.1.24", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.24.tgz", + "integrity": "sha512-wk4Mf4pR5mRP7eYuuTBCIQ9d0ud2Fv2jRLQpfgnRjbOxAFHmjKFValgTpitVKzJJS8ajnYQV2Du1SZ8j6b/EUQ==", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.9", + "call-bound": "^1.0.4", + "for-each": "^0.3.5", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" + }, + "node_modules/xmlbuilder": { + "version": "15.1.1", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-15.1.1.tgz", + "integrity": "sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg==", + "dev": true, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==" + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/yargs": { + "version": "17.7.3", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.3.tgz", + "integrity": "sha512-GZtjxm/J/4TSxuL3FNYjCmLktBTnIw/rVmKSIyKeYAZpmJB2ig9VauCC5xsa82GNKVKDAqpOn3KVzNt0zmrU0g==", + "dev": true, + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zip-stream": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-4.1.1.tgz", + "integrity": "sha512-9qv4rlDiopXg4E69k+vMHjNN63YFMe9sZMrdlvKnCjlCRWeCBswPPMPUfx+ipsAWq1LXHe70RcbaHdJJpS6hyQ==", + "dependencies": { + "archiver-utils": "^3.0.4", + "compress-commons": "^4.1.2", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/zip-stream/node_modules/archiver-utils": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-3.0.4.tgz", + "integrity": "sha512-KVgf4XQVrTjhyWmx6cte4RxonPLR9onExufI1jhvw/MQ4BB6IsZD5gT8Lq+u/+pRkWna/6JoHpiQioaqFP5Rzw==", + "dependencies": { + "glob": "^7.2.3", + "graceful-fs": "^4.2.0", + "lazystream": "^1.0.0", + "lodash.defaults": "^4.2.0", + "lodash.difference": "^4.5.0", + "lodash.flatten": "^4.4.0", + "lodash.isplainobject": "^4.0.6", + "lodash.union": "^4.6.0", + "normalize-path": "^3.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/zod": { + "version": "4.6.7", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.6.7.tgz", + "integrity": "sha512-g8W2D2kU6a8untc053fX1uVBKzM7yqAxW/3zwk83+6RyF2+SKOuV+c0wBDhUwzV1PaGapmZH+rmcUzPf+3CGwg==", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..4d9637e --- /dev/null +++ b/package.json @@ -0,0 +1,84 @@ +{ + "name": "receipt-studio", + "version": "1.0.1", + "private": true, + "description": "离线收据生成与批量打印工具", + "author": "Receipt Studio", + "license": "UNLICENSED", + "main": "dist/main/index.cjs", + "scripts": { + "dev": "node scripts/dev.mjs", + "typecheck": "vue-tsc --noEmit && tsc -p tsconfig.main.json --noEmit", + "build": "npm run typecheck && vite build && node scripts/build-main.mjs", + "test": "vitest run", + "test:desktop": "node scripts/desktop-test.mjs", + "test:loading": "node scripts/loading-test.mjs", + "test:history": "node scripts/history-pagination-test.mjs", + "test:print": "node scripts/print-batch-test.mjs", + "start": "electron .", + "package": "npm run build && electron-builder --win nsis --x64", + "package:dir": "npm run build && electron-builder --win dir --x64", + "test:package": "node scripts/package-test.mjs" + }, + "dependencies": { + "@lucide/vue": "^1.58.0", + "exceljs": "^4.4.0", + "pdf-lib": "^1.17.1", + "pdfjs-dist": "^5.4.149", + "reka-ui": "^2.11.0", + "sql.js": "^1.13.0", + "vue": "^3.5.22", + "zod": "^4.1.12" + }, + "devDependencies": { + "@playwright/test": "^1.56.0", + "@types/node": "^22.18.10", + "@types/sql.js": "^1.4.9", + "@vitejs/plugin-vue": "^6.0.1", + "electron": "^42.0.0", + "electron-builder": "^26.0.12", + "esbuild": "^0.25.11", + "pngjs": "^7.0.0", + "typescript": "^5.9.3", + "vite": "^7.1.10", + "vitest": "^3.2.4", + "vue-tsc": "^3.1.1" + }, + "build": { + "appId": "cn.receiptstudio.desktop", + "productName": "收据工坊", + "directories": { + "output": "release" + }, + "files": [ + "dist/**/*", + "package.json", + "!node_modules/**/*" + ], + "asar": true, + "win": { + "target": [ + { + "target": "nsis", + "arch": [ + "x64" + ] + } + ], + "icon": "build/icon.ico", + "signExecutable": false + }, + "nsis": { + "oneClick": false, + "perMachine": false, + "allowToChangeInstallationDirectory": true, + "createDesktopShortcut": true, + "deleteAppDataOnUninstall": false, + "artifactName": "ReceiptStudio-${version}-win-x64-setup.${ext}" + }, + "electronDist": "node_modules/electron/dist" + }, + "engines": { + "node": ">=22.13.0" + } +} diff --git a/scripts/build-main.mjs b/scripts/build-main.mjs new file mode 100644 index 0000000..bc4bd2c --- /dev/null +++ b/scripts/build-main.mjs @@ -0,0 +1,4 @@ +import { build } from 'esbuild' +import './make-icon.mjs' +await build({ entryPoints: ['src/main/index.ts'], outfile: 'dist/main/index.cjs', bundle: true, platform: 'node', target: 'node22', format: 'cjs', external: ['electron'], sourcemap: true }) +await build({ entryPoints: ['src/main/preload.ts'], outfile: 'dist/main/preload.cjs', bundle: true, platform: 'node', target: 'node22', format: 'cjs', external: ['electron'] }) diff --git a/scripts/desktop-test.mjs b/scripts/desktop-test.mjs new file mode 100644 index 0000000..b301621 --- /dev/null +++ b/scripts/desktop-test.mjs @@ -0,0 +1,165 @@ +import { _electron as electron,expect } from '@playwright/test' +import electronPath from 'electron' +import ExcelJS from 'exceljs' +import { PNG } from 'pngjs' +import { PDFDocument } from 'pdf-lib' +import fs from 'node:fs/promises' +import path from 'node:path' +const root=process.cwd(),qa=path.join(root,'.qa'),profile=path.join(qa,'profile'),fixtures=path.join(qa,'fixtures'),output=path.join(qa,'output'),seals=path.join(fixtures,'seals') +if(!profile.startsWith(root+path.sep))throw Error('Invalid QA path') +await fs.rm(profile,{recursive:true,force:true}) +await fs.mkdir(profile,{recursive:true}) +await fs.writeFile(path.join(profile,'settings.json'),JSON.stringify({createBatchFolder:false,openAfterExport:false})) +await fs.mkdir(seals,{recursive:true});await fs.mkdir(output,{recursive:true}) +const image=new PNG({width:240,height:240}) +for(let y=0;y<240;y++)for(let x=0;x<240;x++){const i=(y*240+x)*4,d=Math.hypot(x-120,y-120),red=(d>99&&d<106)||(d>88&&d<91)||(x>65&&x<175&&y>105&&y<133);image.data[i]=195;image.data[i+1]=30;image.data[i+2]=35;image.data[i+3]=red?255:0} +await fs.writeFile(path.join(seals,'测试印章.png'),PNG.sync.write(image)) +const book=new ExcelJS.Workbook(),sheet=book.addWorksheet('收据数据') +sheet.addRow(['收款日期','学校','年级','姓名','收费项目','金额','出纳','复核']) +sheet.addRow(['2026-10-16','某某学校','初二年级','张三','秋季学期服务费',3500]) +sheet.addRow(['2026-10-16','某某学校','高2028届','','资料费',12.34]) +sheet.addRow(['2026-10-17','较长名称测试:某某学校课程服务中心','高三年级','李四','课程服务项目及相关资料费用',10001.05]) +sheet.addRow(['2026-02-30','某某学校','高2028届','无效行','服务费','12.345']) +const excelFile=path.join(fixtures,'收据测试数据.xlsx');await book.xlsx.writeFile(excelFile) +const env={...process.env};delete env.ELECTRON_RUN_AS_NODE +const errors=[] +let desktop +async function launch(){ + desktop=await electron.launch({executablePath:electronPath,args:['.','--test-mode'],cwd:root,env,timeout:60000}) + desktop.process().stderr.on('data',data=>{const text=data.toString();if(!text.includes('Autofill'))process.stderr.write(text)}) + const page=await desktop.firstWindow();page.setDefaultTimeout(20000) + page.on('pageerror',e=>errors.push(e.message)) + await expect(page.getByRole('heading',{name:'收据工作台',exact:true})).toBeVisible({timeout:30000}) + await expect(page.getByText('把表格交给收据工坊')).toBeVisible({timeout:30000}) + return page +} +try{ + const page=await launch() + await desktop.evaluate(({dialog,shell},p)=>{ + globalThis.openedDirectories=[] + shell.openPath=async folder=>{globalThis.openedDirectories.push(folder);return ''} + dialog.showOpenDialog=async(_window,options)=>({canceled:false,filePaths:[options.title==='选择印章文件夹'?p.seals:options.title==='选择导出文件夹'?p.output:p.excelFile]}) + dialog.showSaveDialog=async()=>({canceled:false,filePath:p.template}) + dialog.showMessageBox=async()=>({response:1,checkboxChecked:false}) + },{seals,output,excelFile,template:path.join(fixtures,'模板.xlsx')}) + await page.screenshot({path:path.join(qa,'01-workspace.png')}) + await page.getByRole('button',{name:'下载模板',exact:true}).click() + await expect.poll(async()=>!!(await fs.stat(path.join(fixtures,'模板.xlsx')).catch(()=>null))).toBe(true) + await page.getByRole('button',{name:'偏好设置',exact:true}).click() + await expect(page.getByRole('button',{name:'更多偏好与数据备份'})).toHaveCount(0) + await expect(page.getByRole('button',{name:'备份数据'})).toHaveCount(0) + await expect(page.getByRole('button',{name:'恢复备份'})).toHaveCount(0) + await expect(page.getByRole('switch',{name:'每次导出建立独立批次文件夹'})).toHaveCount(0) + await expect(page.getByRole('switch',{name:'生成完成后自动打开输出目录'})).toHaveCount(0) + await expect(page.getByLabel('导出的文件叫什么名字')).toBeVisible() + expect(await page.evaluate(()=>({backup:typeof window.receipts.backup,restore:typeof window.receipts.restore}))).toEqual({backup:'undefined',restore:'undefined'}) + expect((await page.evaluate(()=>window.receipts.bootstrap())).settings).toMatchObject({createBatchFolder:true,openAfterExport:true}) + await page.locator('[data-task="settings-folder"][data-task-target="output"]').click() + await page.locator('[data-task="settings-folder"][data-task-target="seal"]').click() + await expect(page.locator('[data-region="settings"]')).not.toHaveAttribute('aria-busy','true') + await page.getByLabel('默认出纳',{exact:true}).fill('李出纳') + await page.getByLabel('默认复核人',{exact:true}).fill('王复核') + await page.getByLabel('编号前缀',{exact:true}).fill('QA') + await page.getByLabel('导出的文件叫什么名字').selectOption('number') + await expect(page.locator('.filename-example')).toContainText('QA00000001.pdf') + await page.getByLabel('导出的文件叫什么名字').selectOption('customer_number') + await expect(page.locator('.filename-example')).toContainText('巴南中学 高2028届 张三_QA00000001.pdf') + await page.getByLabel('导出的文件叫什么名字').selectOption('number_customer') + await expect(page.locator('.filename-example')).toContainText('QA00000001_巴南中学 高2028届 张三.pdf') + await page.getByRole('button',{name:'保存偏好设置',exact:true}).click() + await expect(page.locator('[data-region="settings"]')).not.toHaveAttribute('aria-busy','true') + await expect(page.locator('.main-content > .feedback')).toHaveCount(0) + await page.screenshot({path:path.join(qa,'02-settings.png'),fullPage:true}) + await page.getByRole('button',{name:'印章管理',exact:true}).click() + await page.getByRole('button',{name:/测试印章/}).click() + await page.getByRole('button',{name:'收据工作台',exact:true}).click() + await page.getByRole('button',{name:'导入 Excel',exact:true}).click() + await expect(page.getByText('已选择 3 条',{exact:true})).toBeVisible() + await expect(page.getByText('待修改',{exact:true})).toBeVisible() + const group=page.getByRole('checkbox',{name:'选择分组:某某学校 / 初二年级 / 秋季学期服务费',exact:true}) + await group.uncheck();await expect(page.getByText('已选择 2 条',{exact:true})).toBeVisible() + await expect(page.getByRole('checkbox',{name:'选择分组:某某学校',exact:true})).toHaveJSProperty('indeterminate',true) + await group.check();await expect(page.getByText('已选择 3 条',{exact:true})).toBeVisible() + await expect(page.getByRole('checkbox',{name:'选择分组:某某学校 / 高2028届 / 服务费',exact:true})).toBeDisabled() + await page.getByLabel('单张 PNG',{exact:true}).check() + await page.frameLocator('iframe').getByText('李出纳',{exact:false}).waitFor() + await expect(page.frameLocator('iframe').getByText('某某学校 初二年级 张三',{exact:true})).toBeVisible() + await page.getByRole('button',{name:'A4 打印排版',exact:true}).click() + await expect(page.frameLocator('iframe').getByText('某某学校 初二年级 张三',{exact:true})).toBeVisible() + await expect(page.frameLocator('iframe').getByText('某某学校 高2028届 ',{exact:true})).toHaveCount(0) + await page.getByRole('button',{name:'单张收据',exact:true}).click() + await page.screenshot({path:path.join(qa,'03-import-preview.png'),fullPage:true}) + await page.getByRole('button',{name:/生成 3 张收据/}).click() + await expect.poll(async()=>page.evaluate(async()=> (await window.receipts.listBatches())[0]?.status),{timeout:120000,intervals:[500,1000]}).toBe('completed') + const batches=await page.evaluate(()=>window.receipts.listBatches()),batch=batches[0] + await expect.poll(()=>desktop.evaluate(()=>globalThis.openedDirectories)).toContain(batch.outputDirectory) + expect(batch.count).toBe(3);expect(batch.firstNumber).toBe('QA00000001');expect(batch.lastNumber).toBe('QA00000003') + const files=await fs.readdir(batch.outputDirectory,{recursive:true}) + expect(files.filter(f=>f.endsWith('.pdf'))).toHaveLength(6);expect(files.filter(f=>f.endsWith('.png'))).toHaveLength(3) + const printFiles=files.filter(f=>path.basename(f).startsWith('A4打印版')) + expect(printFiles).toHaveLength(3) + const printFile=printFiles.find(f=>f.includes('秋季学期服务费')) + const pdf=await PDFDocument.load(await fs.readFile(path.join(batch.outputDirectory,printFile))) + expect(pdf.getPageCount()).toBe(1) + expect(Math.abs(pdf.getPage(0).getWidth()-595.28)).toBeLessThan(1) + const singleFile=files.find(f=>path.basename(f).startsWith('QA00000001')&&f.endsWith('.pdf')) + expect(path.dirname(singleFile)).toBe(path.join('某某学校','初二年级','秋季学期服务费')) + expect((await PDFDocument.load(await fs.readFile(path.join(batch.outputDirectory,singleFile)))).getPageCount()).toBe(1) + const pngFile=files.find(f=>path.basename(f).startsWith('QA00000001')&&f.endsWith('.png')) + const raster=PNG.sync.read(await fs.readFile(path.join(batch.outputDirectory,pngFile))) + expect(raster.width).toBeGreaterThan(2200);expect(raster.height).toBeGreaterThan(1600) + await fs.copyFile(path.join(batch.outputDirectory,pngFile),path.join(qa,'04-receipt-300dpi.png')) + await page.getByLabel('单张 PNG',{exact:true}).uncheck();await page.getByLabel('A4 打印版',{exact:true}).uncheck() + await page.getByRole('button',{name:/生成记录/}).click() + await expect(page.getByRole('heading',{name:'最近生成的批次',exact:false})).toBeVisible() + await expect(page.locator('.history-category')).toContainText('某某学校') + await page.getByLabel('按学校查找记录').selectOption('某某学校') + await page.getByLabel('按年级查找记录').selectOption('初二年级') + await page.getByLabel('按收费项目查找记录').selectOption('资料费') + await expect(page.locator('.history-table tbody tr')).toHaveCount(0) + await page.getByRole('button',{name:'清空筛选',exact:true}).click() + await page.getByLabel('搜索生成记录').fill('QA00000002') + await expect(page.locator('.history-match')).toContainText('QA00000002') + await page.getByRole('button',{name:'查看原收据',exact:true}).click() + await expect(page.locator('.modal-card iframe')).toBeVisible() + await expect(page.frameLocator('.modal-card iframe').getByText('QA00000002',{exact:false})).toBeVisible() + await page.getByRole('button',{name:'关闭原收据预览',exact:true}).click() + await expect(page.locator('[data-region="history"]')).not.toHaveAttribute('aria-busy','true') + await page.getByLabel('搜索生成记录').fill('李四') + await expect(page.locator('.history-match')).toContainText('QA00000003') + await page.screenshot({path:path.join(qa,'05-history-filter.png'),fullPage:true}) + await page.getByRole('button',{name:'清空筛选',exact:true}).click() + await expect(page.locator('.history-table tbody tr')).toHaveCount(1) + const originalDirectory=batch.outputDirectory + await page.getByRole('button',{name:'重新导出',exact:true}).first().click() + await expect.poll(async()=>page.evaluate(async old => {const b=(await window.receipts.listBatches())[0];return b?.outputDirectory!==old&&b?.status==='completed'},originalDirectory),{timeout:120000}).toBe(true) + const boot=await page.evaluate(()=>window.receipts.bootstrap()) + expect(boot.nextNumber).toBe('QA00000004') + expect(boot.batches).toHaveLength(1) + const retryFiles=await fs.readdir(boot.batches[0].outputDirectory,{recursive:true}) + expect(retryFiles.filter(f=>f.endsWith('.pdf'))).toHaveLength(3) + expect(retryFiles).toContain(path.join('某某学校','初二年级','秋季学期服务费',path.basename(singleFile))) + await expect.poll(()=>desktop.evaluate(()=>globalThis.openedDirectories)).toContain(boot.batches[0].outputDirectory) + // A large number of schools should not mount every nested checkbox at once. + const largeBook=new ExcelJS.Workbook(),largeSheet=largeBook.addWorksheet('收据数据') + largeSheet.addRow(['收款日期','学校','年级','姓名','收费项目','金额']) + for(let i=1;i<=60;i++)largeSheet.addRow(['2026-10-19','分组测试学校'+i,'高一','测试姓名','服务费',100]) + const largeFile=path.join(fixtures,'多学校分组.xlsx');await largeBook.xlsx.writeFile(largeFile) + await desktop.evaluate(({dialog},file)=>{dialog.showOpenDialog=async()=>({canceled:false,filePaths:[file]})},largeFile) + await page.getByRole('button',{name:'收据工作台',exact:true}).click() + await page.locator('.panel-heading [data-task="import"]').click() + await expect(page.getByText('已选择 60 条',{exact:true})).toBeVisible() + await expect(page.locator('.group-tree-root > .receipt-group-node')).toHaveCount(50) + await page.getByRole('button',{name:'显示更多分组(还有 10 项)',exact:true}).click() + await expect(page.locator('.group-tree-root > .receipt-group-node')).toHaveCount(60) + await page.getByRole('checkbox',{name:'选择分组:分组测试学校60',exact:true}).uncheck() + await expect(page.getByText('已选择 59 条',{exact:true})).toBeVisible() + await desktop.close() + const restarted=await launch() + const boot2=await restarted.evaluate(()=>window.receipts.bootstrap()) + expect(boot2.nextNumber).toBe('QA00000004');expect(boot2.settings.sealDirectory).toBe(seals);expect(boot2.batches).toHaveLength(1) + expect(boot2.settings).toMatchObject({createBatchFolder:true,openAfterExport:true}) + expect(errors).toEqual([]) + await fs.writeFile(path.join(qa,'result.json'),JSON.stringify({passed:true,checks:['Excel 模板','错误行定位','印章文件夹读取','设置持久化','文件命名实时示例','学校/年级/项目分组勾选与错误行禁用','大批分组按需展开与加载','分组 A4 预览','单张 PDF','300 DPI PNG','学校/年级/收费项目分层','分组 A4 打印版','历史组合条件同收据匹配','中间编号及姓名查找','匹配原收据预览','原编号分层重试','备份及更多偏好区域已移除','旧关闭配置自动启用','成功后自动打开批次目录','重启编号连续'],output:batch.outputDirectory,printGroups:printFiles.length,printPages:pdf.getPageCount(),png:{width:raster.width,height:raster.height},rendererErrors:errors},null,2)) + console.log('Desktop verification passed. Artifacts: '+qa) +}finally{if(desktop)await desktop.close().catch(()=>{})} diff --git a/scripts/dev-window-test.mjs b/scripts/dev-window-test.mjs new file mode 100644 index 0000000..247b091 --- /dev/null +++ b/scripts/dev-window-test.mjs @@ -0,0 +1,26 @@ +import { createServer } from 'vite' +import { build } from 'esbuild' +import { _electron as electron,expect } from '@playwright/test' +import electronPath from 'electron' +import path from 'node:path' +import fs from 'node:fs/promises' +for(const entry of ['index','preload'])await build({entryPoints:['src/main/'+entry+'.ts'],outfile:'dist/main/'+entry+'.cjs',bundle:true,platform:'node',format:'cjs',external:['electron'],sourcemap:true}) +const server=await createServer({server:{host:'127.0.0.1',port:5174,strictPort:true}}) +await server.listen() +const profile=path.join(process.cwd(),'.qa','dev-window-profile') +await fs.mkdir(profile,{recursive:true}) +const env={...process.env,ELECTRON_RENDERER_URL:'http://127.0.0.1:5174'} +delete env.ELECTRON_RUN_AS_NODE +let desktop +try{ + desktop=await electron.launch({executablePath:electronPath,args:['.','--user-data-dir='+profile],cwd:process.cwd(),env,timeout:30000}) + const page=await desktop.firstWindow();page.setDefaultTimeout(15000) + await expect(page.getByText('把表格交给收据工坊')).toBeVisible({timeout:30000}) + expect(await page.evaluate(()=>typeof window.receipts?.importExcel)).toBe('function') + expect(await desktop.evaluate(({BrowserWindow})=>BrowserWindow.getAllWindows()[0].isVisible())).toBe(true) + await desktop.evaluate(({BrowserWindow})=>{const w=BrowserWindow.getAllWindows().find(w=>w.webContents.getURL().includes('/index.html'));w.focus();w.webContents.sendInputEvent({type:'keyDown',keyCode:'F12'});w.webContents.sendInputEvent({type:'keyUp',keyCode:'F12'})}) + await expect.poll(()=>desktop.evaluate(({BrowserWindow})=>BrowserWindow.getAllWindows().find(w=>w.webContents.getURL().includes('/index.html'))?.webContents.isDevToolsOpened())).toBe(true) + await desktop.evaluate(({BrowserWindow})=>BrowserWindow.getAllWindows().find(w=>w.webContents.getURL().includes('/index.html'))?.webContents.closeDevTools()) + await fs.writeFile('.qa/dev-window-result.json',JSON.stringify({passed:true,visible:true,rendererReady:true,desktopApi:true,f12:true},null,2)) + console.log('Development window verified: visible, ready, desktop bridge and F12.') +}finally{if(desktop)await desktop.close();await server.close()} diff --git a/scripts/dev.mjs b/scripts/dev.mjs new file mode 100644 index 0000000..4e52ebd --- /dev/null +++ b/scripts/dev.mjs @@ -0,0 +1,25 @@ +import { spawn } from 'node:child_process' +import { createServer } from 'vite' +import { build } from 'esbuild' +import electron from 'electron' +const args=process.argv.slice(2),smoke=args.includes('--smoke-test') +console.log('正在准备收据工坊的本地开发窗口…') +for (const entry of ['index', 'preload']) await build({ entryPoints: ['src/main/'+entry+'.ts'], outfile: 'dist/main/'+entry+'.cjs', bundle: true, platform: 'node', format: 'cjs', external: ['electron'], sourcemap:true }) +const server = await createServer(); await server.listen() +const env={ ...process.env, ELECTRON_RENDERER_URL: 'http://127.0.0.1:5173' } +delete env.ELECTRON_RUN_AS_NODE +const child = spawn(electron, ['.',...args], { stdio: 'inherit', windowsHide: smoke, env }) +let closing=false +async function close(code=0){ + if(closing)return;closing=true + if(child.exitCode===null)child.kill() + await server.close() + process.exit(code) +} +child.on('error',error=>{console.error('无法启动桌面程序:',error.message);void close(1)}) +child.on('exit',code=>void close(code??0)) +process.on('SIGINT',()=>void close()) +process.on('SIGTERM',()=>void close()) +console.log('请在自动打开的“收据工坊”桌面窗口中操作,按 F12 打开调试工具。') +console.log('界面修改会自动更新;主程序修改后请关闭并重新启动。') +if(smoke)setTimeout(()=>void close(),8000).unref() diff --git a/scripts/history-pagination-test.mjs b/scripts/history-pagination-test.mjs new file mode 100644 index 0000000..d90f68b --- /dev/null +++ b/scripts/history-pagination-test.mjs @@ -0,0 +1,98 @@ +import {_electron as electron,expect} from '@playwright/test' +import electronPath from 'electron' +import {build} from 'esbuild' +import {createRequire} from 'node:module' +import fs from 'node:fs/promises' +import path from 'node:path' + +const root=process.cwd(),qa=path.join(root,'.qa','history-pagination') +await fs.mkdir(qa,{recursive:true}) +const run=await fs.mkdtemp(path.join(qa,'run-')),profile=path.join(run,'profile'),output=path.join(run,'output'),runtime=path.join(run,'fixture.cjs') +await fs.mkdir(profile);await fs.mkdir(output) +await build({stdin:{contents:"export {Ledger} from './src/main/ledger';export {defaultSettings} from './src/shared/settings'",resolveDir:root},outfile:runtime,bundle:true,platform:'node',format:'cjs',logLevel:'silent'}) +const {Ledger,defaultSettings}=createRequire(import.meta.url)(runtime) +const settings={...defaultSettings(output),numberPrefix:'PAGE'} +await fs.writeFile(path.join(profile,'settings.json'),JSON.stringify(settings)) +const ledger=new Ledger(path.join(profile,'receipts.sqlite'));await ledger.open() +for(let i=1;i<=13;i++){ + const school=i%2?'分页甲校':'分页乙校',studentName='学生'+String(i).padStart(2,'0') + const batch=await ledger.reserve({name:'分页测试'+String(i).padStart(2,'0')+'.xlsx',hash:String(i)},[{date:'2026-10-19',school,grade:'高一',studentName,customer:school+' 高一 '+studentName,item:'服务费',amountCents:10000,cashier:'',reviewer:''}],settings,null) + batch.status='completed';batch.outputDirectory=output;await ledger.update(batch) +} +const env={...process.env};delete env.ELECTRON_RUN_AS_NODE;delete env.ELECTRON_RENDERER_URL +let desktop +const errors=[],checks=[] +async function launch(){ + desktop=await electron.launch({executablePath:electronPath,args:['.','--test-mode','--user-data-dir='+profile],cwd:root,env,timeout:60000}) + const page=await desktop.firstWindow();page.setDefaultTimeout(20000);page.on('pageerror',error=>errors.push(error.message)) + await page.getByText('把表格交给收据工坊').waitFor() + await page.getByRole('button',{name:/^生成记录/}).click() + return page +} +try{ + let page=await launch() + const rows=()=>page.locator('.history-table tbody tr') + const position=()=>page.locator('.history-page-position') + async function idle(){ + await expect(page.locator('[data-region="history"]')).not.toHaveAttribute('aria-busy','true') + await expect(page.locator('.history-search>span')).not.toContainText('正在') + } + await expect(rows()).toHaveCount(5);await idle() + await expect(page.getByLabel('生成记录每页条数')).toHaveValue('5') + await expect(position()).toHaveText('第 1 / 3 页') + await expect(page.locator('.history-range')).toContainText('共 13 条') + await expect(page.getByRole('button',{name:'生成记录上一页',exact:true})).toBeDisabled() + await expect(rows().first()).toContainText('分页测试13.xlsx') + await page.screenshot({path:path.join(qa,'pagination.png'),fullPage:true}) + await page.getByRole('button',{name:'生成记录下一页',exact:true}).click();await idle() + await expect(position()).toHaveText('第 2 / 3 页');await expect(rows()).toHaveCount(5) + await expect(rows().first()).toContainText('分页测试08.xlsx') + await expect(page.locator('.history-range')).toContainText('第 6–10 条') + await page.getByRole('button',{name:'生成记录末页',exact:true}).click();await idle() + await expect(rows()).toHaveCount(3);await expect(position()).toHaveText('第 3 / 3 页') + await expect(page.getByRole('button',{name:'生成记录下一页',exact:true})).toBeDisabled() + checks.push('Default five rows, accurate total/range, next/last page and disabled boundaries') + await page.getByLabel('生成记录每页条数').selectOption('10');await idle() + await expect(rows()).toHaveCount(10);await expect(position()).toHaveText('第 1 / 2 页') + await page.getByLabel('生成记录每页条数').selectOption('20');await idle() + await expect(rows()).toHaveCount(13);await expect(position()).toHaveText('第 1 / 1 页') + await page.getByLabel('生成记录每页条数').selectOption('5');await idle() + await page.getByLabel('按学校查找记录').selectOption('分页甲校');await idle() + await expect(page.locator('.history-range')).toContainText('共 7 条') + await page.getByRole('button',{name:'生成记录下一页',exact:true}).click();await idle() + await expect(rows()).toHaveCount(2);await expect(position()).toHaveText('第 2 / 2 页') + await page.getByLabel('搜索生成记录').fill('学生01');await idle() + await expect(rows()).toHaveCount(1);await expect(position()).toHaveText('第 1 / 1 页') + await expect(page.locator('.history-match')).toContainText('学生01') + await page.getByRole('button',{name:'清空筛选',exact:true}).click();await idle() + await expect(rows()).toHaveCount(5);await expect(page.locator('.history-range')).toContainText('共 13 条') + checks.push('Page-size changes and filters reset to page one; matching totals span all pages') + await page.getByLabel('生成记录每页条数').selectOption('10');await idle() + await page.getByRole('button',{name:'收据工作台',exact:true}).click() + await page.getByRole('button',{name:/^生成记录/}).click();await idle() + await expect(rows()).toHaveCount(10);await expect(page.getByLabel('生成记录每页条数')).toHaveValue('10') + await desktop.close();desktop=undefined + page=await launch();await idle() + await expect(rows()).toHaveCount(10);await expect(page.getByLabel('生成记录每页条数')).toHaveValue('10') + expect((await page.evaluate(()=>window.receipts.bootstrap())).settings.historyPageSize).toBe(10) + checks.push('Rows per page remembered across navigation and application restart') + await page.getByLabel('生成记录每页条数').selectOption('5');await idle() + await page.getByRole('button',{name:'生成记录末页',exact:true}).click();await idle() + for(let remaining=12;remaining>=10;remaining--){ + await rows().last().getByRole('button',{name:'删除',exact:true}).click() + await page.getByRole('alertdialog').getByRole('button',{name:'删除记录',exact:true}).click();await idle() + await expect(page.locator('.history-range')).toContainText('共 '+remaining+' 条') + } + await expect(position()).toHaveText('第 2 / 2 页');await expect(rows()).toHaveCount(5) + expect((await page.evaluate(()=>window.receipts.bootstrap())).nextNumber).toBe('PAGE00000014') + await page.getByLabel('搜索生成记录').fill('没有对应的学生');await idle() + await expect(rows()).toHaveCount(0);await expect(position()).toHaveText('第 1 / 1 页') + await expect(page.locator('.history-range')).toContainText('共 0 条') + await expect(page.getByRole('button',{name:'生成记录下一页',exact:true})).toBeDisabled() + checks.push('Deleting the last page clamps to a valid page; no-result filters and issued numbers remain correct') + expect(errors).toEqual([]) + await fs.writeFile(path.join(qa,'result.json'),JSON.stringify({passed:true,checks,rendererErrors:errors},null,2)) + console.log('History pagination verified: '+checks.join('; ')) +}finally{ + if(desktop){await desktop.evaluate(({app})=>app.exit(0)).catch(()=>{});await desktop.close().catch(()=>{})} +} diff --git a/scripts/loading-test.mjs b/scripts/loading-test.mjs new file mode 100644 index 0000000..bcf4f6d --- /dev/null +++ b/scripts/loading-test.mjs @@ -0,0 +1,182 @@ +import {createServer} from 'vite' +import {_electron as electron,expect} from '@playwright/test' +import electronPath from 'electron' +import ExcelJS from 'exceljs' +import fs from 'node:fs/promises' +import path from 'node:path' + +// All data and dialog results belong to this isolated run. +const root=process.cwd(),qa=path.join(root,'.qa','loading') +await fs.mkdir(qa,{recursive:true}) +const run=await fs.mkdtemp(path.join(qa,'run-')),profile=path.join(run,'profile'),seals=path.join(run,'seals') +await fs.mkdir(seals) +const file=path.join(run,'动画验证.xlsx'),template=path.join(run,'模板.xlsx') +const book=new ExcelJS.Workbook(),sheet=book.addWorksheet('收据数据') +sheet.addRow(['收款日期','学校','年级','姓名','收费项目','金额']) +sheet.addRow(['2026-10-16','测试学校','高2028届','测试姓名','服务费',3500]) +await book.xlsx.writeFile(file) +const server=await createServer({server:{host:'127.0.0.1',port:5176,strictPort:true}});await server.listen() +const env={...process.env,ELECTRON_RENDERER_URL:'http://127.0.0.1:5176'};delete env.ELECTRON_RUN_AS_NODE +const checks=[],frames=[],errors=[] +let desktop +try{ + desktop=await electron.launch({executablePath:electronPath,args:['.','--test-mode','--user-data-dir='+profile],cwd:root,env}) + const page=await desktop.firstWindow();page.setDefaultTimeout(15000);page.on('pageerror',e=>errors.push(e.message)) + await page.getByText('把表格交给收据工坊').waitFor() + // Hidden Chromium windows do not advance CSS timelines, so inspect a visible, unfocused test window. + await desktop.evaluate(({BrowserWindow,dialog,shell})=>{ + BrowserWindow.getAllWindows()[0].showInactive() + const picker=()=>new Promise(resolve=>{globalThis.finishPicker=resolve}) + dialog.showOpenDialog=picker;dialog.showSaveDialog=picker + shell.openPath=async()=>new Promise(resolve=>{globalThis.finishOpen=()=>resolve('')}) + }) + async function arm(scope){ + await page.evaluate(scope=>{ + window.loadingTrace=new Promise((resolve,reject)=>{ + let start + const transforms=[],deadline=performance.now()+10000 + function sample(now){ + const region=document.querySelector('[data-region="'+scope+'"]'),spinner=region?.querySelector('.region-spinner') + if(spinner&®ion.getAttribute('aria-busy')==='true'){ + start??=now;transforms.push(getComputedStyle(spinner).transform) + }else if(start!==undefined){resolve({duration:now-start,frames:transforms.length,uniqueTransforms:new Set(transforms).size});return} + if(now>deadline){reject(Error('No loading cycle in '+scope));return} + requestAnimationFrame(sample) + } + requestAnimationFrame(sample) + }) + },scope) + } + async function checkTrace(name){ + const result=await page.evaluate(()=>window.loadingTrace) + expect(result.duration).toBeGreaterThanOrEqual(490) + expect(result.duration).toBeLessThan(1800) + expect(result.frames).toBeGreaterThan(8);expect(result.uniqueTransforms).toBeGreaterThan(5) + frames.push({name,...result}) + await expect(page.getByRole('status')).toHaveCount(0) + } + async function noLoading(scope){ + const region=page.locator('[data-region="'+scope+'"]') + await expect(region).not.toHaveAttribute('aria-busy','true') + await expect(region.locator('.region-loading')).toHaveCount(0) + expect(await region.locator('.region-content').evaluate(el=>el.inert)).toBe(false) + } + async function resolvePicker(value){ + await expect.poll(()=>desktop.evaluate(()=>typeof globalThis.finishPicker)).toBe('function') + await desktop.evaluate((_,value)=>{globalThis.finishPicker(value);delete globalThis.finishPicker},value) + } + async function watchNoMasks(){ + await page.evaluate(()=>{ + window.maskCount=0 + window.maskObserver=new MutationObserver(records=>{ + for(const r of records)for(const n of r.addedNodes)if(n instanceof Element&&(n.matches('.region-loading')||n.querySelector('.region-loading')))window.maskCount++ + }) + window.maskObserver.observe(document.body,{childList:true,subtree:true}) + }) + } + async function stopWatching(){ + const count=await page.evaluate(()=>{window.maskObserver.disconnect();return window.maskCount}) + expect(count).toBe(0) + } + await page.getByRole('button',{name:'偏好设置',exact:true}).click() + for(const reducedMotion of ['no-preference','reduce']){ + await page.emulateMedia({reducedMotion}) + await page.getByLabel('默认出纳',{exact:true}).fill('动画验证 '+reducedMotion) + await arm('settings');await page.locator('button[data-task="settings"]').click() + await checkTrace('save / '+reducedMotion) + } + checks.push('Real fast saves rotate continuously for at least 500 ms with both motion preferences') + // A pending native folder picker must leave the form clear and avoid a delayed mask on cancellation. + await watchNoMasks() + await page.locator('[data-task="settings-folder"][data-task-target="output"]').click() + await expect.poll(()=>desktop.evaluate(()=>typeof globalThis.finishPicker)).toBe('function') + await noLoading('settings') + expect(await page.getByLabel('默认出纳',{exact:true}).isEditable()).toBe(true) + await page.evaluate(()=>{window.pickerCancelledAt=performance.now()}) + await resolvePicker({canceled:true,filePaths:[]}) + await expect.poll(()=>page.evaluate(async()=>!(await import('/state.ts')).state.busy.has('settings-folder'))).toBe(true) + const cancelMs=await page.evaluate(()=>performance.now()-window.pickerCancelledAt) + expect(cancelMs).toBeLessThan(450);await stopWatching() + checks.push('Folder selection has no mask; cancellation adds no 500 ms wait') + // Opening a folder should also leave the form interactive. + await watchNoMasks() + await page.evaluate(async()=>{ + const {actions,state}=await import('/state.ts') + void actions.open(state.dataDirectory) + }) + await expect.poll(()=>desktop.evaluate(()=>typeof globalThis.finishOpen)).toBe('function') + await noLoading('settings') + await desktop.evaluate(()=>{globalThis.finishOpen()}) + await desktop.evaluate(({shell})=>{shell.openPath=async()=>''}) + await expect.poll(()=>page.evaluate(async()=>!(await import('/state.ts')).state.busy.has('open'))).toBe(true) + await stopWatching() + checks.push('Opening local folders has no mask') + await page.getByRole('button',{name:'印章管理',exact:true}).click() + await arm('seals');await page.locator('[data-task="refresh-seals"]').click();await checkTrace('refresh seals') + // Binding a folder includes saving its path and reloading images, without a synthetic loading mask. + await watchNoMasks() + await page.locator('.panel-heading [data-task="folder"]').click() + await expect.poll(()=>desktop.evaluate(()=>typeof globalThis.finishPicker)).toBe('function') + await noLoading('seals') + await resolvePicker({canceled:false,filePaths:[seals]}) + await expect.poll(()=>page.evaluate(async()=>!(await import('/state.ts')).state.busy.has('folder'))).toBe(true) + await stopWatching() + checks.push('Choosing and binding a stamp folder has no mask') + await page.getByRole('button',{name:/^生成记录/}).click() + await arm('history');await page.locator('[data-task="refresh-records"]').click();await checkTrace('refresh history') + checks.push('Both manual refresh actions show half a second of real rotation') + await page.getByRole('button',{name:'收据工作台',exact:true}).click() + await arm('preview');await page.locator('[data-region="preview"] [data-task="refresh-seals"]').click();await checkTrace('refresh seals from workbench') + await page.locator('.panel-heading [data-task="import"]').click() + await expect.poll(()=>desktop.evaluate(()=>typeof globalThis.finishPicker)).toBe('function') + await noLoading('data') + await resolvePicker({canceled:true,filePaths:[]}) + await expect.poll(()=>page.evaluate(async()=>!(await import('/state.ts')).state.busy.has('import'))).toBe(true) + await noLoading('data') + await page.locator('.panel-heading [data-task="import"]').click() + await expect.poll(()=>desktop.evaluate(()=>typeof globalThis.finishPicker)).toBe('function') + await noLoading('data');await arm('data') + await resolvePicker({canceled:false,filePaths:[file]}) + await checkTrace('import after file chosen') + await expect(page.getByText('测试学校 高2028届 测试姓名',{exact:true})).toBeVisible() + checks.push('Import rotates during processing only, after accepting the file picker') + await page.locator('[data-task="template"]').click() + await expect.poll(()=>desktop.evaluate(()=>typeof globalThis.finishPicker)).toBe('function') + await noLoading('data');await arm('data') + await resolvePicker({canceled:false,filePath:template}) + await checkTrace('template after destination chosen') + expect((await fs.stat(template)).size).toBeGreaterThan(0) + checks.push('Template starts loading after accepting its picker') + // Generate one isolated batch to verify that deleting waits for confirmation before animating. + await page.evaluate(async output=>{ + const {actions,state}=await import('/state.ts') + await window.receipts.saveSettings({...state.settings,outputDirectory:output,exportPdf:true,exportPng:false,exportPrint:false}) + await actions.reload() + },path.join(run,'output')) + await page.getByRole('button',{name:'收据工作台',exact:true}).click() + await page.locator('button.export-button').click() + await expect.poll(()=>page.evaluate(async()=>(await window.receipts.listBatches())[0]?.status),{timeout:30000}).toBe('completed') + await page.getByRole('button',{name:/^生成记录/}).click() + await page.locator('[data-task="delete-batch"]').click() + await expect(page.getByRole('alertdialog')).toBeVisible();await noLoading('history') + await page.getByRole('alertdialog').getByRole('button',{name:'取消',exact:true}).click() + await expect(page.getByRole('alertdialog')).toHaveCount(0) + await expect.poll(()=>page.evaluate(async()=>!(await import('/state.ts')).state.busy.has('delete-batch'))).toBe(true) + await noLoading('history') + expect((await page.evaluate(()=>window.receipts.listBatches())).length).toBe(1) + await page.locator('[data-task="delete-batch"]').click() + await expect(page.getByRole('alertdialog')).toBeVisible();await noLoading('history') + await arm('history') + await page.getByRole('alertdialog').getByRole('button',{name:'删除记录',exact:true}).click() + await checkTrace('delete after confirmation') + expect((await page.evaluate(()=>window.receipts.listBatches())).length).toBe(0) + expect((await page.evaluate(()=>window.receipts.bootstrap())).nextNumber).toBe('SJ00000002') + checks.push('Delete cancellation is immediate; confirmed deletion rotates and preserves reserved numbers') + await expect(page.locator('.main-content > .feedback')).toHaveCount(0) + expect(errors).toEqual([]) + await fs.writeFile(path.join(qa,'result.json'),JSON.stringify({passed:true,checks,frames,folderCancellationMs:cancelMs,rendererErrors:errors},null,2)) + console.log(JSON.stringify({passed:true,checks,frames,folderCancellationMs:cancelMs},null,2)) +}finally{ + if(desktop){await desktop.evaluate(({app})=>app.exit(0)).catch(()=>{});await desktop.close().catch(()=>{})} + await server.close() +} diff --git a/scripts/make-icon.mjs b/scripts/make-icon.mjs new file mode 100644 index 0000000..ae4713e --- /dev/null +++ b/scripts/make-icon.mjs @@ -0,0 +1,17 @@ +import {PNG} from 'pngjs' +import fs from 'node:fs/promises' +const png=new PNG({width:256,height:256}) +for(let y=0;y<256;y++)for(let x=0;x<256;x++){ + const i=(y*256+x)*4,dx=Math.max(38-x,0,x-217),dy=Math.max(38-y,0,y-217) + let color=[22,133,118,dx*dx+dy*dy<=38*38?255:0] + if(x>=64&&x<=192&&y>=39&&y<=216)color=[255,255,255,255] + if(x>=163&&y<=68&&y<39+(x-163))color=[22,133,118,255] + if((y>=89&&y<=95||y>=112&&y<=118||y>=135&&y<=141)&&x>=83&&x<=171)color=[144,199,185,255] + const r=Math.hypot(x-158,y-183) + if(r>=18&&r<=23)color=[22,133,118,255] + for(let c=0;c<4;c++)png.data[i+c]=color[c] +} +const data=PNG.sync.write(png),head=Buffer.alloc(22) +head.writeUInt16LE(1,2);head.writeUInt16LE(1,4);head[6]=0;head[7]=0;head.writeUInt16LE(1,10);head.writeUInt16LE(32,12);head.writeUInt32LE(data.length,14);head.writeUInt32LE(22,18) +await fs.mkdir('build',{recursive:true});await fs.mkdir('dist',{recursive:true}) +await fs.writeFile('build/icon.png',data);await fs.writeFile('build/icon.ico',Buffer.concat([head,data]));await fs.writeFile('dist/icon.png',data) diff --git a/scripts/package-test.mjs b/scripts/package-test.mjs new file mode 100644 index 0000000..115e191 --- /dev/null +++ b/scripts/package-test.mjs @@ -0,0 +1,39 @@ +import { _electron as electron,expect } from '@playwright/test' +import fs from 'node:fs/promises' +import path from 'node:path' +import {PDFDocument} from 'pdf-lib' +const profile=path.join(process.cwd(),'.qa','packaged-profile-1.0.1') +await fs.mkdir(profile,{recursive:true}) +const env={...process.env};delete env.ELECTRON_RUN_AS_NODE +let app +try{ + app=await electron.launch({executablePath:path.join(process.cwd(),'release','win-unpacked','收据工坊.exe'),args:['--smoke-test','--user-data-dir='+profile],env,timeout:60000}) + const page=await app.firstWindow();page.setDefaultTimeout(20000) + await expect(page.getByRole('heading',{name:'收据工作台',exact:true})).toBeVisible({timeout:30000}) + await expect(page.getByText('把表格交给收据工坊')).toBeVisible({timeout:30000}) + const boot=await page.evaluate(()=>window.receipts.bootstrap()) + expect(boot.dataDirectory).toBe(profile) + expect(boot.nextNumber).toBe('SJ00000001') + const sealFolder=path.join(process.cwd(),'.qa','fixtures','seals'),output=path.join(process.cwd(),'.qa','packaged-output') + await fs.mkdir(output,{recursive:true}) + await app.evaluate(({shell})=>{shell.openPath=async()=>''}) + await page.evaluate(async({output,sealFolder})=>{const b=await window.receipts.bootstrap();await window.receipts.saveSettings({...b.settings,outputDirectory:output,sealDirectory:sealFolder,numberPrefix:'PACK'})},{output,sealFolder}) + const seals=await page.evaluate(()=>window.receipts.listSeals());expect(seals.images.length).toBeGreaterThan(0) + await app.evaluate(({dialog},file)=>{dialog.showOpenDialog=async()=>({canceled:false,filePaths:[file]})},path.join(process.cwd(),'.qa','fixtures','收据测试数据.xlsx')) + const draft=await page.evaluate(()=>window.receipts.importExcel()) + expect(draft.rows[0].receipt.customer).toBe('某某学校 初二年级 张三') + expect(draft.rows[0].receipt.school).toBe('某某学校') + expect(draft.rows[0].receipt.grade).toBe('初二年级') + expect(draft.rows[0].receipt.studentName).toBe('张三') + await page.evaluate(async({id,sealId})=>window.receipts.exportReceipts({draftId:id,rowIndices:[0],sealId,options:{pdf:true,png:true,print:true,receiptsPerPage:2,cutLine:true}}),{id:draft.id,sealId:seals.images[0].id}) + await expect.poll(async()=>page.evaluate(async()=> (await window.receipts.listBatches())[0]?.status),{timeout:90000}).toBe('completed') + const batch=(await page.evaluate(()=>window.receipts.listBatches()))[0] + const files=await fs.readdir(batch.outputDirectory,{recursive:true}) + expect(files.filter(n=>n.endsWith('.png'))).toHaveLength(1) + const png=await fs.readFile(path.join(batch.outputDirectory,files.find(n=>n.endsWith('.png')))) + expect(png.readUInt32BE(png.indexOf('pHYs')+4)).toBe(11811) + const pdf=await PDFDocument.load(await fs.readFile(path.join(batch.outputDirectory,files.find(n=>path.basename(n).startsWith('A4'))))) + expect(pdf.getPageCount()).toBe(1) + await fs.writeFile('.qa/package-result.json',JSON.stringify({passed:true,platform:'Windows 10 x64',packaged:true,renderedPdf:true,renderedPng:true,dpi:300,profile},null,2)) + console.log('Packaged application verified: standalone startup, Excel, seals, PDF and PNG.') +}finally{if(app)await app.close().catch(()=>{})} diff --git a/scripts/print-batch-test.mjs b/scripts/print-batch-test.mjs new file mode 100644 index 0000000..ceff6a7 --- /dev/null +++ b/scripts/print-batch-test.mjs @@ -0,0 +1,80 @@ +import { _electron as electron,expect } from '@playwright/test' +import electronPath from 'electron' +import ExcelJS from 'exceljs' +import { PNG } from 'pngjs' +import { PDFDocument } from 'pdf-lib' +import fs from 'node:fs/promises' +import path from 'node:path' +const root=process.cwd(),qa=path.join(root,'.qa','print-regression'),profile=path.join(qa,'profile'),output=path.join(qa,'output'),seals=path.join(qa,'seals') +if(!qa.startsWith(root+path.sep))throw Error('Invalid QA path') +await fs.rm(qa,{recursive:true,force:true}) +await fs.mkdir(seals,{recursive:true});await fs.mkdir(output,{recursive:true}) +const image=new PNG({width:256,height:256}) +let seed=731 +for(let i=0;i>>0 + image.data[i]=130+(seed&127);image.data[i+1]=(seed>>>8)&63;image.data[i+2]=(seed>>>16)&63;image.data[i+3]=100+(seed>>>24)%156 +} +await fs.writeFile(path.join(seals,'批量打印测试章.png'),PNG.sync.write(image)) +const book=new ExcelJS.Workbook(),sheet=book.addWorksheet('收据数据') +sheet.addRow(['收款日期','学校','年级','姓名','收费项目','金额','出纳','复核']) +for(let i=1;i<=41;i++)sheet.addRow(['2026-10-16','测试学校','高2028届','测试学生'+i,'测试收费项目',3500,'测试出纳','测试复核']) +const excelFile=path.join(qa,'批量测试.xlsx');await book.xlsx.writeFile(excelFile) +const env={...process.env};delete env.ELECTRON_RUN_AS_NODE;delete env.ELECTRON_RENDERER_URL +let desktop +try{ + desktop=await electron.launch({executablePath:electronPath,args:['.','--test-mode','--user-data-dir='+profile],cwd:root,env,timeout:60000}) + const page=await desktop.firstWindow();page.setDefaultTimeout(30000) + await expect(page.getByRole('heading',{name:'收据工作台',exact:true})).toBeVisible() + await expect.poll(()=>page.evaluate(async()=>!!(await window.receipts.bootstrap()).settings)).toBe(true) + await desktop.evaluate(({dialog,shell},p)=>{dialog.showOpenDialog=async()=>({canceled:false,filePaths:[p.excelFile]});shell.openPath=async()=>''},{excelFile}) + await page.evaluate(async p=>{ + const boot=await window.receipts.bootstrap() + await window.receipts.saveSettings({...boot.settings,outputDirectory:p.output,sealDirectory:p.seals,numberPrefix:'REG',exportPdf:false,exportPng:false,exportPrint:true}) + },{output,seals}) + const sealCharacters=await page.evaluate(async()=> (await window.receipts.listSeals()).images[0].dataUrl.length) + expect(sealCharacters*40).toBeGreaterThan(2*1024*1024) + const batchId=await page.evaluate(async()=>{ + const draft=await window.receipts.importExcel(),seal=(await window.receipts.listSeals()).images[0] + window.printTestProgress=[] + window.receipts.onProgress(p=>window.printTestProgress.push({finished:p.finished,status:p.status,errorLength:p.errors.join('').length})) + return window.receipts.exportReceipts({draftId:draft.id,rowIndices:draft.rows.map(r=>r.index),sealId:seal.id,options:{pdf:false,png:false,print:true,receiptsPerPage:2,cutLine:true}}) + }) + await expect.poll(()=>page.evaluate(async()=> (await window.receipts.listBatches())[0]),{timeout:120000,intervals:[500,1000]}).toMatchObject({status:process.argv.includes('--expect-failure')?'failed':'completed'}) + const summary=await page.evaluate(async()=> (await window.receipts.listBatches())[0]) + if(process.argv.includes('--expect-failure')){ + expect(summary.message).toContain('ERR_INVALID_URL') + console.log(JSON.stringify({reproduced:true,receiptCount:summary.count,messageCharacters:summary.message.length,imageCharactersInFirstChunk:sealCharacters*40})) + }else{ + const files=await fs.readdir(summary.outputDirectory,{recursive:true}) + const pdf=await PDFDocument.load(await fs.readFile(path.join(summary.outputDirectory,files.find(f=>f.endsWith('.pdf'))))) + expect(pdf.getPageCount()).toBe(21);expect(Math.abs(pdf.getPage(0).getWidth()-595.28)).toBeLessThan(1) + const manifest=JSON.parse(await fs.readFile(path.join(summary.outputDirectory,'生成清单.json'),'utf8')) + expect(manifest.errors).toEqual([]);expect(manifest.receipts).toHaveLength(41) + expect((await page.evaluate(()=>window.receipts.bootstrap())).nextNumber).toBe('REG00000042') + const firstDirectory=summary.outputDirectory + await desktop.evaluate(({BrowserWindow})=>{ + const original=BrowserWindow.prototype.loadURL + BrowserWindow.prototype.receiptTestLoadURL=original + BrowserWindow.prototype.loadURL=function(url,...args){ + if(url.startsWith('receipt://print/'))return Promise.reject(new Error("ERR_INVALID_URL (-300) loading 'data:text/html;charset=utf-8,"+'sensitive'.repeat(300000)+"'")) + return original.call(this,url,...args) + } + }) + await page.evaluate(async id=>window.receipts.exportReceipts({batchId:id,options:{pdf:false,png:false,print:true,receiptsPerPage:2,cutLine:true}}),batchId) + await expect.poll(()=>page.evaluate(async()=> (await window.receipts.listBatches())[0].status),{timeout:30000}).toBe('failed') + await expect(page.getByRole('alert')).toContainText('ERR_INVALID_URL') + expect((await page.getByRole('alert').innerText()).length).toBeLessThan(450) + await expect(page.getByRole('status')).toHaveCount(0) + const failed=(await page.evaluate(()=>window.receipts.listBatches()))[0] + expect(failed.message.length).toBeLessThan(450);expect(failed.message).not.toContain('sensitive') + await desktop.evaluate(({BrowserWindow})=>{BrowserWindow.prototype.loadURL=BrowserWindow.prototype.receiptTestLoadURL;delete BrowserWindow.prototype.receiptTestLoadURL}) + await page.evaluate(async id=>window.receipts.exportReceipts({batchId:id,options:{pdf:true,png:true,print:true,receiptsPerPage:2,cutLine:true}}),batchId) + await expect.poll(()=>page.evaluate(async old=>{const b=(await window.receipts.listBatches())[0];return b.outputDirectory!==old&&b.status==='completed'},firstDirectory),{timeout:180000,intervals:[1000]}).toBe(true) + const retry=(await page.evaluate(()=>window.receipts.listBatches()))[0],retryFiles=await fs.readdir(retry.outputDirectory,{recursive:true}) + expect(retryFiles.filter(f=>f.endsWith('.pdf'))).toHaveLength(42);expect(retryFiles.filter(f=>f.endsWith('.png'))).toHaveLength(41) + expect((await page.evaluate(()=>window.receipts.bootstrap())).nextNumber).toBe('REG00000042') + await fs.writeFile(path.join(qa,'result.json'),JSON.stringify({passed:true,receiptCount:41,printPages:pdf.getPageCount(),imageCharactersInFirstChunk:sealCharacters*40,retryPdfCount:42,retryPngCount:41,nextNumber:'REG00000042',shortErrorVerified:true,failureNoticeVerified:true,output:retry.outputDirectory},null,2)) + console.log('Large stamp batch printing and original-number retry passed.') + } +}finally{if(desktop)await desktop.close().catch(()=>{})} diff --git a/src/core/date.ts b/src/core/date.ts new file mode 100644 index 0000000..b685f70 --- /dev/null +++ b/src/core/date.ts @@ -0,0 +1,19 @@ +export function parseReceiptDate(value: unknown, date1904=false): string { + if(value instanceof Date) return check(value.getUTCFullYear(),value.getUTCMonth()+1,value.getUTCDate()) + if(typeof value==='number' && Number.isFinite(value)) { + if(!date1904 && Math.floor(value)===60) throw Error('无效日期:1900-02-29') + const base=Date.UTC(date1904?1904:1899,date1904?0:11,date1904?1:31) + const serial=Math.floor(value)-(date1904||value<60?0:1) + return parseReceiptDate(new Date(base+serial*86400000)) + } + if(typeof value==='string') { + const m=value.trim().match(/^(\d{4})[-/.年](\d{1,2})[-/.月](\d{1,2})日?$/) + if(m) return check(Number(m[1]),Number(m[2]),Number(m[3])) + } + throw Error('日期请填写为 YYYY-MM-DD,例如 2026-10-16') +} +function check(y:number,m:number,d:number):string { + const date=new Date(Date.UTC(y,m-1,d)) + if(y<1900||y>2199||date.getUTCFullYear()!==y||date.getUTCMonth()!==m-1||date.getUTCDate()!==d) throw Error('日期无效,请检查年月日') + return [String(y),String(m).padStart(2,'0'),String(d).padStart(2,'0')].join('-') +} diff --git a/src/core/money.ts b/src/core/money.ts new file mode 100644 index 0000000..a8e7ea0 --- /dev/null +++ b/src/core/money.ts @@ -0,0 +1,43 @@ +export const MAX_CENTS = 999_999_999 +export function parseMoney(value: string | number): number { + const raw = String(value).trim().replace(/^[¥¥]\s*/, '') + const normalized = raw.includes(',') ? (/^\d{1,3}(,\d{3})*(\.\d{1,2})?$/.test(raw) ? raw.replaceAll(',', '') : '') : raw + if (!/^\d{1,7}(\.\d{1,2})?$/.test(normalized)) throw new Error('金额须为正数,最多两位小数,最高 9,999,999.99 元') + const [whole, fraction=''] = normalized.split('.') + const cents = Number(whole) * 100 + Number(fraction.padEnd(2, '0')) + if (cents <= 0 || cents > MAX_CENTS) throw new Error('金额须大于 0,且不超过 9,999,999.99 元') + return cents +} +export function formatMoney(cents: number): string { return (cents / 100).toFixed(2) } +export function displayMoney(cents: number): string { + return (cents / 100).toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 }) +} +const digits = '零壹贰叁肆伍陆柒捌玖' +function group(n: number): string { + const str=String(n); let result=''; let pendingZero=false + for(let i=0;iMAX_CENTS) throw Error('金额超出范围') + const yuan=Math.floor(cents/100), jiao=Math.floor(cents%100/10), fen=cents%10 + const high=Math.floor(yuan/10000), low=yuan%10000 + let text=high ? group(high)+'万'+(low && low<1000?'零':'')+(low?group(low):'') : yuan?group(yuan):'零' + text+='元' + if(!jiao&&!fen) return text+'整' + if(jiao) text+=digits[jiao]+'角' + else if(yuan&&fen) text+='零' + if(fen) text+=digits[fen]+'分' + return text +} +export function moneyCells(cents: number): string[] { + const value=String(cents); const cells=Array(9).fill('') as string[] + const start=9-value.length + if(start>0) cells[start-1]='¥' + value.split('').forEach((v,i)=>cells[start+i]=v) + return cells +} diff --git a/src/core/templates.ts b/src/core/templates.ts new file mode 100644 index 0000000..e90dd9d --- /dev/null +++ b/src/core/templates.ts @@ -0,0 +1,72 @@ +import { chineseMoney, formatMoney, moneyCells } from './money' +import type { AppSettings, Receipt, SealImage } from '../shared/types' +export interface TemplateContext { settings: AppSettings; seal: SealImage | null; preview: boolean } +export interface ReceiptTemplate { id: string; name: string; css: string; render(receipt: Receipt, context: TemplateContext): string } +export const escapeHtml = (text: string) => text.replace(/[&<>"']/g, c=>({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c]!)) +const h=escapeHtml +const classic: ReceiptTemplate = { + id:'classic-v1', name:'标准收款收据', + css:` + .receipt { width:180mm;height:120mm;position:relative;color:#111;font-family:SimSun,"宋体","Noto Serif SC",serif;font-size:3.3mm;padding:0 1mm; } + .receipt-title { height:12mm;text-align:center;padding-top:1mm; } + .receipt-title span { font-size:6mm;letter-spacing:4mm;padding:0 2mm 1mm 6mm;border-bottom:1mm double #111; } + .receipt-meta {height:9mm;display:flex;align-items:center;justify-content:space-between;font-size:3.2mm;} + .receipt-no {font-family:Arial,SimSun,serif;letter-spacing:.15mm;font-size:3mm;} + .receipt-table {width:100%;table-layout:fixed;border-collapse:collapse;border:.35mm solid #111;} + .receipt-table td,.receipt-table th {border:.25mm solid #111;font-weight:normal;padding:0 1.5mm;vertical-align:middle;} + .customer-row {height:11mm;} .customer-value {overflow-wrap:anywhere;line-height:4.2mm;text-align:center;} + .side-label {width:13mm;text-align:center;line-height:4.8mm;} + .project-heading {height:12mm;text-align:center;} + .project-row {height:8.5mm;text-align:center;line-height:3.8mm;} + .project-name {font-size:3mm;overflow-wrap:anywhere;} + .receipt-table .amount-area {padding:0;width:56mm;} + .amount-grid {height:100%;display:grid;grid-template-columns:repeat(9,minmax(0,1fr));text-align:center;} + .amount-grid span {border-right:.2mm solid #111;display:flex;align-items:center;justify-content:center;font-family:SimSun,serif;} + .amount-grid span:last-child {border:0;} + .amount-caption {height:5.5mm;border-bottom:.2mm solid #111;display:flex;align-items:center;justify-content:center;} + .amount-units {height:6mm;} + .amount-row {height:8.3mm;font-family:Arial,serif;} + .total-row {height:14mm;font-size:3mm;line-height:4.2mm;} + .total-upper {padding-left:2mm;font-size:3.2mm;} + .total-lower {white-space:nowrap;text-align:center;} + .receipt-footer {display:flex;padding-top:5mm;line-height:5mm;justify-content:space-between;font-size:3.2mm;} + .seal-image {position:absolute;object-fit:contain;max-height:49mm;pointer-events:none;} + .preview-mark {position:absolute;inset:35mm 0 auto;text-align:center;transform:rotate(-20deg);font:16mm sans-serif;color:rgba(100,116,139,.09);pointer-events:none;} + `, + render(r,c) { + const [year,month,day]=r.date.split('-') + const rows=Array.from({length:5},(_,i)=>`${i===0?h(r.item):''}
${(i===0?moneyCells(r.amountCents):Array(9).fill('')).map(v=>''+v+'').join('')}
`).join('') + const seal=c.seal?`单位印章`:'' + return `
收款收据单
+
填表日期:${year} 年 ${Number(month)} 月 ${Number(day)} 日NO:${h(r.number)}
+ + + + ${rows}
客户
名称
${h(r.customer)}
收款
项目
名 称
金额
${'佰拾万仟佰拾元角分'.split('').map(v=>''+v+'').join('')}
合计人民币(大写)
${chineseMoney(r.amountCents)}
(小写)¥${formatMoney(r.amountCents)}
+ + ${seal}${c.preview?'
预览样张
':''}
` + } +} +export class TemplateRegistry { + private readonly entries=new Map() + constructor(templates:ReceiptTemplate[]=[classic]) { for(const t of templates) this.register(t) } + register(template:ReceiptTemplate):void { if(this.entries.has(template.id)) throw Error('模板编号重复'); this.entries.set(template.id,template) } + get(id:string):ReceiptTemplate { const t=this.entries.get(id); if(!t) throw Error('收据模板不存在,请在设置中重新选择'); return t } + list(){ return [...this.entries.values()].map(({id,name})=>({id,name})) } +} +export function buildDocument(template:ReceiptTemplate,receipts:Receipt[],context:TemplateContext,layout:'single'|'print',perPage:1|2=2,cutLine=true):string { + const width=layout==='single'?190:210, height=layout==='single'?140:297 + const capacity=layout==='single'?1:perPage + const pages:string[]=[] + for(let i=0;i'+Array.from({length:capacity},(_,j)=>'
'+(group[j]?template.render(group[j],context):'')+'
').join('')+(layout==='print'&&capacity===2&&cutLine?'
沿此线裁切
':'')+'') + } + return `${pages.join('')}` +} diff --git a/src/main/confirmations.ts b/src/main/confirmations.ts new file mode 100644 index 0000000..1e7125e --- /dev/null +++ b/src/main/confirmations.ts @@ -0,0 +1,24 @@ +import { randomUUID } from 'node:crypto' +import type { ConfirmationOptions,ConfirmationRequest } from '../shared/types' + +// Operations wait for the reply to their own request. Reloading or losing the window cancels it. +export class ConfirmationService { + private pending:{id:string;resolve:(confirmed:boolean)=>void}|null=null + constructor(private send:(request:ConfirmationRequest)=>void){} + get hasPending():boolean{return this.pending!==null} + ask(options:ConfirmationOptions):Promise{ + if(this.pending)return Promise.reject(Error('请先处理当前确认窗口')) + return new Promise((resolve,reject)=>{ + const id=randomUUID();this.pending={id,resolve} + try{this.send({...options,id})}catch(error){this.pending=null;reject(error)} + }) + } + answer(id:string,confirmed:boolean):void{ + if(!this.pending||this.pending.id!==id)throw Error('确认请求已失效,请重试') + const pending=this.pending;this.pending=null;pending.resolve(confirmed) + } + cancel():void{ + if(!this.pending)return + const pending=this.pending;this.pending=null;pending.resolve(false) + } +} diff --git a/src/main/export-layout.ts b/src/main/export-layout.ts new file mode 100644 index 0000000..977439c --- /dev/null +++ b/src/main/export-layout.ts @@ -0,0 +1,41 @@ +import { createHash } from 'node:crypto' +import path from 'node:path' +import type { Receipt } from '../shared/types' +import { safeFileName } from './files' +import {receiptGroupLabels} from '../shared/receipt-groups' + +export interface ReceiptGroup { + directory: string; + labels: string[]; + receipts: Receipt[]; +} + +export function groupReceipts(receipts: Receipt[]): ReceiptGroup[] { + const groups = new Map() + const siblingNames = new Map>() + for (const receipt of receipts) { + const labels = receiptGroupLabels(receipt) + const key = JSON.stringify(labels) + let group = groups.get(key) + if (!group) { + let directory = '' + for (const label of labels) { + let siblings = siblingNames.get(directory) + if (!siblings) siblingNames.set(directory, siblings = new Map()) + const base = safeFileName(label).slice(0, 60).replace(/[. ]+$/, '') || '未分类' + const hash = createHash('sha256').update(label).digest('hex').slice(0, 8) + // Sanitized/truncated names get stable suffixes; Windows also ignores letter case. + let name = base === label ? base : base + '_' + hash + for (let n = 1; siblings.has(name.toLowerCase()) && siblings.get(name.toLowerCase()) !== label; n++) { + name = base + '_' + hash + (n === 1 ? '' : '_' + n) + } + siblings.set(name.toLowerCase(), label) + directory = path.join(directory, name) + } + group = { directory, labels, receipts: [] } + groups.set(key, group) + } + group.receipts.push(receipt) + } + return [...groups.values()] +} diff --git a/src/main/exporter.ts b/src/main/exporter.ts new file mode 100644 index 0000000..32c5e97 --- /dev/null +++ b/src/main/exporter.ts @@ -0,0 +1,82 @@ +import { promises as fs } from 'node:fs' +import path from 'node:path' +import { PDFDocument } from 'pdf-lib' +import type { Batch,ExportOptions,ExportProgress } from '../shared/types' +import type { PdfEngine } from './pdf-engine' +import type { Ledger } from './ledger' +import { atomicWrite,uniquePath } from './files' +import { receiptFileStem } from '../shared/file-names' +import { conciseError } from '../shared/errors' +import { groupReceipts } from './export-layout' +export class ExportCoordinator { + private cancelled=false;active=false;private last:ExportProgress|null=null + constructor(private engine:PdfEngine,private ledger:Ledger,private emit:(p:ExportProgress)=>void){} + cancel(){this.cancelled=true} + progress(){return this.last} + async run(batch:Batch,options:ExportOptions,output:string):Promise{ + this.cancelled=false + const errors:string[]=[], manifest:{number:string;directory:string;files:string[];error?:string}[]=[],printFiles:string[]=[] + const groups=groupReceipts(batch.receipts) + const receiptDirectories=new Map(groups.flatMap(group=>group.receipts.map(receipt=>[receipt.number,group.directory] as const))) + const total=batch.receipts.length+(options.print?groups.length:0) + let completed=0;batch.successCount=0;batch.failureCount=0 + const progress=(stage:string,finished=false)=>{ + this.last={batchId:batch.id,completed,total,stage,finished,status:batch.status,outputDirectory:batch.outputDirectory,errors:[...errors]};this.emit(this.last) + } + try{ + await fs.mkdir(output,{recursive:true}) + const now=new Date(),pad=(value:number)=>String(value).padStart(2,'0') + const time=String(now.getFullYear())+pad(now.getMonth()+1)+pad(now.getDate())+'_'+pad(now.getHours())+pad(now.getMinutes())+pad(now.getSeconds()) + const dir=path.join(output,time+'_'+batch.id.slice(0,8)+'_'+Date.now().toString(36).slice(-4)) + await fs.mkdir(dir,{recursive:true});batch.outputDirectory=dir;batch.status='running';await this.ledger.update(batch) + const relative=(file:string)=>path.relative(dir,file).split(path.sep).join('/') + progress('准备生成收据') + for(const receipt of batch.receipts){ + if(this.cancelled)break + const receiptDirectory=path.join(dir,receiptDirectories.get(receipt.number)!) + const entry:{number:string;directory:string;files:string[];error?:string}={number:receipt.number,directory:relative(receiptDirectory),files:[]} + try{ + if(options.pdf||options.png){ + const pdf=await this.engine.pdf([receipt],batch.settings,batch.seal,'single',1,false) + const pattern=batch.settings.fileNamePattern + const stem=receiptFileStem(receipt.number,receipt.customer,pattern) + if(options.pdf){const file=await uniquePath(receiptDirectory,stem+'.pdf');await atomicWrite(file,pdf);entry.files.push(relative(file))} + if(options.png){const png=await this.engine.png(pdf,batch.settings.pngDpi);const file=await uniquePath(receiptDirectory,stem+'.png');await atomicWrite(file,png);entry.files.push(relative(file))} + } + if(options.pdf||options.png)batch.successCount++ + }catch(e){entry.error=conciseError(e);batch.failureCount++;errors.push(receipt.number+':'+entry.error)} + manifest.push(entry);completed++;progress('正在生成 '+receipt.number) + } + if(options.print){ + for(const group of groups){ + if(this.cancelled)break + const label=group.labels.join(' / ') + progress('正在排版 A4 打印文件:'+label) + try{ + const merged=await PDFDocument.create() + // Group boundaries always start a new print file; even chunks avoid extra blank half-pages. + for(let i=0;i { + await fs.mkdir(path.dirname(file),{recursive:true}) + const temp=file+'.'+randomUUID()+'.tmp' + try { await fs.writeFile(temp,content); await fs.rename(temp,file) } + catch(error) { await fs.rm(temp,{force:true}).catch(()=>{}); throw error } +} +export {safeFileName} from '../shared/file-names' +export async function uniquePath(folder:string,name:string):Promise { + const ext=path.extname(name), stem=name.slice(0,-ext.length)||name + for(let n=0;n<10000;n++) {const file=path.join(folder,n?stem+'_'+n+ext:name); try{await fs.access(file)}catch{return file} } + throw Error('同名文件过多,请选择其他文件夹') +} diff --git a/src/main/index.ts b/src/main/index.ts new file mode 100644 index 0000000..11c98bf --- /dev/null +++ b/src/main/index.ts @@ -0,0 +1,220 @@ +import { app,BrowserWindow,dialog,ipcMain,net,protocol,session,shell } from 'electron' +import { promises as fs } from 'node:fs' +import path from 'node:path' +import { pathToFileURL } from 'node:url' +import { z } from 'zod' +import { SettingsStore } from './settings-store' +import { ConfirmationService } from './confirmations' +import { Ledger } from './ledger' +import { XlsxReader,createWorkbookTemplate } from './workbooks' +import { readSeals,readSeal } from './seals' +import { ElectronPdfEngine } from './pdf-engine' +import { printDocuments } from './print-documents' +import { ExportCoordinator } from './exporter' +import { TemplateRegistry,buildDocument } from '../core/templates' +import { settingsSchema,exportOptionsSchema,historyPageSizeSchema } from '../shared/settings' +import {printPreviewRows} from '../shared/receipt-groups' +import type { Batch,ExportRequest,ImportDraft,Receipt,SealImage,ProcessingTask } from '../shared/types' + +protocol.registerSchemesAsPrivileged([{scheme:'receipt',privileges:{standard:true,secure:true,supportFetchAPI:true,corsEnabled:true}}]) +const testMode=!app.isPackaged&&process.argv.includes('--test-mode') +const profileOverride=app.commandLine.getSwitchValue('user-data-dir') +const smokeMode=process.argv.includes('--smoke-test') +if(profileOverride)app.setPath('userData',path.resolve(profileOverride)) +else if(testMode)app.setPath('userData',path.join(process.cwd(),'.qa','profile')) +else if(!app.isPackaged)app.setPath('userData',path.join(process.cwd(),'.local-data','app')) +const locked=testMode||app.requestSingleInstanceLock() +if(!locked)app.quit() +let mainWindow:BrowserWindow|null=null +let coordinator:ExportCoordinator|undefined +const templates=new TemplateRegistry() +const drafts=new Map() +let forceQuit=false,quitPending=false +const confirmations=new ConfirmationService(request=>{ + if(!mainWindow||mainWindow.isDestroyed())throw Error('确认窗口尚未就绪,请重新打开程序') + mainWindow.webContents.send('receipt:confirmation',request) +}) +function requestQuit(){ + if(quitPending||confirmations.hasPending)return + quitPending=true + void confirmations.ask({title:'收据仍在生成',description:'现在退出会中断本次生成,已分配的编号会保留。',variant:'warning',note:'下次打开程序后,可以在生成记录中使用原编号重试。',confirmLabel:'退出程序',cancelLabel:'继续生成'}).then(confirmed=>{ + if(confirmed){forceQuit=true;app.quit()} + }).catch(error=>console.error('Quit confirmation failed',error)).finally(()=>{quitPending=false}) +} + +async function start(){ + const dataDir=app.getPath('userData');await fs.mkdir(dataDir,{recursive:true}) + const settings=new SettingsStore(path.join(dataDir,'settings.json'),path.join(app.getPath('documents'),'收据工坊')) + await settings.load();templates.get(settings.get().templateId) + const ledger=new Ledger(path.join(dataDir,'receipts.sqlite'));await ledger.open();await ledger.recoverInterrupted() + const reader=new XlsxReader() + const rendererDirectory=path.join(__dirname,'../renderer') + const base=process.env.ELECTRON_RENDERER_URL||'receipt://app' + protocol.handle('receipt',async request=>{ + try{ + const url=new URL(request.url) + if(url.hostname==='print'){ + const html=request.method==='GET'?printDocuments.read(url.href):undefined + return html===undefined?new Response('Not found',{status:404}):new Response(html,{headers:{'Content-Type':'text/html; charset=utf-8','Cache-Control':'no-store'}}) + } + if(url.hostname!=='app')return new Response('Not found',{status:404}) + const file=path.resolve(rendererDirectory,'.'+decodeURIComponent(url.pathname||'/index.html')) + if(!file.startsWith(rendererDirectory+path.sep))return new Response('Forbidden',{status:403}) + return await net.fetch(pathToFileURL(file).toString()) + }catch{return new Response('Not found',{status:404})} + }) + session.defaultSession.setPermissionRequestHandler((_wc,_permission,callback)=>callback(false)) + session.defaultSession.webRequest.onBeforeRequest((details,callback)=>{ + const u=new URL(details.url) + const local=['receipt:','data:','blob:','file:','devtools:'].includes(u.protocol) + const development=!!process.env.ELECTRON_RENDERER_URL&&['http:','ws:'].includes(u.protocol)&&u.host===new URL(base).host + callback({cancel:!local&&!development}) + }) + const engine=new ElectronPdfEngine(templates,base) + coordinator=new ExportCoordinator(engine,ledger,p=>{if(mainWindow&&!mainWindow.isDestroyed())mainWindow.webContents.send('receipt:progress',p)}) + const handle=(name:string,fn:(...args:any[])=>unknown)=>{ + ipcMain.handle('receipt:'+name,async(event,...args)=>{ + if(!mainWindow||event.sender!==mainWindow.webContents||event.senderFrame!==mainWindow.webContents.mainFrame)throw Error('无效来源') + try{return {ok:true,value:await fn(...args)}}catch(e){return {ok:false,error:e instanceof z.ZodError?e.issues.map(i=>i.message).join(';'):(e as Error).message||'操作失败'}} + }) + } + // Notify the renderer only when actual processing starts, after native pickers and confirmations. + const processing=(task:ProcessingTask)=>mainWindow?.webContents.send('receipt:processing',task) + const choose=async(kind:'output'|'seal')=>{ + const current=settings.get() + const result=await dialog.showOpenDialog(mainWindow!,{title:kind==='seal'?'选择印章文件夹':'选择导出文件夹',defaultPath:kind==='seal'?current.sealDirectory:current.outputDirectory,properties:['openDirectory','createDirectory']}) + return result.canceled?null:result.filePaths[0] + } + handle('answerConfirmation',input=>{const answer=z.object({id:z.string().uuid(),confirmed:z.boolean()}).parse(input);confirmations.answer(answer.id,answer.confirmed)}) + handle('bootstrap',()=>({settings:settings.get(),batches:ledger.summaries(),historyTotal:ledger.countBatches(),historyFilters:ledger.historyFilters(),nextNumber:ledger.nextNumber(settings.get().numberPrefix),dataDirectory:dataDir,version:app.getVersion(),templates:templates.list()})) + handle('saveSettings',async value=>{const parsed=settingsSchema.parse(value);templates.get(parsed.templateId);return settings.save(parsed)}) + handle('chooseFolder',kind=>choose(z.enum(['output','seal']).parse(kind))) + handle('listSeals',()=>readSeals(settings.get().sealDirectory)) + handle('importExcel',async()=>{ + const current=settings.get() + const result=await dialog.showOpenDialog(mainWindow!,{title:'导入收据 Excel',defaultPath:current.lastImportDirectory||undefined,properties:['openFile'],filters:[{name:'Excel 工作簿',extensions:['xlsx']}]}) + if(result.canceled)return null + processing('import') + const file=result.filePaths[0],draft=await reader.read(file) + draft.previousBatchId=ledger.previous(draft.hash) + if(drafts.size>=3)drafts.delete(drafts.keys().next().value!) + drafts.set(draft.id,draft) + await settings.save({...current,lastImportDirectory:path.dirname(file)}) + return draft + }) + handle('saveExcelTemplate',async()=>{ + const result=await dialog.showSaveDialog(mainWindow!,{title:'保存 Excel 导入模板',defaultPath:path.join(settings.get().lastImportDirectory||app.getPath('documents'),'收据导入模板.xlsx'),filters:[{name:'Excel 工作簿',extensions:['xlsx']}]}) + if(result.canceled||!result.filePath)return null + processing('template') + await createWorkbookTemplate(result.filePath);return result.filePath + }) + async function findSeal(id?:string):Promise{ + if(!id)return null + try{return await readSeal(settings.get().sealDirectory,id)}catch{throw Error('选择的印章已不存在或无法读取,请刷新印章列表后重新选择')} + } + handle('preview',async input=>{ + const request=z.object({draftId:z.string().optional(),rowIndex:z.number().int().nonnegative().optional(),rowIndices:z.array(z.number().int().nonnegative()).max(5000).optional(),batchId:z.string().optional(),receiptNumber:z.string().min(1).max(40).optional(),sealId:z.string().optional(),layout:z.enum(['single','print'])}).parse(input) + let receipts:Receipt[],seal:SealImage|null,current=settings.get() + if(request.batchId){const b=ledger.get(request.batchId),index=request.receiptNumber?b.receipts.findIndex(receipt=>receipt.number===request.receiptNumber):0;if(index<0)throw Error('本批次中没有找到这张收据');receipts=b.receipts.slice(index,index+(request.layout==='single'?1:2));seal=b.seal;current=b.settings} + else { + const draft=request.draftId?drafts.get(request.draftId):null + const row=draft?.rows.find(r=>r.index===request.rowIndex) + if(request.draftId&&(!row||row.errors.length))throw Error('请先选择一行有效数据进行预览') + const input=row?.receipt??{date:new Date().toLocaleDateString('sv-SE'),customer:'某某学校 初二年级 张三',item:'秋季学期服务费',amountCents:350000,cashier:'',reviewer:''} + receipts=[{...input,cashier:input.cashier||current.defaultCashier,reviewer:input.reviewer||current.defaultReviewer,number:ledger.nextNumber(current.numberPrefix)}] + seal=await findSeal(request.sealId) + if(draft&&request.layout==='print'){ + const selected=new Set(request.rowIndices??draft.rows.filter(row=>!row.errors.length).map(row=>row.index)) + const ordered=draft.rows.filter(row=>!row.errors.length&&selected.has(row.index)) + const shown=printPreviewRows(draft.rows,selected,request.rowIndex??0,current.receiptsPerPage) + if(!shown.length)throw Error('请先勾选有效数据再预览打印排版') + const start=Number(ledger.nextNumber(current.numberPrefix).slice(current.numberPrefix.length)) + receipts=shown.map(row=>({...row.receipt,cashier:row.receipt.cashier||current.defaultCashier,reviewer:row.receipt.reviewer||current.defaultReviewer,number:current.numberPrefix+String(start+ordered.indexOf(row)).padStart(8,'0')})) + } + } + return buildDocument(templates.get(current.templateId),receipts,{settings:current,seal,preview:true},request.layout,current.receiptsPerPage,current.cutLine) + }) + handle('exportReceipts',async(input:ExportRequest)=>{ + if(coordinator!.active)throw Error('已有生成任务正在进行,请等待完成或取消') + coordinator!.active=true + try{ + const request=z.object({draftId:z.string().optional(),batchId:z.string().uuid().optional(),rowIndices:z.array(z.number().int().nonnegative()).max(5000).optional(),sealId:z.string().optional(),options:exportOptionsSchema}).parse(input) + const current=settings.get();templates.get(current.templateId) + if(!current.outputDirectory)throw Error('请先在设置中选择导出文件夹') + await fs.mkdir(current.outputDirectory,{recursive:true});await fs.access(current.outputDirectory,fs.constants.W_OK) + let batch:Batch + if(request.batchId)batch=ledger.get(request.batchId) + else { + const draft=request.draftId?drafts.get(request.draftId):null + if(!draft)throw Error('导入数据已失效,请重新导入 Excel') + const indices=new Set(request.rowIndices) + const rows=draft.rows.filter(r=>indices.has(r.index)) + if(!rows.length||rows.length!==indices.size||rows.some(r=>r.errors.length))throw Error('请选择有效的数据行,错误行不能生成') + if(draft.previousBatchId){ + const confirmed=await confirmations.ask({title:'这个文件已经生成过收据',description:'继续生成会为所选数据分配新的收据编号。',variant:'warning',details:[{label:'导入文件',value:draft.fileName}],note:'如需补打,请取消并到生成记录中重新导出,沿用原编号。',confirmLabel:'生成新编号'}) + if(!confirmed)throw Error('已取消,请在生成记录中重新导出') + } + const seal=await findSeal(request.sealId) + batch=await ledger.reserve({name:draft.fileName,hash:draft.hash},rows.map(r=>r.receipt),current,seal) + draft.previousBatchId=batch.id + } + // Reserve and persist numbers before any output file is created. + void coordinator!.run(batch,request.options,current.outputDirectory).then(async()=>{ + if(current.openAfterExport&&coordinator!.progress()?.status==='completed')await shell.openPath(batch.outputDirectory) + }).catch(e=>console.error('Export failed',e)) + return batch.id + }catch(e){coordinator!.active=false;throw e} + }) + handle('cancelExport',()=>{coordinator!.cancel()}) + const batchQuerySchema=z.object({search:z.string().trim().max(200).optional(),school:z.string().max(80).optional(),grade:z.string().max(80).optional(),item:z.string().max(80).optional()}) + handle('listBatches',input=>ledger.summaries(batchQuerySchema.parse(input??{}))) + handle('listBatchPage',input=>ledger.batchPage(batchQuerySchema.extend({page:z.number().int().min(1).max(1000000000).default(1),pageSize:historyPageSizeSchema.default(5)}).parse(input??{}))) + handle('deleteBatch',async input=>{ + const id=z.string().uuid().parse(input) + if(coordinator!.active)throw Error('生成任务结束后才能删除记录') + const batch=ledger.get(id) + const confirmed=await confirmations.ask({ + title:'删除这条生成记录?',description:'删除后,该批次将无法在生成记录中重新导出。',variant:'danger', + details:[{label:'生成批次',value:batch.sourceName},{label:'收据编号',value:batch.receipts[0].number+' 至 '+batch.receipts.at(-1)!.number}], + note:'已导出的 PDF、图片文件保留,收据编号继续递增。',confirmLabel:'删除记录' + }) + if(!confirmed)return false + if(coordinator!.active)throw Error('生成任务结束后才能删除记录') + processing('delete-batch') + await ledger.deleteBatch(id) + for(const draft of drafts.values()){if(draft.previousBatchId===id)draft.previousBatchId=ledger.previous(draft.hash)} + return true + }) + handle('openFolder',async input=>{ + const folder=z.string().max(1024).parse(input) + if(!path.isAbsolute(folder)||(await fs.stat(folder)).isDirectory()===false)throw Error('文件夹不存在') + const allowed=[settings.get().outputDirectory,settings.get().sealDirectory,dataDir].filter(Boolean) + if(!allowed.some(p=>path.resolve(p)===path.resolve(folder))&&!ledger.isOutputDirectory(folder))throw Error('请从已绑定的文件夹或生成记录中打开目录') + const error=await shell.openPath(folder);if(error)throw Error(error) + }) + mainWindow=new BrowserWindow({width:1440,height:940,minWidth:1120,minHeight:740,show:!testMode&&!smokeMode,icon:path.join(__dirname,'../icon.png'),title:'收据工坊',backgroundColor:'#f5f7fa',autoHideMenuBar:true,webPreferences:{preload:path.join(__dirname,'preload.cjs'),sandbox:true,nodeIntegration:false,contextIsolation:true,backgroundThrottling:false}}) + mainWindow.on('close',event=>{if(coordinator?.active&&!forceQuit){event.preventDefault();requestQuit()}}) + mainWindow.on('closed',()=>confirmations.cancel()) + mainWindow.webContents.on('did-navigate',()=>confirmations.cancel()) + mainWindow.webContents.on('render-process-gone',()=>confirmations.cancel()) + if(!app.isPackaged){ + mainWindow.webContents.on('before-input-event',(event,input)=>{ + if(input.type==='keyDown'&&(input.key==='F12'||input.control&&input.shift&&input.key.toLowerCase()==='i')){event.preventDefault();mainWindow?.webContents.toggleDevTools()} + }) + } + mainWindow.webContents.setWindowOpenHandler(()=>({action:'deny'})) + mainWindow.webContents.on('will-navigate',(event,url)=>{if(url!==base+'/index.html'&&url!==base+'/')event.preventDefault()}) + mainWindow.once('ready-to-show',()=>{if(!testMode&&!smokeMode)mainWindow?.show()}) + await mainWindow.loadURL(base+'/index.html') + if(!testMode&&!smokeMode){mainWindow.show();mainWindow.focus()} + if(process.env.ELECTRON_RENDERER_URL)console.log('收据工坊桌面界面已加载。') +} +if(locked)app.whenReady().then(start).catch(async error=>{console.error(error);await dialog.showMessageBox({type:'error',title:'收据工坊启动失败',message:(error as Error).message});app.exit(1)}) +app.on('second-instance',()=>{mainWindow?.show();mainWindow?.focus()}) +app.on('window-all-closed',()=>app.quit()) +app.on('before-quit',event=>{ + if(coordinator?.active&&!forceQuit){ + event.preventDefault() + requestQuit() + } +}) diff --git a/src/main/ledger.ts b/src/main/ledger.ts new file mode 100644 index 0000000..ab41bf9 --- /dev/null +++ b/src/main/ledger.ts @@ -0,0 +1,135 @@ +import initSqlJs from 'sql.js/dist/sql-wasm-inline.js' +import type { Database,SqlJsStatic } from 'sql.js' +import { promises as fs } from 'node:fs' +import { randomUUID } from 'node:crypto' +import path from 'node:path' +import { z } from 'zod' +import { settingsSchema } from '../shared/settings' +import {historyPageSizes} from '../shared/types' +import type { AppSettings,Batch,BatchSummary,BatchQuery,BatchPageQuery,BatchPage,HistoryFilters,ReceiptInput,SealImage } from '../shared/types' +import {receiptGroupLabels,summarizeReceiptGroups} from '../shared/receipt-groups' +import { atomicWrite } from './files' +import { conciseError } from '../shared/errors' +const receiptSchema=z.object({number:z.string().min(1).max(40),date:z.string().regex(/^\d{4}-\d{2}-\d{2}$/),school:z.string().max(80).optional(),grade:z.string().max(80).optional(),studentName:z.string().max(80).optional(),customer:z.string().min(1).max(80),item:z.string().min(1).max(80),amountCents:z.number().int().positive().max(999999999),cashier:z.string().max(20),reviewer:z.string().max(20)}) +const batchSchema=z.object({id:z.string().uuid(),createdAt:z.string(),sourceName:z.string(),sourceHash:z.string(),receipts:z.array(receiptSchema).min(1).max(5000),settings:settingsSchema,seal:z.object({id:z.string(),name:z.string(),dataUrl:z.string().regex(/^data:image\/(png|jpeg|webp);base64,/)}).nullable(),status:z.enum(['reserved','running','completed','partial','failed','cancelled']),outputDirectory:z.string(),successCount:z.number().int().nonnegative(),failureCount:z.number().int().nonnegative(),message:z.string()}) +export class Ledger { + private db!:Database; private sql!:SqlJsStatic;private queue:Promise=Promise.resolve() + constructor(private readonly file:string){} + async open():Promise{ + this.sql=await initSqlJs() + try{this.db=new this.sql.Database(await fs.readFile(this.file));if(this.db.exec('PRAGMA integrity_check')[0]?.values[0]?.[0]!=='ok')throw Error('integrity')} + catch(e){if((e as NodeJS.ErrnoException).code==='ENOENT')this.db=new this.sql.Database();else throw Error('编号记录无法读取。为避免重复编号,程序已停止分配编号;原文件已保留,请联系维护人员处理。')} + this.db.run('CREATE TABLE IF NOT EXISTS sequences (prefix TEXT PRIMARY KEY, next_value INTEGER NOT NULL); CREATE TABLE IF NOT EXISTS batches (id TEXT PRIMARY KEY, created_at TEXT NOT NULL, source_hash TEXT NOT NULL, data TEXT NOT NULL); CREATE TABLE IF NOT EXISTS receipt_numbers (number TEXT PRIMARY KEY, batch_id TEXT NOT NULL);') + } + private values(sql:string,params:(string|number)[]=[]):unknown[][]{ + const s=this.db.prepare(sql);try{s.bind(params);const rows:unknown[][]=[];while(s.step())rows.push(s.get());return rows}finally{s.free()} + } + private mutate(action:()=>T):Promise{ + const work=this.queue.then(async()=>{ + const before=this.db.export() + try{this.db.run('BEGIN IMMEDIATE');const result=action();this.db.run('COMMIT');await atomicWrite(this.file,this.db.export());return result} + catch(e){this.db.close();this.db=new this.sql.Database(before);throw e} + });this.queue=work.catch(()=>{});return work + } + nextNumber(prefix:string):string {return prefix+String(this.values('SELECT next_value FROM sequences WHERE prefix=?',[prefix])[0]?.[0]??1).padStart(8,'0')} + async reserve(source:{name:string;hash:string},inputs:ReceiptInput[],settings:AppSettings,seal:SealImage|null):Promise{ + return this.mutate(()=>{ + if(!inputs.length||inputs.length>5000)throw Error('收据数量无效') + const prefix=settings.numberPrefix + const start=Number(this.values('SELECT next_value FROM sequences WHERE prefix=?',[prefix])[0]?.[0]??1) + if(start+inputs.length-1>99999999)throw Error('当前编号前缀的流水号已用完,请更换前缀') + const batch:Batch={id:randomUUID(),createdAt:new Date().toISOString(),sourceName:source.name,sourceHash:source.hash,receipts:inputs.map((r,i)=>({...r,cashier:r.cashier||settings.defaultCashier,reviewer:r.reviewer||settings.defaultReviewer,number:prefix+String(start+i).padStart(8,'0')})),settings:{...settings},seal,status:'reserved',outputDirectory:'',successCount:0,failureCount:0,message:''} + batchSchema.parse(batch) + this.db.run('INSERT INTO sequences(prefix,next_value) VALUES(?,?) ON CONFLICT(prefix) DO UPDATE SET next_value=excluded.next_value',[prefix,start+inputs.length]) + this.db.run('INSERT INTO batches VALUES(?,?,?,?)',[batch.id,batch.createdAt,batch.sourceHash,JSON.stringify(batch)]) + for(const r of batch.receipts)this.db.run('INSERT INTO receipt_numbers VALUES(?,?)',[r.number,batch.id]) + return batch + }) + } + get(id:string):Batch {const row=this.values('SELECT data FROM batches WHERE id=?',[id])[0];if(!row)throw Error('生成记录不存在');return batchSchema.parse(JSON.parse(String(row[0])))} + async update(batch:Batch):Promise{await this.mutate(()=>{batchSchema.parse(batch);this.db.run('UPDATE batches SET data=? WHERE id=?',[JSON.stringify(batch),batch.id])})} + async deleteBatch(id:string):Promise{ + await this.mutate(()=>{ + const batch=this.get(id) + if(batch.status==='reserved'||batch.status==='running')throw Error('生成中的批次不能删除,请等待完成或取消') + // Keep issued numbers and sequences reserved after removing the batch snapshot. + this.db.run('DELETE FROM batches WHERE id=?',[id]) + }) + } + private *matchingBatches(query:BatchQuery):Generator<{batch:Batch;matches:Batch['receipts']}>{ + const search=query.search?.trim().toLocaleLowerCase()||'' + const filtering=!!(search||query.school||query.grade||query.item) + const statement=this.db.prepare('SELECT data FROM batches ORDER BY created_at DESC,rowid DESC') + try{ + while(statement.step()){ + const b=batchSchema.parse(JSON.parse(String(statement.get()[0]))) + const sourceMatches=!!search&&b.sourceName.toLocaleLowerCase().includes(search) + const matches=filtering?b.receipts.filter(receipt=>{ + const [school,grade,item]=receiptGroupLabels(receipt) + return (!query.school||school===query.school)&&(!query.grade||grade===query.grade)&&(!query.item||item===query.item)&& + (!search||sourceMatches||[receipt.number,receipt.customer,receipt.studentName||'',school,grade,item].some(value=>value.toLocaleLowerCase().includes(search))) + }):b.receipts + if(matches.length)yield {batch:b,matches} + } + }finally{statement.free()} + } + private summarizeBatch(b:Batch,matches:Batch['receipts'],query:BatchQuery):BatchSummary{ + const filtering=!!(query.search?.trim()||query.school||query.grade||query.item) + return {id:b.id,createdAt:b.createdAt,sourceName:b.sourceName,count:b.receipts.length,totalCents:b.receipts.reduce((s,r)=>s+r.amountCents,0),firstNumber:b.receipts[0].number,lastNumber:b.receipts.at(-1)!.number,status:b.status,outputDirectory:b.outputDirectory,message:conciseError(b.message),groups:summarizeReceiptGroups(b.receipts),...(filtering?{match:{count:matches.length,number:matches[0].number,customer:matches[0].customer}}:{})} + } + summaries(query:BatchQuery={}):BatchSummary[]{ + const result:BatchSummary[]=[] + for(const {batch,matches} of this.matchingBatches(query)){ + result.push(this.summarizeBatch(batch,matches,query)) + if(result.length===200)break + } + return result + } + batchPage(query:BatchPageQuery={}):BatchPage{ + const pageSize=query.pageSize&&historyPageSizes.includes(query.pageSize)?query.pageSize:5 + const requested=Math.max(1,Math.trunc(query.page||1)) + const filtering=!!(query.search?.trim()||query.school||query.grade||query.item) + if(!filtering){ + const total=this.countBatches() + const page=Math.min(requested,Math.max(1,Math.ceil(total/pageSize))) + const items=this.values('SELECT data FROM batches ORDER BY created_at DESC,rowid DESC LIMIT ? OFFSET ?',[pageSize,(page-1)*pageSize]).map(row=>{ + const batch=batchSchema.parse(JSON.parse(String(row[0]))) + return this.summarizeBatch(batch,batch.receipts,query) + }) + return {items,total,page,pageSize} + } + const selected:{batch:Batch;matches:Batch['receipts']}[]=[],offset=(requested-1)*pageSize + let total=0,tail:typeof selected=[] + for(const entry of this.matchingBatches(query)){ + if(total%pageSize===0)tail=[] + tail.push(entry) + if(total>=offset&&selected.lengththis.summarizeBatch(batch,matches,query)),total,page,pageSize} + } + countBatches():number{return Number(this.values('SELECT COUNT(*) FROM batches')[0][0])} + historyFilters():HistoryFilters{ + const sets=[new Set(),new Set(),new Set()] + const statement=this.db.prepare('SELECT data FROM batches') + try{while(statement.step()){ + const b=batchSchema.parse(JSON.parse(String(statement.get()[0]))) + for(const receipt of b.receipts)receiptGroupLabels(receipt).forEach((label,i)=>sets[i].add(label)) + }}finally{statement.free()} + const [schools,grades,items]=sets.map(values=>[...values].sort((a,b)=>a.localeCompare(b,'zh-CN',{numeric:true}))) + return {schools,grades,items} + } + isOutputDirectory(folder:string):boolean{ + const resolved=path.resolve(folder),statement=this.db.prepare('SELECT data FROM batches') + try{while(statement.step()){ + const batch=batchSchema.parse(JSON.parse(String(statement.get()[0]))) + if(batch.outputDirectory&&path.resolve(batch.outputDirectory)===resolved)return true + }}finally{statement.free()} + return false + } + previous(hash:string):string|undefined{return this.values('SELECT id FROM batches WHERE source_hash=? ORDER BY created_at DESC LIMIT 1',[hash])[0]?.[0] as string|undefined} + async recoverInterrupted():Promise{ + for(const row of this.values('SELECT data FROM batches')){const b=batchSchema.parse(JSON.parse(String(row[0])));if(['running','reserved'].includes(b.status)){b.status='partial';b.message='上次生成被中断,可使用原编号重新导出';await this.update(b)}} + } +} diff --git a/src/main/pdf-engine.ts b/src/main/pdf-engine.ts new file mode 100644 index 0000000..89f4148 --- /dev/null +++ b/src/main/pdf-engine.ts @@ -0,0 +1,35 @@ +import { BrowserWindow } from 'electron' +import type { AppSettings,Receipt,SealImage } from '../shared/types' +import { buildDocument,TemplateRegistry } from '../core/templates' +import { conciseError } from '../shared/errors' +import { printDocuments } from './print-documents' +import { withPngDensity } from './png-density' +export interface PdfEngine { + pdf(receipts:Receipt[],settings:AppSettings,seal:SealImage|null,layout:'single'|'print',perPage:1|2,cutLine:boolean):Promise; + png(pdf:Uint8Array,dpi:number):Promise; + close():void; +} +export class ElectronPdfEngine implements PdfEngine { + private printWindow:BrowserWindow|null=null;private rasterWindow:BrowserWindow|null=null + constructor(private templates:TemplateRegistry,private rendererBase:string){} + async pdf(receipts:Receipt[],settings:AppSettings,seal:SealImage|null,layout:'single'|'print',perPage:1|2,cutLine:boolean):Promise{ + this.printWindow??=new BrowserWindow({show:false,width:1000,height:1100,webPreferences:{sandbox:true,contextIsolation:true,nodeIntegration:false,backgroundThrottling:false}}) + const html=buildDocument(this.templates.get(settings.templateId),receipts,{settings,seal,preview:false},layout,perPage,cutLine) + const document=printDocuments.publish(html) + let stage='加载收据页面' + try{ + await this.printWindow.loadURL(document.url) + stage='加载字体或印章' + await this.printWindow.webContents.executeJavaScript('Promise.all([document.fonts.ready, ...Array.from(document.images).map(image => image.decode())]).then(()=>true)') + stage='生成 PDF' + return await this.printWindow.webContents.printToPDF({preferCSSPageSize:true,printBackground:true,displayHeaderFooter:false,margins:{top:0,bottom:0,left:0,right:0}}) + }catch(error){throw Error(stage+'失败:'+conciseError(error))} + finally{document.dispose()} + } + async png(pdf:Uint8Array,dpi:number):Promise{ + if(!this.rasterWindow){this.rasterWindow=new BrowserWindow({show:false,webPreferences:{sandbox:true,contextIsolation:true,nodeIntegration:false,backgroundThrottling:false}});await this.rasterWindow.loadURL(this.rendererBase+'/raster.html')} + const data=await this.rasterWindow.webContents.executeJavaScript('window.renderPdf('+JSON.stringify(Buffer.from(pdf).toString('base64'))+','+dpi+')') as string + return withPngDensity(Buffer.from(data.split(',')[1],'base64'),dpi) + } + close():void{this.printWindow?.destroy();this.rasterWindow?.destroy();this.printWindow=null;this.rasterWindow=null} +} diff --git a/src/main/png-density.ts b/src/main/png-density.ts new file mode 100644 index 0000000..63fd092 --- /dev/null +++ b/src/main/png-density.ts @@ -0,0 +1,22 @@ +function crc32(bytes:Uint8Array):number { + let crc=0xffffffff + for(const byte of bytes){crc^=byte;for(let bit=0;bit<8;bit++)crc=(crc>>>1)^((crc&1)?0xedb88320:0)} + return (crc^0xffffffff)>>>0 +} +// Canvas serialisation may tag PNGs as 96 DPI. Store the requested physical resolution explicitly. +export function withPngDensity(input:Buffer,dpi:number):Buffer { + if(!input.subarray(0,8).equals(Buffer.from([137,80,78,71,13,10,26,10])))throw Error('PNG 数据无效') + const body=Buffer.alloc(13);body.write('pHYs',0,'ascii') + const pixelsPerMeter=Math.round(dpi/0.0254);body.writeUInt32BE(pixelsPerMeter,4);body.writeUInt32BE(pixelsPerMeter,8);body[12]=1 + const chunk=Buffer.alloc(21);chunk.writeUInt32BE(9,0);body.copy(chunk,4);chunk.writeUInt32BE(crc32(body),17) + const parts:Buffer[]=[input.subarray(0,8)] + for(let offset=8;offsetinput.length)throw Error('PNG 文件不完整') + const length=input.readUInt32BE(offset),end=offset+length+12,type=input.toString('ascii',offset+4,offset+8) + if(end>input.length)throw Error('PNG 文件不完整') + if(type!=='pHYs')parts.push(input.subarray(offset,end)) + if(type==='IHDR')parts.push(chunk) + offset=end + } + return Buffer.concat(parts) +} diff --git a/src/main/preload.ts b/src/main/preload.ts new file mode 100644 index 0000000..245b764 --- /dev/null +++ b/src/main/preload.ts @@ -0,0 +1,18 @@ +import { contextBridge,ipcRenderer } from 'electron' +import type { ReceiptAPI,ExportProgress,ConfirmationRequest,ProcessingTask } from '../shared/types' +async function call(method:string,...args:unknown[]):Promise{ + const result=await ipcRenderer.invoke('receipt:'+method,...args) + if(!result.ok)throw Error(result.error) + return result.value as T +} +const api:ReceiptAPI={ + bootstrap:()=>call('bootstrap'),saveSettings:s=>call('saveSettings',s),chooseFolder:k=>call('chooseFolder',k), + importExcel:()=>call('importExcel'),saveExcelTemplate:()=>call('saveExcelTemplate'),listSeals:()=>call('listSeals'), + preview:r=>call('preview',r),exportReceipts:r=>call('exportReceipts',r),cancelExport:()=>call('cancelExport'), + listBatches:query=>call('listBatches',query),listBatchPage:query=>call('listBatchPage',query),deleteBatch:id=>call('deleteBatch',id),openFolder:p=>call('openFolder',p), + answerConfirmation:(id,confirmed)=>call('answerConfirmation',{id,confirmed}), + onProcessing:callback=>{const listener=(_event:Electron.IpcRendererEvent,task:ProcessingTask)=>callback(task);ipcRenderer.on('receipt:processing',listener);return()=>ipcRenderer.removeListener('receipt:processing',listener)}, + onConfirmation:callback=>{const listener=(_event:Electron.IpcRendererEvent,data:ConfirmationRequest)=>callback(data);ipcRenderer.on('receipt:confirmation',listener);return()=>ipcRenderer.removeListener('receipt:confirmation',listener)}, + onProgress:callback=>{const listener=(_event:Electron.IpcRendererEvent,data:ExportProgress)=>callback(data);ipcRenderer.on('receipt:progress',listener);return()=>ipcRenderer.removeListener('receipt:progress',listener)} +} +contextBridge.exposeInMainWorld('receipts',api) diff --git a/src/main/print-documents.ts b/src/main/print-documents.ts new file mode 100644 index 0000000..cc8d324 --- /dev/null +++ b/src/main/print-documents.ts @@ -0,0 +1,13 @@ +import { randomUUID } from 'node:crypto' + +// Keep print content in memory. The renderer receives a short local URL even when stamps are large. +export class PrintDocumentStore { + private documents=new Map() + publish(html:string):{url:string;dispose:()=>void}{ + const url='receipt://print/'+randomUUID()+'/index.html' + this.documents.set(url,html) + return {url,dispose:()=>{this.documents.delete(url)}} + } + read(url:string):string|undefined{return this.documents.get(url)} +} +export const printDocuments=new PrintDocumentStore() diff --git a/src/main/seals.ts b/src/main/seals.ts new file mode 100644 index 0000000..62aea15 --- /dev/null +++ b/src/main/seals.ts @@ -0,0 +1,28 @@ +import { nativeImage } from 'electron' +import { promises as fs } from 'node:fs' +import path from 'node:path' +import type { SealImage,SealList } from '../shared/types' +const cache=new Map() +export async function readSeal(folder:string,name:string):Promise{ + if(!folder||name!==path.basename(name)||!/^.+\.(png|jpe?g|webp)$/i.test(name))throw Error('印章文件名无效') + const file=path.join(folder,name),stat=await fs.stat(file) + if(!stat.isFile()||stat.size>8*1024*1024)throw Error('印章图片须小于 8 MB') + const signature=stat.mtimeMs+':'+stat.size,hit=cache.get(file) + if(hit?.signature===signature)return hit.image + let img=nativeImage.createFromPath(file);const size=img.getSize() + if(img.isEmpty()||Math.max(size.width,size.height)>8192)throw Error('图片无法读取或尺寸过大') + if(Math.max(size.width,size.height)>1000)img=img.resize(size.width>=size.height?{width:1000}:{height:1000}) + const image={id:name,name:path.parse(name).name,dataUrl:img.toDataURL()} + if(cache.size>=100)cache.delete(cache.keys().next().value!) + cache.set(file,{signature,image});return image +} +export async function readSeals(folder:string):Promise{ + if(!folder)return {images:[],warnings:[]} + let names:string[] + try{names=await fs.readdir(folder)}catch{throw Error('印章文件夹无法访问,请检查是否被移动,或在设置中重新绑定')} + const result:SealList={images:[],warnings:[]} + const files=names.filter(n=>/\.(png|jpe?g|webp)$/i.test(n)).sort((a,b)=>a.localeCompare(b,'zh-CN')) + if(files.length>100)result.warnings.push('仅加载前 100 张印章,请整理文件夹后刷新') + for(const name of files.slice(0,100)){try{result.images.push(await readSeal(folder,name))}catch{result.warnings.push(name+' 无法加载,请使用有效的 PNG、JPG 或 WebP 图片')}} + return result +} diff --git a/src/main/settings-store.ts b/src/main/settings-store.ts new file mode 100644 index 0000000..b6a1451 --- /dev/null +++ b/src/main/settings-store.ts @@ -0,0 +1,24 @@ +import { promises as fs } from 'node:fs' +import path from 'node:path' +import { defaultSettings,settingsSchema } from '../shared/settings' +import type { AppSettings } from '../shared/types' +import { atomicWrite } from './files' +function activeSettings(input:unknown):AppSettings{ + // Retain legacy fields for old files and snapshots; these behaviors are now always enabled. + return {...settingsSchema.parse(input),createBatchFolder:true,openAfterExport:true} +} +export class SettingsStore { + private current:AppSettings + constructor(private readonly file:string,output:string){this.current=defaultSettings(output)} + async load():Promise{ + try {this.current=activeSettings({...this.current,...JSON.parse(await fs.readFile(this.file,'utf8'))})} + catch(e){if((e as NodeJS.ErrnoException).code!=='ENOENT')throw Error('设置文件无法读取,原文件已保留,请联系维护人员处理')} + return this.get() + } + get():AppSettings{return {...this.current}} + async save(input:unknown):Promise{ + const next=activeSettings(input) + for(const folder of [next.outputDirectory,next.sealDirectory]) if(folder&&!path.isAbsolute(folder))throw Error('请选择完整的文件夹路径') + await atomicWrite(this.file,JSON.stringify(next,null,2));this.current=next;return this.get() + } +} diff --git a/src/main/workbooks.ts b/src/main/workbooks.ts new file mode 100644 index 0000000..e57348f --- /dev/null +++ b/src/main/workbooks.ts @@ -0,0 +1,71 @@ +import ExcelJS from 'exceljs' +import { createHash,randomUUID } from 'node:crypto' +import { promises as fs } from 'node:fs' +import path from 'node:path' +import { parseMoney } from '../core/money' +import { parseReceiptDate } from '../core/date' +import type { ImportDraft,ImportRow } from '../shared/types' +export const MAX_ROWS=5000 +const fields=['收款日期','学校','年级','姓名','收费项目','金额','出纳','复核'] as const +const requiredFields=fields.slice(0,6) +function value(cell:ExcelJS.Cell):unknown { + const v=cell.value + if(v&&typeof v==='object'&&'formula' in v) return v.result + if(v&&typeof v==='object'&&'richText' in v) return v.richText.map(x=>x.text).join('') + if(v&&typeof v==='object'&&'text' in v) return v.text + return v +} +function text(v:unknown):string { return v===null||v===undefined?'':String(v).trim() } +export interface WorkbookReader { read(file:string):Promise } +export class XlsxReader implements WorkbookReader { + async read(file:string):Promise { + if(path.extname(file).toLowerCase()!=='.xlsx') throw Error('请使用 .xlsx 文件;旧版 .xls 请先另存为 .xlsx') + const stat=await fs.stat(file); if(stat.size>20*1024*1024) throw Error('Excel 文件不能超过 20 MB') + const bytes=await fs.readFile(file) + const book=new ExcelJS.Workbook() + try{await book.xlsx.load(bytes as unknown as ExcelJS.Buffer)}catch{throw Error('无法读取 Excel,请检查文件是否损坏或设置了密码')} + const sheet=book.worksheets.find(s=>s.state==='visible'); if(!sheet) throw Error('文件中没有可读取的工作表') + if(sheet.rowCount>MAX_ROWS+1) throw Error('单次最多导入 '+MAX_ROWS+' 行,请拆分文件后导入') + const columns=new Map() + sheet.getRow(1).eachCell((cell,col)=>{const name=text(value(cell));if(fields.includes(name as typeof fields[number])){if(columns.has(name))throw Error('表头重复:'+name);columns.set(name,col)}}) + const missing=requiredFields.filter(f=>!columns.has(f)) + if(missing.length) throw Error('第一行缺少表头:'+missing.join('、')+'。请使用程序提供的 Excel 模板') + const rows:ImportRow[]=[], seen=new Set() + for(let n=2;n<=sheet.rowCount;n++){ + const row=sheet.getRow(n), get=(field:string)=>columns.has(field)?value(row.getCell(columns.get(field)!)):null + if(fields.every(f=>!text(get(f)))) continue + const errors:string[]=[],warnings:string[]=[] + let date='',amountCents=0 + try{date=parseReceiptDate(get('收款日期'),book.properties.date1904)}catch(e){errors.push((e as Error).message)} + try{amountCents=parseMoney(text(get('金额')))}catch(e){errors.push((e as Error).message)} + const school=text(get('学校')),grade=text(get('年级')),studentName=text(get('姓名')) + const customer=[school,grade,studentName].filter(Boolean).join(' ') + const item=text(get('收费项目')),cashier=text(get('出纳')),reviewer=text(get('复核')) + if(!school)errors.push('学校不能为空') + if(!grade)errors.push('年级不能为空') + if(!studentName)errors.push('姓名不能为空') + if(customer.length>80)errors.push('学校、年级、姓名拼接后最多 80 个字符') + if(!item)errors.push('收费项目不能为空');else if(item.length>80)errors.push('收费项目最多 80 个字符') + if(cashier.length>20||reviewer.length>20)errors.push('出纳、复核姓名最多 20 个字符') + const receipt={date,school,grade,studentName,customer,item,amountCents,cashier,reviewer} + const key=JSON.stringify(receipt);if(seen.has(key))warnings.push('与文件中的另一行内容相同,请核对是否重复');seen.add(key) + rows.push({index:rows.length,excelRow:n,receipt,errors,warnings}) + } + if(!rows.length) throw Error('Excel 中没有收据数据,请从第二行开始填写') + return {id:randomUUID(),fileName:path.basename(file),hash:createHash('sha256').update(bytes).digest('hex'),rows} + } +} +export async function createWorkbookTemplate(file:string):Promise{ + const book=new ExcelJS.Workbook(),sheet=book.addWorksheet('收据数据') + sheet.columns=fields.map((name,i)=>({header:name,key:name,width:[18,30,20,18,38,18,16,16][i]})) + sheet.addRow(['2026-10-16','某某学校','初二年级','张三','秋季学期服务费',3500,'','']) + sheet.getRow(1).height=28 + sheet.getRow(1).eachCell(c=>{c.font={bold:true,color:{argb:'FFFFFFFF'}};c.fill={type:'pattern',pattern:'solid',fgColor:{argb:'FF157F73'}};c.alignment={vertical:'middle'}}) + for(const column of [1,2,3,4])sheet.getColumn(column).numFmt='@' + sheet.getColumn(6).numFmt='0.00' + sheet.views=[{state:'frozen',ySplit:1}];sheet.autoFilter='A1:H1' + const info=book.addWorksheet('填写说明') + for(const line of ['每行生成一张收据,第一行表头请保留。','收款日期、学校、年级、姓名、收费项目、金额为必填。','收据客户名称按“学校 年级 姓名”拼接,各列之间使用一个空格。','每列首尾空白会自动去除;拼接后最多 80 个字符。','金额须大于 0,最多两位小数,最高 9,999,999.99 元。','出纳、复核留空时使用程序设置中的默认值。','编号由系统生成,Excel 中不需要填写。','第一行数据是示例,请替换或删除。','单次最多导入 5000 行,支持 .xlsx。'])info.addRow([line]) + info.getColumn(1).width=95 + await book.xlsx.writeFile(file) +} diff --git a/src/renderer/App.vue b/src/renderer/App.vue new file mode 100644 index 0000000..b81a3d0 --- /dev/null +++ b/src/renderer/App.vue @@ -0,0 +1,44 @@ + + diff --git a/src/renderer/components/ActionButton.vue b/src/renderer/components/ActionButton.vue new file mode 100644 index 0000000..d3c5aaf --- /dev/null +++ b/src/renderer/components/ActionButton.vue @@ -0,0 +1,12 @@ + + diff --git a/src/renderer/components/ConfirmDialog.vue b/src/renderer/components/ConfirmDialog.vue new file mode 100644 index 0000000..65e8061 --- /dev/null +++ b/src/renderer/components/ConfirmDialog.vue @@ -0,0 +1,24 @@ + + diff --git a/src/renderer/components/LoadingRegion.vue b/src/renderer/components/LoadingRegion.vue new file mode 100644 index 0000000..e618300 --- /dev/null +++ b/src/renderer/components/LoadingRegion.vue @@ -0,0 +1,46 @@ + + diff --git a/src/renderer/components/ReceiptGroupTree.vue b/src/renderer/components/ReceiptGroupTree.vue new file mode 100644 index 0000000..32682da --- /dev/null +++ b/src/renderer/components/ReceiptGroupTree.vue @@ -0,0 +1,29 @@ + + diff --git a/src/renderer/components/ReceiptPreview.vue b/src/renderer/components/ReceiptPreview.vue new file mode 100644 index 0000000..ec365c7 --- /dev/null +++ b/src/renderer/components/ReceiptPreview.vue @@ -0,0 +1,20 @@ + + diff --git a/src/renderer/confirmations.ts b/src/renderer/confirmations.ts new file mode 100644 index 0000000..3fa2227 --- /dev/null +++ b/src/renderer/confirmations.ts @@ -0,0 +1,23 @@ +import { shallowRef } from 'vue' +import type { ConfirmationOptions } from '../shared/types' +export const confirmation=shallowRef(null) +let resolvePending:((confirmed:boolean)=>void)|null=null +let previousFocus:HTMLElement|null=null + +export function askConfirmation(options:ConfirmationOptions):Promise{ + if(resolvePending)return Promise.resolve(false) + previousFocus=document.activeElement instanceof HTMLElement?document.activeElement:null + confirmation.value=options + return new Promise(resolve=>{resolvePending=resolve}) +} +export function answerConfirmation(confirmed:boolean):void{ + const resolve=resolvePending + resolvePending=null;confirmation.value=null + resolve?.(confirmed) +} +export function restoreConfirmationFocus(event:Event):void{ + event.preventDefault() + if(previousFocus?.isConnected&&previousFocus!==document.body&&!previousFocus.matches(':disabled'))previousFocus.focus({preventScroll:true}) + else document.querySelector('.nav-item.active')?.focus({preventScroll:true}) + previousFocus=null +} diff --git a/src/renderer/env.d.ts b/src/renderer/env.d.ts new file mode 100644 index 0000000..2a1b396 --- /dev/null +++ b/src/renderer/env.d.ts @@ -0,0 +1,3 @@ +import type { ReceiptAPI } from '../shared/types' +declare global { interface Window { receipts: ReceiptAPI } } +export {} diff --git a/src/renderer/index.html b/src/renderer/index.html new file mode 100644 index 0000000..0796af5 --- /dev/null +++ b/src/renderer/index.html @@ -0,0 +1 @@ +收据工坊
\ No newline at end of file diff --git a/src/renderer/loading.ts b/src/renderer/loading.ts new file mode 100644 index 0000000..240d488 --- /dev/null +++ b/src/renderer/loading.ts @@ -0,0 +1,21 @@ +import { computed } from 'vue' +import { state } from './state' +export type PanelScope='settings'|'seals'|'history'|'data'|'preview'|'export' +const labels:Record={ + settings:'正在保存设置…', + 'refresh-seals':'正在刷新印章列表…', + 'refresh-records':'正在刷新生成记录…','history-preview':'正在加载收据预览…','delete-batch':'正在删除批次记录…', + 'history-page':'正在加载生成记录…', + import:'正在导入收据数据…',template:'正在保存导入模板…',export:'正在准备生成…',cancel:'正在取消…' +} +export function usePanelState(scope:PanelScope){ + return computed(()=>{ + const error=state.panelErrors.get(scope)||'' + if(state.exporting&&scope===state.exportPanel){ + const progress=state.progress?Math.round(state.progress.completed/Math.max(1,state.progress.total)*100):undefined + return {loading:true,delay:0,label:state.cancelRequested?'正在取消…':state.progress?.stage||'正在准备生成收据…',progress,error} + } + const task=[...state.loadingTasks].find(key=>state.taskPanels.get(key)===scope) + return {loading:!!task||scope==='preview'&&state.previewLoading,delay:task?0:120,label:task?labels[task]||'正在处理…':'正在更新收据预览…',progress:undefined,error} + }) +} diff --git a/src/renderer/main.ts b/src/renderer/main.ts new file mode 100644 index 0000000..4387016 --- /dev/null +++ b/src/renderer/main.ts @@ -0,0 +1,4 @@ +import { createApp } from 'vue' +import App from './App.vue' +import './style.css' +createApp(App).mount('#app') diff --git a/src/renderer/public/licenses/reka-ui.txt b/src/renderer/public/licenses/reka-ui.txt new file mode 100644 index 0000000..c6be661 --- /dev/null +++ b/src/renderer/public/licenses/reka-ui.txt @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 UnoVue + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/src/renderer/raster.html b/src/renderer/raster.html new file mode 100644 index 0000000..5596baa --- /dev/null +++ b/src/renderer/raster.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/renderer/raster.ts b/src/renderer/raster.ts new file mode 100644 index 0000000..049ff37 --- /dev/null +++ b/src/renderer/raster.ts @@ -0,0 +1,15 @@ +import { getDocument,GlobalWorkerOptions } from 'pdfjs-dist' +import workerUrl from 'pdfjs-dist/build/pdf.worker.min.mjs?url' +GlobalWorkerOptions.workerSrc=workerUrl +declare global { interface Window {renderPdf:(base64:string,dpi:number)=>Promise} } +window.renderPdf=async(base64,dpi)=>{ + const data=Uint8Array.from(atob(base64),v=>v.charCodeAt(0)) + const doc=await getDocument({data}).promise + try{ + const page=await doc.getPage(1),viewport=page.getViewport({scale:dpi/72}) + const canvas=document.getElementById('canvas') as HTMLCanvasElement + canvas.width=Math.ceil(viewport.width);canvas.height=Math.ceil(viewport.height) + await page.render({canvas,canvasContext:canvas.getContext('2d')!,viewport,background:'white'}).promise + return canvas.toDataURL('image/png') + }finally{await doc.destroy()} +} diff --git a/src/renderer/state.ts b/src/renderer/state.ts new file mode 100644 index 0000000..712582d --- /dev/null +++ b/src/renderer/state.ts @@ -0,0 +1,176 @@ +import { nextTick,reactive } from 'vue' +import { defaultSettings } from '../shared/settings' +import { conciseError } from '../shared/errors' +import type { AppSettings,BatchSummary,ExportProgress,HistoryFilters,ImportDraft,SealImage } from '../shared/types' +export const state=reactive({ + page:'workbench' as 'workbench'|'seals'|'history'|'settings',settings:defaultSettings(),draft:null as ImportDraft|null, + selected:new Set(),rowIndex:0,seals:[] as SealImage[],sealWarnings:[] as string[],currentSealId:'', + previewHtml:'',previewLayout:'single' as 'single'|'print',previewLoading:false,batches:[] as BatchSummary[],historyTotal:0,historyFilters:{schools:[],grades:[],items:[]} as HistoryFilters,nextNumber:'', + dataDirectory:'',version:'1.0.0',templates:[] as {id:string;name:string}[], + settingsDirty:false,busy:new Set(),loadingTasks:new Set(),taskTargets:new Map(),taskPanels:new Map(),panelErrors:new Map(), + error:'',cancelRequested:false,exporting:false,exportBatchId:'',exportPanel:'export',progress:null as ExportProgress|null,ready:false +}) +interface TaskFeedback {target?:string;feedback?:boolean;scope?:string} +const processingTasks=new Set(['import','template','delete-batch']) +const quietTasks=new Set(['init','settings-folder','folder','open','seal','cancel']) +const loadingMinimums=new Map>() +function startLoading(key:string,minimumMs=500){ + if(state.loadingTasks.has(key))return + state.loadingTasks.add(key) + loadingMinimums.set(key,(async()=>{ + await nextTick() + // Count visible time after the first painted frame, not from the click or IPC call. + await new Promise(resolve=>{ + let frame=0 + const finish=()=>{clearTimeout(timer);cancelAnimationFrame(frame);resolve()} + const timer=setTimeout(finish,100) + frame=requestAnimationFrame(()=>{frame=requestAnimationFrame(finish)}) + }) + if(minimumMs)await new Promise(resolve=>setTimeout(resolve,minimumMs)) + })()) +} +function taskPanel(key:string):string{ + if(['settings','settings-folder'].includes(key))return 'settings' + if(['refresh-records','history-preview','delete-batch'].includes(key))return 'history' + if(['import','template'].includes(key))return 'data' + if(['export','cancel'].includes(key))return state.page==='history'?'history':'export' + if(key==='refresh-seals'||key==='seal')return state.page==='seals'?'seals':state.page==='settings'?'settings':'preview' + if(key==='folder')return state.page==='seals'?'seals':state.taskTargets.get(key)==='output'?'export':'preview' + return state.page==='workbench'?'export':state.page +} +let previewVersion=0,recordsVersion=0,sealsVersion=0 +async function fetchRecords(){ + const version=++recordsVersion,boot=await window.receipts.bootstrap() + if(version===recordsVersion){state.batches=boot.batches;state.historyTotal=boot.historyTotal??boot.batches.length;state.historyFilters=boot.historyFilters||{schools:[],grades:[],items:[]};state.nextNumber=boot.nextNumber} +} +async function fetchSeals():Promise{ + const version=++sealsVersion + try{ + const list=await window.receipts.listSeals() + if(version===sealsVersion){state.seals=list.images;state.sealWarnings=list.warnings} + return true + }catch(error){ + if(version===sealsVersion){state.seals=[];state.sealWarnings=[conciseError(error)]} + return false + } +} +export const actions={ + reportError(scope:string,message:string){if(scope==='init')state.error=message;else state.panelErrors.set(scope,message)}, + clearError(scope:string){state.panelErrors.delete(scope)}, + async task(key:string,work:()=>Promise,options:TaskFeedback={}):Promise{ + if(state.busy.has(key))return null + if(options.target!==undefined)state.taskTargets.set(key,options.target);else state.taskTargets.delete(key) + const scope=options.scope||taskPanel(key);state.taskPanels.set(key,scope);state.panelErrors.delete(scope) + state.busy.add(key);if(key==='init')state.error='' + if(!quietTasks.has(key)&&(!processingTasks.has(key)||!window.receipts.onProcessing))startLoading(key,options.feedback===false?0:500) + let result:T|null=null,errorMessage='' + try{result=await work()} + catch(error){errorMessage=conciseError(error)} + finally{ + await loadingMinimums.get(key) + loadingMinimums.delete(key);state.loadingTasks.delete(key);state.busy.delete(key) + if(errorMessage)actions.reportError(scope,errorMessage) + } + return result + }, + async init(){ + if(!window.receipts){state.error='请使用桌面程序打开收据工坊';return} + window.receipts.onProcessing?.(key=>{if(state.busy.has(key))startLoading(key)}) + window.receipts.onProgress(p=>{ + state.progress=p;state.exporting=!p.finished;if(p.finished)state.cancelRequested=false + if(p.finished){ + const failed=p.status==='failed'||p.status==='partial' + if(failed)actions.reportError(state.exportPanel,conciseError(p.errors.slice(0,3).join(';')||p.stage)) + void actions.refreshRecords() + } + }) + await actions.task('init',async()=>{await actions.reload();state.ready=true;await actions.loadSeals();await actions.refreshPreview()},{feedback:false,scope:'init'}) + }, + async reload(){ + const boot=await window.receipts.bootstrap() + state.settings=boot.settings;state.batches=boot.batches;state.historyTotal=boot.historyTotal??boot.batches.length;state.historyFilters=boot.historyFilters||{schools:[],grades:[],items:[]};state.nextNumber=boot.nextNumber + state.dataDirectory=boot.dataDirectory;state.version=boot.version;state.templates=boot.templates + state.currentSealId=boot.settings.defaultSealId + }, + async refreshRecords(feedback=false){ + if(!feedback)return fetchRecords() + await actions.task('refresh-records',async()=>{await fetchRecords();return true}) + }, + async loadSeals(feedback=false){ + if(!feedback)return fetchSeals() + return actions.task('refresh-seals',async()=>{ + if(!await fetchSeals())throw Error(state.sealWarnings[0]||'印章列表刷新失败') + return true + }) + }, + async importExcel(){ + await actions.task('import',async()=>{ + const draft=await window.receipts.importExcel();if(!draft)return false + state.draft=draft;state.selected=new Set(draft.rows.filter(r=>!r.errors.length).map(r=>r.index)) + state.rowIndex=draft.rows.find(r=>!r.errors.length)?.index??0;state.page='workbench';state.progress=null + await actions.refreshPreview();return true + }) + }, + async template(){ + let saved='' + await actions.task('template',async()=>{saved=await window.receipts.saveExcelTemplate()||'';return !!saved}) + }, + async refreshPreview(showLoading=true){ + const version=++previewVersion + const row=state.draft?.rows.find(r=>r.index===state.rowIndex) + if(row?.errors.length){state.previewHtml='';state.previewLoading=false;return} + if(state.draft&&state.previewLayout==='print'&&!state.selected.size){state.previewHtml='';state.previewLoading=false;return} + state.previewLoading=showLoading + try{ + const html=await window.receipts.preview({draftId:state.draft?.id,rowIndex:state.rowIndex,rowIndices:state.previewLayout==='print'?[...state.selected]:undefined,sealId:state.currentSealId,layout:state.previewLayout}) + if(version===previewVersion)state.previewHtml=html + }catch(e){if(version===previewVersion){state.previewHtml='';actions.reportError(state.page==='settings'?'settings':'preview',conciseError(e))}} + finally{if(version===previewVersion)state.previewLoading=false} + }, + async selectSeal(id:string){ + await actions.task('seal',async()=>{ + state.settings=await window.receipts.saveSettings({...state.settings,defaultSealId:id}) + state.currentSealId=id;await actions.refreshPreview();return true + },{target:id}) + }, + async chooseAndSaveFolder(kind:'output'|'seal'){ + await actions.task('folder',async()=>{ + const folder=await window.receipts.chooseFolder(kind);if(!folder)return false + state.settings=await window.receipts.saveSettings({...state.settings,[kind==='seal'?'sealDirectory':'outputDirectory']:folder,...(kind==='seal'?{defaultSealId:''}:{})}) + if(kind==='seal'){state.currentSealId='';await actions.loadSeals();await actions.refreshPreview(false)} + return true + },{target:kind}) + }, + async saveSettings(settings:AppSettings){ + return actions.task('settings',async()=>{ + state.settings=await window.receipts.saveSettings(settings);state.currentSealId=state.settings.defaultSealId + await actions.loadSeals();await actions.refreshRecords();await actions.refreshPreview();return true + }) + }, + async export(batchId?:string){ + if(state.exporting||state.busy.has('delete-batch'))return + if(!batchId&&(!state.draft||!state.selected.size)){actions.reportError('export','请先导入 Excel 并选择有效数据');return} + if(!state.settings.exportPdf&&!state.settings.exportPng&&!state.settings.exportPrint){actions.reportError(state.page==='history'?'history':'export','请至少选择一种导出内容');return} + await actions.task('export',async()=>{ + state.exporting=true;state.cancelRequested=false;state.exportBatchId=batchId||'';state.exportPanel=state.page==='history'?'history':'export';state.progress=null + try{ + state.settings=await window.receipts.saveSettings({...state.settings}) + await window.receipts.exportReceipts({batchId,draftId:state.draft?.id,rowIndices:[...state.selected],sealId:state.currentSealId, + options:{pdf:state.settings.exportPdf,png:state.settings.exportPng,print:state.settings.exportPrint,receiptsPerPage:state.settings.receiptsPerPage,cutLine:state.settings.cutLine}}) + }catch(e){state.exporting=false;throw e} + },{target:batchId||''}) + }, + async deleteBatch(id:string){ + if(state.exporting)return + await actions.task('delete-batch',async()=>{ + if(!window.receipts.deleteBatch)throw Error('请关闭程序并重新启动本地调试,以启用删除记录功能') + if(!await window.receipts.deleteBatch(id))return false + if(state.draft?.previousBatchId===id)state.draft.previousBatchId=undefined + if(state.progress?.batchId===id)state.progress=null + await actions.refreshRecords();return true + },{target:id}) + }, + async open(folder:string){await actions.task('open',async()=>{await window.receipts.openFolder(folder);return true},{target:folder})}, + async cancel(){await actions.task('cancel',async()=>{await window.receipts.cancelExport();state.cancelRequested=true;return true})} +} +export function statusText(status:BatchSummary['status']):string{return {reserved:'已分配编号',running:'生成中',completed:'已完成',partial:'部分完成',failed:'失败',cancelled:'已取消'}[status]} diff --git a/src/renderer/style.css b/src/renderer/style.css new file mode 100644 index 0000000..6746d9e --- /dev/null +++ b/src/renderer/style.css @@ -0,0 +1,23 @@ + +:root{font-family:"Segoe UI","Microsoft YaHei UI","Microsoft YaHei",sans-serif;color:#253443;background:#f5f7fa;font-synthesis:none;font-size:13px;--green:#168576;--green-dark:#106e62;--muted:#89939f;--line:#e9edf1;--radius:13px} +*{box-sizing:border-box}body{margin:0}button,input,select{font:inherit}button{cursor:pointer}button:disabled{cursor:not-allowed;opacity:.5}button,input,select{outline:none}button:focus-visible,input:focus-visible,select:focus-visible{outline:2px solid #47ab9b;outline-offset:3px}button{color:inherit}h1,h2,h3,p{margin:0}h2{font-size:15px;font-weight:650}h3{font-size:17px}input[type=checkbox]{accent-color:var(--green);width:15px;height:15px;cursor:pointer;vertical-align:middle;flex-shrink:0}input[type=range]{accent-color:var(--green);width:100%}input[readonly]{cursor:default}select{cursor:pointer} +.app-shell{display:flex;min-height:100vh}.sidebar{position:fixed;inset:0 auto 0 0;width:208px;background:white;border-right:1px solid var(--line);padding:30px 16px 18px;display:flex;flex-direction:column;z-index:2}.brand{display:flex;gap:11px;align-items:center;padding:0 9px 41px}.brand-mark{width:39px;height:42px;border-radius:12px;background:var(--green);color:#fff;display:grid;place-items:center;font-size:22px;font-family:SimSun,serif;box-shadow:0 5px 12px #1685761f}.brand strong{display:block;font-size:19px;letter-spacing:1.5px;font-weight:700}.brand span{display:block;color:#9ba7b0;font-size:8px;letter-spacing:1.7px;margin-top:4px}.nav-label{font-size:10px;color:#a4adb6;letter-spacing:1px;padding:0 15px 12px}.nav-item{width:100%;border:0;background:none;display:flex;align-items:center;gap:11px;text-align:left;padding:13px 15px;margin-bottom:7px;border-radius:9px;color:#7c8793;font-size:13px;font-weight:500}.nav-item:hover{background:#f5f8f8}.nav-item.active{color:var(--green);background:#eaf5f1;font-weight:650}.nav-count{margin-left:auto;font-size:10px;background:#e9eef0;padding:2px 6px;border-radius:5px}.sidebar-bottom{margin-top:auto}.offline-note{display:flex;align-items:center;gap:9px;padding:15px 10px;background:#f8faf9;border:1px solid #edf2ee;border-radius:10px;color:var(--green)}.offline-note strong{display:block;font-size:11px;font-weight:550;color:#536c64}.offline-note span{font-size:10px;color:#98a79f;display:block;margin-top:4px}.app-version{padding:19px 8px 0;color:#acb5bd;font-size:10px;display:flex;justify-content:space-between}.app-version span{font-size:8px;letter-spacing:.5px} +.main-content{margin-left:208px;padding:30px 30px 24px;flex:1;min-width:0;max-width:1900px}.page-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:25px}.eyebrow{font-size:9px;color:#94a3a8;letter-spacing:1.7px;margin-bottom:9px}h1{font-size:25px;letter-spacing:.5px;font-weight:670}.page-header p{font-size:12px;color:#89939c;margin-top:9px}.header-status{display:flex;align-items:center;gap:8px;font-size:11px;color:#7d8b8c;background:#fff;border:1px solid var(--line);padding:9px 11px;border-radius:7px}.status-dot{width:6px;height:6px;background:#32a78d;border-radius:50%;display:inline-block;flex-shrink:0}.feedback{padding:11px 14px;display:flex;align-items:center;justify-content:space-between;gap:12px;border-radius:8px;margin-bottom:16px;font-size:12px;line-height:1.6;overflow-wrap:anywhere}.feedback.error{background:#fff1ef;color:#a84e43;border:1px solid #f3d7d2}.feedback.success{background:#eaf6f0;color:#347361;border:1px solid #d6e9df}.feedback button{border:0;background:none;color:inherit;display:flex}.stats-grid{display:grid;grid-template-columns:1fr 1.15fr 1.15fr;gap:18px;margin-bottom:22px}.stat-card{background:#fff;border:1px solid var(--line);border-radius:var(--radius);display:flex;align-items:center;gap:13px;padding:20px;min-width:0;position:relative;min-height:98px}.stat-icon{width:43px;height:43px;display:grid;place-items:center;border-radius:12px;flex-shrink:0}.stat-icon.green{background:#eaf6f0;color:#299a7e}.stat-icon.amber{background:#fff6e9;color:#c4994f;font-size:22px}.stat-icon.blue{background:#eef2fb;color:#7993ca}.stat-label{display:block;font-size:11px;color:#909ba5;margin-bottom:7px}.stat-card strong{font-size:26px;line-height:1;font-weight:640;letter-spacing:.4px}.stat-card small{font-size:11px;color:#a1a9af;font-weight:400}.stat-sub{font-size:10px;color:#a2acb4;margin-left:auto;align-self:flex-end;white-space:nowrap}.stat-card strong.money-stat{font-size:25px}.stat-seal{min-width:0;flex:1}.stat-card strong.seal-name{font-size:13px;display:block;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;line-height:1.5;letter-spacing:0} +.panel{background:#fff;border:1px solid var(--line);border-radius:var(--radius);overflow:hidden}.workspace-grid{display:grid;grid-template-columns:minmax(365px,1.13fr) minmax(345px,1fr);gap:20px;align-items:stretch}.data-panel{display:flex;flex-direction:column;min-height:0}.data-panel>*{flex-shrink:0}.data-panel .import-empty{flex:1}.panel-heading{padding:21px 20px 18px;display:flex;gap:10px;align-items:center;justify-content:space-between;border-bottom:1px solid var(--line)}.panel-heading p{font-size:11px;color:#9aa4ad;margin-top:7px;line-height:1.6}.button-row{display:flex;align-items:center;gap:8px;flex-shrink:0}.button{border:1px solid #dce4e7;background:white;display:inline-flex;align-items:center;justify-content:center;gap:7px;border-radius:7px;padding:10px 15px;white-space:nowrap;font-size:12px;font-weight:500;transition:background .15s}.button:hover:not(:disabled){background:#f5f8f7}.button.primary{background:var(--green);border-color:var(--green);color:white;box-shadow:0 2px 3px #0f5e5512}.button.primary:hover:not(:disabled){background:var(--green-dark)}.button.subtle{background:#f9fbfa;border-color:#e4ebe9;color:#60766e;box-shadow:none}.button.danger{background:#fff7f5;border-color:#f0dcd7;color:#b3695b}.button.danger:hover:not(:disabled){background:#ffede8}.button.compact{font-size:11px;padding:8px 10px;gap:5px}.text-button{border:0;padding:0;background:none;color:var(--green);display:inline-flex;align-items:center;justify-content:center;gap:4px;font-size:11px;white-space:nowrap}.text-button:hover{text-decoration:underline}.icon-button{border:0;background:none;padding:6px;color:#8b99a3;display:inline-flex;align-items:center;justify-content:center;border-radius:6px}.icon-button:hover:not(:disabled){background:#f0f5f3;color:var(--green)}.badge{display:inline-flex;padding:2px 6px;background:#eff3f5;border-radius:5px;color:#92a0a8;font-size:10px;margin-left:4px;vertical-align:middle}.import-empty{min-height:424px;display:flex;align-items:center;flex-direction:column;justify-content:center;padding:30px 20px}.empty-file-icon{position:relative;width:81px;height:90px;background:#eff7f3;color:#62a28e;border:1px solid #dbece3;border-radius:14px;display:grid;place-items:center;margin-bottom:23px;transform:rotate(-4deg)}.file-tag{position:absolute;bottom:12px;right:-11px;background:white;color:#4c9480;box-shadow:0 2px 7px #557d6c12;padding:4px 8px;border-radius:4px;font-size:8px;font-weight:700;letter-spacing:.5px;transform:rotate(4deg)}.import-empty h3{font-size:16px;font-weight:550;margin-bottom:11px}.import-empty p{font-size:11px;color:#9ba6af;line-height:1.9;text-align:center;margin-bottom:23px}.import-empty>.text-button{margin-top:15px;font-size:10px}.import-steps{display:flex;align-items:center;gap:12px;margin-top:38px;color:#a1adb4;font-size:10px}.import-steps span:not(.step-line){display:flex;gap:5px;align-items:center}.import-steps i{width:16px;height:16px;border-radius:50%;background:#f1f5f4;font:9px "Segoe UI";display:grid;place-items:center;color:#91a89e}.step-line{height:1px;width:26px;background:#e7eeea}.preview-live{font-size:10px;color:#89a69b;display:flex;align-items:center;gap:5px;white-space:nowrap}.preview-toolbar{display:flex;justify-content:space-between;align-items:center;gap:6px;padding:14px 17px}.preview-toolbar>span{font-size:9px;color:#a3adb5;white-space:nowrap}.segmented{display:inline-flex;padding:3px;background:#f2f5f5;border-radius:6px;gap:2px}.segmented button{border:0;background:none;border-radius:4px;color:#919fa7;font-size:10px;padding:6px 9px;white-space:nowrap}.segmented button.active{background:white;color:#376b5c;box-shadow:0 1px 4px #354e4210}.preview-canvas{padding:22px 0;min-height:290px;background:#f1f4f6;display:flex;justify-content:center;align-items:center;overflow:hidden;background-image:radial-gradient(#d6dee5 .65px,transparent .65px);background-size:10px 10px}.paper-wrap{position:relative;background:white;box-shadow:0 4px 18px #283e4c13}.paper-wrap iframe{position:absolute;left:0;top:0;transform-origin:top left;border:0;display:block;background:white;pointer-events:none}.preview-placeholder{color:#99a5af;font-size:12px}.seal-select-row{display:flex;gap:8px;align-items:center;padding:18px 18px 0;color:#87968e}.seal-select-row label{font-size:11px;white-space:nowrap}.seal-select-row select{flex:1;min-width:0;font-size:11px;border:1px solid #e2e8e7;padding:8px;border-radius:6px;background:#fff;color:#4f625c}.preview-footnote{text-align:center;font-size:9px;color:#a0acb3;padding:14px}.bind-seal-link{display:flex;justify-content:center;align-items:center;gap:5px;background:none;border:0;color:var(--green);font-size:10px;margin:10px auto 0}.export-panel{margin-top:21px}.export-options{display:flex;flex-wrap:wrap;align-items:center;gap:20px;padding:19px 21px;border-bottom:1px solid var(--line)}.export-title{display:flex;gap:8px;align-items:center;color:#52665e;margin-right:8px}.export-title h2{font-size:13px;color:#42544d}.export-options label,.checkbox-row label{display:flex;gap:6px;align-items:center;font-size:11px;color:#6e7c85}.export-format-groups{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1.2fr);gap:16px;flex:1 1 540px;min-width:0}.export-format-group{min-width:0;margin:0;border:1px solid #e5ebe9;border-radius:8px;padding:10px 14px 12px}.export-format-group legend{padding:0 5px;color:#60766e;font-size:11px;font-weight:550}.export-format-controls{display:flex;flex-wrap:wrap;align-items:center;gap:12px 20px;min-height:29px}.export-format-group p{font-size:10px;color:#98a59f;margin-top:8px;line-height:1.6}.export-print-group{background:#f8fbfa}.export-format-controls .print-layout-control{gap:8px}.export-options select{font-size:11px;border:1px solid #e3e8eb;border-radius:5px;padding:6px 9px;background:#fff;color:#6e8077}.export-options select:disabled{background:#f0f3f2;color:#a7b1ac;cursor:not-allowed}.export-bottom{display:flex;align-items:center;justify-content:space-between;gap:20px;padding:17px 21px}.output-location{display:flex;align-items:center;gap:10px;color:#95a2aa;min-width:0}.output-location>div{min-width:0}.output-location span{font-size:9px;color:#a3adb5;display:block;margin-bottom:5px}.output-location>div>button{display:block;max-width:560px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;border:0;background:none;padding:0;text-align:left;color:#6b7c84;font-size:11px}.export-button{padding:11px 20px}.progress-area{padding:15px 22px;background:#fbfdfb}.progress-area>div:first-child{display:flex;justify-content:space-between;font-size:11px;color:#6c8479}.progress-track{height:5px;border-radius:10px;background:#e9f0ec;margin-top:9px;overflow:hidden}.progress-track div{height:100%;background:#39a58b;transition:width .2s}.file-bar{display:flex;gap:7px;align-items:center;padding:12px 18px;background:#fafcfb;font-size:11px;color:#5b7f70}.file-bar>span{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0}.file-bar small{margin-left:auto;white-space:nowrap;color:#9aa8a0}.inline-note{margin:10px 18px;padding:10px 12px;background:#f4f8f6;color:#71897e;font-size:11px;line-height:1.7;border-radius:6px;display:flex;gap:6px;align-items:flex-start}.inline-note.warning{background:#fff8e9;color:#ae8b4c}.inline-note .text-button{margin-left:auto}.table-toolbar{display:flex;align-items:center;justify-content:space-between;padding:12px 16px;gap:12px}.search-input{display:flex;gap:5px;align-items:center;max-width:170px;color:#abb5bc}.search-input input{border:0;width:100%;min-width:0;font-size:10px;padding:4px;background:none}.data-table-scroll{overflow:auto;flex:1 1 0px;min-height:285px}.data-table{border-collapse:collapse;width:100%;font-size:11px;text-align:left}.data-table th{background:#f8fafb;color:#94a0ab;font-size:10px;font-weight:500;padding:12px 12px;white-space:nowrap;position:sticky;top:0;z-index:1}.data-table td{padding:15px 12px;border-bottom:1px solid #f0f3f5;line-height:1.5;vertical-align:middle}.data-table tbody tr{cursor:pointer}.data-table tbody tr:hover{background:#f8fcfa}.data-table tr.focused{background:#f0f8f4}.data-table strong{font-weight:500}.check-cell{width:34px;padding-left:16px!important;padding-right:2px!important}.row-number{font-size:10px;color:#a2adb5}.table-secondary{display:block;font-size:10px;color:#9aa6af;margin-top:5px}.align-right{text-align:right}.numeric{font-variant-numeric:tabular-nums;white-space:nowrap}.row-error,.row-warning{display:block;font-size:10px;color:#ba665a;margin-top:5px;max-width:300px;white-space:normal}.row-warning{color:#b38e4d}.state-tag{display:inline-flex;border-radius:4px;font-size:10px;padding:3px 6px;white-space:nowrap}.state-tag.danger{color:#ba6b60;background:#fff0ed}.state-tag.warning{color:#b29250;background:#fff8e9}.state-tag.ok{color:#438d74;background:#eaf6ef}.valid-label{display:inline-flex;align-items:center;gap:3px;white-space:nowrap;color:#6b9b84;font-size:10px}.table-footer{padding:12px 16px;font-size:9px;color:#9ba7b0;display:flex;align-items:center;justify-content:space-between;gap:8px;border-top:1px solid var(--line)}.pagination{display:flex;align-items:center;gap:7px;white-space:nowrap}.pagination button{border:0;background:none;color:#8fa39a;display:flex;padding:2px}.empty-table{text-align:center;color:#a2adb4;height:100px} +.folder-banner{display:flex;align-items:center;gap:10px;margin:20px;padding:14px 15px;background:#f7faf8;border:1px solid #e7eeea;border-radius:8px;color:#83978c;font-size:12px}.folder-banner>span{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.folder-banner .text-button{margin-left:auto}.large-empty{display:flex;flex-direction:column;align-items:center;justify-content:center;min-height:390px;padding:35px}.empty-circle{width:78px;height:78px;display:grid;place-items:center;background:#eef6f2;color:#78aa95;border-radius:22px;margin-bottom:24px}.large-empty h3{font-size:17px;font-weight:550;margin-bottom:12px}.large-empty p{font-size:12px;color:#9aa6ad;margin-bottom:25px;line-height:1.8;text-align:center}.seal-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(210px,1fr));gap:19px;padding:3px 22px 25px}.seal-card{border:1px solid #e5eaed;background:#fff;border-radius:11px;padding:12px;text-align:left;transition:border-color .15s}.seal-card.selected{border-color:#39a48b;box-shadow:0 0 0 2px #3f9b8010}.seal-card:hover{border-color:#75b8a3}.seal-image-stage{height:150px;position:relative;background-color:#fafafa;background-image:linear-gradient(45deg,#f0f0f0 25%,transparent 25%),linear-gradient(-45deg,#f0f0f0 25%,transparent 25%),linear-gradient(45deg,transparent 75%,#f0f0f0 75%),linear-gradient(-45deg,transparent 75%,#f0f0f0 75%);background-size:16px 16px;background-position:0 0,0 8px,8px -8px,-8px 0;display:flex;align-items:center;justify-content:center;border-radius:7px;overflow:hidden}.seal-image-stage img{max-width:85%;max-height:85%;object-fit:contain}.seal-check{position:absolute;right:9px;top:9px;width:23px;height:23px;display:grid;place-items:center;color:#fff;background:var(--green);border-radius:50%}.seal-card-label{display:flex;align-items:center;gap:7px;margin-top:13px;color:#80908b}.seal-card-label strong{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:12px;font-weight:550;color:#53695e}.seal-card>small{display:block;color:#a2ada7;font-size:10px;margin:7px 0 3px 22px}.helper-note{display:flex;align-items:center;gap:10px;padding:20px 5px;font-size:11px;color:#8e9e96}.helper-note .text-button{margin-left:auto}.history-search{padding:18px 21px;display:flex;align-items:center;gap:15px}.history-search input{max-width:330px}.history-search>span{font-size:11px;color:#a0acb4;margin-left:auto}.history-table-wrap{overflow:auto;padding-bottom:15px}.history-table td,.history-table th{padding:18px 20px}.history-table th{font-size:12px;font-weight:600;color:#6b7d87}.history-table tr{cursor:default!important}.history-message{max-width:150px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.modal-backdrop{position:fixed;inset:0;z-index:5;background:#253a4859;display:flex;align-items:center;justify-content:center;backdrop-filter:blur(2px)}.modal-card{width:760px;max-width:90vw;background:white;border-radius:15px;overflow:hidden;box-shadow:0 20px 80px #20354130}.modal-footer{padding:17px;text-align:center;font-size:11px;color:#93a098} +.settings-layout{display:grid;grid-template-columns:1fr 1fr;gap:20px}.settings-panel{padding:23px}.settings-section-heading{display:flex;align-items:center;gap:12px;margin-bottom:23px}.setting-icon{width:41px;height:41px;border-radius:11px;background:#eff7f3;color:#579981;display:grid;place-items:center}.settings-section-heading p{font-size:11px;color:#9da8af;margin-top:6px}.form-field{margin-bottom:20px}.form-field>label{display:block;font-size:12px;color:#65766f;margin-bottom:9px}.form-field>label>span{float:right;color:#84a394;font-size:11px}.input,.path-input{border:1px solid #e0e7e8;border-radius:7px;background:white;min-height:38px;width:100%;font-size:12px;color:#53665c}.input{padding:9px 11px}.input::placeholder,.path-input input::placeholder{color:#b0b9be}.path-input{display:flex;align-items:center;padding:4px;gap:7px}.path-input input{border:0;flex:1;min-width:0;color:#6a7b72;background:none;padding:3px 8px;font-size:11px}.path-input .button{border:0}.form-field small{display:block;font-size:10px;color:#a1ada6;line-height:1.7;margin-top:7px}.form-two-columns{display:grid;grid-template-columns:1fr 1fr;gap:22px}.field-note{font-size:10px;color:#a3afa8;line-height:1.8;margin-top:-4px}.checkbox-row{display:flex;gap:20px;flex-wrap:wrap;padding:3px 0 4px}.toggle-line{display:flex;align-items:center;justify-content:space-between;gap:15px;font-size:12px;color:#73847b;margin:15px 0}.toggle-line input{appearance:none;width:32px;height:19px;background:#dfe7e2;border:0;border-radius:20px;position:relative;transition:background .2s}.toggle-line input:after{content:"";position:absolute;top:3px;left:3px;background:white;width:13px;height:13px;border-radius:50%;box-shadow:0 1px 3px #1c4c3c14;transition:transform .2s}.toggle-line input:checked{background:var(--green)}.toggle-line input:checked:after{transform:translateX(13px)}.number-preview{border:1px solid #e1ece5;background:#f6faf7;border-radius:8px;padding:15px;margin-bottom:19px}.number-preview span{font-size:10px;color:#95a59a;display:block;margin-bottom:9px}.number-preview strong{font-size:18px;letter-spacing:1px;font-variant-numeric:tabular-nums;color:#4e7d67;font-weight:550}.settings-panel>.inline-note{margin:0 0 15px}.full-width{grid-column:1/-1}.advanced-toggle{padding:0;border:0;background:none;display:flex;width:100%;align-items:center;justify-content:space-between;color:#6e8076;font-size:12px;font-weight:550}.rotated{transform:rotate(180deg)}.advanced-content{margin-top:24px}.backup-box{display:flex;gap:12px;color:#86a193;background:#f7faf8;border:1px solid #e8efea;padding:17px;border-radius:9px}.backup-box>svg{flex-shrink:0}.backup-box strong{font-size:12px;color:#637e6e;font-weight:550}.backup-box p{font-size:10px;color:#99a99e;line-height:1.9;margin:7px 0 13px}.data-location{font-size:10px;color:#a0ada4;padding-top:22px;display:flex;gap:15px;overflow-wrap:anywhere}.settings-save-bar{grid-column:1/-1;display:flex;justify-content:space-between;align-items:center;padding:3px 0 12px;color:#a0ada6;font-size:11px}.loading-state{min-height:400px;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:18px;color:#8fa299}.spin{animation:spin 1.2s linear infinite}@keyframes spin{to{transform:rotate(360deg)}} +::-webkit-scrollbar{width:7px;height:7px}::-webkit-scrollbar-thumb{background:#dce4e7;border-radius:10px}::-webkit-scrollbar-track{background:transparent} +@media(max-width:1250px){.sidebar{width:185px;padding-left:12px;padding-right:12px}.main-content{margin-left:185px;padding:25px 22px}.brand{padding-left:6px;gap:8px}.brand strong{font-size:17px}.brand span{font-size:7px}.stat-card{padding:16px 13px;gap:9px}.stat-sub{display:none}.stat-card strong.money-stat{font-size:22px}.stat-card strong{font-size:23px}.panel-heading{padding:17px 14px}.panel-heading h2{font-size:14px}.panel-heading .button-row{gap:5px}.panel-heading .button{padding:7px;font-size:10px}.preview-toolbar{padding:13px 10px}.preview-toolbar>span{display:none}.export-options{gap:16px}.data-table td,.data-table th{padding-left:8px;padding-right:8px}.settings-panel{padding:20px}.form-two-columns{gap:15px}.history-table td,.history-table th{padding:14px 13px}} + +.confirm-overlay{position:fixed;inset:0;z-index:20;background:#243c4d40;backdrop-filter:blur(3px);animation:confirm-fade .16s ease-out}.confirm-dialog{position:fixed;z-index:21;left:50%;top:50%;transform:translate(-50%,-50%);width:min(540px,calc(100vw - 40px));max-height:calc(100vh - 64px);overflow:auto;background:#fff;border:1px solid #ffffffc7;border-radius:18px;padding:30px;box-shadow:0 24px 90px #1c35452b,0 4px 16px #1c354512;outline:none;animation:confirm-appear .16s ease-out}.confirm-close{position:absolute;top:14px;right:14px}.confirm-heading{display:flex;align-items:flex-start;gap:16px;padding-right:14px}.confirm-icon{width:48px;height:48px;border-radius:14px;display:grid;place-items:center;flex-shrink:0;background:#eaf5f0;color:#348c75}.confirm-danger .confirm-icon{background:#fff0eb;color:#be6a58}.confirm-warning .confirm-icon{background:#fff6e6;color:#b08c49}.confirm-title{font-size:19px;line-height:1.5;font-weight:650;color:#283d47;margin:0 0 7px}.confirm-description{font-size:12px;color:#8b989e;line-height:1.85;margin:0}.confirm-details{margin:23px 0 0;padding:16px 18px;border:1px solid #e9eeec;background:#f8faf9;border-radius:10px}.confirm-details>div{display:flex;align-items:flex-start;gap:16px}.confirm-details>div+div{margin-top:12px}.confirm-details dt{font-size:11px;color:#94a29c;min-width:52px;line-height:1.8}.confirm-details dd{margin:0;min-width:0;overflow-wrap:anywhere;font-size:12px;color:#52685d;line-height:1.8}.confirm-note{display:flex;align-items:flex-start;gap:8px;margin-top:17px;font-size:11px;color:#84958b;line-height:1.85}.confirm-note svg{color:#5b9a80;flex-shrink:0;margin-top:2px}.confirm-actions{display:flex;justify-content:flex-end;gap:10px;margin-top:27px;padding-top:20px;border-top:1px solid #eef2f0}.confirm-actions .button{min-width:90px;padding:10px 19px;border-radius:8px;font-size:12px}.confirm-actions .destructive{color:#fff;background:#bf6d5b;border-color:#bf6d5b}.confirm-actions .destructive:hover:not(:disabled){background:#ae5b4a}.confirm-actions .destructive:focus-visible{outline-color:#c67d6b}@keyframes confirm-fade{from{opacity:0}to{opacity:1}}@keyframes confirm-appear{from{opacity:0;transform:translate(-50%,-48%) scale(.98)}to{opacity:1;transform:translate(-50%,-50%) scale(1)}}@media(prefers-reduced-motion:reduce){.confirm-overlay,.confirm-dialog{animation:none}} + +/* Shared action feedback keeps controls legible while operations run. */ +.action-icon{display:inline-flex;align-items:center;justify-content:center;flex-shrink:0;line-height:1}.button,.icon-button,.text-button{transition:background .15s,border-color .15s,box-shadow .15s,transform .12s,opacity .15s}.button:active:not(:disabled),.icon-button:active:not(:disabled){transform:translateY(1px) scale(.985)}.text-button:active:not(:disabled){transform:translateY(1px)}button.is-working{opacity:.9;cursor:progress}.operation-done:not(.primary):not(.danger){color:#298269;border-color:#bcded0;background:#f0f8f4}.action-check{animation:action-check-in .2s ease-out}.is-working .spin{animation-duration:.8s}.feedback{animation:feedback-arrive .22s ease-out}.feedback-message{display:flex;align-items:flex-start;gap:8px;min-width:0}.feedback-message>svg{flex-shrink:0;margin-top:1px}.content-refreshing{opacity:.5;pointer-events:none;transition:opacity .18s}.seal-grid,.history-table-wrap,.large-empty{transition:opacity .18s}.seal-card{transition:border-color .15s,box-shadow .15s,transform .12s}.seal-card:active:not(:disabled){transform:scale(.99)}.seal-check{animation:action-check-in .2s ease-out}.advanced-toggle svg{transition:transform .2s}.preview-canvas{position:relative}.preview-loading{position:absolute;inset:0;z-index:1;background:#f5f8f977;display:grid;place-items:center;animation:preview-loading-appear .15s .12s both}.preview-loading>span{display:flex;align-items:center;gap:8px;background:#ffffffed;border:1px solid #e1ebe7;box-shadow:0 4px 15px #31574910;border-radius:8px;padding:10px 14px;font-size:11px;color:#6b8d7d}.progress-stage{display:flex;align-items:center;gap:7px}.output-location .output-path-action span{display:inline;font-size:inherit;color:inherit;margin:0}.output-location .output-path-action .action-icon{display:inline-flex;margin-right:5px}.history-row-enter-active,.history-row-leave-active{transition:opacity .18s}.history-row-enter-from,.history-row-leave-to{opacity:0}@keyframes action-check-in{from{opacity:0;transform:scale(.65)}to{opacity:1;transform:scale(1)}}@keyframes feedback-arrive{from{opacity:0;transform:translateY(-5px)}to{opacity:1;transform:translateY(0)}}@keyframes preview-loading-appear{from{opacity:0}to{opacity:1}}@media(prefers-reduced-motion:reduce){.spin,.action-check,.feedback,.seal-check,.preview-loading{animation:none}.button,.icon-button,.text-button,.seal-card,.content-refreshing,.seal-grid,.history-table-wrap,.large-empty,.advanced-toggle svg,.history-row-enter-active,.history-row-leave-active{transition:none}} + +/* Loading belongs to the full work area; the overlay remains visible in tall, scrolled panels. */ +.loading-region{position:relative;isolation:isolate;min-width:0}.region-content{display:contents}.region-loading{position:absolute;inset:0;z-index:4;background:rgba(248,251,250,.82);backdrop-filter:blur(1px);border-radius:inherit;cursor:progress}.region-loading-indicator{position:absolute;left:50%;transform:translate(-50%,-50%);display:flex;flex-direction:column;align-items:center;gap:12px;padding:16px 22px;max-width:90%;color:#168576;text-align:center}.region-loading-indicator>span{font-size:12px;line-height:1.7;color:#597b6c;white-space:normal;min-width:120px}.region-spinner{animation:region-spin .7s linear infinite;transform-origin:center;transform-box:fill-box;will-change:transform}@keyframes region-spin{from{transform:rotate(0deg)}to{transform:rotate(360deg)}}.region-loading-indicator>.button{margin-top:2px;cursor:pointer;font-size:11px;padding:7px 14px}.region-loading-progress{display:flex;align-items:center;gap:8px;width:180px;max-width:100%;font-size:10px;color:#7f9b8e}.region-loading-progress>div{flex:1;height:4px;background:#dfece5;border-radius:4px;overflow:hidden}.region-loading-progress i{display:block;height:100%;background:#309e83;transition:width .2s}.region-loading-progress>span{font-variant-numeric:tabular-nums;min-width:28px}.region-error{display:flex;align-items:flex-start;gap:8px;padding:12px 16px;margin:12px;border:1px solid #f0d9d3;border-radius:8px;background:#fff4f1;color:#aa6254;font-size:12px;line-height:1.7;grid-column:1/-1;flex-shrink:0}.region-error>svg{flex-shrink:0;margin-top:2px}.region-error>span{flex:1;min-width:0;overflow-wrap:anywhere}.region-error .icon-button{color:inherit;padding:2px}.settings-layout>.region-error{margin:0}.region-loading-leave-active{transition:opacity .1s;pointer-events:none}.region-loading-leave-to{opacity:0}.data-panel>.region-content>*{flex-shrink:0}.data-panel .data-table-scroll{flex:1 1 0px}.data-panel .import-empty{flex:1}.loading-region[aria-busy=true]>.region-content{user-select:none}@media(prefers-reduced-motion:reduce){.region-loading-enter-active,.region-loading-leave-active{transition:none}/* The progress spinner remains animated; only decorative transitions are reduced. */.region-loading-progress i{transition:none}} +/* Receipt review and history search */ +.filename-example{margin-top:10px;padding:12px 14px;border:1px solid #dcebe4;background:#f5faf7;border-radius:8px}.filename-example>span{display:block;color:#758b7f;font-size:12px;margin-bottom:7px}.filename-example strong{display:block;overflow-wrap:anywhere;font-size:13px;line-height:1.7;color:#2e7460;font-weight:550}.group-review{margin-bottom:20px}.group-review-heading{list-style:none;display:flex;align-items:center;justify-content:space-between;gap:16px;padding:17px 20px;cursor:pointer}.group-review-heading::-webkit-details-marker,.receipt-group-choice::-webkit-details-marker{display:none}.group-review-heading h2{font-size:14px}.group-review-heading h2:before{content:'▾';color:#6b9581;margin-right:8px}.group-review:not([open]) .group-review-heading h2:before{content:'▸'}.group-review-heading p{font-size:12px;color:#83958a;line-height:1.7;margin-top:6px}.group-review-heading>span{font-size:12px;color:#557666;white-space:nowrap}.group-review-body{max-height:285px;overflow:auto;padding:0 18px 10px;border-top:1px solid #edf2ef}.receipt-group-tree .receipt-group-tree{padding-left:24px}.receipt-group-node{min-width:0}.receipt-group-choice{display:flex;align-items:center;gap:9px;min-height:43px;padding:8px 10px;border-radius:6px;list-style:none;cursor:pointer;font-size:13px;color:#526d5e}.receipt-group-choice:hover{background:#f4f9f6}.receipt-group-choice input{flex-shrink:0;margin:0}.group-chevron{flex-shrink:0;transition:transform .15s}details[open]>.receipt-group-choice>.group-chevron{transform:rotate(90deg)}.group-leaf-mark{width:14px;flex-shrink:0}.group-name{min-width:0;overflow-wrap:anywhere;flex:1}.group-count{font-size:12px;color:#819589;white-space:nowrap}.group-amount{width:125px;text-align:right;font-size:13px;white-space:nowrap;font-variant-numeric:tabular-nums;color:#36785e}.group-invalid{font-size:11px;color:#b26f4d;white-space:nowrap}.group-review-footer{padding:11px 20px;display:flex;justify-content:space-between;align-items:center;gap:12px;border-top:1px solid #edf2ef;font-size:11px;color:#86988d}.history-filters{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:15px;padding:20px 21px 0}.history-filters label{font-size:12px;color:#687e70;min-width:0}.history-filters select{margin-top:8px;font-size:13px}.history-search input{max-width:410px;font-size:13px}.history-filter-note{padding:0 21px 15px;font-size:12px;color:#81958a;line-height:1.7}.history-table{min-width:1060px}.history-category{min-width:175px;max-width:265px}.history-category>strong,.history-category>.table-secondary{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:225px}.history-source{max-width:200px;overflow-wrap:anywhere}.history-no-results{padding:38px 20px;text-align:center;color:#86998d;font-size:13px}.history-table .table-secondary{font-size:11px}.history-search>span{font-size:12px}.history-table td{font-size:12px}@media(max-width:1250px){.group-review-heading{padding:15px}.group-review-heading>span{font-size:11px}.group-review-heading p{font-size:11px}.group-amount{width:105px}.receipt-group-choice{gap:7px;font-size:12px}.receipt-group-tree .receipt-group-tree{padding-left:16px}.group-review-body{padding-left:10px;padding-right:10px}.history-filters{padding:17px 14px 0;gap:12px}.history-filter-note{padding-left:14px;padding-right:14px}}@media(prefers-reduced-motion:reduce){.group-chevron{transition:none}} +.history-match{display:block;margin-top:7px;font-size:11px;line-height:1.7;color:#338267;overflow-wrap:anywhere} +.group-show-more{margin:10px 0 12px 30px;padding:5px} +.history-pagination{padding:15px 20px;border-top:1px solid var(--line);display:flex;flex-wrap:wrap;align-items:center;gap:14px;color:#73877b;font-size:12px}.history-range{margin-right:auto;font-variant-numeric:tabular-nums}.history-page-size{display:flex;align-items:center;gap:9px;white-space:nowrap}.history-page-size select{width:98px;min-height:34px;padding:6px 8px;font-size:12px}.history-page-controls{display:flex;flex-wrap:wrap;align-items:center;gap:5px}.history-page-button{display:inline-flex;align-items:center;justify-content:center;min-width:32px;height:33px;padding:0 9px;border:1px solid #e2eae6;border-radius:6px;background:#fff;color:#647f6e;font-size:12px}.history-page-button:hover:not(:disabled){background:#f0f8f3;border-color:#a6ceba}.history-page-button.active{background:var(--green);border-color:var(--green);color:#fff}.history-page-button:disabled{opacity:.45;cursor:not-allowed}.history-page-position{margin-left:7px;white-space:nowrap;font-size:12px;font-variant-numeric:tabular-nums}@media(max-width:1250px){.history-pagination{padding:14px;gap:12px}.history-page-controls{margin-left:auto}.history-range{min-width:175px}} diff --git a/src/renderer/views/HistoryView.vue b/src/renderer/views/HistoryView.vue new file mode 100644 index 0000000..cdd7119 --- /dev/null +++ b/src/renderer/views/HistoryView.vue @@ -0,0 +1,94 @@ + + diff --git a/src/renderer/views/SealsView.vue b/src/renderer/views/SealsView.vue new file mode 100644 index 0000000..4d0f795 --- /dev/null +++ b/src/renderer/views/SealsView.vue @@ -0,0 +1,21 @@ + + diff --git a/src/renderer/views/SettingsView.vue b/src/renderer/views/SettingsView.vue new file mode 100644 index 0000000..1b611d5 --- /dev/null +++ b/src/renderer/views/SettingsView.vue @@ -0,0 +1,49 @@ + + diff --git a/src/renderer/views/WorkbenchView.vue b/src/renderer/views/WorkbenchView.vue new file mode 100644 index 0000000..fc63587 --- /dev/null +++ b/src/renderer/views/WorkbenchView.vue @@ -0,0 +1,94 @@ + + diff --git a/src/shared/errors.ts b/src/shared/errors.ts new file mode 100644 index 0000000..cd9aab9 --- /dev/null +++ b/src/shared/errors.ts @@ -0,0 +1,6 @@ +// Electron loading errors can contain the entire receipt HTML and its embedded stamp. +export function conciseError(error:unknown):string{ + const raw=error instanceof Error?error.message:typeof error==='string'?error:'操作失败' + const safe=raw.replace(/\bdata:[^\s"'<>]+/gi,'[页面内容已省略]') + return safe.length>400?safe.slice(0,399)+'…':safe +} diff --git a/src/shared/file-names.ts b/src/shared/file-names.ts new file mode 100644 index 0000000..c6074d3 --- /dev/null +++ b/src/shared/file-names.ts @@ -0,0 +1,10 @@ +import type {AppSettings} from './types' + +export function safeFileName(name:string):string{ + const clean=name.replace(/[<>:"/\\|?*\u0000-\u001f]/g,'_').replace(/[. ]+$/,'').trim().slice(0,100) + return !clean?'收据':/^(con|prn|aux|nul|com[1-9]|lpt[1-9])(?:\.|$)/i.test(clean)?'_'+clean:clean +} + +export function receiptFileStem(number:string,customer:string,pattern:AppSettings['fileNamePattern']):string{ + return safeFileName(pattern==='number'?number:pattern==='customer_number'?customer+'_'+number:number+'_'+customer) +} diff --git a/src/shared/receipt-groups.ts b/src/shared/receipt-groups.ts new file mode 100644 index 0000000..398c81b --- /dev/null +++ b/src/shared/receipt-groups.ts @@ -0,0 +1,56 @@ +import type {ImportRow,ReceiptInput,ReceiptGroupSummary} from './types' + +export const receiptGroupLevels=[ + {field:'school',fallback:'未分类学校'}, + {field:'grade',fallback:'未分类年级'}, + {field:'item',fallback:'未分类收费项目'} +] as const + +export function receiptGroupLabels(receipt:ReceiptInput):string[]{ + return receiptGroupLevels.map(level=>receipt[level.field]?.trim()||level.fallback) +} + +export function summarizeReceiptGroups(receipts:ReceiptInput[]):ReceiptGroupSummary[]{ + const groups=new Map() + for(const receipt of receipts){ + const [school,grade,item]=receiptGroupLabels(receipt),key=JSON.stringify([school,grade,item]) + let group=groups.get(key) + if(!group){group={school,grade,item,count:0,totalCents:0};groups.set(key,group)} + group.count++;group.totalCents+=receipt.amountCents + } + return [...groups.values()] +} + +export interface ImportGroupNode{ + key:string;label:string;indices:number[];selectedCount:number;totalCents:number;selectedCents:number;invalidCount:number;children:ImportGroupNode[]; +} + +export function importGroupTree(rows:ImportRow[],selected:ReadonlySet):ImportGroupNode[]{ + const roots:ImportGroupNode[]=[],nodes=new Map() + for(const row of rows){ + const labels=receiptGroupLabels(row.receipt) + let siblings=roots + for(let depth=0;depth,rowIndex:number,perPage:1|2):ImportRow[]{ + const valid=rows.filter(row=>!row.errors.length&&selected.has(row.index)) + const focused=valid.find(row=>row.index===rowIndex)||valid[0] + if(!focused)return [] + const key=JSON.stringify(receiptGroupLabels(focused.receipt)) + const group=valid.filter(row=>JSON.stringify(receiptGroupLabels(row.receipt))===key) + const start=Math.floor(group.indexOf(focused)/perPage)*perPage + return group.slice(start,start+perPage) +} diff --git a/src/shared/settings.ts b/src/shared/settings.ts new file mode 100644 index 0000000..37e4445 --- /dev/null +++ b/src/shared/settings.ts @@ -0,0 +1,20 @@ +import { z } from 'zod' +import type { AppSettings } from './types' +export const historyPageSizeSchema=z.union([z.literal(5),z.literal(10),z.literal(20),z.literal(50)]) +export const settingsSchema = z.object({ + schemaVersion: z.literal(1), outputDirectory: z.string().max(1024), sealDirectory: z.string().max(1024), + defaultSealId: z.string().max(260), exportPdf: z.boolean(), exportPng: z.boolean(), exportPrint: z.boolean(), + receiptsPerPage: z.union([z.literal(1), z.literal(2)]), cutLine: z.boolean(), + createBatchFolder: z.boolean(), openAfterExport: z.boolean(), numberPrefix: z.string().regex(/^[A-Za-z0-9_-]{1,16}$/, '编号前缀须为 1–16 位字母、数字、短横线或下划线'), + defaultCashier: z.string().max(20), defaultReviewer: z.string().max(20), + sealWidthMm: z.number().min(15).max(55), sealOffsetX: z.number().min(-20).max(20), + sealOffsetY: z.number().min(-12).max(12), pngDpi: z.union([z.literal(150), z.literal(300)]), + lastImportDirectory: z.string().max(1024), templateId: z.string().min(1).max(80), fileNamePattern:z.enum(['number_customer','customer_number','number']).default('number_customer'),historyPageSize:historyPageSizeSchema.default(5) +}) +export function defaultSettings(outputDirectory = ''): AppSettings { + return { schemaVersion: 1, outputDirectory, sealDirectory: '', defaultSealId: '', exportPdf: true, + exportPng: false, exportPrint: true, receiptsPerPage: 2, cutLine: true, createBatchFolder: true, + openAfterExport: true, numberPrefix: 'SJ', defaultCashier: '', defaultReviewer: '', + sealWidthMm: 35, sealOffsetX: 0, sealOffsetY: 0, pngDpi: 300, lastImportDirectory: '', templateId: 'classic-v1', fileNamePattern:'number_customer',historyPageSize:5 } +} +export const exportOptionsSchema = z.object({ pdf:z.boolean(), png:z.boolean(), print:z.boolean(), receiptsPerPage:z.union([z.literal(1),z.literal(2)]), cutLine:z.boolean() }).refine(v=>v.pdf||v.png||v.print, '请至少选择一种导出内容') diff --git a/src/shared/types.ts b/src/shared/types.ts new file mode 100644 index 0000000..7c7239f --- /dev/null +++ b/src/shared/types.ts @@ -0,0 +1,57 @@ +export interface AppSettings { + schemaVersion: 1; outputDirectory: string; sealDirectory: string; defaultSealId: string; + exportPdf: boolean; exportPng: boolean; exportPrint: boolean; receiptsPerPage: 1 | 2; + cutLine: boolean; createBatchFolder: boolean; openAfterExport: boolean; + numberPrefix: string; defaultCashier: string; defaultReviewer: string; + sealWidthMm: number; sealOffsetX: number; sealOffsetY: number; pngDpi: 150 | 300; + lastImportDirectory: string; templateId: string; fileNamePattern: 'number_customer' | 'customer_number' | 'number'; + historyPageSize:HistoryPageSize; +} +export interface ReceiptInput { date: string; school?: string; grade?: string; studentName?: string; customer: string; item: string; amountCents: number; cashier: string; reviewer: string } +export interface ImportRow { index: number; excelRow: number; receipt: ReceiptInput; errors: string[]; warnings: string[] } +export interface ImportDraft { id: string; fileName: string; hash: string; rows: ImportRow[]; previousBatchId?: string } +export interface SealImage { id: string; name: string; dataUrl: string } +export interface SealList { images: SealImage[]; warnings: string[] } +export interface Receipt extends ReceiptInput { number: string } +export interface Batch { + id: string; createdAt: string; sourceName: string; sourceHash: string; receipts: Receipt[]; + settings: AppSettings; seal: SealImage | null; status: 'reserved' | 'running' | 'completed' | 'partial' | 'failed' | 'cancelled'; + outputDirectory: string; successCount: number; failureCount: number; message: string; +} +export interface ReceiptGroupSummary {school:string;grade:string;item:string;count:number;totalCents:number} +export interface BatchQuery {search?:string;school?:string;grade?:string;item?:string} +export const historyPageSizes=[5,10,20,50] as const +export type HistoryPageSize=typeof historyPageSizes[number] +export interface BatchPageQuery extends BatchQuery {page?:number;pageSize?:HistoryPageSize} +export interface BatchPage {items:BatchSummary[];total:number;page:number;pageSize:HistoryPageSize} +export interface HistoryFilters {schools:string[];grades:string[];items:string[]} +export interface BatchSummary { id: string; createdAt: string; sourceName: string; count: number; totalCents: number; firstNumber: string; lastNumber: string; status: Batch['status']; outputDirectory: string; message: string; groups:ReceiptGroupSummary[];match?:{count:number;number:string;customer:string} } +export interface ExportOptions { pdf: boolean; png: boolean; print: boolean; receiptsPerPage: 1 | 2; cutLine: boolean } +export interface ExportRequest { draftId?: string; rowIndices?: number[]; batchId?: string; sealId?: string; options: ExportOptions } +export interface ExportProgress { batchId: string; completed: number; total: number; stage: string; finished: boolean; status: Batch['status']; outputDirectory: string; errors: string[] } +export interface Bootstrap { settings: AppSettings; batches: BatchSummary[]; historyTotal:number; historyFilters:HistoryFilters; nextNumber: string; dataDirectory: string; version: string; templates: { id: string; name: string }[] } +export interface ConfirmationOptions { + title:string;description:string;variant?:'default'|'warning'|'danger'; + details?:{label:string;value:string}[];note?:string;confirmLabel?:string;cancelLabel?:string; +} +export interface ConfirmationRequest extends ConfirmationOptions { id:string } +export type ProcessingTask = 'import' | 'template' | 'delete-batch' +export interface ReceiptAPI { + bootstrap(): Promise; + saveSettings(settings: AppSettings): Promise; + chooseFolder(kind: 'output' | 'seal'): Promise; + importExcel(): Promise; + saveExcelTemplate(): Promise; + listSeals(): Promise; + preview(request: { draftId?: string; rowIndex?: number; rowIndices?:number[]; batchId?: string; receiptNumber?:string; sealId?: string; layout: 'single' | 'print' }): Promise; + exportReceipts(request: ExportRequest): Promise; + cancelExport(): Promise; + listBatches(query?:BatchQuery): Promise; + listBatchPage(query?:BatchPageQuery): Promise; + deleteBatch(id: string): Promise; + openFolder(path: string): Promise; + onProcessing(callback:(task:ProcessingTask)=>void):()=>void; + onConfirmation(callback:(request:ConfirmationRequest)=>void):()=>void; + answerConfirmation(id:string,confirmed:boolean):Promise; + onProgress(callback: (progress: ExportProgress) => void): () => void; +} diff --git a/src/types/sql-asm.d.ts b/src/types/sql-asm.d.ts new file mode 100644 index 0000000..d390ae7 --- /dev/null +++ b/src/types/sql-asm.d.ts @@ -0,0 +1 @@ +declare module 'sql.js/dist/sql-wasm-inline.js' { import init from 'sql.js'; export default init } diff --git a/tests/confirmations.test.ts b/tests/confirmations.test.ts new file mode 100644 index 0000000..4548e6d --- /dev/null +++ b/tests/confirmations.test.ts @@ -0,0 +1,37 @@ +import {it,expect} from 'vitest' +import {ConfirmationService} from '../src/main/confirmations' +import type {ConfirmationRequest} from '../src/shared/types' +const options={title:'删除记录?',description:'删除本批次',variant:'danger' as const,confirmLabel:'删除记录'} +it('操作等待匹配确认,其他请求的回复不能批准当前操作',async()=>{ + let request:ConfirmationRequest|undefined + const service=new ConfirmationService(value=>{request=value}) + const answer=service.ask(options) + expect(service.hasPending).toBe(true);expect(request).toMatchObject(options) + expect(()=>service.answer('wrong-request',true)).toThrow('已失效') + expect(service.hasPending).toBe(true) + service.answer(request!.id,true) + expect(await answer).toBe(true);expect(service.hasPending).toBe(false) + expect(()=>service.answer(request!.id,true)).toThrow('已失效') +}) +it('取消与界面失联均不批准操作',async()=>{ + let request:ConfirmationRequest|undefined + const service=new ConfirmationService(value=>{request=value}) + const cancelled=service.ask(options);service.answer(request!.id,false);expect(await cancelled).toBe(false) + const interrupted=service.ask(options);service.cancel();expect(await interrupted).toBe(false) + service.cancel();expect(service.hasPending).toBe(false) +}) +it('重复请求不替换等待中的确认窗口',async()=>{ + let request:ConfirmationRequest|undefined + const service=new ConfirmationService(value=>{request=value}) + const first=service.ask(options),id=request!.id + await expect(service.ask({...options,title:'另一个请求'})).rejects.toThrow('当前确认') + expect(request!.id).toBe(id) + service.answer(id,true);expect(await first).toBe(true) +}) +it('发送失败后可重新请求确认',async()=>{ + let failed=true,request:ConfirmationRequest|undefined + const service=new ConfirmationService(value=>{if(failed)throw Error('window unavailable');request=value}) + await expect(service.ask(options)).rejects.toThrow('window unavailable');expect(service.hasPending).toBe(false) + failed=false + const answer=service.ask(options);service.answer(request!.id,false);expect(await answer).toBe(false) +}) diff --git a/tests/core.test.ts b/tests/core.test.ts new file mode 100644 index 0000000..a891a83 --- /dev/null +++ b/tests/core.test.ts @@ -0,0 +1,29 @@ +import { describe,it,expect,beforeEach,afterEach } from 'vitest' +import { mkdtemp,rm,writeFile } from 'node:fs/promises' +import { tmpdir } from 'node:os' +import path from 'node:path' +import { parseMoney,chineseMoney,moneyCells,MAX_CENTS } from '../src/core/money' +import { parseReceiptDate } from '../src/core/date' +import { buildDocument,TemplateRegistry } from '../src/core/templates' +import { defaultSettings } from '../src/shared/settings' +import { Ledger } from '../src/main/ledger' +import { XlsxReader,createWorkbookTemplate } from '../src/main/workbooks' +import ExcelJS from 'exceljs' +describe('金额与日期',()=>{ + it('以整数分保存,金额格不截断',()=>{expect(parseMoney('3,500.01')).toBe(350001);expect(parseMoney('0.01')).toBe(1);expect(parseMoney('9999999.99')).toBe(MAX_CENTS);expect(moneyCells(MAX_CENTS).join('')).toBe('999999999')}) + it.each(['-1','0','1.001','1e3','12,34','10000000','abc'])('拒绝无效金额 %s',v=>{expect(()=>parseMoney(v)).toThrow()}) + it.each([[350000,'叁仟伍佰元整'],[100101,'壹仟零壹元零壹分'],[100000001,'壹佰万元零壹分'],[1010000,'壹万零壹佰元整'],[1234,'壹拾贰元叁角肆分']])('正确转换金额大写', (n,text)=>expect(chineseMoney(n as number)).toBe(text)) + it('验证真实日期与 Excel 的 1900/1904 日期制',()=>{expect(parseReceiptDate('2024-2-29')).toBe('2024-02-29');expect(()=>parseReceiptDate('2026-2-29')).toThrow();expect(parseReceiptDate(61)).toBe('1900-03-01');expect(parseReceiptDate(0,true)).toBe('1904-01-01');expect(()=>parseReceiptDate(60)).toThrow()}) +}) +let dir:string +beforeEach(async()=>{dir=await mkdtemp(path.join(tmpdir(),'receipt-studio-test-'))}) +afterEach(async()=>{await rm(dir,{recursive:true,force:true})}) +const input={date:'2026-10-16',customer:'测试客户',item:'服务费',amountCents:350000,cashier:'',reviewer:''} +describe('持久编号',()=>{ + it('预览不推进,保留批次快照并在重启后继续编号',async()=>{const store=new Ledger(path.join(dir,'data.sqlite'));await store.open();expect(store.nextNumber('SJ')).toBe('SJ00000001');expect(store.nextNumber('SJ')).toBe('SJ00000001');const settings={...defaultSettings(),defaultCashier:'张三'};const batch=await store.reserve({name:'a.xlsx',hash:'hash'},[input,input],settings,null);expect(batch.receipts.map(r=>r.number)).toEqual(['SJ00000001','SJ00000002']);expect(batch.receipts[0].cashier).toBe('张三');const again=new Ledger(path.join(dir,'data.sqlite'));await again.open();expect(again.nextNumber('SJ')).toBe('SJ00000003');expect(again.get(batch.id).receipts).toEqual(batch.receipts)}) + it('数据损坏时停止分配编号,保留原文件',async()=>{const file=path.join(dir,'bad.sqlite');await writeFile(file,'broken');await expect(new Ledger(file).open()).rejects.toThrow('停止分配编号')}) +}) +describe('模板和工作簿',()=>{ + it('模板转义用户文字,奇数张 A4 保留空白半页',()=>{const registry=new TemplateRegistry(),r={...input,number:'SJ00000001',customer:''};const html=buildDocument(registry.get('classic-v1'),[r,r,r],{settings:defaultSettings(),seal:null,preview:false},'print',2,true);expect(html).not.toContain('