From 2caac3c31f31652dceba12f41eea6f2aa5362983 Mon Sep 17 00:00:00 2001 From: fuenmao <980740792@qq.com> Date: Mon, 19 Oct 2026 11:09:09 +0800 Subject: [PATCH] Initial commit: Receipt Studio desktop application --- .gitattributes | 4 + .gitignore | 39 + .npmrc | 2 + README.md | 64 + build/icon.ico | Bin 0 -> 1701 bytes build/icon.png | Bin 0 -> 1679 bytes docs/ARCHITECTURE.md | 101 + docs/VERIFICATION.md | 112 + package-lock.json | 6550 ++++++++++++++++++ package.json | 84 + scripts/build-main.mjs | 4 + scripts/desktop-test.mjs | 165 + scripts/dev-window-test.mjs | 26 + scripts/dev.mjs | 25 + scripts/history-pagination-test.mjs | 98 + scripts/loading-test.mjs | 182 + scripts/make-icon.mjs | 17 + scripts/package-test.mjs | 39 + scripts/print-batch-test.mjs | 80 + src/core/date.ts | 19 + src/core/money.ts | 43 + src/core/templates.ts | 72 + src/main/confirmations.ts | 24 + src/main/export-layout.ts | 41 + src/main/exporter.ts | 82 + src/main/files.ts | 15 + src/main/index.ts | 220 + src/main/ledger.ts | 135 + src/main/pdf-engine.ts | 35 + src/main/png-density.ts | 22 + src/main/preload.ts | 18 + src/main/print-documents.ts | 13 + src/main/seals.ts | 28 + src/main/settings-store.ts | 24 + src/main/workbooks.ts | 71 + src/renderer/App.vue | 44 + src/renderer/components/ActionButton.vue | 12 + src/renderer/components/ConfirmDialog.vue | 24 + src/renderer/components/LoadingRegion.vue | 46 + src/renderer/components/ReceiptGroupTree.vue | 29 + src/renderer/components/ReceiptPreview.vue | 20 + src/renderer/confirmations.ts | 23 + src/renderer/env.d.ts | 3 + src/renderer/index.html | 1 + src/renderer/loading.ts | 21 + src/renderer/main.ts | 4 + src/renderer/public/licenses/reka-ui.txt | 21 + src/renderer/raster.html | 1 + src/renderer/raster.ts | 15 + src/renderer/state.ts | 176 + src/renderer/style.css | 23 + src/renderer/views/HistoryView.vue | 94 + src/renderer/views/SealsView.vue | 21 + src/renderer/views/SettingsView.vue | 49 + src/renderer/views/WorkbenchView.vue | 94 + src/shared/errors.ts | 6 + src/shared/file-names.ts | 10 + src/shared/receipt-groups.ts | 56 + src/shared/settings.ts | 20 + src/shared/types.ts | 57 + src/types/sql-asm.d.ts | 1 + tests/confirmations.test.ts | 37 + tests/core.test.ts | 29 + tests/export-layout.test.ts | 34 + tests/exporter.test.ts | 106 + tests/history-search.test.ts | 64 + tests/ledger-delete.test.ts | 40 + tests/png.test.ts | 11 + tests/print-documents.test.ts | 24 + tests/receipt-groups.test.ts | 26 + tests/settings-store.test.ts | 22 + tests/workbook-fields.test.ts | 50 + tsconfig.json | 27 + tsconfig.main.json | 18 + vite.config.ts | 8 + vitest.config.ts | 2 + 启动本地调试.cmd | 23 + 77 files changed, 9846 insertions(+) create mode 100644 .gitattributes create mode 100644 .gitignore create mode 100644 .npmrc create mode 100644 README.md create mode 100644 build/icon.ico create mode 100644 build/icon.png create mode 100644 docs/ARCHITECTURE.md create mode 100644 docs/VERIFICATION.md create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 scripts/build-main.mjs create mode 100644 scripts/desktop-test.mjs create mode 100644 scripts/dev-window-test.mjs create mode 100644 scripts/dev.mjs create mode 100644 scripts/history-pagination-test.mjs create mode 100644 scripts/loading-test.mjs create mode 100644 scripts/make-icon.mjs create mode 100644 scripts/package-test.mjs create mode 100644 scripts/print-batch-test.mjs create mode 100644 src/core/date.ts create mode 100644 src/core/money.ts create mode 100644 src/core/templates.ts create mode 100644 src/main/confirmations.ts create mode 100644 src/main/export-layout.ts create mode 100644 src/main/exporter.ts create mode 100644 src/main/files.ts create mode 100644 src/main/index.ts create mode 100644 src/main/ledger.ts create mode 100644 src/main/pdf-engine.ts create mode 100644 src/main/png-density.ts create mode 100644 src/main/preload.ts create mode 100644 src/main/print-documents.ts create mode 100644 src/main/seals.ts create mode 100644 src/main/settings-store.ts create mode 100644 src/main/workbooks.ts create mode 100644 src/renderer/App.vue create mode 100644 src/renderer/components/ActionButton.vue create mode 100644 src/renderer/components/ConfirmDialog.vue create mode 100644 src/renderer/components/LoadingRegion.vue create mode 100644 src/renderer/components/ReceiptGroupTree.vue create mode 100644 src/renderer/components/ReceiptPreview.vue create mode 100644 src/renderer/confirmations.ts create mode 100644 src/renderer/env.d.ts create mode 100644 src/renderer/index.html create mode 100644 src/renderer/loading.ts create mode 100644 src/renderer/main.ts create mode 100644 src/renderer/public/licenses/reka-ui.txt create mode 100644 src/renderer/raster.html create mode 100644 src/renderer/raster.ts create mode 100644 src/renderer/state.ts create mode 100644 src/renderer/style.css create mode 100644 src/renderer/views/HistoryView.vue create mode 100644 src/renderer/views/SealsView.vue create mode 100644 src/renderer/views/SettingsView.vue create mode 100644 src/renderer/views/WorkbenchView.vue create mode 100644 src/shared/errors.ts create mode 100644 src/shared/file-names.ts create mode 100644 src/shared/receipt-groups.ts create mode 100644 src/shared/settings.ts create mode 100644 src/shared/types.ts create mode 100644 src/types/sql-asm.d.ts create mode 100644 tests/confirmations.test.ts create mode 100644 tests/core.test.ts create mode 100644 tests/export-layout.test.ts create mode 100644 tests/exporter.test.ts create mode 100644 tests/history-search.test.ts create mode 100644 tests/ledger-delete.test.ts create mode 100644 tests/png.test.ts create mode 100644 tests/print-documents.test.ts create mode 100644 tests/receipt-groups.test.ts create mode 100644 tests/settings-store.test.ts create mode 100644 tests/workbook-fields.test.ts create mode 100644 tsconfig.json create mode 100644 tsconfig.main.json create mode 100644 vite.config.ts create mode 100644 vitest.config.ts create mode 100644 启动本地调试.cmd 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 0000000000000000000000000000000000000000..b319f79cdc160dc90fe1212beaa74bb52d9f27dc GIT binary patch literal 1701 zcma)74@?th6fdPMj9M3Et#Nf)v`WeiS;SPQ1IkM0wVHxxTAf-QQm0$;g1NRQMIbbns)4cC@@6Y$% z@4a`WP-5Uqeqty*N-cvz;lTMwo_rH4;nf59qvgQ6hF}`g_0MyM3?-z*i{+;cY;mqD$ z1kahP!G@sU31ZQOm5=nOaj}mO^1Zg9wOp%8K758HO%q@?#c(GakoDlPnaOvumw1wh zFx`qLOtmEmETa5(eGi3v=K@rQJT4UHr7-)Fn zQA5)t9j^mXPRA1)0MYO|5apoz($I7dbff?T@w)+$f`&7};SnAGloWu#p&t+_0D(g- zAP{48oCyxMsl-LN?|qZ4OCLKaz^vdlNY7vHW<@viR;C9<`>2AG0Y;@r_K_#M)MUG^ zH~0KiHk1ck{CC|iP<$-;aeQiQ?_E!4`FQnc;#|uX?YhCvGKq233?}E=k<|Y?2X^Gw zCxaCygA=1+bCbGOUHT}} zt#oJ>&T{;pJY{AR?D9@FQ6+ON*=7u5FcYJUb z%zn5rjT{s2*HM4Ez67~$6>R=z#qmguU>CHF- zJdNu-zHeo&;~#iTt>>2S(R#88d#z0sfU+Y6l8y^{^D>$1<_$myRtkK7q2_8U5L~Y2 z{*h-aOoP?mh~Sq2K}&W6T87}PUg3HN*{SkY(F6@var{N2)WTO9CH|C8=+echGuirk zJBNBJ9a}h4EB@J4UmDZ8X2)aK^h=Gy_x@mcI{jYjuXHHehAYV14yU5VmCQJDS88@) z(F9IS{H=35q9OG>8D{tlUD~G&#rkv{eny=ok=6`0^EmQXj%%{<6}#4p_jl5zwc5}Y zUm+M+ElgTH7{qf{w-q;9)^V*53?Z2>lfxhAzc61h>K;~$@s`)`YYQuw`-J>RVThq9 z(u8`e9Gg>bUhzj#&j+xQ?<^a4w0~t8G@ui@P~!JxCoE$I)Kw6QndC}#a+c=4^UH1c PqoGJ8@|@PU->du+Z36az literal 0 HcmV?d00001 diff --git a/build/icon.png b/build/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..9c0e876e8e1d063f9b37b9cd3070674bac7711fc GIT binary patch literal 1679 zcma)7ZA?>V6fUJLj9M3Et#NemMj(70j^%o1v1TndO@He;;_jp_7Gs{}g`9Jq6Fn*$cJU*~=9`FPKH z&N=TrnkU=BihDJVLZPrEXm&n@5(UR7N-P7!p5kv*6beHw$Z~e9rA*3$G5w zykrn%WoO2}-CtL6D7vbA?`a#{Y_)-z?03 z*ZWYwb1Xuo$m2p$ezMWtPL*g${Vg3TO#=S}ek)i#{|+&_tL#>}<+#52(lJRW`d zL$~ok61Kco7d^NoA-&np==2B;c`jDTZeyfd#l2ffCA@utD|#9pe@x#zMaSzvl+*F} z20%2t4n#TVzSKA0105*%8|39Hir!u?eKNk5}fDE-KtRcf?e*O_|%DjUiJF8;gj z7brd!|2Q!{zVEKPt9+t*EPlRat7gMcSDDzbW)_q4?MUkXodY}k>yx31lYz;x5OO1T zelyOV8Z3J<$|f3Ua~$V3ZO&El_TvvLo|=p91?rue%=t-Ot1f*M?ors)i|080Po6Te z2zGfFo2ZgHmu)wMFqny2wpQ&>v~4qlS^zb&wMh=enA{LL;%D60mk@4__)=`jtJ88j zVpVq3<49(&;7W$v&a^4tyr?s6w<#x3!UmqVs!NPG@~}aF;HaOG3EI;UpDucD5_Wlq zA(ZZ7jlB^VPIo9u8ikuggsniu?G;=f(3$*4#xMfv-#t{f`h)Pghtc(G;F_jz>&({%O z8sBpY=CMJ=eX9}@l+9`rzR00y+D5P&*I+fr^dq0aKCGY3w>sG~8Y^|)-DHf^_!<4M zO|m5<>uVvKG@#^nDiQ5sE#!MfmjavX0>V2lkt32cleX>fOcBwIshI3p+T+iP=*)S} zDV12@V%6|H`G&$=hXR7Y4%9R4X?=o&xr8mk zW81lvdo}JX!d7cl`l0M_fw=R6&a^`6ym z>P=-@*6n=koO#qZJkJm2r!()`K&4&LK2ky6b|eKgtY*fLyHc|oi^OqiVsD*GAR1E6 zlVL{A&?WtvV6<1u;icD^<7q8mvw$Oe<%Bv5U$uLq=s*`;QmYB(cniV6VrJ6n!625i zw!OH~yn$=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('