diff --git a/package-lock.json b/package-lock.json index 57fb75f..8964947 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,10 +16,12 @@ "@xterm/xterm": "^6.0.0", "antd": "^6.6.2", "dockview-react": "^8.2.0", + "electron-updater": "^6.8.9", "font-list": "^2.1.0", "react": "^19.2.8", "react-dom": "^19.2.8", "ssh2": "^1.17.0", + "zmodem.js": "^0.1.10", "zustand": "^5.0.15" }, "devDependencies": { @@ -28,6 +30,7 @@ "@types/react-dom": "^19.2.0", "@vitejs/plugin-react": "^5.0.0", "electron": "^44.2.0", + "electron-builder": "^26.15.3", "electron-vite": "^5.0.0", "typescript": "~5.9.0", "vite": "^7.0.0" @@ -443,6 +446,86 @@ "node": ">=22.12.0" } }, + "node_modules/@electron/asar": { + "version": "3.4.1", + "resolved": "https://registry.npmmirror.com/@electron/asar/-/asar-3.4.1.tgz", + "integrity": "sha512-i4/rNPRS84t0vSRa2HorerGRXWyF4vThfHesw0dmcWHp+cspK743UanA0suA5Q5y8kzY2y6YKrvbIUn69BCAiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "commander": "^5.0.0", + "glob": "^7.1.6", + "minimatch": "^3.0.4" + }, + "bin": { + "asar": "bin/asar.js" + }, + "engines": { + "node": ">=10.12.0" + } + }, + "node_modules/@electron/asar/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@electron/asar/node_modules/brace-expansion": { + "version": "1.1.18", + "resolved": "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-1.1.18.tgz", + "integrity": "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@electron/asar/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmmirror.com/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@electron/fuses": { + "version": "1.8.0", + "resolved": "https://registry.npmmirror.com/@electron/fuses/-/fuses-1.8.0.tgz", + "integrity": "sha512-zx0EIq78WlY/lBb1uXlziZmDZI4ubcCXIMJ4uGjXzZW0nS19TjSPeXPAjzzTmKQlJUZm0SbmZhPKP7tuQ1SsEw==", + "dev": true, + "license": "MIT", + "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.npmmirror.com/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/@electron/get": { "version": "5.1.0", "resolved": "https://registry.npmmirror.com/@electron/get/-/get-5.1.0.tgz", @@ -477,6 +560,199 @@ "node": ">=10" } }, + "node_modules/@electron/notarize": { + "version": "2.5.0", + "resolved": "https://registry.npmmirror.com/@electron/notarize/-/notarize-2.5.0.tgz", + "integrity": "sha512-jNT8nwH1f9X5GEITXaQ8IF/KdskvIkOFfB2CvwumsveVidzpSc+mvhhTMdAGSYF3O+Nq49lJ7y+ssODRXu06+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.1.1", + "fs-extra": "^9.0.1", + "promise-retry": "^2.0.1" + }, + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/@electron/notarize/node_modules/fs-extra": { + "version": "9.1.0", + "resolved": "https://registry.npmmirror.com/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@electron/osx-sign": { + "version": "1.3.3", + "resolved": "https://registry.npmmirror.com/@electron/osx-sign/-/osx-sign-1.3.3.tgz", + "integrity": "sha512-KZ8mhXvWv2rIEgMbWZ4y33bDHyUKMXnx4M0sTyPNK/vcB81ImdeY9Ggdqy0SWbMDgmbqyQ+phgejh6V3R2QuSg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "compare-version": "^0.1.2", + "debug": "^4.3.4", + "fs-extra": "^10.0.0", + "isbinaryfile": "^4.0.8", + "minimist": "^1.2.6", + "plist": "^3.0.5" + }, + "bin": { + "electron-osx-flat": "bin/electron-osx-flat.js", + "electron-osx-sign": "bin/electron-osx-sign.js" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/@electron/osx-sign/node_modules/isbinaryfile": { + "version": "4.0.10", + "resolved": "https://registry.npmmirror.com/isbinaryfile/-/isbinaryfile-4.0.10.tgz", + "integrity": "sha512-iHrqe5shvBUcFbmZq9zOQHBoeOhZJu6RQGrDpBgenUm/Am+F3JM2MgQj+rK3Z601fzrL5gLZWtAPH2OBaSVcyw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/gjtorikian/" + } + }, + "node_modules/@electron/rebuild": { + "version": "4.2.0", + "resolved": "https://registry.npmmirror.com/@electron/rebuild/-/rebuild-4.2.0.tgz", + "integrity": "sha512-RKL/O+jGoXJMxrx/5771y1n0xTKmFuOYGO3gMmwypBM6rsH0kou0mswwdXA2JrhIkE4xyC7v9vGk0n6NPzgOxQ==", + "dev": true, + "license": "MIT", + "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.npmmirror.com/@electron/universal/-/universal-2.0.3.tgz", + "integrity": "sha512-Wn9sPYIVFRFl5HmwMJkARCCf7rqK/EurkfQ/rJZ14mHP3iYTjZSIOSVonEAnhWeAXwtw7zOekGRlc6yTtZ0t+g==", + "dev": true, + "license": "MIT", + "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.npmmirror.com/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@electron/universal/node_modules/brace-expansion": { + "version": "2.1.4", + "resolved": "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-2.1.4.tgz", + "integrity": "sha512-hGfVzPxthbf3+2yjg/RBs60cB0FhqBS/zvdV/4wn4/BmN0bNMMHPc4V/BbFieqf1TKAGGAHnY4eSjajCl0f2Xg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@electron/universal/node_modules/fs-extra": { + "version": "11.4.0", + "resolved": "https://registry.npmmirror.com/fs-extra/-/fs-extra-11.4.0.tgz", + "integrity": "sha512-EQsFzMUJkCKGr1ePqlYADkIUmHW1s3ZXr5Yqy6wbGrfUCphpl2maM/kyOIRA2HpP3AaFQTZXD4ldjek+nccddA==", + "dev": true, + "license": "MIT", + "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.npmmirror.com/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", + "dev": true, + "license": "ISC", + "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.npmmirror.com/@electron/windows-sign/-/windows-sign-1.2.2.tgz", + "integrity": "sha512-dfZeox66AvdPtb2lD8OsIIQh12Tp0GNCRUDfBHIKGpbmopZto2/A8nSpYYLoedPIHpqkeblZ/k8OV0Gy7PYuyQ==", + "dev": true, + "license": "BSD-2-Clause", + "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.0", + "resolved": "https://registry.npmmirror.com/fs-extra/-/fs-extra-11.4.0.tgz", + "integrity": "sha512-EQsFzMUJkCKGr1ePqlYADkIUmHW1s3ZXr5Yqy6wbGrfUCphpl2maM/kyOIRA2HpP3AaFQTZXD4ldjek+nccddA==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, "node_modules/@emotion/hash": { "version": "0.8.0", "resolved": "https://registry.npmmirror.com/@emotion/hash/-/hash-0.8.0.tgz", @@ -931,6 +1207,19 @@ "node": ">=18" } }, + "node_modules/@isaacs/fs-minipass": { + "version": "4.0.1", + "resolved": "https://registry.npmmirror.com/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", + "integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^7.0.4" + }, + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/@jridgewell/gen-mapping": { "version": "0.3.13", "resolved": "https://registry.npmmirror.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", @@ -1073,6 +1362,61 @@ "win32" ] }, + "node_modules/@malept/cross-spawn-promise": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/@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" + } + ], + "license": "Apache-2.0", + "dependencies": { + "cross-spawn": "^7.0.1" + }, + "engines": { + "node": ">= 12.13.0" + } + }, + "node_modules/@malept/flatpak-bundler": { + "version": "0.4.0", + "resolved": "https://registry.npmmirror.com/@malept/flatpak-bundler/-/flatpak-bundler-0.4.0.tgz", + "integrity": "sha512-9QOtNffcOF/c1seMCDnjckb3R9WHcG34tky+FHpNKKCW0wc/scYLwMtO+ptyGUfMW0/b/n4qRiALlaFHc9Oj7Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.1.1", + "fs-extra": "^9.0.0", + "lodash": "^4.17.15", + "tmp-promise": "^3.0.2" + }, + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/@malept/flatpak-bundler/node_modules/fs-extra": { + "version": "9.1.0", + "resolved": "https://registry.npmmirror.com/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/@napi-rs/lzma-linux-x64-gnu": { "version": "1.5.1", "resolved": "https://registry.npmmirror.com/@napi-rs/lzma-linux-x64-gnu/-/lzma-linux-x64-gnu-1.5.1.tgz", @@ -1093,6 +1437,74 @@ "node": "^22.20 || ^24.12 || >=25" } }, + "node_modules/@noble/hashes": { + "version": "2.4.0", + "resolved": "https://registry.npmmirror.com/@noble/hashes/-/hashes-2.4.0.tgz", + "integrity": "sha512-X5XaVWZIBCT7HHZGm5I7ZQXDwLG+bGXuSrMQAW+7Zvl87h1kmc1ZB1VSRJcpUfoUrGQp4Fkoxm5kZ+Ms+aW+eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@peculiar/asn1-schema": { + "version": "2.9.4", + "resolved": "https://registry.npmmirror.com/@peculiar/asn1-schema/-/asn1-schema-2.9.4.tgz", + "integrity": "sha512-GjzePcT9Iw8NzeOPf73iNS9xM+TBhd/FilAfP+RQGkTMQJTVWtytN3JHJACCjf/ABNau5S7mS3g+DcuxmRgYEg==", + "dev": true, + "license": "MIT", + "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.npmmirror.com/@peculiar/json-schema/-/json-schema-1.1.12.tgz", + "integrity": "sha512-coUfuoMeIB7B8/NMekxaDzLhaYmp0HZNPEjYRm9goRou8UZIC3z21s0sL9AWoCw4EG876QyO3kYrc61WNF9B/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@peculiar/utils": { + "version": "2.0.3", + "resolved": "https://registry.npmmirror.com/@peculiar/utils/-/utils-2.0.3.tgz", + "integrity": "sha512-+oL3HPFRIZ1St2K50lWCXiioIgSoxzz7R1J3uF6neO2yl1sgmpgY6XXJH4BdpoDkMWznQTeYF6oWNDZLCdQ4eQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.8.1" + } + }, + "node_modules/@peculiar/webcrypto": { + "version": "1.7.1", + "resolved": "https://registry.npmmirror.com/@peculiar/webcrypto/-/webcrypto-1.7.1.tgz", + "integrity": "sha512-ODOov0sGMJMf3jPonOkgGqPknTsu+DdQ7kD++gz8aI+aFMOMHFbWAA2taqXXVTdP+OTOQR/znGvSpmkeI0WTYQ==", + "dev": true, + "license": "MIT", + "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/@rc-component/async-validator": { "version": "6.0.0", "resolved": "https://registry.npmmirror.com/@rc-component/async-validator/-/async-validator-6.0.0.tgz", @@ -2210,6 +2622,32 @@ "win32" ] }, + "node_modules/@sindresorhus/is": { + "version": "4.6.0", + "resolved": "https://registry.npmmirror.com/@sindresorhus/is/-/is-4.6.0.tgz", + "integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/is?sponsor=1" + } + }, + "node_modules/@szmarczak/http-timer": { + "version": "4.0.6", + "resolved": "https://registry.npmmirror.com/@szmarczak/http-timer/-/http-timer-4.0.6.tgz", + "integrity": "sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==", + "dev": true, + "license": "MIT", + "dependencies": { + "defer-to-connect": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/@types/babel__core": { "version": "7.20.5", "resolved": "https://registry.npmmirror.com/@types/babel__core/-/babel__core-7.20.5.tgz", @@ -2255,6 +2693,29 @@ "@babel/types": "^7.28.2" } }, + "node_modules/@types/cacheable-request": { + "version": "6.0.3", + "resolved": "https://registry.npmmirror.com/@types/cacheable-request/-/cacheable-request-6.0.3.tgz", + "integrity": "sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/http-cache-semantics": "*", + "@types/keyv": "^3.1.4", + "@types/node": "*", + "@types/responselike": "^1.0.0" + } + }, + "node_modules/@types/debug": { + "version": "4.1.13", + "resolved": "https://registry.npmmirror.com/@types/debug/-/debug-4.1.13.tgz", + "integrity": "sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/ms": "*" + } + }, "node_modules/@types/estree": { "version": "1.0.9", "resolved": "https://registry.npmmirror.com/@types/estree/-/estree-1.0.9.tgz", @@ -2262,6 +2723,40 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/fs-extra": { + "version": "9.0.13", + "resolved": "https://registry.npmmirror.com/@types/fs-extra/-/fs-extra-9.0.13.tgz", + "integrity": "sha512-nEnwB++1u5lVDM2UI4c1+5R+FYaKfaAzS4OococimjVm3nQw3TuzH5UNsocrcTBbhnerblyHj4A49qXbIiZdpA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/http-cache-semantics": { + "version": "4.2.0", + "resolved": "https://registry.npmmirror.com/@types/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz", + "integrity": "sha512-L3LgimLHXtGkWikKnsPg0/VFx9OGZaC+eN1u4r+OB1XRqH3meBIAVC2zr1WdMH+RHmnRkqliQAOHNJ/E0j/e0Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/keyv": { + "version": "3.1.4", + "resolved": "https://registry.npmmirror.com/@types/keyv/-/keyv-3.1.4.tgz", + "integrity": "sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/ms": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/@types/ms/-/ms-2.1.0.tgz", + "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/node": { "version": "24.13.3", "resolved": "https://registry.npmmirror.com/@types/node/-/node-24.13.3.tgz", @@ -2292,6 +2787,16 @@ "@types/react": "^19.2.0" } }, + "node_modules/@types/responselike": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/@types/responselike/-/responselike-1.0.3.tgz", + "integrity": "sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@vitejs/plugin-react": { "version": "5.2.0", "resolved": "https://registry.npmmirror.com/@vitejs/plugin-react/-/plugin-react-5.2.0.tgz", @@ -2313,6 +2818,16 @@ "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" } }, + "node_modules/@xmldom/xmldom": { + "version": "0.8.15", + "resolved": "https://registry.npmmirror.com/@xmldom/xmldom/-/xmldom-0.8.15.tgz", + "integrity": "sha512-/5NV/vDALVFDXgLmfsy9TRCBlKwO2LNBFzpzvb9iIj+jR+eSc6DLYYvVOdivT/jm7MtU6TebYuRmzEOI7w40UA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + } + }, "node_modules/@xterm/addon-fit": { "version": "0.11.0", "resolved": "https://registry.npmmirror.com/@xterm/addon-fit/-/addon-fit-0.11.0.tgz", @@ -2346,6 +2861,69 @@ "addons/*" ] }, + "node_modules/abbrev": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/abbrev/-/abbrev-4.0.0.tgz", + "integrity": "sha512-a1wflyaL0tHtJSmLSOVybYhy22vRih4eduhhrkcjgrWGnRfrZtovJ2FRjxuTtkkj47O/baf0R86QU5OuYpz8fA==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmmirror.com/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/ajv": { + "version": "8.20.0", + "resolved": "https://registry.npmmirror.com/ajv/-/ajv-8.20.0.tgz", + "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, "node_modules/antd": { "version": "6.6.2", "resolved": "https://registry.npmmirror.com/antd/-/antd-6.6.2.tgz", @@ -2410,6 +2988,175 @@ "react-dom": ">=18.0.0" } }, + "node_modules/app-builder-lib": { + "version": "26.15.3", + "resolved": "https://registry.npmmirror.com/app-builder-lib/-/app-builder-lib-26.15.3.tgz", + "integrity": "sha512-2VnyWkqsP5v5XbBhL3tD5Syx8iNPBYsoU7kY4S2fz7wg8Rj/nztWKCUzGKaFRTv0Xwf3/H058CR1Kvtd/3lRow==", + "dev": true, + "license": "MIT", + "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.npmmirror.com/@electron/get/-/get-3.1.0.tgz", + "integrity": "sha512-F+nKc0xW+kVbBRhFzaMgPy3KwmuNTYX1fx6+FxxoSnNgwYX6LD7AKBTWkU0MQ6IBoe7dz069CNkR673sPAgkCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.1.1", + "env-paths": "^2.2.0", + "fs-extra": "^8.1.0", + "got": "^11.8.5", + "progress": "^2.0.3", + "semver": "^6.2.0", + "sumchecker": "^3.0.1" + }, + "engines": { + "node": ">=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.npmmirror.com/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + }, + "engines": { + "node": ">=6 <7 || >=8" + } + }, + "node_modules/app-builder-lib/node_modules/@electron/get/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmmirror.com/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/app-builder-lib/node_modules/ci-info": { + "version": "4.3.1", + "resolved": "https://registry.npmmirror.com/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" + } + ], + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/app-builder-lib/node_modules/env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmmirror.com/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/app-builder-lib/node_modules/jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", + "dev": true, + "license": "MIT", + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/app-builder-lib/node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmmirror.com/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/app-builder-lib/node_modules/universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmmirror.com/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "license": "Python-2.0" + }, "node_modules/asn1": { "version": "0.2.6", "resolved": "https://registry.npmmirror.com/asn1/-/asn1-0.2.6.tgz", @@ -2419,6 +3166,93 @@ "safer-buffer": "~2.1.0" } }, + "node_modules/asn1js": { + "version": "3.0.10", + "resolved": "https://registry.npmmirror.com/asn1js/-/asn1js-3.0.10.tgz", + "integrity": "sha512-S2s3aOytiKdFRdulw2qPE51MzjzVOisppcVv7jVFR+Kw0kxwvFrDcYA0h7Ndqbmj0HkMIXYWaoj7fli8kgx1eg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "pvtsutils": "^1.3.6", + "pvutils": "^1.1.5", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/async": { + "version": "3.2.6", + "resolved": "https://registry.npmmirror.com/async/-/async-3.2.6.tgz", + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", + "dev": true, + "license": "MIT" + }, + "node_modules/async-exit-hook": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/async-exit-hook/-/async-exit-hook-2.0.1.tgz", + "integrity": "sha512-NW2cX8m1Q7KPA7a5M2ULQeZ2wR5qI5PAbw5L0UOMxdioVk9PMZ0h1TmyZEkPYrCvYjDlFICusOu1dlEKAAeXBw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmmirror.com/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/at-least-node": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/at-least-node/-/at-least-node-1.0.0.tgz", + "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/aws4": { + "version": "1.13.2", + "resolved": "https://registry.npmmirror.com/aws4/-/aws4-1.13.2.tgz", + "integrity": "sha512-lHe62zvbTB5eEABUVi/AwVh0ZKY9rMMDhmm+eeyuuUQbQ3+J+fONVQOZyj+DdrvD4BY33uYniyRJ4UJIaSKAfw==", + "dev": true, + "license": "MIT" + }, + "node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmmirror.com/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmmirror.com/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, "node_modules/baseline-browser-mapping": { "version": "2.11.21", "resolved": "https://registry.npmmirror.com/baseline-browser-mapping/-/baseline-browser-mapping-2.11.21.tgz", @@ -2441,6 +3275,35 @@ "tweetnacl": "^0.14.3" } }, + "node_modules/bluebird": { + "version": "3.7.2", + "resolved": "https://registry.npmmirror.com/bluebird/-/bluebird-3.7.2.tgz", + "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", + "dev": true, + "license": "MIT" + }, + "node_modules/boolean": { + "version": "3.2.0", + "resolved": "https://registry.npmmirror.com/boolean/-/boolean-3.2.0.tgz", + "integrity": "sha512-d0II/GO9uf9lfUHH2BQsjxzRJZBdsjgsBiW4BvhWk/3qoKwQFjIDVN19PfX8F2D/r9PCMTtLWjYVCFrpeYUzsw==", + "deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/brace-expansion": { + "version": "5.0.9", + "resolved": "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-5.0.9.tgz", + "integrity": "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "20 || >=22" + } + }, "node_modules/browserslist": { "version": "4.28.9", "resolved": "https://registry.npmmirror.com/browserslist/-/browserslist-4.28.9.tgz", @@ -2475,6 +3338,13 @@ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true, + "license": "MIT" + }, "node_modules/buildcheck": { "version": "0.0.7", "resolved": "https://registry.npmmirror.com/buildcheck/-/buildcheck-0.0.7.tgz", @@ -2484,6 +3354,55 @@ "node": ">=10.0.0" } }, + "node_modules/builder-util": { + "version": "26.15.3", + "resolved": "https://registry.npmmirror.com/builder-util/-/builder-util-26.15.3.tgz", + "integrity": "sha512-q2hn7Mbo2nFNkVekPiHFx6Nfo3hURmES3tfBn+k5Pqxl2RkmP3QGqZUhH/q9Pch/4G05NRhPjDlVj1O8q4Txvw==", + "dev": true, + "license": "MIT", + "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.npmmirror.com/builder-util-runtime/-/builder-util-runtime-9.7.0.tgz", + "integrity": "sha512-g/kR520giAFYkSXTzcmF3kqQq7wi8F6N6SzeDgZrqTBN+VHdmgWOyTdD1yD7AATDId/yXLvuP34CxW46/BwCdw==", + "license": "MIT", + "dependencies": { + "debug": "^4.3.4", + "sax": "^1.2.4" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/bytestreamjs": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/bytestreamjs/-/bytestreamjs-2.0.1.tgz", + "integrity": "sha512-U1Z/ob71V/bXfVABvNr/Kumf5VyeQRBEm6Txb0PQ6S7V5GpBM3w4Cbqz/xPDicR5tN0uvDifng8C+5qECeGwyQ==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/cac": { "version": "6.7.14", "resolved": "https://registry.npmmirror.com/cac/-/cac-6.7.14.tgz", @@ -2494,6 +3413,49 @@ "node": ">=8" } }, + "node_modules/cacheable-lookup": { + "version": "5.0.4", + "resolved": "https://registry.npmmirror.com/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz", + "integrity": "sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.6.0" + } + }, + "node_modules/cacheable-request": { + "version": "7.0.4", + "resolved": "https://registry.npmmirror.com/cacheable-request/-/cacheable-request-7.0.4.tgz", + "integrity": "sha512-v+p6ongsrp0yTGbJXjgxPow2+DL93DASP4kXCDKb8/bwRtt9OEF3whggkkDkGNzgcWy2XaF4a8nZglC7uElscg==", + "dev": true, + "license": "MIT", + "dependencies": { + "clone-response": "^1.0.2", + "get-stream": "^5.1.0", + "http-cache-semantics": "^4.0.0", + "keyv": "^4.0.0", + "lowercase-keys": "^2.0.0", + "normalize-url": "^6.0.1", + "responselike": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/caniuse-lite": { "version": "1.0.30001810", "resolved": "https://registry.npmmirror.com/caniuse-lite/-/caniuse-lite-1.0.30001810.tgz", @@ -2515,6 +3477,84 @@ ], "license": "CC-BY-4.0" }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmmirror.com/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chownr": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/chownr/-/chownr-3.0.0.tgz", + "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/chromium-pickle-js": { + "version": "0.2.0", + "resolved": "https://registry.npmmirror.com/chromium-pickle-js/-/chromium-pickle-js-0.2.0.tgz", + "integrity": "sha512-1R5Fho+jBq0DDydt+/vHWj5KJNJCKdARKOCwZUen84I5BreWoLqRLANH1U87eJy1tiASPtMnGqJJq0ZsLoRPOw==", + "dev": true, + "license": "MIT" + }, + "node_modules/ci-info": { + "version": "4.4.0", + "resolved": "https://registry.npmmirror.com/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" + } + ], + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmmirror.com/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/clone-response": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/clone-response/-/clone-response-1.0.3.tgz", + "integrity": "sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-response": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/clsx": { "version": "2.1.1", "resolved": "https://registry.npmmirror.com/clsx/-/clsx-2.1.1.tgz", @@ -2524,12 +3564,72 @@ "node": ">=6" } }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmmirror.com/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmmirror.com/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/commander": { + "version": "5.1.0", + "resolved": "https://registry.npmmirror.com/commander/-/commander-5.1.0.tgz", + "integrity": "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/compare-version": { + "version": "0.1.2", + "resolved": "https://registry.npmmirror.com/compare-version/-/compare-version-0.1.2.tgz", + "integrity": "sha512-pJDh5/4wrEnXX/VWRZvruAGHkzKdr46z11OlTPN+VrATlWWhSKewNCJ1futCO5C7eJB3nPMFZA1LeYtcFboZ2A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/compute-scroll-into-view": { "version": "3.1.1", "resolved": "https://registry.npmmirror.com/compute-scroll-into-view/-/compute-scroll-into-view-3.1.1.tgz", "integrity": "sha512-VRhuHOLoKYOy4UbilLbUzbYg93XLjv2PncJC50EuTWPA3gaja1UjBsUP/D/9/juV3vQFr6XBEzn9KCAHdUvOHw==", "license": "MIT" }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmmirror.com/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, "node_modules/convert-source-map": { "version": "2.0.0", "resolved": "https://registry.npmmirror.com/convert-source-map/-/convert-source-map-2.0.0.tgz", @@ -2537,6 +3637,13 @@ "dev": true, "license": "MIT" }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "dev": true, + "license": "MIT" + }, "node_modules/cpu-features": { "version": "0.0.10", "resolved": "https://registry.npmmirror.com/cpu-features/-/cpu-features-0.0.10.tgz", @@ -2551,6 +3658,65 @@ "node": ">=10.0.0" } }, + "node_modules/crc-32": { + "version": "1.2.2", + "resolved": "https://registry.npmmirror.com/crc-32/-/crc-32-1.2.2.tgz", + "integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==", + "license": "Apache-2.0", + "bin": { + "crc32": "bin/crc32.njs" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/cross-dirname": { + "version": "0.1.0", + "resolved": "https://registry.npmmirror.com/cross-dirname/-/cross-dirname-0.1.0.tgz", + "integrity": "sha512-+R08/oI0nl3vfPcqftZRpytksBXDzOUveBq/NBVx0sUp1axwzPQrKinNx5yd5sxPu8j1wIy8AfnVQ+5eFdha6Q==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmmirror.com/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/cross-spawn/node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/cross-spawn/node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, "node_modules/csstype": { "version": "3.2.3", "resolved": "https://registry.npmmirror.com/csstype/-/csstype-3.2.3.tgz", @@ -2567,7 +3733,6 @@ "version": "4.4.3", "resolved": "https://registry.npmmirror.com/debug/-/debug-4.4.3.tgz", "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "dev": true, "license": "MIT", "dependencies": { "ms": "^2.1.3" @@ -2581,6 +3746,156 @@ } } }, + "node_modules/decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmmirror.com/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-response": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/decompress-response/node_modules/mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/defer-to-connect": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/defer-to-connect/-/defer-to-connect-2.0.1.tgz", + "integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmmirror.com/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmmirror.com/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/detect-node": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/detect-node/-/detect-node-2.1.0.tgz", + "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/dir-compare": { + "version": "4.2.0", + "resolved": "https://registry.npmmirror.com/dir-compare/-/dir-compare-4.2.0.tgz", + "integrity": "sha512-2xMCmOoMrdQIPHdsTawECdNPwlVFB9zGcz3kuhmBO6U3oU+UQjsue0i8ayLKpgBcm+hcXPMVSGUN9d+pvJ6+VQ==", + "dev": true, + "license": "MIT", + "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.npmmirror.com/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/dir-compare/node_modules/brace-expansion": { + "version": "1.1.18", + "resolved": "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-1.1.18.tgz", + "integrity": "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/dir-compare/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmmirror.com/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/dmg-builder": { + "version": "26.15.3", + "resolved": "https://registry.npmmirror.com/dmg-builder/-/dmg-builder-26.15.3.tgz", + "integrity": "sha512-O3zJUFUYHJKgzPqioHxfxzBzlSC1eXCSr79gMSBKBP5AgjjpmrydMsMLotEg9fAJF36vdUncb+4ndRNxoPdlSQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "app-builder-lib": "26.15.3", + "builder-util": "26.15.3", + "fs-extra": "^10.1.0", + "js-yaml": "^4.1.0" + } + }, "node_modules/dockview": { "version": "8.2.0", "resolved": "https://registry.npmmirror.com/dockview/-/dockview-8.2.0.tgz", @@ -2609,6 +3924,76 @@ "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, + "node_modules/dotenv": { + "version": "16.6.1", + "resolved": "https://registry.npmmirror.com/dotenv/-/dotenv-16.6.1.tgz", + "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/dotenv-expand": { + "version": "11.0.7", + "resolved": "https://registry.npmmirror.com/dotenv-expand/-/dotenv-expand-11.0.7.tgz", + "integrity": "sha512-zIHwmZPRshsCdpMDyVsqGmgyP0yT8GAgXUnkdAoJisxvf33k7yO6OuoKmcTGuXPWSsm8Oh88nZicRLA9Y0rUeA==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "dotenv": "^16.4.5" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/duplexer2": { + "version": "0.1.4", + "resolved": "https://registry.npmmirror.com/duplexer2/-/duplexer2-0.1.4.tgz", + "integrity": "sha512-asLFVfWWtJ90ZyOUHMqk7/S2w2guQKxUI2itj3d92ADHhxUSbCMGi1f1cBcJ7xM1To+pE/Khbwo1yuNbMEPKeA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "readable-stream": "^2.0.2" + } + }, + "node_modules/ejs": { + "version": "3.1.10", + "resolved": "https://registry.npmmirror.com/ejs/-/ejs-3.1.10.tgz", + "integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "jake": "^10.8.5" + }, + "bin": { + "ejs": "bin/cli.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/electron": { "version": "44.2.0", "resolved": "https://registry.npmmirror.com/electron/-/electron-44.2.0.tgz", @@ -2628,6 +4013,63 @@ "node": ">= 22.12.0" } }, + "node_modules/electron-builder": { + "version": "26.15.3", + "resolved": "https://registry.npmmirror.com/electron-builder/-/electron-builder-26.15.3.tgz", + "integrity": "sha512-a1KM5heqS3gQCZzizXEI8RjJy3QVogULPdeSknt76uLDpBIW/HDGsMg/XgP0riP6PI9COsRvFITKKGDqA8fJxA==", + "dev": true, + "license": "MIT", + "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.npmmirror.com/electron-builder-squirrel-windows/-/electron-builder-squirrel-windows-26.15.3.tgz", + "integrity": "sha512-Jc19XPV9y9+2bAdZPkXuVNGNIEFBq9poHC61l8Kv6FdK7DRG3+Ic0rerC0DXOaeHNz8yW0fg/JnF8GQROOF5MA==", + "dev": true, + "license": "MIT", + "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.npmmirror.com/electron-publish/-/electron-publish-26.15.3.tgz", + "integrity": "sha512-g/2bn8YTavY4cuS5F+jOS7zmZbXXBV8KZ8yHKfJjFPoKtzBqrpCdNPxBd3tqdBwP7BVd0lGzf7Bk2s0KesWZ4Q==", + "dev": true, + "license": "MIT", + "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-to-chromium": { "version": "1.5.422", "resolved": "https://registry.npmmirror.com/electron-to-chromium/-/electron-to-chromium-1.5.422.tgz", @@ -2635,6 +4077,34 @@ "dev": true, "license": "ISC" }, + "node_modules/electron-updater": { + "version": "6.8.9", + "resolved": "https://registry.npmmirror.com/electron-updater/-/electron-updater-6.8.9.tgz", + "integrity": "sha512-ZhVxM9iGONUpZGI1FxdMRgJjUFXi7AYGVa5PwKlO1tV1/4zDxQmfKpXOHVztKrd6L9rLcFjERvi1Mf2vxyTkig==", + "license": "MIT", + "dependencies": { + "builder-util-runtime": "9.7.0", + "fs-extra": "^10.1.0", + "js-yaml": "^4.1.0", + "lazy-val": "^1.0.5", + "lodash.escaperegexp": "^4.1.2", + "lodash.isequal": "^4.5.0", + "semver": "~7.7.3", + "tiny-typed-emitter": "^2.1.0" + } + }, + "node_modules/electron-updater/node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmmirror.com/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/electron-vite": { "version": "5.0.0", "resolved": "https://registry.npmmirror.com/electron-vite/-/electron-vite-5.0.0.tgz", @@ -2665,6 +4135,83 @@ } } }, + "node_modules/electron-winstaller": { + "version": "5.4.0", + "resolved": "https://registry.npmmirror.com/electron-winstaller/-/electron-winstaller-5.4.0.tgz", + "integrity": "sha512-bO3y10YikuUwUuDUQRM4KfwNkKhnpVO7IPdbsrejwN9/AABJzzTQ4GeHwyzNSrVO+tEH3/Np255a3sVZpZDjvg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "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.npmmirror.com/fs-extra/-/fs-extra-7.0.1.tgz", + "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", + "dev": true, + "license": "MIT", + "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.npmmirror.com/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", + "dev": true, + "license": "MIT", + "peer": true, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/electron-winstaller/node_modules/universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmmirror.com/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmmirror.com/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/end-of-stream": { + "version": "1.4.5", + "resolved": "https://registry.npmmirror.com/end-of-stream/-/end-of-stream-1.4.5.tgz", + "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "once": "^1.4.0" + } + }, "node_modules/env-paths": { "version": "3.0.0", "resolved": "https://registry.npmmirror.com/env-paths/-/env-paths-3.0.0.tgz", @@ -2678,6 +4225,70 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/err-code": { + "version": "2.0.3", + "resolved": "https://registry.npmmirror.com/err-code/-/err-code-2.0.3.tgz", + "integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==", + "dev": true, + "license": "MIT" + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmmirror.com/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/es-object-atoms/-/es-object-atoms-1.1.2.tgz", + "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es6-error": { + "version": "4.1.1", + "resolved": "https://registry.npmmirror.com/es6-error/-/es6-error-4.1.1.tgz", + "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==", + "dev": true, + "license": "MIT", + "optional": true + }, "node_modules/esbuild": { "version": "0.25.12", "resolved": "https://registry.npmmirror.com/esbuild/-/esbuild-0.25.12.tgz", @@ -2730,6 +4341,51 @@ "node": ">=6" } }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/exponential-backoff": { + "version": "3.1.3", + "resolved": "https://registry.npmmirror.com/exponential-backoff/-/exponential-backoff-3.1.3.tgz", + "integrity": "sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmmirror.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-uri": { + "version": "3.1.7", + "resolved": "https://registry.npmmirror.com/fast-uri/-/fast-uri-3.1.7.tgz", + "integrity": "sha512-dOvZVzjdZdz7phd9v6jCbwxrBW3fK6n8Rc0CtdmM4bumzMnxywBYhuph6J819RRw/ku+rLbelwfMunktuzVVHg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, "node_modules/fdir": { "version": "6.5.0", "resolved": "https://registry.npmmirror.com/fdir/-/fdir-6.5.0.tgz", @@ -2748,12 +4404,90 @@ } } }, + "node_modules/filelist": { + "version": "1.0.6", + "resolved": "https://registry.npmmirror.com/filelist/-/filelist-1.0.6.tgz", + "integrity": "sha512-5giy2PkLYY1cP39p17Ech+2xlpTRL9HLspOfEgm0L6CwBXBTgsK5ou0JtzYuepxkaQ/tvhCFIJ5uXo0OrM2DxA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "minimatch": "^5.0.1" + } + }, + "node_modules/filelist/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/filelist/node_modules/brace-expansion": { + "version": "2.1.4", + "resolved": "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-2.1.4.tgz", + "integrity": "sha512-hGfVzPxthbf3+2yjg/RBs60cB0FhqBS/zvdV/4wn4/BmN0bNMMHPc4V/BbFieqf1TKAGGAHnY4eSjajCl0f2Xg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/filelist/node_modules/minimatch": { + "version": "5.1.9", + "resolved": "https://registry.npmmirror.com/minimatch/-/minimatch-5.1.9.tgz", + "integrity": "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/font-list": { "version": "2.1.0", "resolved": "https://registry.npmmirror.com/font-list/-/font-list-2.1.0.tgz", "integrity": "sha512-GMDcD8Wymmhz7pxOzu+Gm/bbVzxvUHJJSiLPCF/rt02g2W7P08Nhz6XiaYjXo8xklXWhLoCiyi1cBPwwASC2fQ==", "license": "MIT" }, + "node_modules/form-data": { + "version": "4.0.6", + "resolved": "https://registry.npmmirror.com/form-data/-/form-data-4.0.6.tgz", + "integrity": "sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==", + "dev": true, + "license": "MIT", + "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-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmmirror.com/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "license": "MIT", + "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.npmmirror.com/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true, + "license": "ISC" + }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmmirror.com/fsevents/-/fsevents-2.3.3.tgz", @@ -2769,6 +4503,16 @@ "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmmirror.com/gensync/-/gensync-1.0.0-beta.2.tgz", @@ -2779,19 +4523,461 @@ "node": ">=6.9.0" } }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmmirror.com/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmmirror.com/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmmirror.com/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmmirror.com/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/glob/node_modules/brace-expansion": { + "version": "1.1.18", + "resolved": "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-1.1.18.tgz", + "integrity": "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/glob/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmmirror.com/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/global-agent": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/global-agent/-/global-agent-3.0.0.tgz", + "integrity": "sha512-PT6XReJ+D07JvGoxQMkT6qji/jVNfX/h364XHZOWeRzy64sSFr+xJ5OX7LI3b4MPQzdL4H8Y8M0xzPpsVMwA8Q==", + "dev": true, + "license": "BSD-3-Clause", + "optional": true, + "dependencies": { + "boolean": "^3.0.1", + "es6-error": "^4.1.1", + "matcher": "^3.0.0", + "roarr": "^2.15.3", + "semver": "^7.3.2", + "serialize-error": "^7.0.1" + }, + "engines": { + "node": ">=10.0" + } + }, + "node_modules/global-agent/node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmmirror.com/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "dev": true, + "license": "ISC", + "optional": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/globalthis": { + "version": "1.0.4", + "resolved": "https://registry.npmmirror.com/globalthis/-/globalthis-1.0.4.tgz", + "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "define-properties": "^1.2.1", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/got": { + "version": "11.8.6", + "resolved": "https://registry.npmmirror.com/got/-/got-11.8.6.tgz", + "integrity": "sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sindresorhus/is": "^4.0.0", + "@szmarczak/http-timer": "^4.0.5", + "@types/cacheable-request": "^6.0.1", + "@types/responselike": "^1.0.0", + "cacheable-lookup": "^5.0.3", + "cacheable-request": "^7.0.2", + "decompress-response": "^6.0.0", + "http2-wrapper": "^1.0.0-beta.5.2", + "lowercase-keys": "^2.0.0", + "p-cancelable": "^2.0.0", + "responselike": "^2.0.0" + }, + "engines": { + "node": ">=10.19.0" + }, + "funding": { + "url": "https://github.com/sindresorhus/got?sponsor=1" + } + }, "node_modules/graceful-fs": { "version": "4.2.11", "resolved": "https://registry.npmmirror.com/graceful-fs/-/graceful-fs-4.2.11.tgz", "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "license": "ISC" + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.4", + "resolved": "https://registry.npmmirror.com/hasown/-/hasown-2.0.4.tgz", + "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hosted-git-info": { + "version": "4.1.0", + "resolved": "https://registry.npmmirror.com/hosted-git-info/-/hosted-git-info-4.1.0.tgz", + "integrity": "sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==", + "dev": true, + "license": "ISC", + "dependencies": { + "lru-cache": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/hosted-git-info/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmmirror.com/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/hosted-git-info/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "dev": true, "license": "ISC" }, + "node_modules/http-cache-semantics": { + "version": "4.2.0", + "resolved": "https://registry.npmmirror.com/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz", + "integrity": "sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmmirror.com/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/http2-wrapper": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/http2-wrapper/-/http2-wrapper-1.0.3.tgz", + "integrity": "sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "quick-lru": "^5.1.1", + "resolve-alpn": "^1.0.0" + }, + "engines": { + "node": ">=10.19.0" + } + }, + "node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmmirror.com/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmmirror.com/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "dev": true, + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmmirror.com/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/is-mobile": { "version": "5.0.0", "resolved": "https://registry.npmmirror.com/is-mobile/-/is-mobile-5.0.0.tgz", "integrity": "sha512-Tz/yndySvLAEXh+Uk8liFCxOwVH6YutuR74utvOcu7I9Di+DwM0mtdPVZNaVvvBUM2OXxne/NhOs1zAO7riusQ==", "license": "MIT" }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/isbinaryfile": { + "version": "5.0.7", + "resolved": "https://registry.npmmirror.com/isbinaryfile/-/isbinaryfile-5.0.7.tgz", + "integrity": "sha512-gnWD14Jh3FzS3CPhF0AxNOJ8CxqeblPTADzI38r0wt8ZyQl5edpy75myt08EG2oKvpyiqSqsx+Wkz9vtkbTqYQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 18.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/gjtorikian/" + } + }, + "node_modules/isexe": { + "version": "3.1.5", + "resolved": "https://registry.npmmirror.com/isexe/-/isexe-3.1.5.tgz", + "integrity": "sha512-6B3tLtFqtQS4ekarvLVMZ+X+VlvQekbe4taUkf/rhVO3d/h0M2rfARm/pXLcPEsjjMsFgrFgSrhQIxcSVrBz8w==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/jake": { + "version": "10.9.4", + "resolved": "https://registry.npmmirror.com/jake/-/jake-10.9.4.tgz", + "integrity": "sha512-wpHYzhxiVQL+IV05BLE2Xn34zW1S223hvjtqk0+gsPrwd/8JNLXJgZZM/iPFsYc1xyphF+6M6EvdE5E9MBGkDA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "async": "^3.2.6", + "filelist": "^1.0.4", + "picocolors": "^1.1.1" + }, + "bin": { + "jake": "bin/cli.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/jiti": { + "version": "2.7.0", + "resolved": "https://registry.npmmirror.com/jiti/-/jiti-2.7.0.tgz", + "integrity": "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==", + "dev": true, + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmmirror.com/js-tokens/-/js-tokens-4.0.0.tgz", @@ -2799,6 +4985,28 @@ "dev": true, "license": "MIT" }, + "node_modules/js-yaml": { + "version": "4.3.2", + "resolved": "https://registry.npmmirror.com/js-yaml/-/js-yaml-4.3.2.tgz", + "integrity": "sha512-SFNOvSJ+Dgf/9An904Yx+CgSlIPCkIpao4qo51lpee25TIRejdH3rhR4EZMGoNx3/TP3O+wzWuiTFl4sqbltzA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/nodeca" + } + ], + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, "node_modules/jsesc": { "version": "3.1.0", "resolved": "https://registry.npmmirror.com/jsesc/-/jsesc-3.1.0.tgz", @@ -2812,6 +5020,28 @@ "node": ">=6" } }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmmirror.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", + "dev": true, + "license": "ISC", + "optional": true + }, "node_modules/json2mq": { "version": "0.2.0", "resolved": "https://registry.npmmirror.com/json2mq/-/json2mq-0.2.0.tgz", @@ -2834,6 +5064,64 @@ "node": ">=6" } }, + "node_modules/jsonfile": { + "version": "6.2.1", + "resolved": "https://registry.npmmirror.com/jsonfile/-/jsonfile-6.2.1.tgz", + "integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==", + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmmirror.com/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/lazy-val": { + "version": "1.0.5", + "resolved": "https://registry.npmmirror.com/lazy-val/-/lazy-val-1.0.5.tgz", + "integrity": "sha512-0/BnGCCfyUMkBpeDgWihanIAF9JmZhHBgUhEqzvf+adhNGLoP6TaiI5oF8oyb3I45P+PcnrqihSf01M0l0G5+Q==", + "license": "MIT" + }, + "node_modules/lodash": { + "version": "4.18.1", + "resolved": "https://registry.npmmirror.com/lodash/-/lodash-4.18.1.tgz", + "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.escaperegexp": { + "version": "4.1.2", + "resolved": "https://registry.npmmirror.com/lodash.escaperegexp/-/lodash.escaperegexp-4.1.2.tgz", + "integrity": "sha512-TM9YBvyC84ZxE3rgfefxUWiQKLilstD6k7PTGt6wfbtXF8ixIJLOL3VYyV/z+ZiPLsVxAsKAFVwWlWeb2Y8Yyw==", + "license": "MIT" + }, + "node_modules/lodash.isequal": { + "version": "4.5.0", + "resolved": "https://registry.npmmirror.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz", + "integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==", + "deprecated": "This package is deprecated. Use require('node:util').isDeepStrictEqual instead.", + "license": "MIT" + }, + "node_modules/lowercase-keys": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/lowercase-keys/-/lowercase-keys-2.0.0.tgz", + "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/lru-cache": { "version": "5.1.1", "resolved": "https://registry.npmmirror.com/lru-cache/-/lru-cache-5.1.1.tgz", @@ -2854,11 +5142,143 @@ "@jridgewell/sourcemap-codec": "^1.5.5" } }, + "node_modules/matcher": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/matcher/-/matcher-3.0.0.tgz", + "integrity": "sha512-OkeDaAZ/bQCxeFAozM55PKcKU0yJMPGifLwV4Qgjitu+5MoAfSQN4lsLJeXZ1b8w0x+/Emda6MZgXS1jvsapng==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "escape-string-regexp": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/mime": { + "version": "2.6.0", + "resolved": "https://registry.npmmirror.com/mime/-/mime-2.6.0.tgz", + "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", + "dev": true, + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmmirror.com/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmmirror.com/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-response": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/mimic-response/-/mimic-response-1.0.1.tgz", + "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/minimatch": { + "version": "10.2.6", + "resolved": "https://registry.npmmirror.com/minimatch/-/minimatch-10.2.6.tgz", + "integrity": "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==", + "dev": true, + "license": "BlueOak-1.0.0", + "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.npmmirror.com/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/minipass": { + "version": "7.1.3", + "resolved": "https://registry.npmmirror.com/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/minizlib": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/minizlib/-/minizlib-3.1.0.tgz", + "integrity": "sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "minipass": "^7.1.2" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmmirror.com/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "minimist": "^1.2.6" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, "license": "MIT" }, "node_modules/nan": { @@ -2887,6 +5307,146 @@ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, + "node_modules/node-abi": { + "version": "4.35.0", + "resolved": "https://registry.npmmirror.com/node-abi/-/node-abi-4.35.0.tgz", + "integrity": "sha512-ymk4aIzxdPopw2giv8Fs1Ec6vybGkjmyxUwVqhkI4MCy2tVfXdkOGGWieWVjL0THgH+7a8lRdevyupoYj3Js/Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.6.3" + }, + "engines": { + "node": ">=22.12.0" + } + }, + "node_modules/node-abi/node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmmirror.com/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/node-api-version": { + "version": "0.2.1", + "resolved": "https://registry.npmmirror.com/node-api-version/-/node-api-version-0.2.1.tgz", + "integrity": "sha512-2xP/IGGMmmSQpI1+O/k72jF/ykvZ89JeuKX3TLJAYPDVLUalrshrLHkeVcCCZqG/eEa635cr8IBYzgnDvM2O8Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.3.5" + } + }, + "node_modules/node-api-version/node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmmirror.com/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/node-gyp": { + "version": "12.4.0", + "resolved": "https://registry.npmmirror.com/node-gyp/-/node-gyp-12.4.0.tgz", + "integrity": "sha512-OMcPNvqTCFUnNaBlmdgq+lfNqY7gTiSmNRDjY3uAXRyudeKZEZxu3CLtjMQrx4zZxCX2b/mpNqTtwuCJgXhHkw==", + "dev": true, + "license": "MIT", + "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.npmmirror.com/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/node-gyp/node_modules/isexe": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/isexe/-/isexe-4.0.0.tgz", + "integrity": "sha512-FFUtZMpoZ8RqHS3XeXEmHWLA4thH+ZxCv2lOiPIn1Xc7CxrqhWzNSDzD+/chS/zbYezmiwWLdQC09JdQKmthOw==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=20" + } + }, + "node_modules/node-gyp/node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmmirror.com/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/node-gyp/node_modules/undici": { + "version": "6.28.1", + "resolved": "https://registry.npmmirror.com/undici/-/undici-6.28.1.tgz", + "integrity": "sha512-zWpdTVD54H48CIybL0rWQ3ukpb9d23wM7eH5RtfdmeP70cWHNjtfo7P4vZX+5CoDcO53J4Pu5uXp7lNfjc6DRA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.17" + } + }, + "node_modules/node-gyp/node_modules/which": { + "version": "6.0.1", + "resolved": "https://registry.npmmirror.com/which/-/which-6.0.1.tgz", + "integrity": "sha512-oGLe46MIrCRqX7ytPUf66EAYvdeMIZYn3WaocqqKZAxrBpkqHfL/qvTyJ/bTk5+AqHCjXmrv3CEWgy368zhRUg==", + "dev": true, + "license": "ISC", + "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.npmmirror.com/node-int64/-/node-int64-0.4.0.tgz", + "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", + "dev": true, + "license": "MIT" + }, "node_modules/node-releases": { "version": "2.0.54", "resolved": "https://registry.npmmirror.com/node-releases/-/node-releases-2.0.54.tgz", @@ -2897,6 +5457,117 @@ "node": ">=18" } }, + "node_modules/nopt": { + "version": "9.0.0", + "resolved": "https://registry.npmmirror.com/nopt/-/nopt-9.0.0.tgz", + "integrity": "sha512-Zhq3a+yFKrYwSBluL4H9XP3m3y5uvQkB/09CwDruCiRmR/UJYnn9W4R48ry0uGC70aeTPKLynBtscP9efFFcPw==", + "dev": true, + "license": "ISC", + "dependencies": { + "abbrev": "^4.0.0" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/normalize-url": { + "version": "6.1.0", + "resolved": "https://registry.npmmirror.com/normalize-url/-/normalize-url-6.1.0.tgz", + "integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmmirror.com/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/p-cancelable": { + "version": "2.1.1", + "resolved": "https://registry.npmmirror.com/p-cancelable/-/p-cancelable-2.1.1.tgz", + "integrity": "sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmmirror.com/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/pe-library": { + "version": "0.4.1", + "resolved": "https://registry.npmmirror.com/pe-library/-/pe-library-0.4.1.tgz", + "integrity": "sha512-eRWB5LBz7PpDu4PUlwT0PhnQfTQJlDDdPa35urV4Osrm0t0AqQFGn+UIkU3klZvwJ8KPO3VbBFsXquA6p6kqZw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12", + "npm": ">=6" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/jet2jet" + } + }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmmirror.com/picocolors/-/picocolors-1.1.1.tgz", @@ -2917,6 +5588,52 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/pkijs": { + "version": "3.4.0", + "resolved": "https://registry.npmmirror.com/pkijs/-/pkijs-3.4.0.tgz", + "integrity": "sha512-emEcLuomt2j03vxD54giVB4SxTjnsqkU692xZOZXHDVoYyypEm+b3jpiTcc+Cf+myooc+/Ly0z01jqeNHVgJGw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@noble/hashes": "1.4.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.4.0", + "resolved": "https://registry.npmmirror.com/@noble/hashes/-/hashes-1.4.0.tgz", + "integrity": "sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/plist": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/plist/-/plist-3.1.0.tgz", + "integrity": "sha512-uysumyrvkUX0rX/dEVqt8gC3sTBzd4zoWfLeS29nb53imdaXVvLINYXTI2GNqzaMuvacNx4uJQ8+b3zXR0pkgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@xmldom/xmldom": "^0.8.8", + "base64-js": "^1.5.1", + "xmlbuilder": "^15.1.1" + }, + "engines": { + "node": ">=10.4.0" + } + }, "node_modules/postcss": { "version": "8.5.28", "resolved": "https://registry.npmmirror.com/postcss/-/postcss-8.5.28.tgz", @@ -2946,6 +5663,53 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/postject": { + "version": "1.0.0-alpha.6", + "resolved": "https://registry.npmmirror.com/postject/-/postject-1.0.0-alpha.6.tgz", + "integrity": "sha512-b9Eb8h2eVqNE8edvKdwqkrY6O7kAwmI8kcnBv1NScolYJbo59XUF0noFq+lxbC1yN20bmC0WBEbDC5H/7ASb0A==", + "dev": true, + "license": "MIT", + "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.npmmirror.com/commander/-/commander-9.5.0.tgz", + "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "engines": { + "node": "^12.20.0 || >=14" + } + }, + "node_modules/proc-log": { + "version": "6.1.0", + "resolved": "https://registry.npmmirror.com/proc-log/-/proc-log-6.1.0.tgz", + "integrity": "sha512-iG+GYldRf2BQ0UDUAd6JQ/RwzaQy6mXmsk/IzlYyal4A4SNFw54MeH4/tLkF4I5WoWG9SQwuqWzS99jaFQHBuQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "dev": true, + "license": "MIT" + }, "node_modules/progress": { "version": "2.0.3", "resolved": "https://registry.npmmirror.com/progress/-/progress-2.0.3.tgz", @@ -2956,6 +5720,76 @@ "node": ">=0.4.0" } }, + "node_modules/promise-retry": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/promise-retry/-/promise-retry-2.0.1.tgz", + "integrity": "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "err-code": "^2.0.2", + "retry": "^0.12.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/proper-lockfile": { + "version": "4.1.2", + "resolved": "https://registry.npmmirror.com/proper-lockfile/-/proper-lockfile-4.1.2.tgz", + "integrity": "sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA==", + "dev": true, + "license": "MIT", + "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.npmmirror.com/pump/-/pump-3.0.4.tgz", + "integrity": "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/pvtsutils": { + "version": "1.3.6", + "resolved": "https://registry.npmmirror.com/pvtsutils/-/pvtsutils-1.3.6.tgz", + "integrity": "sha512-PLgQXQ6H2FWCaeRak8vvk1GW462lMxB5s3Jm673N82zI4vqtVUPuZdffdZbPDFRoU8kAhItWFtPCWiPpp4/EDg==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.8.1" + } + }, + "node_modules/pvutils": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/pvutils/-/pvutils-1.2.0.tgz", + "integrity": "sha512-BbubeCEyTuQjVMakvJQ/Sxbc93F2pwmbsxONT/ZRrwU7Ua38d8unYTwXpTVLAKJ4BDuH9IGztCjQcd/N/39Dvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/quick-lru": { + "version": "5.1.1", + "resolved": "https://registry.npmmirror.com/quick-lru/-/quick-lru-5.1.1.tgz", + "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/react": { "version": "19.2.8", "resolved": "https://registry.npmmirror.com/react/-/react-19.2.8.tgz", @@ -2993,6 +5827,137 @@ "node": ">=0.10.0" } }, + "node_modules/read-binary-file-arch": { + "version": "1.0.6", + "resolved": "https://registry.npmmirror.com/read-binary-file-arch/-/read-binary-file-arch-1.0.6.tgz", + "integrity": "sha512-BNg9EN3DD3GsDXX7Aa8O4p92sryjkmzYYgmgTAc6CA4uGLEDzFfxOxugu21akOxpcXHiEgsYkC6nPsQvLLLmEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.3.4" + }, + "bin": { + "read-binary-file-arch": "cli.js" + } + }, + "node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmmirror.com/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmmirror.com/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resedit": { + "version": "1.7.2", + "resolved": "https://registry.npmmirror.com/resedit/-/resedit-1.7.2.tgz", + "integrity": "sha512-vHjcY2MlAITJhC0eRD/Vv8Vlgmu9Sd3LX9zZvtGzU5ZImdTN3+d6e/4mnTyV8vEbyf1sgNIrWxhWlrys52OkEA==", + "dev": true, + "license": "MIT", + "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.npmmirror.com/resolve-alpn/-/resolve-alpn-1.2.1.tgz", + "integrity": "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==", + "dev": true, + "license": "MIT" + }, + "node_modules/responselike": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/responselike/-/responselike-2.0.1.tgz", + "integrity": "sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw==", + "dev": true, + "license": "MIT", + "dependencies": { + "lowercase-keys": "^2.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmmirror.com/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/rimraf": { + "version": "2.6.3", + "resolved": "https://registry.npmmirror.com/rimraf/-/rimraf-2.6.3.tgz", + "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "dev": true, + "license": "ISC", + "peer": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + } + }, + "node_modules/roarr": { + "version": "2.15.4", + "resolved": "https://registry.npmmirror.com/roarr/-/roarr-2.15.4.tgz", + "integrity": "sha512-CHhPh+UNHD2GTXNYhPWLnU8ONHdI+5DI+4EYIAOaiD63rHeYlZvyh8P+in5999TTSFgUYuKUAjzRI4mdh/p+2A==", + "dev": true, + "license": "BSD-3-Clause", + "optional": true, + "dependencies": { + "boolean": "^3.0.1", + "detect-node": "^2.0.4", + "globalthis": "^1.0.1", + "json-stringify-safe": "^5.0.1", + "semver-compare": "^1.0.0", + "sprintf-js": "^1.1.2" + }, + "engines": { + "node": ">=8.0" + } + }, "node_modules/rollup": { "version": "4.63.1", "resolved": "https://registry.npmmirror.com/rollup/-/rollup-4.63.1.tgz", @@ -3039,12 +6004,38 @@ "fsevents": "~2.3.2" } }, + "node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmmirror.com/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true, + "license": "MIT" + }, "node_modules/safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmmirror.com/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "license": "MIT" }, + "node_modules/sanitize-filename": { + "version": "1.6.4", + "resolved": "https://registry.npmmirror.com/sanitize-filename/-/sanitize-filename-1.6.4.tgz", + "integrity": "sha512-9ZyI08PsvdQl2r/bBIGubpVdR3RR9sY6RDiWFPreA21C/EFlQhmgo20UZlNjZMMZNubusLhAQozkA0Od5J21Eg==", + "dev": true, + "license": "WTFPL OR ISC", + "dependencies": { + "truncate-utf8-bytes": "^1.0.0" + } + }, + "node_modules/sax": { + "version": "1.6.1", + "resolved": "https://registry.npmmirror.com/sax/-/sax-1.6.1.tgz", + "integrity": "sha512-42tBVwLWnaQvW5zc4HbZrTuWccECCZfBi92FDuwtqxasH+JbPB3/FOKb1m222K42R4WxuxzzMsTswfzgtSu64Q==", + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=11.0.0" + } + }, "node_modules/scheduler": { "version": "0.27.0", "resolved": "https://registry.npmmirror.com/scheduler/-/scheduler-0.27.0.tgz", @@ -3070,6 +6061,97 @@ "semver": "bin/semver.js" } }, + "node_modules/semver-compare": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/semver-compare/-/semver-compare-1.0.0.tgz", + "integrity": "sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/serialize-error": { + "version": "7.0.1", + "resolved": "https://registry.npmmirror.com/serialize-error/-/serialize-error-7.0.1.tgz", + "integrity": "sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "type-fest": "^0.13.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmmirror.com/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/simple-update-notifier": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", + "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/simple-update-notifier/node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmmirror.com/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmmirror.com/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/source-map-js": { "version": "1.2.1", "resolved": "https://registry.npmmirror.com/source-map-js/-/source-map-js-1.2.1.tgz", @@ -3080,6 +6162,25 @@ "node": ">=0.10.0" } }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmmirror.com/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/sprintf-js": { + "version": "1.1.3", + "resolved": "https://registry.npmmirror.com/sprintf-js/-/sprintf-js-1.1.3.tgz", + "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==", + "dev": true, + "license": "BSD-3-Clause", + "optional": true + }, "node_modules/ssh2": { "version": "1.17.0", "resolved": "https://registry.npmmirror.com/ssh2/-/ssh2-1.17.0.tgz", @@ -3097,12 +6198,60 @@ "nan": "^2.23.0" } }, + "node_modules/stat-mode": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/stat-mode/-/stat-mode-1.0.0.tgz", + "integrity": "sha512-jH9EhtKIjuXZ2cWxmXS8ZP80XyC3iasQxMDV8jzhNJpfDb7VbQLVW4Wvsxz9QZvzV+G4YoSfBUVKDOyxLzi/sg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, "node_modules/string-convert": { "version": "0.2.1", "resolved": "https://registry.npmmirror.com/string-convert/-/string-convert-0.2.1.tgz", "integrity": "sha512-u/1tdPl4yQnPBjnVrmdLo9gtuLvELKsAoRapekWggdiQNvvvum+jYF329d84NAa660KQw7pB2n36KrIKVoXa3A==", "license": "MIT" }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmmirror.com/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/stylis": { "version": "4.4.0", "resolved": "https://registry.npmmirror.com/stylis/-/stylis-4.4.0.tgz", @@ -3122,6 +6271,72 @@ "node": ">= 8.0" } }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmmirror.com/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tar": { + "version": "7.5.22", + "resolved": "https://registry.npmmirror.com/tar/-/tar-7.5.22.tgz", + "integrity": "sha512-MFO/QzvtAOmJbkhOaCTvbGcFN9L9b+JunIsDwaKljSOdcLMea3NJ1k9Usz/rjdfSXTq4dfzfeS7W4p4YOAAHeA==", + "dev": true, + "license": "BlueOak-1.0.0", + "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/node_modules/yallist": { + "version": "5.0.0", + "resolved": "https://registry.npmmirror.com/yallist/-/yallist-5.0.0.tgz", + "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/temp": { + "version": "0.9.4", + "resolved": "https://registry.npmmirror.com/temp/-/temp-0.9.4.tgz", + "integrity": "sha512-yYrrsWnrXMcdsnu/7YMYAofM1ktpL5By7vZhf15CrXijWWrEYZks5AXBudalfSWJLlnen/QUJUB5aoB0kqZUGA==", + "dev": true, + "license": "MIT", + "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.npmmirror.com/temp-file/-/temp-file-3.4.0.tgz", + "integrity": "sha512-C5tjlC/HCtVUOi3KWVokd4vHVViOmGjtLwIh4MuzPo/nMYTV/p1urt3RnMz2IWXDdKEGJH3k5+KPxtqRsUYGtg==", + "dev": true, + "license": "MIT", + "dependencies": { + "async-exit-hook": "^2.0.1", + "fs-extra": "^10.0.0" + } + }, "node_modules/throttle-debounce": { "version": "5.0.2", "resolved": "https://registry.npmmirror.com/throttle-debounce/-/throttle-debounce-5.0.2.tgz", @@ -3131,6 +6346,32 @@ "node": ">=12.22" } }, + "node_modules/tiny-async-pool": { + "version": "1.3.0", + "resolved": "https://registry.npmmirror.com/tiny-async-pool/-/tiny-async-pool-1.3.0.tgz", + "integrity": "sha512-01EAw5EDrcVrdgyCLgoSPvqznC0sVxDSVeiOz09FUpjh71G79VCqneOr+xvt7T1r76CF6ZZfPjHorN2+d+3mqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^5.5.0" + } + }, + "node_modules/tiny-async-pool/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmmirror.com/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/tiny-typed-emitter": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/tiny-typed-emitter/-/tiny-typed-emitter-2.1.0.tgz", + "integrity": "sha512-qVtvMxeXbVej0cQWKqVSSAHmKZEHAvxdF8HEUBFWts8h+xEo5m/lEiPakuyZ3BnCBjOD8i24kzNOiOLLgsSxhA==", + "license": "MIT" + }, "node_modules/tinyglobby": { "version": "0.2.17", "resolved": "https://registry.npmmirror.com/tinyglobby/-/tinyglobby-0.2.17.tgz", @@ -3148,12 +6389,63 @@ "url": "https://github.com/sponsors/SuperchupuDev" } }, + "node_modules/tmp": { + "version": "0.2.7", + "resolved": "https://registry.npmmirror.com/tmp/-/tmp-0.2.7.tgz", + "integrity": "sha512-e0votIpp4Uo2AJYSzVHV6xCcawuiez3DzqDAbrTc3YxBkplN6e+dM13ZeIcZnDg/QpSuU2zfZ3rzwY8ukEnaXw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.14" + } + }, + "node_modules/tmp-promise": { + "version": "3.0.3", + "resolved": "https://registry.npmmirror.com/tmp-promise/-/tmp-promise-3.0.3.tgz", + "integrity": "sha512-RwM7MoPojPxsOBYnyd2hy0bxtIlVrihNs9pj5SUvY8Zz1sQcQG2tG1hSr8PDxfgEB8RNKDhqbIlroIarSNDNsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "tmp": "^0.2.0" + } + }, + "node_modules/truncate-utf8-bytes": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/truncate-utf8-bytes/-/truncate-utf8-bytes-1.0.2.tgz", + "integrity": "sha512-95Pu1QXQvruGEhv62XCMO3Mm90GscOCClvrIUwCM0PYOXK3kaF3l3sIHxx71ThJfcbM2O5Au6SO3AWCSEfW4mQ==", + "dev": true, + "license": "WTFPL", + "dependencies": { + "utf8-byte-length": "^1.0.1" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmmirror.com/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD" + }, "node_modules/tweetnacl": { "version": "0.14.5", "resolved": "https://registry.npmmirror.com/tweetnacl/-/tweetnacl-0.14.5.tgz", "integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==", "license": "Unlicense" }, + "node_modules/type-fest": { + "version": "0.13.1", + "resolved": "https://registry.npmmirror.com/type-fest/-/type-fest-0.13.1.tgz", + "integrity": "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "optional": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/typescript": { "version": "5.9.3", "resolved": "https://registry.npmmirror.com/typescript/-/typescript-5.9.3.tgz", @@ -3186,6 +6478,44 @@ "dev": true, "license": "MIT" }, + "node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/unzipper": { + "version": "0.12.5", + "resolved": "https://registry.npmmirror.com/unzipper/-/unzipper-0.12.5.tgz", + "integrity": "sha512-tXYOi9R57Uj/2Z25SOs5RRSzq886MBQj2gY8dPL+xl/kv6s6SvByoKfAtvfVeEuhntWDgjd2o9p2lb4TVPAz0A==", + "dev": true, + "license": "MIT", + "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.npmmirror.com/fs-extra/-/fs-extra-11.3.1.tgz", + "integrity": "sha512-eXvGGwZ5CL17ZSwHWd3bbgk7UUpF6IFHtP57NYYakPvHOs8GDgDe5KJI36jIJzDkJ6eJjuzRA8eBQb6SkKue0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, "node_modules/update-browserslist-db": { "version": "1.3.2", "resolved": "https://registry.npmmirror.com/update-browserslist-db/-/update-browserslist-db-1.3.2.tgz", @@ -3217,6 +6547,20 @@ "browserslist": ">= 4.21.0" } }, + "node_modules/utf8-byte-length": { + "version": "1.0.5", + "resolved": "https://registry.npmmirror.com/utf8-byte-length/-/utf8-byte-length-1.0.5.tgz", + "integrity": "sha512-Xn0w3MtiQ6zoz2vFyUVruaCL53O/DwUvkEeOvj+uulMm0BkUGYWmBYVyElqZaSLhY6ZD0ulfU3aBra2aVT4xfA==", + "dev": true, + "license": "(WTFPL OR MIT)" + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true, + "license": "MIT" + }, "node_modules/vite": { "version": "7.3.6", "resolved": "https://registry.npmmirror.com/vite/-/vite-7.3.6.tgz", @@ -3776,6 +7120,81 @@ "@esbuild/win32-x64": "0.28.2" } }, + "node_modules/webcrypto-core": { + "version": "1.9.2", + "resolved": "https://registry.npmmirror.com/webcrypto-core/-/webcrypto-core-1.9.2.tgz", + "integrity": "sha512-gsXecm82UQNlTBURJGuqOWy1Ww08S3kZUcr3aOJS02Pk0xLtkfeUAVC0u0xhgdonFme80edSJUIJyuvL/7250Q==", + "dev": true, + "license": "MIT", + "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.npmmirror.com/which/-/which-5.0.0.tgz", + "integrity": "sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^3.1.1" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmmirror.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/xmlbuilder": { + "version": "15.1.1", + "resolved": "https://registry.npmmirror.com/xmlbuilder/-/xmlbuilder-15.1.1.tgz", + "integrity": "sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.0" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmmirror.com/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, "node_modules/yallist": { "version": "3.1.1", "resolved": "https://registry.npmmirror.com/yallist/-/yallist-3.1.1.tgz", @@ -3783,6 +7202,57 @@ "dev": true, "license": "ISC" }, + "node_modules/yargs": { + "version": "17.7.3", + "resolved": "https://registry.npmmirror.com/yargs/-/yargs-17.7.3.tgz", + "integrity": "sha512-GZtjxm/J/4TSxuL3FNYjCmLktBTnIw/rVmKSIyKeYAZpmJB2ig9VauCC5xsa82GNKVKDAqpOn3KVzNt0zmrU0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmmirror.com/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmmirror.com/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zmodem.js": { + "version": "0.1.10", + "resolved": "https://registry.npmmirror.com/zmodem.js/-/zmodem.js-0.1.10.tgz", + "integrity": "sha512-Z1DWngunZ/j3BmIzSJpFZVNV73iHkj89rxXX4IciJdU9ga3nZ7rJ5LkfjV/QDsKhc7bazDWTTJCLJ+iRXD82dw==", + "license": "Apache-2.0", + "dependencies": { + "crc-32": "^1.1.1" + } + }, "node_modules/zustand": { "version": "5.0.15", "resolved": "https://registry.npmmirror.com/zustand/-/zustand-5.0.15.tgz", diff --git a/package.json b/package.json index c58ed02..656a9db 100644 --- a/package.json +++ b/package.json @@ -21,10 +21,12 @@ "@xterm/xterm": "^6.0.0", "antd": "^6.6.2", "dockview-react": "^8.2.0", + "electron-updater": "^6.8.9", "font-list": "^2.1.0", "react": "^19.2.8", "react-dom": "^19.2.8", "ssh2": "^1.17.0", + "zmodem.js": "^0.1.10", "zustand": "^5.0.15" }, "devDependencies": { @@ -33,6 +35,7 @@ "@types/react-dom": "^19.2.0", "@vitejs/plugin-react": "^5.0.0", "electron": "^44.2.0", + "electron-builder": "^26.15.3", "electron-vite": "^5.0.0", "typescript": "~5.9.0", "vite": "^7.0.0" diff --git a/src/main/globalShortcuts.ts b/src/main/globalShortcuts.ts new file mode 100644 index 0000000..a05e68a --- /dev/null +++ b/src/main/globalShortcuts.ts @@ -0,0 +1,37 @@ +import { BrowserWindow, globalShortcut } from 'electron' + +/** + * Register the global show/hide toggle for the main window. + * + * - accelerator '' / undefined => disabled (no global key bound). + * - Passing an invalid accelerator string makes Electron's register() throw; + * we swallow that here so a bad user-supplied value never crashes the app. + * - register() returning false means the accelerator is already taken by + * another application; we only log a warning and keep running. + */ +export function applyGlobalShortcut(accelerator: string | undefined): void { + // No other global shortcuts are registered anywhere in this app, so a full + // unregister is safe and guarantees a clean re-register on every change. + globalShortcut.unregisterAll() + if (!accelerator) return + + const handler = (): void => { + const win = BrowserWindow.getAllWindows()[0] + if (!win || win.isDestroyed()) return + if (win.isVisible() && win.isFocused()) { + win.hide() + } else { + win.show() + win.focus() + } + } + + try { + const ok = globalShortcut.register(accelerator, handler) + if (!ok) { + console.warn(`[global-shortcut] register "${accelerator}" failed (conflict with another app)`) + } + } catch (err) { + console.warn(`[global-shortcut] register "${accelerator}" threw`, err) + } +} \ No newline at end of file diff --git a/src/main/index.ts b/src/main/index.ts index feaf1e1..5c1e36b 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -1,4 +1,4 @@ -import { app, BrowserWindow, shell } from 'electron' +import { app, BrowserWindow, globalShortcut, shell } from 'electron' import { join } from 'path' import { registerIpc } from './ipc' import { killAllPtys } from './pty' @@ -62,6 +62,7 @@ app.whenReady().then(() => { app.on('before-quit', () => { killAllPtys() + globalShortcut.unregisterAll() }) app.on('window-all-closed', () => { diff --git a/src/main/ipc.ts b/src/main/ipc.ts index 34fb5e8..235c4d0 100644 --- a/src/main/ipc.ts +++ b/src/main/ipc.ts @@ -26,6 +26,8 @@ import { broadcast } from './broadcast' import { CommandsStore, defaultCommandsPath } from './commands' import type { CommandItem } from '../shared/commands' import { createPty, killPty, resizePty, writePty, openSession, configureSessionRuntime, getSessionReplay, registerLogHooks } from './pty' +import { respondZmodem } from './zmodem' +import type { ZmodemResponse } from '../shared/ipc' let commandsStore: CommandsStore | undefined @@ -85,6 +87,11 @@ export function registerIpc(): void { ) ipcMain.on(Ipc.PTY_KILL, (_event, id: string) => killPty(id)) + // ---- zmodem (M6: main-process engine over ssh sessions) ---- + // ZMODEM_OFFER / ZMODEM_DONE are broadcasts (main -> renderer); this is the + // renderer's answer, forwarded to the engine (which routes on resp.id). + ipcMain.on(Ipc.ZMODEM_RESPOND, (_event, resp: ZmodemResponse) => respondZmodem(resp)) + // ---- sysinfo (M3: remote hardware monitoring, ssh sessions only) ---- ipcMain.on(Ipc.SYSINFO_START, (_event, id: string) => startPolling(id)) ipcMain.on(Ipc.SYSINFO_STOP, (_event, id: string) => stopPolling(id)) diff --git a/src/main/pty.ts b/src/main/pty.ts index cc3083e..867b480 100644 --- a/src/main/pty.ts +++ b/src/main/pty.ts @@ -8,6 +8,7 @@ import { connectSsh, type SshSessionHandle } from './ssh' import type { HostKeyCheckResult } from './knownHosts' import { configureSysinfo, registerSysinfoClient, stopPolling } from './sysinfo' import { registerSftpClientProvider, closeSftp } from './sftp' +import { attachZmodem, detachZmodem, feedZmodem, isZmodemActive } from './zmodem' // Re-export so the session-layer loopback bundle (tests/*-e2e.mjs) can drive the // M3 polling engine without importing src/main/sysinfo.ts separately. @@ -203,21 +204,43 @@ export async function openSession(opts: SessionOpenOptions): Promise<{ id: strin }) sessions.set(handle.id, { kind: 'ssh', ssh: handle }) - handle.stream.on('data', (data: Buffer) => { - try { - const text = data.toString('utf8') - appendReplay(handle.id, text) - safeLog(handle.id, text) - deps.broadcast(Ipc.PTY_DATA, { id: handle.id, data: text }) - } catch { - // never crash the event loop + // ZMODEM (M6): attach the in-process engine to the raw ssh stream. Only ssh + // sessions are supported -- node-pty/Windows ConPTY hands out UTF-8 strings + // only and would corrupt binary frames. The sentry inspects every data chunk: + // non-ZMODEM traffic is routed back through toTerminal below; once a ZMODEM + // header is spotted the transfer suppresses the terminal data plane (and + // writePty drops user keystrokes while isZmodemActive is true). + attachZmodem(handle.id, { + broadcast: (channel, ...args) => deps.broadcast(channel, ...args), + toTerminal: (id, text) => { + appendReplay(id, text) + safeLog(id, text) + deps.broadcast(Ipc.PTY_DATA, { id, data: text }) + }, + writeStream: (id, data) => { + const s = sessions.get(id) + if (s?.kind === 'ssh') { + try { + s.ssh.stream.write(data) + } catch { + // stream may already be dead + } + } } }) + handle.stream.on('data', (data: Buffer) => { + // Route through the ZMODEM sentry. It forwards non-ZMODEM bytes to + // deps.toTerminal and absorbs the transfer untouched; while a transfer is + // active the engine suppresses the normal data plane entirely. + feedZmodem(handle.id, data) + }) + handle.stream.on('close', () => { try { stopPolling(handle.id) closeSftp(handle.id) + detachZmodem(handle.id) safeStopLog(handle.id) sessions.delete(handle.id) deps.broadcast(Ipc.PTY_EXIT, { id: handle.id, exitCode: 0 }) @@ -230,6 +253,9 @@ export async function openSession(opts: SessionOpenOptions): Promise<{ id: strin } export function writePty(id: string, data: string): void { + // While a ZMODEM transfer is active the stream carries binary frames; user + // keystrokes must be dropped (they would corrupt the transfer). + if (isZmodemActive(id)) return const session = sessions.get(id) if (!session) return try { @@ -255,7 +281,7 @@ export function resizePty(id: string, cols: number, rows: number): void { export function killPty(id: string): void { stopPolling(id) closeSftp(id) - closeSftp(id) + detachZmodem(id) safeStopLog(id) replayBuffers.delete(id) const session = sessions.get(id) @@ -285,6 +311,7 @@ export function killAllPtys(): void { for (const id of sessions.keys()) { stopPolling(id) closeSftp(id) + detachZmodem(id) safeStopLog(id) const session = sessions.get(id) if (!session) continue diff --git a/src/main/settingsStore.ts b/src/main/settingsStore.ts index 13bde89..3d0db01 100644 --- a/src/main/settingsStore.ts +++ b/src/main/settingsStore.ts @@ -12,10 +12,11 @@ import { } from '../shared/settings' import type { TerminalTheme } from '../shared/theme' import { broadcast } from './broadcast' +import { applyGlobalShortcut } from './globalShortcuts' const settingsPath = (): string => join(app.getPath('userData'), 'settings.json') -const DEFAULT_SYSTEM: SystemSettings = { launchAtLogin: false, preventSleep: false } +const DEFAULT_SYSTEM: SystemSettings = { launchAtLogin: false, preventSleep: false, globalShowHide: '' } const TERMINAL_KEYS = new Set(Object.keys(DEFAULT_SETTINGS.terminal) as (keyof TerminalSettings)[]) @@ -74,9 +75,12 @@ function deepMerge(raw: unknown): { settings: AppSettings; errors: string[] } { const sys = (raw as { system?: unknown }).system if (sys !== null && typeof sys === 'object') { const candidate = sys as Record + const globalShowHide = + typeof candidate.globalShowHide === 'string' ? candidate.globalShowHide : '' system = { launchAtLogin: candidate.launchAtLogin === true, - preventSleep: candidate.preventSleep === true + preventSleep: candidate.preventSleep === true, + globalShowHide } } } @@ -111,6 +115,8 @@ function applySystemSettings(system: SystemSettings): void { powerSaveBlocker.stop(sleepBlockerId) sleepBlockerId = undefined } + // Re-register the global show/hide shortcut whenever system settings change. + applyGlobalShortcut(system.globalShowHide) } /** Apply system side effects for the settings loaded at startup. */ diff --git a/src/main/zmodem-js.d.ts b/src/main/zmodem-js.d.ts new file mode 100644 index 0000000..69a9c04 --- /dev/null +++ b/src/main/zmodem-js.d.ts @@ -0,0 +1,125 @@ +/** + * Minimal ambient typings for the `zmodem.js` package (v0.1.10). + * + * zmodem.js ships no type declarations and `@types/zmodem.js` is not installed; + * npm install is out of scope here, so this file declares exactly the surface + * src/main/zmodem.ts consumes. Verified against the sources in + * node_modules/zmodem.js/src/{zsentry,zsession,zvalidation,zheader}.js: + * + * - Sentry.consume() accepts a Uint8Array / ArrayBuffer / octet Array. + * - Detection.confirm() returns the matched Session; the Session's `.type` + * is "receive" (remote `sz` -> we download) or "send" (remote `rz` -> we + * upload). + * - The byte arrays passed to `to_terminal` / `sender` are plain number[]. + */ +declare module 'zmodem.js' { + /** one batch of a file being received (see Offer) */ + export interface FileDetails { + name: string + /** can be null when the peer streams an unknown size; zmodem.js accepts */ + size?: number | null + /** POSIX mode bits (0x8000 | perm); optional */ + mode?: number | null + /** Date or epoch seconds; optional */ + mtime?: Date | number | null + files_remaining?: number | null + bytes_remaining?: number | null + } + + /** A sender-side single-file transfer handle (result of send_offer()). */ + export interface Transfer { + get_details(): FileDetails + get_offset(): number + /** Send a non-terminal chunk. */ + send(arrayLike: number[] | Uint8Array): void + /** Send the terminal chunk; resolves when the receiver confirms file end. */ + end(arrayLike?: number[] | Uint8Array): Promise + } + + /** A receiver-side offerable file (shown on a receive Session's "offer"). */ + export interface Offer { + get_details(): FileDetails + get_offset(): number + /** Skip this file (not an error, unlike abort()). */ + skip(): Promise + /** + * Accept the offer. With { on_input: fn } the payload of every data + * subpacket is delivered to `fn` (streaming, no full spool in memory). + */ + accept(opts?: { + offset?: number + on_input?: 'spool_array' | 'spool_uint8array' | ((payload: Uint8Array) => void) + }): Promise + on(event: 'input', cb: (payload: Uint8Array) => void): Offer + on(event: 'complete', cb: () => void): Offer + } + + /** Base ZMODEM session (Send/Receive share this). */ + export interface Session { + type: 'send' | 'receive' + set_sender(fn: (octets: number[]) => void): void + has_ended(): boolean + aborted(): boolean + /** Abort the transfer (sends the ZMODEM CAN sequence to the peer). */ + abort(): void + /** Register a session_end handler (fires once the transfer is done). */ + on(event: 'session_end', cb: () => void): void + /** Catch-all overload (keeps the Send|Receive union from collapsing to never). */ + on(event: never, cb: (...args: unknown[]) => void): void + } + + /** Send session (we upload, remote ran rz). */ + export interface SendSession extends Session { + type: 'send' + /** Offer one file; resolves with the Transfer or undefined if skipped. */ + send_offer(params: FileDetails): Promise + /** Ends a send session gracefully; resolves when the receiver acks ZFIN. */ + close(): Promise + } + + /** Receive session (we download, remote ran sz). */ + export interface ReceiveSession extends Session { + type: 'receive' + on(event: 'offer', cb: (offer: Offer) => void): void + /** Begin receiving: tells the peer we are ready for the first offer. */ + start(): Promise + } + + export interface SentryOptions { + /** Non-ZMODEM octets to forward to the terminal. */ + to_terminal(octets: number[]): void + /** Called with a Detection when a ZMODEM header is spotted. */ + on_detect(detection: Detection): void + /** Called when a pending Detection is retracted (no session after all). */ + on_retract(): void + /** Send octets to the peer (the ssh stream). */ + sender(octets: number[]): void + } + + export interface Detection { + /** Whether the detection is still valid (not retracted). */ + is_valid(): boolean + /** Confirm the ZMODEM session and return the active Session object. */ + confirm(): SendSession | ReceiveSession + /** Give the session's role: 'send' or 'receive'. */ + get_session_role(): 'send' | 'receive' + /** Reject: tell the peer to abort the session. */ + deny(): void + } + + export const Sentry: new (options: SentryOptions) => Sentry + export interface Sentry { + consume(input: Uint8Array | ArrayBuffer | number[]): void + get_confirmed_session(): Session | null + } + + export const ZMLIB: { ABORT_SEQUENCE: number[] } + export const ZDLE: new () => unknown + export const CRC: { crc16(input: ArrayLike): number } + export const Header: Record + export const Subpacket: Record + export const Session: { + Send: new (zrinitHeader: unknown) => SendSession + Receive: new () => ReceiveSession + } +} \ No newline at end of file diff --git a/src/main/zmodem.ts b/src/main/zmodem.ts new file mode 100644 index 0000000..3446ff3 --- /dev/null +++ b/src/main/zmodem.ts @@ -0,0 +1,476 @@ +/** + * ZMODEM engine (M6) - lives in the MAIN process and only ever runs over an SSH + * session stream, using zmodem.js@0.1.10. + * + * LOCAL PTY SESSIONS ARE DELIBERATELY NOT SUPPORTED: node-pty / Windows ConPTY + * only hand us UTF-8 strings and are lossy for arbitrary bytes, so a binary + * ZMODEM stream would be corrupted (and there is no file dialog on the local + * side anyway). The engine is therefore only ever attached to an ssh2 channel, + * where we get raw Buffers. + * + * Design mirrors pty.ts's dependency-injection pattern: this module holds no + * Electron import. pty.ts injects `broadcast`/`toTerminal`/`writeStream`, and + * routes the ssh stream's 'data' through `feedZmodem`. While a transfer is + * active we suppress the normal terminal data-plane (binary frames must never + * reach the terminal), and pty.ts's writePty drops user keystrokes via + * isZmodemActive. + */ +import { basename, join } from 'path' +import { createReadStream, createWriteStream } from 'fs' +import { stat } from 'fs/promises' +import type { Writable } from 'stream' +import * as Zmodem from 'zmodem.js' +import { Ipc } from '../shared/ipc' +import type { ZmodemDoneEvent, ZmodemResponse } from '../shared/ipc' +import type { TransferProgressEvent } from '../shared/sftp' + +/** How long to wait for the renderer to answer a ZMODEM_OFFER (ms). */ +const OFFER_TIMEOUT_MS = 120_000 +/** Abort a transfer that makes no byte progress for this long (ms). */ +const STALL_TIMEOUT_MS = 90_000 + +const ABORT_BUFFER = Buffer.from(Zmodem.ZMLIB.ABORT_SEQUENCE) + +export interface ZmodemDeps { + /** Emit a main->renderer broadcast (ZMODEM_OFFER / ZMODEM_DONE / TRANSFER_PROGRESS). */ + broadcast(channel: string, ...args: unknown[]): void + /** + * Forward NON-ZMODEM octets back through the normal terminal data path + * (utf8 + replay + session log + PTY_DATA). pty.ts injects this; the engine + * only calls it when no transfer is active. + */ + toTerminal(sessionId: string, text: string): void + /** Write bytes out to the ssh stream (zmodem frames + CAN abort sequence). */ + writeStream(sessionId: string, data: Buffer): void +} + +interface Engine { + id: string + deps: ZmodemDeps + sentry: Zmodem.Sentry + detection: Zmodem.Detection | null + session: Zmodem.SendSession | Zmodem.ReceiveSession | null + /** True from header detection until the session ends / teardown. */ + active: boolean + mode: 'send' | 'receive' | null + dir: string | null + /** True once the detection is confirmed and files start flowing. */ + confirmed: boolean + transferId: string + /** one progress terminal event at most */ + progressEmitted: boolean + doneEmitted: boolean + offerTimer: NodeJS.Timeout | null + stallTimer: NodeJS.Timeout | null + receiveStream: Writable | null + bytes: number + totalBytes: number +} + +const engines = new Map() + +function current(sessionId: string): Engine | undefined { + return engines.get(sessionId) +} + +function cancelTimers(engine: Engine): void { + if (engine.offerTimer) clearTimeout(engine.offerTimer) + if (engine.stallTimer) clearTimeout(engine.stallTimer) + engine.offerTimer = null + engine.stallTimer = null +} + +function emitProgress(engine: Engine, patch: Partial): void { + const evt: TransferProgressEvent = { + transferId: engine.transferId, + kind: engine.mode === 'receive' ? 'zmodem-download' : 'zmodem-upload', + state: 'running', + file: '', + bytes: engine.bytes, + totalBytes: engine.totalBytes, + ...patch + } + try { + engine.deps.broadcast(Ipc.TRANSFER_PROGRESS, evt) + } catch { + // never crash the event loop + } +} + +function emitDone(engine: Engine, ok: boolean, message?: string): void { + if (engine.doneEmitted) return + engine.doneEmitted = true + const evt: ZmodemDoneEvent = { id: engine.id, ok, message } + try { + engine.deps.broadcast(Ipc.ZMODEM_DONE, evt) + } catch { + // never crash the event loop + } +} + +/** + * End-of-session / cancel / error common path: stop suppressing the terminal + * data plane, drop timers, release streams, and emit the terminal events once. + */ +function finalize( + engine: Engine, + ok: boolean, + message?: string, + failState: 'cancelled' | 'error' = 'error' +): void { + engine.active = false + cancelTimers(engine) + if (engine.receiveStream) { + try { + engine.receiveStream.end() + } catch { + // already closed + } + engine.receiveStream = null + } + engine.session = null // the zsession already ended; drop the reference + engine.detection = null + + if (!engine.progressEmitted) { + emitProgress(engine, { state: ok ? 'done' : failState, file: message ?? '' }) + } + engine.progressEmitted = true + + if (ok) emitDone(engine, true) + else emitDone(engine, false, message ?? '传输失败') +} + +/** Called by the zsession's own 'session_end' event (its `this` is the session). */ +function makeSessionEnd(engine: Engine): () => void { + return () => finalize(engine, true) +} + +function touchActivity(engine: Engine): void { + if (engine.stallTimer) clearTimeout(engine.stallTimer) + const timer = setTimeout(() => { + if (engine.stallTimer === null) return // already finalized + finalize(engine, false, '传输超时') + }, STALL_TIMEOUT_MS) + engine.stallTimer = timer +} + +function wireSession(engine: Engine): void { + const session = engine.session + if (!session) return + const sendToPeer = (octets: number[]): void => { + try { + engine.deps.writeStream(engine.id, Buffer.from(octets)) + } catch { + // stream gone mid-transfer + } + } + session.set_sender(sendToPeer) + ;(session as unknown as { on: (event: string, cb: () => void) => void }).on('session_end', makeSessionEnd(engine)) + touchActivity(engine) +} + +// --------------------------------------------------------------------------- +// Sending (remote `rz` -> we upload local files) +// --------------------------------------------------------------------------- + +async function sendOneFile( + engine: Engine, + session: Zmodem.SendSession, + file: { path: string; name: string; size: number; mtimeMs: number } +): Promise { + const xfer = await session.send_offer({ + name: file.name, + size: file.size, + mtime: new Date(file.mtimeMs), + mode: undefined + }) + if (!xfer) return // receiver skipped the file + emitProgress(engine, { file: file.name, bytes: engine.bytes, totalBytes: engine.totalBytes }) + + await new Promise((resolve, reject) => { + const rs = createReadStream(file.path) + rs.on('data', (chunk: string | Buffer) => { + rs.pause() + const buf = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk) + try { + xfer.send(new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength)) + } catch (err) { + rs.destroy() + reject(err instanceof Error ? err : new Error('发送失败')) + return + } + engine.bytes += buf.length + emitProgress(engine, { file: file.name, bytes: engine.bytes, totalBytes: engine.totalBytes }) + touchActivity(engine) + rs.resume() + }) + rs.on('end', () => { + xfer + .end() + .then(() => resolve()) + .catch(reject) + }) + rs.on('error', reject) + }) +} + +async function runSend(engine: Engine, paths: string[]): Promise { + try { + const session = engine.session as Zmodem.SendSession + const files = await Promise.all( + paths.map(async (p) => { + const s = await stat(p) + return { path: p, name: basename(p), size: s.size, mtimeMs: s.mtimeMs } + }) + ) + engine.totalBytes = files.reduce((acc, f) => acc + f.size, 0) + + for (const f of files) { + emitProgress(engine, { file: f.name, bytes: engine.bytes, totalBytes: engine.totalBytes }) + await sendOneFile(engine, session, f) + } + emitProgress(engine, { file: '', bytes: engine.totalBytes, totalBytes: engine.totalBytes }) + // Final state + ZMODEM_DONE come from the session_end event fired by close(). + await session.close() + } catch (err) { + finalize(engine, false, err instanceof Error ? err.message : '上传失败') + } +} + +// --------------------------------------------------------------------------- +// Receiving (remote `sz` -> we download into a chosen local directory) +// --------------------------------------------------------------------------- + +function handleOffer(engine: Engine, offer: Zmodem.Offer): void { + const details = offer.get_details() + const name = basename(String(details.name ?? 'file')) + const dest = join(engine.dir ?? '.', name) + if (details.size) engine.totalBytes += details.size + + const stream = createWriteStream(dest) + engine.receiveStream = stream + emitProgress(engine, { file: name, bytes: engine.bytes, totalBytes: engine.totalBytes }) + + offer + .accept({ + on_input: (payload: Uint8Array | number[]) => { + // zmodem.js delivers the payload as a plain octet Array (not a + // Uint8Array); coerce defensively to a Buffer before writing. + const buf = Array.isArray(payload) ? Buffer.from(payload) : Buffer.from(payload.buffer, payload.byteOffset, payload.byteLength) + if (!stream.destroyed && !stream.closed) { + stream.write(buf) + } + engine.bytes += buf.byteLength + emitProgress(engine, { file: name, bytes: engine.bytes, totalBytes: engine.totalBytes }) + touchActivity(engine) + } + }) + .then(() => { + stream.end(() => { + if (engine.receiveStream === stream) engine.receiveStream = null + emitProgress(engine, { file: name, bytes: engine.bytes, totalBytes: engine.totalBytes }) + }) + }) + .catch((err: unknown) => { + try { + stream.destroy() + } catch { + // already gone + } + finalize(engine, false, err instanceof Error ? err.message : '接收失败') + }) +} + +// --------------------------------------------------------------------------- +// Public API (used by pty.ts / ipc.ts) +// --------------------------------------------------------------------------- + +/** + * Attach a ZMODEM Sentry to an ssh session. Only call for ssh sessions (see + * module docs: local ptys are unsupported by design). deps are injected by + * pty.ts so this module stays Electron-free. + */ +export function attachZmodem(sessionId: string, deps: ZmodemDeps): void { + const engine: Engine = { + id: sessionId, + deps, + sentry: new Zmodem.Sentry({ + to_terminal: (octets: number[]) => { + // Analysis: also defensive against active http-parsing leftovers. + if (engine.active) return // suppress binary terminal output during transfer + try { + deps.toTerminal(sessionId, Buffer.from(octets).toString('utf8')) + } catch { + // never crash the event loop + } + }, + on_detect: (detection: Zmodem.Detection) => { + const role = detection.get_session_role() + engine.mode = role === 'receive' ? 'receive' : 'send' + engine.detection = detection + engine.active = true + engine.transferId = `zm-${sessionId}` + emitProgress(engine, { file: '', bytes: 0, totalBytes: 0 }) + try { + deps.broadcast(Ipc.ZMODEM_OFFER, { id: sessionId, mode: engine.mode }) + } catch { + // never crash the event loop + } + touchActivity(engine) + engine.offerTimer = setTimeout(() => { + if (engine.detection && !engine.confirmed) abortWithoutSession(engine, '未选择文件,已取消') + }, OFFER_TIMEOUT_MS) + }, + on_retract: () => { + // The detected "session" turned out not to be ZMODEM; return to normal. + engine.detection = null + engine.mode = null + engine.active = false + cancelTimers(engine) + }, + sender: (octets: number[]) => { + try { + deps.writeStream(sessionId, Buffer.from(octets)) + } catch { + // stream gone mid-transfer + } + } + }), + detection: null, + session: null, + active: false, + mode: null, + dir: null, + confirmed: false, + transferId: `zm-${sessionId}`, + progressEmitted: false, + doneEmitted: false, + offerTimer: null, + stallTimer: null, + receiveStream: null, + bytes: 0, + totalBytes: 0 + } + engines.set(sessionId, engine) +} + +/** + * Abort an unconfirmed (or pre-session) offer. No live zsession yet, so we write + * the CAN abort sequence directly so the remote program exits. + */ +function abortWithoutSession(engine: Engine, message: string): void { + try { + engine.deps.writeStream(engine.id, ABORT_BUFFER) + } catch { + // stream may be gone + } + engine.active = false + cancelTimers(engine) + if (!engine.progressEmitted) emitProgress(engine, { state: 'cancelled', file: message }) + engine.progressEmitted = true + emitDone(engine, false, message) +} + +/** Abort a confirmed session cleanly (sends CAN so remote rz/sz exits). */ +function abortSession(engine: Engine, message: string): void { + try { + engine.session?.abort() + } catch { + // fall through to write the abort sequence ourselves + } + try { + engine.deps.writeStream(engine.id, ABORT_BUFFER) + } catch { + // stream may be gone + } + finalize(engine, false, message, 'cancelled') +} + +/** Feed raw ssh-stream bytes into the engine. Only call when isZmodemActive(). */ +export function feedZmodem(sessionId: string, data: Buffer): void { + const engine = current(sessionId) + if (!engine) return + try { + engine.sentry.consume(data) + } catch (err) { + // A zmodem.js Error (peer abort / corrupt frame) ends the session. + if (!engine.doneEmitted) finalize(engine, false, err instanceof Error ? err.message : 'ZMODEM 传输异常') + } +} + +/** Whether a zmodem transfer is active (pty.ts suppresses user writes while true). */ +export function isZmodemActive(sessionId: string): boolean { + return current(sessionId)?.active ?? false +} + +/** + * The renderer answered an offer (via ipc.ts). `cancelled` aborts; otherwise + * the flow proceeds according to the detected mode. + */ +export function respondZmodem(resp: ZmodemResponse): void { + const engine = current(resp.id) + if (!engine || engine.confirmed) return + + if (resp.cancelled) { + abortWithoutSession(engine, '已取消') + return + } + + const detection = engine.detection + if (!detection || !detection.is_valid()) { + abortWithoutSession(engine, '会话已失效') + return + } + + engine.confirmed = true + // The offer was answered — stop the offer watchdog; the stall timer takes over. + if (engine.offerTimer) { + clearTimeout(engine.offerTimer) + engine.offerTimer = null + } + engine.session = detection.confirm() + if (!engine.session) { + abortWithoutSession(engine, '无法建立 ZMODEM 会话') + return + } + wireSession(engine) + + if (engine.mode === 'receive') { + if (!resp.dir) { + abortSession(engine, '未指定保存目录') + return + } + engine.dir = resp.dir + const session = engine.session as Zmodem.ReceiveSession + session.on('offer', (offer) => handleOffer(engine, offer)) + void session + .start() + .catch((err) => finalize(engine, false, err instanceof Error ? err.message : '接收失败')) + } else { + const paths = resp.paths ?? [] + if (paths.length === 0) { + abortSession(engine, '未选择文件') + return + } + void runSend(engine, paths) + } +} + +/** Detach + clean up (session close / kill). Safe to call any number of times. */ +export function detachZmodem(sessionId: string): void { + const engine = current(sessionId) + if (!engine) return + engine.active = false + cancelTimers(engine) + if (engine.receiveStream) { + try { + engine.receiveStream.end() + } catch { + // ignore + } + engine.receiveStream = null + } + engine.session = null + engine.detection = null + engines.delete(sessionId) +} \ No newline at end of file diff --git a/src/preload/index.ts b/src/preload/index.ts index ce9d872..93da59c 100644 --- a/src/preload/index.ts +++ b/src/preload/index.ts @@ -2,7 +2,7 @@ import { contextBridge, ipcRenderer } from 'electron' import { Ipc } from '../shared/ipc' import type { AppSettings } from '../shared/settings' import type { AppApi } from '../shared/api' -import type { LayoutMeta, PtyCreateOptions, PtyDataEvent, PtyExitEvent } from '../shared/ipc' +import type { LayoutMeta, PtyCreateOptions, PtyDataEvent, PtyExitEvent, ZmodemOfferEvent, ZmodemResponse, ZmodemDoneEvent } from '../shared/ipc' import type { HostKeyAction, HostKeyPromptEvent, @@ -84,6 +84,18 @@ const api: AppApi = { listConnections: () => ipcRenderer.invoke(Ipc.CONNECTIONS_LIST), saveConnection: (input: SshConnectionInput) => ipcRenderer.invoke(Ipc.CONNECTIONS_SAVE, input), deleteConnection: (id: string) => ipcRenderer.invoke(Ipc.CONNECTIONS_DELETE, id), + + onZmodemOffer: (cb: (e: ZmodemOfferEvent) => void) => { + const listener = (_: unknown, e: ZmodemOfferEvent): void => cb(e) + ipcRenderer.on(Ipc.ZMODEM_OFFER, listener) + return () => ipcRenderer.removeListener(Ipc.ZMODEM_OFFER, listener) + }, + zmodemRespond: (resp: ZmodemResponse) => ipcRenderer.send(Ipc.ZMODEM_RESPOND, resp), + onZmodemDone: (cb: (e: ZmodemDoneEvent) => void) => { + const listener = (_: unknown, e: ZmodemDoneEvent): void => cb(e) + ipcRenderer.on(Ipc.ZMODEM_DONE, listener) + return () => ipcRenderer.removeListener(Ipc.ZMODEM_DONE, listener) + }, onHostKeyPrompt: (cb: (e: HostKeyPromptEvent) => void) => { const listener = (_: unknown, e: HostKeyPromptEvent): void => cb(e) ipcRenderer.on(Ipc.HOSTKEY_PROMPT, listener) diff --git a/src/renderer/src/main.tsx b/src/renderer/src/main.tsx index f69e6a4..0f76e71 100644 --- a/src/renderer/src/main.tsx +++ b/src/renderer/src/main.tsx @@ -1,9 +1,12 @@ import ReactDOM from 'react-dom/client' +import { useEffect } from 'react' import { App as AntdApp, ConfigProvider, theme as antdTheme } from 'antd' import type { AppApi } from '@shared/api' import { DEFAULT_SETTINGS } from '@shared/settings' import App from './App' import { ErrorBoundary } from './ErrorBoundary' +import ZmodemOffers from './workspace/ZmodemOffers' +import { useSettingsStore } from './settings/store' import './global.css' // Dev convenience: allow opening the renderer in a plain browser (vite page) @@ -69,6 +72,9 @@ if (typeof window !== 'undefined' && !window.api) { savedAuth: { hasPassword: false, hasKeyContent: false, hasPassphrase: false } }), deleteConnection: async () => undefined, + onZmodemOffer: () => noop, + zmodemRespond: noop, + onZmodemDone: () => noop, onHostKeyPrompt: () => noop, respondHostKey: noop, getSettings: async () => DEFAULT_SETTINGS, @@ -83,6 +89,57 @@ if (typeof window !== 'undefined' && !window.api) { ;(window as unknown as { api: AppApi }).api = api } +const MIN_FONT = 8 +const MAX_FONT = 40 +const RESET_FONT = 14 + +/** + * Window-level font-size hotkeys (Ctrl+Equal/+ / Ctrl+Minus/- / Ctrl+Digit0). + * Uses the same updateTerminal store path as the settings dialog so TerminalView + * re-renders immediately. Skipped while typing in an input/textarea/contentEditable. + */ +function FontHotkeyListener(): null { + const updateTerminal = useSettingsStore((s) => s.updateTerminal) + + useEffect(() => { + const onKeyDown = (e: KeyboardEvent): void => { + if (!e.ctrlKey || e.metaKey) return + const target = e.target as HTMLElement | null + const isXtermHelper = + target instanceof HTMLTextAreaElement && target.classList.contains('xterm-helper-textarea') + if ( + target && + !isXtermHelper && + (target.tagName === 'INPUT' || + target.tagName === 'TEXTAREA' || + target.isContentEditable) + ) { + return + } + + let delta: number | undefined + if (e.key === '=' || e.key === '+') delta = 1 + else if (e.key === '-') delta = -1 + else if (e.key === '0') delta = 0 + + if (delta === undefined) return + e.preventDefault() + e.stopPropagation() + + const current = useSettingsStore.getState().settings.terminal.fontSize + const next = + delta === 0 + ? RESET_FONT + : Math.min(MAX_FONT, Math.max(MIN_FONT, current + delta)) + void updateTerminal({ fontSize: next }) + } + window.addEventListener('keydown', onKeyDown, true) + return () => window.removeEventListener('keydown', onKeyDown, true) + }, [updateTerminal]) + + return null +} + // antd provides the context that App.useApp() consumers (workspace // message/notification) rely on; without it they get empty stubs and crash. // ErrorBoundary keeps any render crash from black-screening the whole window. @@ -96,6 +153,8 @@ ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render( + + diff --git a/src/renderer/src/settings/SettingsTabs.tsx b/src/renderer/src/settings/SettingsTabs.tsx index ce79a76..e37c5ee 100644 --- a/src/renderer/src/settings/SettingsTabs.tsx +++ b/src/renderer/src/settings/SettingsTabs.tsx @@ -1,6 +1,6 @@ import type { ReactNode } from 'react' import { useMemo, useState } from 'react' -import { InputNumber, Radio, Select, Switch } from 'antd' +import { Input, InputNumber, Radio, Select, Switch } from 'antd' import type { ThemeColors } from '@shared/theme' import { useSettingsStore, useResolvedTheme } from './store' import { @@ -84,7 +84,7 @@ export function FontSettingsTab(): React.JSX.Element { { if (v !== null) void updateTerminal({ fontSize: v }) @@ -307,6 +307,21 @@ export function SystemSettingsTab(): React.JSX.Element { /> } /> + void updateSystem({ globalShowHide: e.target.value })} + /> + } + /> +
+ 配合全局键可在任何界面唤起/隐藏窗口;若注册失败(与其他软件冲突)则不生效,应用不会报错。 +
) } diff --git a/src/renderer/src/settings/settings.css b/src/renderer/src/settings/settings.css index 3580f93..585fbe2 100644 --- a/src/renderer/src/settings/settings.css +++ b/src/renderer/src/settings/settings.css @@ -106,6 +106,10 @@ margin: -8px 0 16px; } +.settings-shortcut-hint { + margin: 2px 8px 0; +} + /* ---------- font tab: live terminal preview ---------- */ .settings-preview { diff --git a/src/renderer/src/sftp/TransferPanel.tsx b/src/renderer/src/sftp/TransferPanel.tsx index b356cd9..9ba2fb3 100644 --- a/src/renderer/src/sftp/TransferPanel.tsx +++ b/src/renderer/src/sftp/TransferPanel.tsx @@ -1,9 +1,9 @@ import { useEffect, useRef, useState } from 'react' import { CloseOutlined } from '@ant-design/icons' -import type { TransferProgressEvent } from '@shared/sftp' +import type { TransferKind, TransferProgressEvent } from '@shared/sftp' interface TransferState { - kind: 'upload' | 'download' + kind: TransferKind file: string bytes: number totalBytes: number @@ -89,7 +89,11 @@ export function TransferPanel(): React.JSX.Element | null { ? '传输完成' : t.kind === 'upload' ? '上传中' - : '下载中'} + : t.kind === 'download' + ? '下载中' + : t.kind === 'zmodem-upload' + ? 'ZMODEM 上传中' + : 'ZMODEM 下载中'} {(t.state === 'error' || t.state === 'done') && ( + +
+ setValue(e.target.value)} + onPressEnter={onEnter} + /> + +
+ +
+
快捷命令
+ {library.length === 0 ? ( +
暂无快捷命令
+ ) : ( +
+ {library.map((item) => ( + + ))} +
+ )} +
+ +
+
最近命令
+ {history.length === 0 ? ( +
暂无历史记录
+ ) : ( +
+ {history.map((item) => ( + + ))} +
+ )} +
+ + ) +} \ No newline at end of file diff --git a/src/renderer/src/workspace/TerminalPanel.tsx b/src/renderer/src/workspace/TerminalPanel.tsx index 94af398..734acdf 100644 --- a/src/renderer/src/workspace/TerminalPanel.tsx +++ b/src/renderer/src/workspace/TerminalPanel.tsx @@ -1,4 +1,4 @@ -import { useRef, useState } from 'react' +import { useEffect, useRef, useState } from 'react' import type { IDockviewPanelProps } from 'dockview-react' import { Button, Tabs } from 'antd' import { AreaChartOutlined, FolderOpenOutlined } from '@ant-design/icons' @@ -6,6 +6,7 @@ import { AreaChartOutlined, FolderOpenOutlined } from '@ant-design/icons' import { TerminalView } from '../terminal/TerminalView' import { MonitorPanel } from '../monitor/MonitorPanel' import { FilePanel } from '../sftp/FilePanel' +import { useBroadcastStore } from './broadcastStore' import type { TerminalParams } from './Workspace' export interface TerminalPanelExtraProps { @@ -38,6 +39,22 @@ export function TerminalPanel({ params, api, onSessionDead }: TerminalPanelProps /** Right sidebar visibility for ssh sessions — expanded by default. */ const [sideOpen, setSideOpen] = useState(true) + // M6 broadcast: keep the session in the broadcast registry while mounted so + // it can be picked as a target / receive fan-out writes (idempotent upsert). + useEffect(() => { + useBroadcastStore.getState().registerSession({ id: sessionId, title: api.title ?? '', isSsh }) + return () => useBroadcastStore.getState().unregisterSession(sessionId) + // eslint-disable-next-line react-hooks/exhaustive-deps -- api is stable; title kept fresh below + }, [sessionId]) + + // M6: keep the registered broadcast title in sync with the tab title. + useEffect(() => { + const disposable = api.onDidTitleChange((event) => { + useBroadcastStore.getState().registerSession({ id: sessionId, title: event.title, isSsh }) + }) + return () => disposable.dispose() + }, [api, sessionId, isSsh]) + return (
{isSsh && ( diff --git a/src/renderer/src/workspace/TerminalTab.tsx b/src/renderer/src/workspace/TerminalTab.tsx index 3ba29f6..367c482 100644 --- a/src/renderer/src/workspace/TerminalTab.tsx +++ b/src/renderer/src/workspace/TerminalTab.tsx @@ -4,6 +4,7 @@ import type { MenuProps } from 'antd' import { CloudServerOutlined } from '@ant-design/icons' import type { IDockviewPanelHeaderProps } from 'dockview-react' +import { useBroadcastStore } from './broadcastStore' import type { LayoutMeta } from '@shared/ipc' export type TerminalTabProps = IDockviewPanelHeaderProps<{ @@ -28,6 +29,8 @@ export function TerminalTab({ api, params }: TerminalTabProps): React.JSX.Elemen const selfIndex = panel.panels.findIndex((p) => p.api === api) const isSsh = params?.sessionKind === 'ssh' + // M6 broadcast: show a dot on tabs that are current broadcast targets. + const isBroadcastTarget = useBroadcastStore((state) => state.targets.has(params?.sessionId ?? '')) // Keep the tab title live (dockview re-renders tab components only on // params changes; title updates arrive through this event). @@ -68,6 +71,13 @@ export function TerminalTab({ api, params }: TerminalTabProps): React.JSX.Elemen
{isSsh && } {title} + {isBroadcastTarget && ( + + )}
+ {/* M6: floating quick-input panel (bottom-right). Sends to the active + session; broadcast target → fan-out. Sits above the sftp transfer + overlay's 14px bottom offset so the two never overlap. */} + + {/* SSH connect flow: secret prompt (cancellable) → connecting modal (not). */} {requestedConn != null && ( + + + +
+ ) +} + +/** "+" renders the new-terminal button (per-group tab addition). */ +function AddTerminalGroupButton({ onClick }: { onClick: () => void }): React.JSX.Element { return ( ) } +/** 广播 target picker: toggle + Popover with per-session checkboxes. */ +function BroadcastToggleButton(): React.JSX.Element { + const enabled = useBroadcastStore((s) => s.enabled) + const targets = useBroadcastStore((s) => s.targets) + const sessions = useBroadcastStore((s) => s.sessions) + const toggleTarget = useBroadcastStore((s) => s.toggleTarget) + const setEnabled = useBroadcastStore((s) => s.setEnabled) + + const cls = [ + 'workspace-tabbar-btn2', + enabled ? ' is-active' : '' + ].join('') + return ( + + } + > + + + ) +} + +/** Popover body: list open sessions with a checkbox each; enable gate >= 2. */ +function WorkspaceBroadcastPopover({ + enabled, + targets, + sessions, + onToggleTarget, + onSetEnabled +}: { + enabled: boolean + targets: Set + sessions: { id: string; title: string; isSsh: boolean }[] + onToggleTarget: (id: string) => void + onSetEnabled: (enabled: boolean) => void +}): React.JSX.Element { + const label = (s: { id: string; title: string; isSsh: boolean }): string => + `${s.title}${s.isSsh ? ' (SSH)' : ''}` + return ( +
+
+ 广播开关 + onSetEnabled(e.target.checked)} + > + {enabled ? `已启用 (${targets.size})` : targets.size < 2 ? '需至少选择 2 个目标' : '未启用'} + +
+
+ {sessions.length === 0 ? ( +
暂无打开的终端
+ ) : ( + sessions.map((s) => ( +
+ onToggleTarget(s.id)}> + {label(s)} + +
+ )) + )} +
+
选择 ≥2 个终端后广播自动开启;关闭任意一个使目标少于 2 个时自动关闭。
+
+ ) +} + +/** 快捷输入 panel toggle (ThunderboltOutlined) — state lives in the shared store. */ +function QuickInputToggleButton(): React.JSX.Element { + const toggle = useQuickInputStore((s) => s.toggle) + const open = useQuickInputStore((s) => s.open) + return ( + + + + ) +} + /** * XTerminal-style slim icon rail on the left edge — replaces the top toolbar. * Top: workspace actions; bottom: settings. diff --git a/src/renderer/src/workspace/ZmodemOffers.tsx b/src/renderer/src/workspace/ZmodemOffers.tsx new file mode 100644 index 0000000..76e33c1 --- /dev/null +++ b/src/renderer/src/workspace/ZmodemOffers.tsx @@ -0,0 +1,126 @@ +import { useCallback, useEffect, useRef, useState } from 'react' +import { App as AntdApp, Button, Modal, Space, Typography } from 'antd' +import type { ZmodemOfferEvent, ZmodemResponse } from '@shared/ipc' + +/** + * M6 ZMODEM offer/done UX: renders one dialog per active zmodem offer. + * + * - mode='send' : remote ran `rz` -> user picks local files to upload. + * - mode='receive': remote ran `sz` -> user picks a local save directory. + * + * Binary details never touch the renderer; it only answers offers (paths/dir) + * and shows completion via App.useApp().message. Progress reuses the shared + * transfer panel (kind 'zmodem-upload' / 'zmodem-download'). + */ +export default function ZmodemOffers(): null | React.JSX.Element { + const { message } = AntdApp.useApp() + const [offers, setOffers] = useState>(new Map()) + const busyRef = useRef>(new Set()) + + const respond = useCallback((resp: ZmodemResponse) => { + window.api.zmodemRespond(resp) + busyRef.current.delete(resp.id) + setOffers((prev) => { + const next = new Map(prev) + next.delete(resp.id) + return next + }) + }, []) + + const handleCancel = useCallback( + (id: string) => respond({ id, cancelled: true }), + [respond] + ) + + const handlePickFiles = useCallback( + async (id: string) => { + if (busyRef.current.has(id)) return + busyRef.current.add(id) + try { + const paths = await window.api.pickFiles() + if (paths.length > 0) { + respond({ id, cancelled: false, paths }) + } else { + busyRef.current.delete(id) + } + } catch { + busyRef.current.delete(id) + } + }, + [respond] + ) + + const handlePickDir = useCallback( + async (id: string) => { + if (busyRef.current.has(id)) return + busyRef.current.add(id) + try { + const dir = await window.api.pickDirectory() + if (dir) { + respond({ id, cancelled: false, dir }) + } else { + busyRef.current.delete(id) + } + } catch { + busyRef.current.delete(id) + } + }, + [respond] + ) + + // queue: more than one offer may arrive before the user answers the first + useEffect(() => { + const off = window.api.onZmodemOffer((evt: ZmodemOfferEvent) => { + setOffers((prev) => { + const next = new Map(prev) + next.set(evt.id, evt) + return next + }) + }) + return off + }, []) + + const offDone = useCallback( + (evt: { id: string; ok: boolean; message?: string }) => { + if (evt.ok) message.success('ZMODEM 传输完成') + else message.error(`ZMODEM 传输失败:${evt.message ?? '未知错误'}`) + }, + [message] + ) + useEffect(() => { + const off = window.api.onZmodemDone(offDone) + return off + }, [offDone]) + + const pending = [...offers.values()] + const first = pending[0] + if (!first) return null + + const isReceive = first.mode === 'receive' + return ( + + + + + } + > + + {isReceive + ? '检测到 sz 下载请求(远端正在发送文件),请选择保存在本地的目录。' + : '检测到 rz 上传请求(远端正在接收文件),请选择要上传的文件。'} + + + ) +} \ No newline at end of file diff --git a/src/renderer/src/workspace/broadcastStore.ts b/src/renderer/src/workspace/broadcastStore.ts new file mode 100644 index 0000000..e42cb90 --- /dev/null +++ b/src/renderer/src/workspace/broadcastStore.ts @@ -0,0 +1,100 @@ +import { create } from 'zustand' + +/** One open terminal session eligible to be a broadcast target. */ +export interface BroadcastSession { + /** pty / ssh session id (also the dockview panel id) */ + id: string + title: string + isSsh: boolean +} + +export interface BroadcastState { + /** master switch; only settable while there are >= 2 open sessions */ + enabled: boolean + /** session ids receiving broadcast input */ + targets: Set + /** open terminal sessions, registration order preserved */ + sessions: BroadcastSession[] + /** register a session as broadcast-eligible (idempotent, keeps it fresh) */ + registerSession: (session: BroadcastSession) => void + /** remove a session; if it was a target, prune it (may auto-disable <2 targets) */ + unregisterSession: (id: string) => void + /** add/remove one target; never lets the set drop below 2 while enabled */ + toggleTarget: (id: string) => void + /** turn broadcast on/off */ + setEnabled: (enabled: boolean) => void +} + +/** + * Resolve the set of sessions a keypress should be written to. + * - Broadcast disabled OR the originating session is not a target → the + * session alone (existing single-write behaviour; the origin's own fallback + * echo is handled by individual pty instances). + * - Broadcast enabled and origin is a target → every target session. + */ +export function broadcastFanOut(originId: string, targets: Set, enabled: boolean): string[] { + if (!enabled) return [originId] + if (!targets.has(originId)) return [originId] + return [...targets] +} + +function pruneOnTargetLoss(enabled: boolean, nextTargets: Set): { enabled: boolean; targets: Set } { + if (nextTargets.size < 2) return { enabled: false, targets: new Set() } + return { enabled, targets: nextTargets } +} + +export const useBroadcastStore = create((set) => ({ + enabled: false, + targets: new Set(), + sessions: [], + + registerSession: (session) => + set((state) => { + const exists = state.sessions.some((s) => s.id === session.id) + if (exists) { + // keep title/isSsh fresh (e.g. template apply retitles a panel) + const sessions = state.sessions.map((s) => (s.id === session.id ? session : s)) + const targets = new Set(state.targets) + return { sessions, targets } + } + return { sessions: [...state.sessions, session] } + }), + + unregisterSession: (id) => + set((state) => { + const sessions = state.sessions.filter((s) => s.id !== id) + if (!state.targets.has(id)) return { sessions } + const targets = new Set(state.targets) + targets.delete(id) + return { sessions, ...pruneOnTargetLoss(state.enabled, targets) } + }), + + toggleTarget: (id) => + set((state) => { + const targets = new Set(state.targets) + if (targets.has(id)) { + targets.delete(id) + return pruneOnTargetLoss(state.enabled, targets) + } + targets.add(id) + // requires >= 2 targets to become/become-enabled + if (targets.size < 2) return { enabled: false, targets } + return { enabled: true, targets } + }), + + setEnabled: (enabled) => + set((state) => { + if (enabled && state.targets.size < 2) return { enabled: state.enabled } + return { enabled } + }) +})) + +/** Write `data` to the full broadcast fan-out for an originating session. */ +export function writeBroadcast(originId: string, data: string): void { + const { targets, enabled, sessions } = useBroadcastStore.getState() + const out = broadcastFanOut(originId, targets, enabled) + for (const id of out) { + // never write to a session that is no longer open + if (sessions.some((s) => s.id === id)) window.api.writePty(id, data) + } +} \ No newline at end of file diff --git a/src/renderer/src/workspace/workspace.css b/src/renderer/src/workspace/workspace.css index 77c378e..f82a240 100644 --- a/src/renderer/src/workspace/workspace.css +++ b/src/renderer/src/workspace/workspace.css @@ -294,6 +294,252 @@ color: #ffffff; } +/* ---- M6 tab-bar workspace actions (broadcast + quick-input) ---- */ +.workspace-tabbar-actions { + display: flex; + align-items: center; + gap: 2px; +} + +.workspace-tabbar-btn2 { + display: flex; + align-items: center; + justify-content: center; + gap: 3px; + width: 24px; + height: 24px; + margin: 0 2px; + padding: 0; + border: none; + border-radius: 3px; + background: transparent; + color: #8a8a8a; + font-size: 11px; + cursor: pointer; +} + +.workspace-tabbar-btn2:hover { + background: var(--dv-icon-hover-background-color, rgba(90, 93, 94, 0.31)); + color: #ffffff; +} + +.workspace-tabbar-btn2.is-active { + color: #3fb950; + background: rgba(63, 185, 80, 0.12); +} + +.workspace-tabbar-bcast-count { + font-size: 10px; + font-weight: 600; + color: #3fb950; + line-height: 1; +} + +/* ---- broadcast popover ---- */ +.workspace-bcast-pop { + display: flex; + flex-direction: column; + gap: 8px; + min-width: 220px; +} + +.workspace-bcast-pop-switch { + display: flex; + align-items: center; + justify-content: space-between; + gap: 10px; + padding-bottom: 6px; + border-bottom: 1px solid var(--chrome-border); +} + +.workspace-bcast-pop-label { + font-size: 12px; + color: var(--chrome-fg); +} + +.workspace-bcast-pop-list { + display: flex; + flex-direction: column; + gap: 2px; + max-height: 220px; + overflow-y: auto; +} + +.workspace-bcast-pop-item { + padding: 2px 0; +} + +.workspace-bcast-pop-item .ant-checkbox-wrapper { + font-size: 12px; + color: var(--chrome-fg); +} + +.workspace-bcast-pop-empty { + padding: 10px 0; + text-align: center; + color: #8a8a8a; + font-size: 12px; +} + +.workspace-bcast-pop-hint { + font-size: 11px; + color: #8a8a8a; + line-height: 1.4; + border-top: 1px solid var(--chrome-border); + padding-top: 6px; +} + +/* ---- tab broadcast-target dot ---- */ +.workspace-terminal-tab-bcast { + flex: 0 0 auto; + width: 6px; + height: 6px; + border-radius: 50%; + background: #3fb950; + box-shadow: 0 0 4px rgba(63, 185, 80, 0.8); +} + +/* ---- M6 quick-input floating panel (bottom-right) ---- + bottom: 44px so it clears the sftp transfer overlay (right/bottom 14px). */ +.quick-input-panel { + position: fixed; + right: 14px; + bottom: 44px; + z-index: 950; + width: 320px; + max-height: 60vh; + display: flex; + flex-direction: column; + gap: 8px; + padding: 10px; + background: #1f1f1f; + border: 1px solid var(--chrome-border); + border-radius: 8px; + box-shadow: 0 6px 24px rgba(0, 0, 0, 0.5); + color: var(--chrome-fg); +} + +.quick-input-headbar { + display: flex; + align-items: center; + justify-content: space-between; +} + +.quick-input-headbar-title { + font-size: 12px; + font-weight: 600; + color: var(--chrome-fg); +} + +.quick-input-close { + border: none; + background: none; + color: #8a8a8a; + font-size: 15px; + line-height: 1; + cursor: pointer; + padding: 0 2px; +} + +.quick-input-close:hover { + color: #ffffff; +} + +.quick-input-row { + display: flex; + flex-direction: column; + gap: 6px; +} + +.quick-input-field .ant-input { + background: var(--chrome-bg); + border-color: var(--chrome-border); + color: var(--chrome-fg); +} + +.quick-input-autoenter { + display: flex; + align-items: center; + gap: 5px; + font-size: 11px; + color: #9a9a9a; + cursor: pointer; + user-select: none; +} + +.quick-input-autoenter input { + accent-color: var(--chrome-accent); +} + +.quick-input-section { + display: flex; + flex-direction: column; + gap: 4px; + min-height: 0; +} + +.quick-input-head { + font-size: 11px; + color: #8a8a8a; + text-transform: uppercase; + letter-spacing: 0.4px; +} + +.quick-input-list { + display: flex; + flex-direction: column; + gap: 1px; + max-height: 140px; + overflow-y: auto; +} + +.quick-input-item { + display: flex; + align-items: center; + gap: 6px; + border: none; + background: transparent; + color: var(--chrome-fg); + font: inherit; + font-size: 12px; + text-align: left; + padding: 3px 6px; + border-radius: 4px; + cursor: pointer; + min-width: 0; +} + +.quick-input-item:hover { + background: rgba(59, 142, 234, 0.18); + color: #ffffff; +} + +.quick-input-item-cmd { + flex: 1 1 auto; + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + font-family: 'Cascadia Mono', 'Consolas', 'SFMono-Regular', monospace; +} + +.quick-input-item-tag { + flex: 0 0 auto; + font-size: 10px; + line-height: 1; + padding: 2px 5px; + border-radius: 4px; + color: #9a9a9a; + background: rgba(255, 255, 255, 0.08); +} + +.quick-input-empty { + padding: 8px 4px; + color: #8a8a8a; + font-size: 12px; + text-align: center; +} + /* ---- custom tab (title + close + context menu) ---- */ .workspace-terminal-tab { position: relative; diff --git a/src/shared/api.ts b/src/shared/api.ts index 03dcd24..f53f87d 100644 --- a/src/shared/api.ts +++ b/src/shared/api.ts @@ -18,6 +18,7 @@ import type { import type { SysinfoMeta, SysinfoSample } from './sysinfo' import type { SftpEntry, TransferProgressEvent } from './sftp' import type { CommandItem, SessionLogMeta } from './commands' +import type { ZmodemDoneEvent, ZmodemOfferEvent, ZmodemResponse } from './ipc' /** * The single API surface exposed on `window.api` by the preload script. @@ -85,6 +86,11 @@ export interface AppApi { onHostKeyPrompt(cb: (e: HostKeyPromptEvent) => void): () => void respondHostKey(promptId: string, action: HostKeyAction): void + // ---- zmodem (M6: main-process engine over ssh sessions; progress reuses transfer events) ---- + onZmodemOffer(cb: (e: ZmodemOfferEvent) => void): () => void + zmodemRespond(resp: ZmodemResponse): void + onZmodemDone(cb: (e: ZmodemDoneEvent) => void): () => void + // ---- settings ---- getSettings(): Promise saveSettings(next: AppSettings): Promise diff --git a/src/shared/ipc.ts b/src/shared/ipc.ts index 27568ce..38c9910 100644 --- a/src/shared/ipc.ts +++ b/src/shared/ipc.ts @@ -81,6 +81,14 @@ export const Ipc = { LOG_LIST: 'log:list', LOG_OPEN_DIR: 'log:openDir', + // ---- zmodem (M6: main-process engine, ssh sessions only) ---- + /** main -> renderer: remote ran rz (mode=send) or sz (mode=receive); renderer must ask the user */ + ZMODEM_OFFER: 'zmodem:offer', + /** renderer -> main: user answered the zmodem offer */ + ZMODEM_RESPOND: 'zmodem:respond', + /** main -> renderer: zmodem session finished (ok, cancelled or failed) */ + ZMODEM_DONE: 'zmodem:done', + // ---- misc ---- APP_INFO: 'app:info' } as const @@ -128,3 +136,26 @@ export interface HostKeyResponse { promptId: string action: 'accept' | 'reject' } + +// ---- zmodem (M6) ---- +/** 'send' = remote ran `rz`, we upload files; 'receive' = remote ran `sz`, we download */ +export interface ZmodemOfferEvent { + id: string + mode: 'send' | 'receive' +} + +export interface ZmodemResponse { + id: string + cancelled: boolean + /** local files to upload (mode=send) */ + paths?: string[] + /** local directory to save into (mode=receive) */ + dir?: string +} + +export interface ZmodemDoneEvent { + id: string + ok: boolean + /** human-readable failure reason when ok=false */ + message?: string +} diff --git a/src/shared/settings.ts b/src/shared/settings.ts index 1728945..416aeea 100644 --- a/src/shared/settings.ts +++ b/src/shared/settings.ts @@ -41,6 +41,8 @@ export interface SystemSettings { launchAtLogin: boolean /** hold a powerSaveBlocker so the OS never auto-suspends while running */ preventSleep: boolean + /** Electron accelerator that toggles window visibility; '' = disabled (M6) */ + globalShowHide?: string } export interface AppSettings { @@ -95,5 +97,5 @@ export const DEFAULT_SETTINGS: AppSettings = { }, customThemes: [], highlightRules: DEFAULT_HIGHLIGHT_RULES, - system: { launchAtLogin: false, preventSleep: false } + system: { launchAtLogin: false, preventSleep: false, globalShowHide: '' } } diff --git a/src/shared/sftp.ts b/src/shared/sftp.ts index 187b71d..d084b8f 100644 --- a/src/shared/sftp.ts +++ b/src/shared/sftp.ts @@ -16,7 +16,7 @@ export interface SftpEntry { gid?: number } -export type TransferKind = 'upload' | 'download' +export type TransferKind = 'upload' | 'download' | 'zmodem-upload' | 'zmodem-download' export type TransferState = 'running' | 'done' | 'error' | 'cancelled' export interface TransferProgressEvent { diff --git a/tests/.hl-split-smoke.cjs b/tests/.hl-split-smoke.cjs new file mode 100644 index 0000000..e4ed885 --- /dev/null +++ b/tests/.hl-split-smoke.cjs @@ -0,0 +1,236 @@ +// src/renderer/src/terminal/highlightEngine.ts +var ESCAPE_RE = /(\x1b\[[0-9;:?]*[A-Za-z]|\x1b\][^\x07\x1b]*(?:\x07|\x1b\\)|\x1b[()][0-9A-B]|\x1b[=>])/g; +var MAX_PER_RULE = 300; +var MAX_CHUNK = 512 * 1024; +function hexToRgb(hex) { + let h = (hex || "").trim().replace(/^#/, ""); + if (h.length === 3) h = h.replace(/./g, (c) => c + c); + if (!/^[0-9a-fA-F]{6}$/.test(h)) return { r: 255, g: 255, b: 255 }; + const n = parseInt(h, 16); + return { r: n >> 16 & 255, g: n >> 8 & 255, b: n & 255 }; +} +function tokenize(input) { + const out = []; + let acc = ""; + let i = 0; + let m; + while ((m = ESCAPE_RE.exec(input)) !== null) { + if (m.index > i) acc += input.slice(i, m.index); + if (acc.length > 0) { + out.push({ kind: "text", value: acc }); + acc = ""; + } + out.push({ kind: "seq", value: m[0] }); + i = m.index + m[0].length; + } + if (i < input.length) acc += input.slice(i); + if (acc.length > 0) out.push({ kind: "text", value: acc }); + return out; +} +function compileRules(rules2) { + const out = []; + for (const rule of rules2) { + if (!rule || rule.enabled === false) continue; + const pattern = rule.pattern; + if (typeof pattern !== "string" || pattern.length === 0) continue; + let regex; + try { + regex = new RegExp(pattern, "g"); + } catch { + continue; + } + const fg = typeof rule.color?.fg === "string" ? rule.color.fg : ""; + if (!/^#[0-9a-fA-F]{3}$|^#[0-9a-fA-F]{6}$/.test(fg)) continue; + const bg = typeof rule.color?.bg === "string" ? rule.color.bg : void 0; + out.push({ id: rule.id, regex, fg, bg, priority: rule.priority }); + } + out.sort((a, b) => a.priority - b.priority); + return out; +} +function wrap(text, fgHex, bgHex) { + const f = hexToRgb(fgHex); + let s = `\x1B[38;2;${f.r};${f.g};${f.b}`; + if (bgHex) { + const b = hexToRgb(bgHex); + s += `;48;2;${b.r};${b.g};${b.b}`; + } + return `${s}m${text}\x1B[0m`; +} +function overlaps(claimed, s, e) { + for (const c of claimed) { + if (s < c.end && e > c.start) return true; + } + return false; +} +function applyRun(text, rules2, budgets) { + if (text.length === 0) return ""; + const claimed = []; + for (let ri = 0; ri < rules2.length; ri++) { + const rule = rules2[ri]; + if (budgets[ri] <= 0) continue; + rule.regex.lastIndex = 0; + let m; + let made = 0; + while ((m = rule.regex.exec(text)) !== null) { + const start = m.index; + const end = start + m[0].length; + if (end <= start) { + rule.regex.lastIndex++; + continue; + } + if (!overlaps(claimed, start, end)) { + claimed.push({ start, end, fg: rule.fg, bg: rule.bg }); + made++; + budgets[ri]--; + if (budgets[ri] <= 0) break; + } + if (rule.regex.lastIndex === start) rule.regex.lastIndex++; + } + } + if (claimed.length === 0) return text; + claimed.sort((a, b) => a.start - b.start); + let out = ""; + let pos = 0; + for (const c of claimed) { + if (c.start > pos) out += text.slice(pos, c.start); + out += wrap(text.slice(c.start, c.end), c.fg, c.bg); + pos = c.end; + } + if (pos < text.length) out += text.slice(pos); + return out; +} +function applyHighlights(chunk, rules2) { + if (!chunk || rules2.length === 0) return chunk; + if (chunk.length > MAX_CHUNK) return chunk; + let budgets = rules2.map(() => MAX_PER_RULE); + let usedBudget = false; + let out = ""; + for (const token of tokenize(chunk)) { + if (token.kind === "seq") { + out += token.value; + continue; + } + if (token.value.includes("\x1B")) { + out += token.value; + continue; + } + const before = budgets.reduce((a, b) => a + b, 0); + out += applyRun(token.value, rules2, budgets); + const after = budgets.reduce((a, b) => a + b, 0); + if (after < before) usedBudget = true; + } + return usedBudget ? out : chunk; +} +function isIncompleteEscape(seg) { + if (seg === "\x1B") return true; + if (/^\x1b\[[0-9;:?]*$/.test(seg)) return true; + if (seg.startsWith("\x1B]") && !seg.includes("\x07") && !seg.includes("\x1B\\")) return true; + return false; +} +function lastEscapeEnd(input) { + let end = -1; + let m; + ESCAPE_RE.lastIndex = 0; + while ((m = ESCAPE_RE.exec(input)) !== null) end = m.index + m[0].length; + return end; +} +function findIncompleteIndex(input) { + const idx = input.lastIndexOf("\x1B"); + if (idx < 0) return -1; + return isIncompleteEscape(input.slice(idx)) ? idx : -1; +} +var MAX_CARRY = 1024; +var HighlightStream = class { + rules; + buffer = ""; + constructor(rules2) { + this.rules = rules2; + } + /** Swap the active rules without touching any buffered content. */ + setRules(rules2) { + this.rules = rules2; + } + /** True while content is held back (a pending flush will release it). */ + hasPending() { + return this.buffer.length > 0; + } + /** Join held bytes with `chunk`, return content safe to hand to xterm. */ + push(chunk) { + this.buffer += chunk; + const incompleteAt = findIncompleteIndex(this.buffer); + if (incompleteAt >= 0) { + const head2 = this.buffer.slice(0, incompleteAt); + const tail = this.buffer.slice(incompleteAt); + if (tail.length > MAX_CARRY) { + this.buffer = ""; + return applyHighlights(head2, this.rules) + tail; + } + this.buffer = tail; + return applyHighlights(head2, this.rules); + } + const lastEnd = lastEscapeEnd(this.buffer); + if (lastEnd < 0) { + if (this.buffer.length > MAX_CHUNK) { + const raw = this.buffer; + this.buffer = ""; + return applyHighlights(raw, this.rules); + } + return ""; + } + const head = this.buffer.slice(0, lastEnd); + this.buffer = this.buffer.slice(lastEnd); + return applyHighlights(head, this.rules); + } + /** Flush any held content (session end) — highlighted as a final run. */ + flush() { + const b = this.buffer; + this.buffer = ""; + return applyHighlights(b, this.rules); + } +}; + +// tests/hl-split-smoke.mjs +var rules = compileRules([ + { id: "okstate", pattern: "\\b(SUCCESS|PASS|OK|DONE)\\b", enabled: true, priority: 5, color: { fg: "#3fb950" } }, + { id: "badstate", pattern: "\\b(ERROR|FAILED)\\b", enabled: true, priority: 6, color: { fg: "#f85149" } }, + { id: "numbers", pattern: "\\b\\d+(?:\\.\\d+)?(?:%)?\\b", enabled: true, priority: 25, color: { fg: "#f2cc60" } }, + { id: "url", pattern: "https?://[^\\s]+", enabled: true, priority: 30, color: { fg: "#58a6ff" } } +]); +var banner = "\x1B]0;kimi \u2014 session\x07\x1B[38;2;79;168;255m\u256D\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256E\x1B[0m\r\n\x1B[38;2;136;136;136mWelcome to Kimi Code!\x1B[0m \x1B[38;2;79;168;255m\u2502\x1B[0m send /help\r\n\x1B[38;2;224;224;224mModel: MiniMax-M3 0.41.038 ERROR 99% https://example.com/x\x1B[0m\r\n"; +var reference = applyHighlights(banner, rules); +var failed = 0; +var splitAt = (s, i) => [s.slice(0, i), s.slice(i)]; +for (let i = 1; i < banner.length - 1; i++) { + const [a, b] = splitAt(banner, i); + const stream = new HighlightStream(rules); + const out = stream.push(a) + stream.push(b) + stream.flush(); + if (out !== reference) { + failed++; + if (failed <= 3) { + console.log(`MISMATCH at split ${i}:`); + console.log(" got: " + JSON.stringify(out.slice(0, 120))); + console.log(" expected: " + JSON.stringify(reference.slice(0, 120))); + } + } +} +{ + const i = Math.floor(banner.length / 3); + const j = Math.floor(banner.length * 2 / 3); + const stream = new HighlightStream(rules); + const out = stream.push(banner.slice(0, i)) + stream.push(banner.slice(i, j)) + stream.push(banner.slice(j)) + stream.flush(); + if (out !== reference) failed++; +} +{ + const stream = new HighlightStream(rules); + let out = ""; + for (const ch of banner) out += stream.push(ch); + out += stream.flush(); + if (out !== reference) failed++; +} +if (failed === 0) { + console.log("SPLIT-SMOKE PASS: all split points byte-identical to single-chunk reference"); + process.exit(0); +} else { + console.log(`SPLIT-SMOKE FAIL: ${failed} mismatches`); + process.exit(1); +} diff --git a/tests/.zmodem-e2e.cjs b/tests/.zmodem-e2e.cjs new file mode 100644 index 0000000..52fc547 --- /dev/null +++ b/tests/.zmodem-e2e.cjs @@ -0,0 +1,3371 @@ +var __create = Object.create; +var __defProp = Object.defineProperty; +var __getOwnPropDesc = Object.getOwnPropertyDescriptor; +var __getOwnPropNames = Object.getOwnPropertyNames; +var __getProtoOf = Object.getPrototypeOf; +var __hasOwnProp = Object.prototype.hasOwnProperty; +var __commonJS = (cb, mod) => function __require() { + return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports; +}; +var __export = (target, all) => { + for (var name2 in all) + __defProp(target, name2, { get: all[name2], enumerable: true }); +}; +var __copyProps = (to, from, except, desc) => { + if (from && typeof from === "object" || typeof from === "function") { + for (let key of __getOwnPropNames(from)) + if (!__hasOwnProp.call(to, key) && key !== except) + __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); + } + return to; +}; +var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( + // If the importer is in node compatibility mode or this is not an ESM + // file that has been converted to a CommonJS file using a Babel- + // compatible transform (i.e. "__esModule" has not been set), then set + // "default" to the CommonJS "module.exports" for node compatibility. + isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, + mod +)); +var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); + +// node_modules/zmodem.js/src/zmlib.js +var require_zmlib = __commonJS({ + "node_modules/zmodem.js/src/zmlib.js"(exports2, module2) { + "use strict"; + var Zmodem2 = module2.exports; + var ZDLE = 24; + var XON = 17; + var XOFF = 19; + var XON_HIGH = 128 | XON; + var XOFF_HIGH = 128 | XOFF; + var CAN = 24; + Zmodem2.ZMLIB = { + /** + * @property {number} The ZDLE constant, which ZMODEM uses for escaping + */ + ZDLE, + /** + * @property {number} XON - ASCII XON + */ + XON, + /** + * @property {number} XOFF - ASCII XOFF + */ + XOFF, + /** + * @property {number[]} ABORT_SEQUENCE - ZMODEM’s abort sequence + */ + ABORT_SEQUENCE: [CAN, CAN, CAN, CAN, CAN], + /** + * Remove octet values from the given array that ZMODEM always ignores. + * This will mutate the given array. + * + * @param {number[]} octets - The octet values to transform. + * Each array member should be an 8-bit unsigned integer (0-255). + * This object is mutated in the function. + * + * @returns {number[]} The passed-in array. This is the same object that is + * passed in. + */ + strip_ignored_bytes: function strip_ignored_bytes(octets) { + for (var o = octets.length - 1; o >= 0; o--) { + switch (octets[o]) { + case XON: + case XON_HIGH: + case XOFF: + case XOFF_HIGH: + octets.splice(o, 1); + continue; + } + } + return octets; + }, + /** + * Like Array.prototype.indexOf, but searches for a subarray + * rather than just a particular value. + * + * @param {Array} haystack - The array to search, i.e., the bigger. + * + * @param {Array} needle - The array whose values to find, + * i.e., the smaller. + * + * @returns {number} The position in “haystack” where “needle” + * first appears—or, -1 if “needle” doesn’t appear anywhere + * in “haystack”. + */ + find_subarray: function find_subarray(haystack, needle) { + var h = 0, n; + var start = Date.now(); + HAYSTACK: + while (h !== -1) { + h = haystack.indexOf(needle[0], h); + if (h === -1) break HAYSTACK; + for (n = 1; n < needle.length; n++) { + if (haystack[h + n] !== needle[n]) { + h++; + continue HAYSTACK; + } + } + return h; + } + return -1; + } + }; + } +}); + +// node_modules/zmodem.js/src/encode.js +var require_encode = __commonJS({ + "node_modules/zmodem.js/src/encode.js"(exports2, module2) { + "use strict"; + var Zmodem2 = module2.exports; + var HEX_DIGITS = [48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 97, 98, 99, 100, 101, 102]; + var HEX_OCTET_VALUE = {}; + for (hd = 0; hd < HEX_DIGITS.length; hd++) { + HEX_OCTET_VALUE[HEX_DIGITS[hd]] = hd; + } + var hd; + Zmodem2.ENCODELIB = { + /** + * Return an array with the given number as 2 big-endian bytes. + * + * @param {number} number - The number to encode. + * + * @returns {number[]} The octet values. + */ + pack_u16_be: function pack_u16_be(number) { + if (number > 65535) throw "Number cannot exceed 16 bits: " + number; + return [number >> 8, number & 255]; + }, + /** + * Return an array with the given number as 4 little-endian bytes. + * + * @param {number} number - The number to encode. + * + * @returns {number[]} The octet values. + */ + pack_u32_le: function pack_u32_le(number) { + var high_bytes = number / 65536; + return [ + number & 255, + (number & 65535) >> 8, + high_bytes & 255, + high_bytes >> 8 + ]; + }, + /** + * The inverse of pack_u16_be() - i.e., take in 2 octet values + * and parse them as an unsigned, 2-byte big-endian number. + * + * @param {number[]} octets - The octet values (2 of them). + * + * @returns {number} The decoded number. + */ + unpack_u16_be: function unpack_u16_be(bytes_arr) { + return (bytes_arr[0] << 8) + bytes_arr[1]; + }, + /** + * The inverse of pack_u32_le() - i.e., take in a 4-byte sequence + * and parse it as an unsigned, 4-byte little-endian number. + * + * @param {number[]} octets - The octet values (4 of them). + * + * @returns {number} The decoded number. + */ + unpack_u32_le: function unpack_u32_le(octets) { + return octets[0] + (octets[1] << 8) + (octets[2] << 16) + octets[3] * 16777216; + }, + /** + * Encode a series of octet values to be the octet values that + * correspond to the ASCII hex characters for each octet. The + * returned array is suitable for use as binary data. + * + * For example: + * + * Original Hex Returned + * 254 fe 102, 101 + * 12 0c 48, 99 + * 129 81 56, 49 + * + * @param {number[]} octets - The original octet values. + * + * @returns {number[]} The octet values that correspond to an ASCII + * representation of the given octets. + */ + octets_to_hex: function octets_to_hex(octets) { + var hex = []; + for (var o = 0; o < octets.length; o++) { + hex.push( + HEX_DIGITS[octets[o] >> 4], + HEX_DIGITS[octets[o] & 15] + ); + } + return hex; + }, + /** + * The inverse of octets_to_hex(): takes an array + * of hex octet pairs and returns their octet values. + * + * @param {number[]} hex_octets - The hex octet values. + * + * @returns {number[]} The parsed octet values. + */ + parse_hex_octets: function parse_hex_octets(hex_octets) { + var octets = new Array(hex_octets.length / 2); + for (var i = 0; i < octets.length; i++) { + octets[i] = (HEX_OCTET_VALUE[hex_octets[2 * i]] << 4) + HEX_OCTET_VALUE[hex_octets[1 + 2 * i]]; + } + return octets; + } + }; + } +}); + +// node_modules/zmodem.js/src/text.js +var require_text = __commonJS({ + "node_modules/zmodem.js/src/text.js"(exports2, module2) { + var _my_TextEncoder = class { + encode(text) { + text = unescape(encodeURIComponent(text)); + var bytes = new Array(text.length); + for (var b = 0; b < text.length; b++) { + bytes[b] = text.charCodeAt(b); + } + return new Uint8Array(bytes); + } + }; + var _my_TextDecoder = class { + decode(bytes) { + return decodeURIComponent(escape(String.fromCharCode.apply(String, bytes))); + } + }; + var Zmodem2 = module2.exports; + Zmodem2.Text = { + Encoder: typeof TextEncoder !== "undefined" ? TextEncoder : _my_TextEncoder, + Decoder: typeof TextDecoder !== "undefined" ? TextDecoder : _my_TextDecoder + }; + } +}); + +// node_modules/zmodem.js/src/zdle.js +var require_zdle = __commonJS({ + "node_modules/zmodem.js/src/zdle.js"(exports2, module2) { + "use strict"; + var Zmodem2 = module2.exports; + Object.assign( + Zmodem2, + require_zmlib() + ); + var encode_cur; + var encode_todo; + var ZDLE = Zmodem2.ZMLIB.ZDLE; + Zmodem2.ZDLE = class ZmodemZDLE { + /** + * Create a ZDLE encoder. + * + * @param {object} [config] - The initial configuration. + * @param {object} config.escape_ctrl_chars - Whether the ZDLE encoder + * should escape control characters. + */ + constructor(config) { + this._config = {}; + if (config) { + this.set_escape_ctrl_chars(!!config.escape_ctrl_chars); + } + } + /** + * Enable or disable control-character escaping. + * You should probably enable this for sender sessions. + * + * @param {boolean} value - Whether to enable (true) or disable (false). + */ + set_escape_ctrl_chars(value) { + if (typeof value !== "boolean") throw "need boolean!"; + if (value !== this._config.escape_ctrl_chars) { + this._config.escape_ctrl_chars = value; + this._setup_zdle_table(); + } + } + /** + * Whether or not control-character escaping is enabled. + * + * @return {boolean} Whether the escaping is on (true) or off (false). + */ + escapes_ctrl_chars() { + return !!this._config.escape_ctrl_chars; + } + //I don’t know of any Zmodem implementations that use ZESC8 + //(“escape_8th_bit”)?? + /* + ZMODEM software escapes ZDLE, 020, 0220, 021, 0221, 023, and 0223. If + preceded by 0100 or 0300 (@), 015 and 0215 are also escaped to protect the + Telenet command escape CR-@-CR. + */ + /** + * Encode an array of octet values and return it. + * This will mutate the given array. + * + * @param {number[]} octets - The octet values to transform. + * Each array member should be an 8-bit unsigned integer (0-255). + * This object is mutated in the function. + * + * @returns {number[]} The passed-in array, transformed. This is the + * same object that is passed in. + */ + encode(octets) { + if (!this._zdle_table) throw "No ZDLE encode table configured!"; + var zdle_table = this._zdle_table; + var last_code = this._lastcode; + var arrbuf = new ArrayBuffer(2 * octets.length); + var arrbuf_uint8 = new Uint8Array(arrbuf); + var escctl_yn = this._config.escape_ctrl_chars; + var arrbuf_i = 0; + for (encode_cur = 0; encode_cur < octets.length; encode_cur++) { + encode_todo = zdle_table[octets[encode_cur]]; + if (!encode_todo) { + console.trace(); + console.error("bad encode() call:", JSON.stringify(octets)); + this._lastcode = last_code; + throw "Invalid octet: " + octets[encode_cur]; + } + last_code = octets[encode_cur]; + if (encode_todo === 1) { + } else if (escctl_yn || encode_todo === 2 || (last_code & 127) === 64) { + arrbuf_uint8[arrbuf_i] = ZDLE; + arrbuf_i++; + last_code ^= 64; + } + arrbuf_uint8[arrbuf_i] = last_code; + arrbuf_i++; + } + this._lastcode = last_code; + octets.splice(0); + octets.push.apply(octets, new Uint8Array(arrbuf, 0, arrbuf_i)); + return octets; + } + /** + * Decode an array of octet values and return it. + * This will mutate the given array. + * + * @param {number[]} octets - The octet values to transform. + * Each array member should be an 8-bit unsigned integer (0-255). + * This object is mutated in the function. + * + * @returns {number[]} The passed-in array. + * This is the same object that is passed in. + */ + static decode(octets) { + for (var o = octets.length - 1; o >= 0; o--) { + if (octets[o] === ZDLE) { + octets.splice(o, 2, octets[o + 1] - 64); + } + } + return octets; + } + /** + * Remove, ZDLE-decode, and return bytes from the passed-in array. + * If the requested number of ZDLE-encoded bytes isn’t available, + * then the passed-in array is unmodified (and the return is undefined). + * + * @param {number[]} octets - The octet values to transform. + * Each array member should be an 8-bit unsigned integer (0-255). + * This object is mutated in the function. + * + * @param {number} offset - The number of (undecoded) bytes to skip + * at the beginning of the “octets” array. + * + * @param {number} count - The number of bytes (octet values) to return. + * + * @returns {number[]|undefined} An array with the requested number of + * decoded octet values, or undefined if that number of decoded + * octets isn’t available (given the passed-in offset). + */ + static splice(octets, offset, count) { + var so_far = 0; + if (!offset) offset = 0; + for (var i = offset; i < octets.length && so_far < count; i++) { + so_far++; + if (octets[i] === ZDLE) i++; + } + if (so_far === count) { + if (octets.length === i - 1) return; + octets.splice(0, offset); + return ZmodemZDLE.decode(octets.splice(0, i - offset)); + } + return; + } + _setup_zdle_table() { + var zsendline_tab = new Array(256); + for (var i = 0; i < zsendline_tab.length; i++) { + if (i & 96) { + zsendline_tab[i] = 1; + } else { + switch (i) { + case ZDLE: + //NB: no (ZDLE | 0x80) + case Zmodem2.ZMLIB.XOFF: + case Zmodem2.ZMLIB.XON: + case Zmodem2.ZMLIB.XOFF | 128: + case Zmodem2.ZMLIB.XON | 128: + zsendline_tab[i] = 2; + break; + case 16: + // 020 + case 144: + zsendline_tab[i] = this._config.turbo_escape ? 1 : 2; + break; + case 13: + // 015 + case 141: + zsendline_tab[i] = this._config.escape_ctrl_chars ? 2 : !this._config.turbo_escape ? 3 : 1; + break; + default: + zsendline_tab[i] = this._config.escape_ctrl_chars ? 2 : 1; + } + } + } + this._zdle_table = zsendline_tab; + } + }; + } +}); + +// node_modules/crc-32/crc32.js +var require_crc32 = __commonJS({ + "node_modules/crc-32/crc32.js"(exports2) { + var CRC32; + (function(factory) { + if (typeof DO_NOT_EXPORT_CRC === "undefined") { + if ("object" === typeof exports2) { + factory(exports2); + } else if ("function" === typeof define && define.amd) { + define(function() { + var module3 = {}; + factory(module3); + return module3; + }); + } else { + factory(CRC32 = {}); + } + } else { + factory(CRC32 = {}); + } + })(function(CRC322) { + CRC322.version = "1.2.2"; + function signed_crc_table() { + var c = 0, table = new Array(256); + for (var n = 0; n != 256; ++n) { + c = n; + c = c & 1 ? -306674912 ^ c >>> 1 : c >>> 1; + c = c & 1 ? -306674912 ^ c >>> 1 : c >>> 1; + c = c & 1 ? -306674912 ^ c >>> 1 : c >>> 1; + c = c & 1 ? -306674912 ^ c >>> 1 : c >>> 1; + c = c & 1 ? -306674912 ^ c >>> 1 : c >>> 1; + c = c & 1 ? -306674912 ^ c >>> 1 : c >>> 1; + c = c & 1 ? -306674912 ^ c >>> 1 : c >>> 1; + c = c & 1 ? -306674912 ^ c >>> 1 : c >>> 1; + table[n] = c; + } + return typeof Int32Array !== "undefined" ? new Int32Array(table) : table; + } + var T0 = signed_crc_table(); + function slice_by_16_tables(T) { + var c = 0, v = 0, n = 0, table = typeof Int32Array !== "undefined" ? new Int32Array(4096) : new Array(4096); + for (n = 0; n != 256; ++n) table[n] = T[n]; + for (n = 0; n != 256; ++n) { + v = T[n]; + for (c = 256 + n; c < 4096; c += 256) v = table[c] = v >>> 8 ^ T[v & 255]; + } + var out = []; + for (n = 1; n != 16; ++n) out[n - 1] = typeof Int32Array !== "undefined" ? table.subarray(n * 256, n * 256 + 256) : table.slice(n * 256, n * 256 + 256); + return out; + } + var TT = slice_by_16_tables(T0); + var T1 = TT[0], T2 = TT[1], T3 = TT[2], T4 = TT[3], T5 = TT[4]; + var T6 = TT[5], T7 = TT[6], T8 = TT[7], T9 = TT[8], Ta = TT[9]; + var Tb = TT[10], Tc = TT[11], Td = TT[12], Te = TT[13], Tf = TT[14]; + function crc32_bstr(bstr, seed) { + var C = seed ^ -1; + for (var i = 0, L = bstr.length; i < L; ) C = C >>> 8 ^ T0[(C ^ bstr.charCodeAt(i++)) & 255]; + return ~C; + } + function crc32_buf(B, seed) { + var C = seed ^ -1, L = B.length - 15, i = 0; + for (; i < L; ) C = Tf[B[i++] ^ C & 255] ^ Te[B[i++] ^ C >> 8 & 255] ^ Td[B[i++] ^ C >> 16 & 255] ^ Tc[B[i++] ^ C >>> 24] ^ Tb[B[i++]] ^ Ta[B[i++]] ^ T9[B[i++]] ^ T8[B[i++]] ^ T7[B[i++]] ^ T6[B[i++]] ^ T5[B[i++]] ^ T4[B[i++]] ^ T3[B[i++]] ^ T2[B[i++]] ^ T1[B[i++]] ^ T0[B[i++]]; + L += 15; + while (i < L) C = C >>> 8 ^ T0[(C ^ B[i++]) & 255]; + return ~C; + } + function crc32_str(str, seed) { + var C = seed ^ -1; + for (var i = 0, L = str.length, c = 0, d = 0; i < L; ) { + c = str.charCodeAt(i++); + if (c < 128) { + C = C >>> 8 ^ T0[(C ^ c) & 255]; + } else if (c < 2048) { + C = C >>> 8 ^ T0[(C ^ (192 | c >> 6 & 31)) & 255]; + C = C >>> 8 ^ T0[(C ^ (128 | c & 63)) & 255]; + } else if (c >= 55296 && c < 57344) { + c = (c & 1023) + 64; + d = str.charCodeAt(i++) & 1023; + C = C >>> 8 ^ T0[(C ^ (240 | c >> 8 & 7)) & 255]; + C = C >>> 8 ^ T0[(C ^ (128 | c >> 2 & 63)) & 255]; + C = C >>> 8 ^ T0[(C ^ (128 | d >> 6 & 15 | (c & 3) << 4)) & 255]; + C = C >>> 8 ^ T0[(C ^ (128 | d & 63)) & 255]; + } else { + C = C >>> 8 ^ T0[(C ^ (224 | c >> 12 & 15)) & 255]; + C = C >>> 8 ^ T0[(C ^ (128 | c >> 6 & 63)) & 255]; + C = C >>> 8 ^ T0[(C ^ (128 | c & 63)) & 255]; + } + } + return ~C; + } + CRC322.table = T0; + CRC322.bstr = crc32_bstr; + CRC322.buf = crc32_buf; + CRC322.str = crc32_str; + }); + } +}); + +// node_modules/zmodem.js/src/zerror.js +var require_zerror = __commonJS({ + "node_modules/zmodem.js/src/zerror.js"(exports2, module2) { + "use strict"; + var Zmodem2 = module2.exports; + function _crc_message(got, expected) { + this.got = got.slice(0); + this.expected = expected.slice(0); + return "CRC check failed! (got: " + got.join() + "; expected: " + expected.join() + ")"; + } + function _pass(val) { + return val; + } + var TYPE_MESSAGE = { + aborted: "Session aborted", + peer_aborted: "Peer aborted session", + already_aborted: "Session already aborted", + crc: _crc_message, + validation: _pass + }; + function _generate_message(type) { + const msg = TYPE_MESSAGE[type]; + switch (typeof msg) { + case "string": + return msg; + case "function": + var args_after_type = [].slice.call(arguments).slice(1); + return msg.apply(this, args_after_type); + } + return null; + } + Zmodem2.Error = class ZmodemError extends Error { + constructor(msg_or_type) { + super(); + var generated = _generate_message.apply(this, arguments); + if (generated) { + this.type = msg_or_type; + this.message = generated; + } else { + this.message = msg_or_type; + } + } + }; + } +}); + +// node_modules/zmodem.js/src/zcrc.js +var require_zcrc = __commonJS({ + "node_modules/zmodem.js/src/zcrc.js"(exports2, module2) { + "use strict"; + var CRC32_MOD = require_crc32(); + var Zmodem2 = module2.exports; + Object.assign( + Zmodem2, + require_zerror(), + require_encode() + ); + var _crctab; + var crc_width = 16; + var crc_polynomial = 4129; + var crc_castmask = 65535; + var crc_msbmask = 1 << crc_width - 1; + function _compute_crctab() { + _crctab = new Array(256); + var divident_shift = crc_width - 8; + for (var divident = 0; divident < 256; divident++) { + var currByte = divident << divident_shift & crc_castmask; + for (var bit = 0; bit < 8; bit++) { + if ((currByte & crc_msbmask) !== 0) { + currByte <<= 1; + currByte ^= crc_polynomial; + } else { + currByte <<= 1; + } + } + _crctab[divident] = currByte & crc_castmask; + } + } + function _updcrc(cp, crc) { + if (!_crctab) _compute_crctab(); + return _crctab[crc >> 8 & 255] ^ (255 & crc) << 8 ^ cp; + } + function __verify(expect, got) { + var err; + if (expect.join() !== got.join()) { + throw new Zmodem2.Error("crc", got, expect); + } + } + Zmodem2.CRC = { + //https://www.lammertbies.nl/comm/info/crc-calculation.html + //CRC-CCITT (XModem) + /** + * Deduce a given set of octet values’ CRC16, as per the CRC16 + * variant that ZMODEM uses (CRC-CCITT/XModem). + * + * @param {Array} octets - The array of octet values. + * Each array member should be an 8-bit unsigned integer (0-255). + * + * @returns {Array} crc - The CRC, expressed as an array of octet values. + */ + crc16: function crc16(octet_nums) { + var crc = octet_nums[0]; + for (var b = 1; b < octet_nums.length; b++) { + crc = _updcrc(octet_nums[b], crc); + } + crc = _updcrc(0, _updcrc(0, crc)); + return Zmodem2.ENCODELIB.pack_u16_be(crc); + }, + /** + * Deduce a given set of octet values’ CRC32. + * + * @param {Array} octets - The array of octet values. + * Each array member should be an 8-bit unsigned integer (0-255). + * + * @returns {Array} crc - The CRC, expressed as an array of octet values. + */ + crc32: function crc32(octet_nums) { + return Zmodem2.ENCODELIB.pack_u32_le( + CRC32_MOD.buf(octet_nums) >>> 0 + //bit-shift to get unsigned + ); + }, + /** + * Verify a given set of octet values’ CRC16. + * An exception is thrown on failure. + * + * @param {Array} bytes_arr - The array of octet values. + * Each array member should be an 8-bit unsigned integer (0-255). + * + * @param {Array} crc - The CRC to check against, expressed as + * an array of octet values. + */ + verify16: function verify16(bytes_arr, got) { + return __verify(this.crc16(bytes_arr), got); + }, + /** + * Verify a given set of octet values’ CRC32. + * An exception is thrown on failure. + * + * @param {Array} bytes_arr - The array of octet values. + * Each array member should be an 8-bit unsigned integer (0-255). + * + * @param {Array} crc - The CRC to check against, expressed as + * an array of octet values. + */ + verify32: function verify32(bytes_arr, crc) { + try { + __verify(this.crc32(bytes_arr), crc); + } catch (err) { + err.input = bytes_arr.slice(0); + throw err; + } + } + }; + } +}); + +// node_modules/zmodem.js/src/zheader.js +var require_zheader = __commonJS({ + "node_modules/zmodem.js/src/zheader.js"(exports2, module2) { + "use strict"; + var Zmodem2 = module2.exports; + Object.assign( + Zmodem2, + require_encode(), + require_zdle(), + require_zmlib(), + require_zcrc(), + require_zerror() + ); + var ZPAD = "*".charCodeAt(0); + var ZBIN = "A".charCodeAt(0); + var ZHEX = "B".charCodeAt(0); + var ZBIN32 = "C".charCodeAt(0); + var HEX_HEADER_CRLF = [13, 10]; + var HEX_HEADER_CRLF_XON = HEX_HEADER_CRLF.slice(0).concat([Zmodem2.ZMLIB.XON]); + var HEX_HEADER_PREFIX = [ZPAD, ZPAD, Zmodem2.ZMLIB.ZDLE, ZHEX]; + var BINARY16_HEADER_PREFIX = [ZPAD, Zmodem2.ZMLIB.ZDLE, ZBIN]; + var BINARY32_HEADER_PREFIX = [ZPAD, Zmodem2.ZMLIB.ZDLE, ZBIN32]; + Zmodem2.Header = class ZmodemHeader { + //lrzsz’s “sz” command sends a random (?) CR/0x0d byte + //after ZEOF. Let’s accommodate 0x0a, 0x0d, 0x8a, and 0x8d. + // + //Also, when you skip a file, sz outputs a message about it. + // + //It appears that we’re supposed to ignore anything until + //[ ZPAD, ZDLE ] when we’re looking for a header. + /** + * Weed out the leading bytes that aren’t valid to start a ZMODEM header. + * + * @param {number[]} ibuffer - The octet values to parse. + * Each array member should be an 8-bit unsigned integer (0-255). + * This object is mutated in the function. + * + * @returns {number[]} The octet values that were removed from the start + * of “ibuffer”. Order is preserved. + */ + static trim_leading_garbage(ibuffer) { + var garbage = []; + var discard_all, parser, next_ZPAD_at_least = 0; + TRIM_LOOP: + while (ibuffer.length && !parser) { + var first_ZPAD = ibuffer.indexOf(ZPAD); + if (first_ZPAD === -1) { + discard_all = true; + break TRIM_LOOP; + } else { + garbage.push.apply(garbage, ibuffer.splice(0, first_ZPAD)); + if (ibuffer.length < 2) { + break TRIM_LOOP; + } else if (ibuffer[1] === ZPAD) { + if (ibuffer.length < HEX_HEADER_PREFIX.length) { + if (ibuffer.join() === HEX_HEADER_PREFIX.slice(0, ibuffer.length).join()) { + break TRIM_LOOP; + } + } else if (ibuffer[2] === HEX_HEADER_PREFIX[2] && ibuffer[3] === HEX_HEADER_PREFIX[3]) { + parser = _parse_hex; + } + } else if (ibuffer[1] === Zmodem2.ZMLIB.ZDLE) { + if (ibuffer.length < BINARY16_HEADER_PREFIX.length) { + break TRIM_LOOP; + } + if (ibuffer[2] === BINARY16_HEADER_PREFIX[2]) { + parser = _parse_binary16; + } else if (ibuffer[2] === BINARY32_HEADER_PREFIX[2]) { + parser = _parse_binary32; + } + } + if (!parser) { + garbage.push(ibuffer.shift()); + } + } + } + if (discard_all) { + garbage.push.apply(garbage, ibuffer.splice(0)); + } + return garbage; + } + /** + * Parse out a Header object from a given array of octet values. + * + * An exception is thrown if the given bytes are definitively invalid + * as header values. + * + * @param {number[]} octets - The octet values to parse. + * Each array member should be an 8-bit unsigned integer (0-255). + * This object is mutated in the function. + * + * @returns {Header|undefined} An instance of the appropriate Header + * subclass, or undefined if not enough octet values are given + * to determine whether there is a valid header here or not. + */ + static parse(octets) { + var hdr; + if (octets[1] === ZPAD) { + hdr = _parse_hex(octets); + return hdr && [hdr, 16]; + } else if (octets[2] === ZBIN) { + hdr = _parse_binary16(octets, 3); + return hdr && [hdr, 16]; + } else if (octets[2] === ZBIN32) { + hdr = _parse_binary32(octets); + return hdr && [hdr, 32]; + } + if (octets.length < 3) return; + throw "Unrecognized/unsupported octets: " + octets.join(); + } + /** + * Build a Header subclass given a name and arguments. + * + * @param {string} name - The header type name, e.g., “ZRINIT”. + * + * @param {...*} args - The arguments to pass to the appropriate + * subclass constructor. These aren’t documented currently + * but are pretty easy to glean from the code. + * + * @returns {Header} An instance of the appropriate Header subclass. + */ + static build(name2) { + var args = arguments.length === 1 ? [arguments[0]] : Array.apply(null, arguments); + var Ctr = FRAME_NAME_CREATOR[name2]; + if (!Ctr) throw "No frame class \u201C" + name2 + "\u201D is defined!"; + args.shift(); + var hdr = new (Ctr.bind.apply(Ctr, [null].concat(args)))(); + return hdr; + } + /** + * Return the octet values array that represents the object + * in ZMODEM hex encoding. + * + * @returns {number[]} An array of octet values suitable for sending + * as binary data. + */ + to_hex() { + var to_crc = this._crc_bytes(); + return HEX_HEADER_PREFIX.concat( + Zmodem2.ENCODELIB.octets_to_hex(to_crc.concat(Zmodem2.CRC.crc16(to_crc))), + this._hex_header_ending + ); + } + /** + * Return the octet values array that represents the object + * in ZMODEM binary encoding with a 16-bit CRC. + * + * @param {ZDLE} zencoder - A ZDLE instance to use for + * ZDLE encoding. + * + * @returns {number[]} An array of octet values suitable for sending + * as binary data. + */ + to_binary16(zencoder) { + return this._to_binary(zencoder, BINARY16_HEADER_PREFIX, Zmodem2.CRC.crc16); + } + /** + * Return the octet values array that represents the object + * in ZMODEM binary encoding with a 32-bit CRC. + * + * @param {ZDLE} zencoder - A ZDLE instance to use for + * ZDLE encoding. + * + * @returns {number[]} An array of octet values suitable for sending + * as binary data. + */ + to_binary32(zencoder) { + return this._to_binary(zencoder, BINARY32_HEADER_PREFIX, Zmodem2.CRC.crc32); + } + //This is never called directly, but only as super(). + constructor() { + if (!this._bytes4) { + this._bytes4 = [0, 0, 0, 0]; + } + } + _to_binary(zencoder, prefix, crc_func) { + var to_crc = this._crc_bytes(); + var octets = prefix.concat( + zencoder.encode(to_crc.concat(crc_func(to_crc))) + ); + return octets; + } + _crc_bytes() { + return [this.TYPENUM].concat(this._bytes4); + } + }; + Zmodem2.Header.prototype._hex_header_ending = HEX_HEADER_CRLF_XON; + var ZRQINIT_HEADER = class extends Zmodem2.Header { + }; + var ZRINIT_FLAG = { + //---------------------------------------------------------------------- + // Bit Masks for ZRINIT flags byte ZF0 + //---------------------------------------------------------------------- + CANFDX: 1, + // Rx can send and receive true FDX + CANOVIO: 2, + // Rx can receive data during disk I/O + CANBRK: 4, + // Rx can send a break signal + CANCRY: 8, + // Receiver can decrypt -- nothing does this + CANLZW: 16, + // Receiver can uncompress -- nothing does this + CANFC32: 32, + // Receiver can use 32 bit Frame Check + ESCCTL: 64, + // Receiver expects ctl chars to be escaped + ESC8: 128 + // Receiver expects 8th bit to be escaped + }; + function _get_ZRINIT_flag_num(fl) { + if (!ZRINIT_FLAG[fl]) { + throw new Zmodem2.Error("Invalid ZRINIT flag: " + fl); + } + return ZRINIT_FLAG[fl]; + } + var ZRINIT_HEADER = class extends Zmodem2.Header { + constructor(flags_arr, bufsize) { + super(); + var flags_num = 0; + if (!bufsize) bufsize = 0; + flags_arr.forEach(function(fl) { + flags_num |= _get_ZRINIT_flag_num(fl); + }); + this._bytes4 = [ + bufsize & 255, + bufsize >> 8, + 0, + flags_num + ]; + } + //undefined if nonstop I/O is allowed + get_buffer_size() { + return Zmodem2.ENCODELIB.unpack_u16_be(this._bytes4.slice(0, 2)) || void 0; + } + //Unimplemented: + // can_decrypt + // can_decompress + //---------------------------------------------------------------------- + //function names taken from Jacques Mattheij’s implementation, + //as used in syncterm. + can_full_duplex() { + return !!(this._bytes4[3] & ZRINIT_FLAG.CANFDX); + } + can_overlap_io() { + return !!(this._bytes4[3] & ZRINIT_FLAG.CANOVIO); + } + can_break() { + return !!(this._bytes4[3] & ZRINIT_FLAG.CANBRK); + } + can_fcs_32() { + return !!(this._bytes4[3] & ZRINIT_FLAG.CANFC32); + } + escape_ctrl_chars() { + return !!(this._bytes4[3] & ZRINIT_FLAG.ESCCTL); + } + //Is this used? I don’t see it used in lrzsz or syncterm + //Looks like it was a “foreseen” feature that Forsberg + //never implemented. (The need for it went away, maybe?) + escape_8th_bit() { + return !!(this._bytes4[3] & ZRINIT_FLAG.ESC8); + } + }; + var ZSINIT_FLAG = { + ESCCTL: 64, + // Transmitter will escape ctl chars + ESC8: 128 + // Transmitter will escape 8th bit + }; + function _get_ZSINIT_flag_num(fl) { + if (!ZSINIT_FLAG[fl]) { + throw "Invalid ZSINIT flag: " + fl; + } + return ZSINIT_FLAG[fl]; + } + var ZSINIT_HEADER = class extends Zmodem2.Header { + constructor(flags_arr, attn_seq_arr) { + super(); + var flags_num = 0; + flags_arr.forEach(function(fl) { + flags_num |= _get_ZSINIT_flag_num(fl); + }); + this._bytes4 = [0, 0, 0, flags_num]; + if (attn_seq_arr) { + if (attn_seq_arr.length > 31) { + throw "Attn sequence must be <= 31 bytes"; + } + if (attn_seq_arr.some(function(num) { + return num > 255; + })) { + throw "Attn sequence (" + attn_seq_arr + ") must be <256"; + } + this._data = attn_seq_arr.concat([0]); + } + } + escape_ctrl_chars() { + return !!(this._bytes4[3] & ZSINIT_FLAG.ESCCTL); + } + //Is this used? I don’t see it used in lrzsz or syncterm + escape_8th_bit() { + return !!(this._bytes4[3] & ZSINIT_FLAG.ESC8); + } + }; + var ZACK_HEADER = class extends Zmodem2.Header { + constructor(payload4) { + super(); + if (payload4) { + this._bytes4 = payload4.slice(); + } + } + }; + ZACK_HEADER.prototype._hex_header_ending = HEX_HEADER_CRLF; + var ZFILE_VALUES = { + //ZF3 (i.e., first byte) + extended: { + sparse: 64 + //ZXSPARS + }, + //ZF2 + transport: [ + void 0, + "compress", + //ZTLZW + "encrypt", + //ZTCRYPT + "rle" + //ZTRLE + ], + //ZF1 + management: [ + void 0, + "newer_or_longer", + //ZF1_ZMNEWL + "crc", + //ZF1_ZMCRC + "append", + //ZF1_ZMAPND + "clobber", + //ZF1_ZMCLOB + "newer", + //ZF1_ZMNEW + "mtime_or_length", + //ZF1_ZMNEW + "protect", + //ZF1_ZMPROT + "rename" + //ZF1_ZMPROT + ], + //ZF0 (i.e., last byte) + conversion: [ + void 0, + "binary", + //ZCBIN + "text", + //ZCNL + "resume" + //ZCRESUM + ] + }; + var ZFILE_ORDER = ["extended", "transport", "management", "conversion"]; + var ZMSKNOLOC = 128; + var MANAGEMENT_MASK = 31; + var ZXSPARS = 64; + var ZFILE_HEADER = class extends Zmodem2.Header { + //TODO: allow options on instantiation + get_options() { + var opts = { + sparse: !!(this._bytes4[0] & ZXSPARS) + }; + var bytes_copy = this._bytes4.slice(0); + ZFILE_ORDER.forEach(function(key, i) { + if (ZFILE_VALUES[key] instanceof Array) { + if (key === "management") { + opts.skip_if_absent = !!(bytes_copy[i] & ZMSKNOLOC); + bytes_copy[i] &= MANAGEMENT_MASK; + } + opts[key] = ZFILE_VALUES[key][bytes_copy[i]]; + } else { + for (var extkey in ZFILE_VALUES[key]) { + opts[extkey] = !!(bytes_copy[i] & ZFILE_VALUES[key][extkey]); + if (opts[extkey]) { + bytes_copy[i] ^= ZFILE_VALUES[key][extkey]; + } + } + } + if (!opts[key] && bytes_copy[i]) { + opts[key] = "unknown:" + bytes_copy[i]; + } + }); + return opts; + } + }; + var ZSKIP_HEADER = class extends Zmodem2.Header { + }; + var ZABORT_HEADER = class extends Zmodem2.Header { + }; + var ZFIN_HEADER = class extends Zmodem2.Header { + }; + var ZFERR_HEADER = class extends Zmodem2.Header { + }; + ZFIN_HEADER.prototype._hex_header_ending = HEX_HEADER_CRLF; + var ZOffsetHeader = class extends Zmodem2.Header { + constructor(offset) { + super(); + this._bytes4 = Zmodem2.ENCODELIB.pack_u32_le(offset); + } + get_offset() { + return Zmodem2.ENCODELIB.unpack_u32_le(this._bytes4); + } + }; + var ZRPOS_HEADER = class extends ZOffsetHeader { + }; + var ZDATA_HEADER = class extends ZOffsetHeader { + }; + var ZEOF_HEADER = class extends ZOffsetHeader { + }; + var FRAME_CLASS_TYPES = [ + [ZRQINIT_HEADER, "ZRQINIT"], + [ZRINIT_HEADER, "ZRINIT"], + [ZSINIT_HEADER, "ZSINIT"], + [ZACK_HEADER, "ZACK"], + [ZFILE_HEADER, "ZFILE"], + [ZSKIP_HEADER, "ZSKIP"], + void 0, + // [ ZNAK_HEADER, "ZNAK" ], + [ZABORT_HEADER, "ZABORT"], + [ZFIN_HEADER, "ZFIN"], + [ZRPOS_HEADER, "ZRPOS"], + [ZDATA_HEADER, "ZDATA"], + [ZEOF_HEADER, "ZEOF"], + [ZFERR_HEADER, "ZFERR"], + //see note + void 0, + //[ ZCRC_HEADER, "ZCRC" ], + void 0, + //[ ZCHALLENGE_HEADER, "ZCHALLENGE" ], + void 0, + //[ ZCOMPL_HEADER, "ZCOMPL" ], + void 0, + //[ ZCAN_HEADER, "ZCAN" ], + void 0, + //[ ZFREECNT_HEADER, "ZFREECNT" ], + void 0, + //[ ZCOMMAND_HEADER, "ZCOMMAND" ], + void 0 + //[ ZSTDERR_HEADER, "ZSTDERR" ], + ]; + var FRAME_NAME_CREATOR = {}; + for (fc = 0; fc < FRAME_CLASS_TYPES.length; fc++) { + if (!FRAME_CLASS_TYPES[fc]) continue; + FRAME_NAME_CREATOR[FRAME_CLASS_TYPES[fc][1]] = FRAME_CLASS_TYPES[fc][0]; + Object.assign( + FRAME_CLASS_TYPES[fc][0].prototype, + { + TYPENUM: fc, + NAME: FRAME_CLASS_TYPES[fc][1] + } + ); + } + var fc; + var CREATORS = [ + ZRQINIT_HEADER, + ZRINIT_HEADER, + ZSINIT_HEADER, + ZACK_HEADER, + ZFILE_HEADER, + ZSKIP_HEADER, + "ZNAK", + ZABORT_HEADER, + ZFIN_HEADER, + ZRPOS_HEADER, + ZDATA_HEADER, + ZEOF_HEADER, + ZFERR_HEADER, + "ZCRC", + //ZCRC_HEADER, -- leaving unimplemented? + "ZCHALLENGE", + "ZCOMPL", + "ZCAN", + "ZFREECNT", + // ZFREECNT_HEADER, + "ZCOMMAND", + "ZSTDERR" + ]; + function _get_blank_header(typenum) { + var creator = CREATORS[typenum]; + if (typeof creator === "string") { + throw "Received unsupported header: " + creator; + } + return _get_blank_header_from_constructor(creator); + } + function _get_blank_header_from_constructor(creator) { + if (creator.prototype instanceof ZOffsetHeader) { + return new creator(0); + } + return new creator([]); + } + function _parse_binary16(bytes_arr) { + var zdle_decoded = Zmodem2.ZDLE.splice(bytes_arr, BINARY16_HEADER_PREFIX.length, 7); + return zdle_decoded && _parse_non_zdle_binary16(zdle_decoded); + } + function _parse_non_zdle_binary16(decoded) { + Zmodem2.CRC.verify16( + decoded.slice(0, 5), + decoded.slice(5) + ); + var typenum = decoded[0]; + var hdr = _get_blank_header(typenum); + hdr._bytes4 = decoded.slice(1, 5); + return hdr; + } + function _parse_binary32(bytes_arr) { + var zdle_decoded = Zmodem2.ZDLE.splice( + bytes_arr, + //omit the leading "*", ZDLE, and "C" + BINARY32_HEADER_PREFIX.length, + 9 + ); + if (!zdle_decoded) return; + Zmodem2.CRC.verify32( + zdle_decoded.slice(0, 5), + zdle_decoded.slice(5) + ); + var typenum = zdle_decoded[0]; + var hdr = _get_blank_header(typenum); + hdr._bytes4 = zdle_decoded.slice(1, 5); + return hdr; + } + function _parse_hex(bytes_arr) { + var lf_pos = bytes_arr.indexOf(138); + if (-1 === lf_pos) { + lf_pos = bytes_arr.indexOf(10); + } + var hdr_err, hex_bytes; + if (-1 === lf_pos) { + if (bytes_arr.length > 11) { + hdr_err = "Invalid hex header - no LF detected within 12 bytes!"; + } + return; + } else { + hex_bytes = bytes_arr.splice(0, lf_pos); + bytes_arr.shift(); + if (hex_bytes.length === 19) { + var preceding = hex_bytes.pop(); + if (preceding !== 13 && preceding !== 141) { + hdr_err = "Invalid hex header: (CR/)LF doesn\u2019t have CR!"; + } + } else if (hex_bytes.length !== 18) { + hdr_err = "Invalid hex header: invalid number of bytes before LF!"; + } + } + if (hdr_err) { + hdr_err += " (" + hex_bytes.length + " bytes: " + hex_bytes.join() + ")"; + throw hdr_err; + } + hex_bytes.splice(0, 4); + var octets = Zmodem2.ENCODELIB.parse_hex_octets(hex_bytes); + return _parse_non_zdle_binary16(octets); + } + Zmodem2.Header.parse_hex = _parse_hex; + } +}); + +// node_modules/zmodem.js/src/zsubpacket.js +var require_zsubpacket = __commonJS({ + "node_modules/zmodem.js/src/zsubpacket.js"(exports2, module2) { + "use strict"; + var Zmodem2 = module2.exports; + Object.assign( + Zmodem2, + require_zcrc(), + require_zdle(), + require_zmlib(), + require_zerror() + ); + var ZCRCE = 104; + var ZCRCG = 105; + var ZCRCQ = 106; + var ZCRCW = 107; + var SUBPACKET_BUILDER; + Zmodem2.Subpacket = class ZmodemSubpacket { + /** + * Build a Subpacket subclass given a payload and frame end string. + * + * @param {Array} octets - The octet values to parse. + * Each array member should be an 8-bit unsigned integer (0-255). + * + * @param {string} frameend - One of: + * - `no_end_no_ack` + * - `end_no_ack` + * - `no_end_ack` (unused currently) + * - `end_ack` + * + * @returns {Subpacket} An instance of the appropriate Subpacket subclass. + */ + static build(octets, frameend) { + var Ctr = SUBPACKET_BUILDER[frameend]; + if (!Ctr) { + throw "No subpacket type \u201C" + frameend + "\u201D is defined! Try one of: " + Object.keys(SUBPACKET_BUILDER).join(", "); + } + return new Ctr(octets); + } + /** + * Return the octet values array that represents the object + * encoded with a 16-bit CRC. + * + * @param {ZDLE} zencoder - A ZDLE instance to use for ZDLE encoding. + * + * @returns {number[]} An array of octet values suitable for sending + * as binary data. + */ + encode16(zencoder) { + return this._encode(zencoder, Zmodem2.CRC.crc16); + } + /** + * Return the octet values array that represents the object + * encoded with a 32-bit CRC. + * + * @param {ZDLE} zencoder - A ZDLE instance to use for ZDLE encoding. + * + * @returns {number[]} An array of octet values suitable for sending + * as binary data. + */ + encode32(zencoder) { + return this._encode(zencoder, Zmodem2.CRC.crc32); + } + /** + * Return the subpacket payload’s octet values. + * + * NOTE: For speed, this returns the actual data in the subpacket; + * if you mutate this return value, you alter the Subpacket object + * internals. This is OK if you won’t need the Subpacket anymore, but + * just be careful. + * + * @returns {number[]} The subpacket’s payload, represented as an + * array of octet values. **DO NOT ALTER THIS ARRAY** unless you + * no longer need the Subpacket. + */ + get_payload() { + return this._payload; + } + /** + * Parse out a Subpacket object from a given array of octet values, + * assuming a 16-bit CRC. + * + * An exception is thrown if the given bytes are definitively invalid + * as subpacket values with 16-bit CRC. + * + * @param {number[]} octets - The octet values to parse. + * Each array member should be an 8-bit unsigned integer (0-255). + * This object is mutated in the function. + * + * @returns {Subpacket|undefined} An instance of the appropriate Subpacket + * subclass, or undefined if not enough octet values are given + * to determine whether there is a valid subpacket here or not. + */ + static parse16(octets) { + return ZmodemSubpacket._parse(octets, 2); + } + //parse32 test: + //[102, 105, 108, 101, 110, 97, 109, 101, 119, 105, 116, 104, 115, 112, 97, 99, 101, 115, 0, 49, 55, 49, 51, 49, 52, 50, 52, 51, 50, 49, 55, 50, 49, 48, 48, 54, 52, 52, 48, 49, 49, 55, 0, 43, 8, 63, 115, 23, 17] + /** + * Same as parse16(), but assuming a 32-bit CRC. + * + * @param {number[]} octets - The octet values to parse. + * Each array member should be an 8-bit unsigned integer (0-255). + * This object is mutated in the function. + * + * @returns {Subpacket|undefined} An instance of the appropriate Subpacket + * subclass, or undefined if not enough octet values are given + * to determine whether there is a valid subpacket here or not. + */ + static parse32(octets) { + return ZmodemSubpacket._parse(octets, 4); + } + /** + * Not used directly. + */ + constructor(payload) { + this._payload = payload; + } + _encode(zencoder, crc_func) { + return zencoder.encode(this._payload.slice(0)).concat( + [Zmodem2.ZMLIB.ZDLE, this._frameend_num], + zencoder.encode(crc_func(this._payload.concat(this._frameend_num))) + ); + } + //Because of ZDLE encoding, we’ll never see any of the frame-end octets + //in a stream except as the ends of data payloads. + static _parse(bytes_arr, crc_len) { + var end_at; + var creator; + var _frame_ends_lookup = { + 104: ZEndNoAckSubpacket, + 105: ZNoEndNoAckSubpacket, + 106: ZNoEndAckSubpacket, + 107: ZEndAckSubpacket + }; + var zdle_at = 0; + while (zdle_at < bytes_arr.length) { + zdle_at = bytes_arr.indexOf(Zmodem2.ZMLIB.ZDLE, zdle_at); + if (zdle_at === -1) return; + var after_zdle = bytes_arr[zdle_at + 1]; + creator = _frame_ends_lookup[after_zdle]; + if (creator) { + end_at = zdle_at + 1; + break; + } + zdle_at++; + } + if (!creator) return; + var frameend_num = bytes_arr[end_at]; + if (bytes_arr[end_at - 1] !== Zmodem2.ZMLIB.ZDLE) { + throw "Byte before frame end should be ZDLE, not " + bytes_arr[end_at - 1]; + } + var zdle_encoded_payload = bytes_arr.splice(0, end_at - 1); + var got_crc = Zmodem2.ZDLE.splice(bytes_arr, 2, crc_len); + if (!got_crc) { + bytes_arr.unshift.apply(bytes_arr, zdle_encoded_payload); + return; + } + var payload = Zmodem2.ZDLE.decode(zdle_encoded_payload); + Zmodem2.CRC[crc_len === 2 ? "verify16" : "verify32"]( + payload.concat([frameend_num]), + got_crc + ); + return new creator(payload, got_crc); + } + }; + var ZEndSubpacketBase = class extends Zmodem2.Subpacket { + frame_end() { + return true; + } + }; + var ZNoEndSubpacketBase = class extends Zmodem2.Subpacket { + frame_end() { + return false; + } + }; + var ZEndNoAckSubpacket = class extends ZEndSubpacketBase { + ack_expected() { + return false; + } + }; + ZEndNoAckSubpacket.prototype._frameend_num = ZCRCE; + var ZEndAckSubpacket = class extends ZEndSubpacketBase { + ack_expected() { + return true; + } + }; + ZEndAckSubpacket.prototype._frameend_num = ZCRCW; + var ZNoEndNoAckSubpacket = class extends ZNoEndSubpacketBase { + ack_expected() { + return false; + } + }; + ZNoEndNoAckSubpacket.prototype._frameend_num = ZCRCG; + var ZNoEndAckSubpacket = class extends ZNoEndSubpacketBase { + ack_expected() { + return true; + } + }; + ZNoEndAckSubpacket.prototype._frameend_num = ZCRCQ; + SUBPACKET_BUILDER = { + end_no_ack: ZEndNoAckSubpacket, + end_ack: ZEndAckSubpacket, + no_end_no_ack: ZNoEndNoAckSubpacket, + no_end_ack: ZNoEndAckSubpacket + }; + } +}); + +// node_modules/zmodem.js/src/zvalidation.js +var require_zvalidation = __commonJS({ + "node_modules/zmodem.js/src/zvalidation.js"(exports2, module2) { + "use strict"; + var Zmodem2 = module2.exports; + Object.assign( + Zmodem2, + require_zerror() + ); + var LOOKS_LIKE_ZMODEM_HEADER = /\*\x18[AC]|\*\*\x18B/; + function _validate_number(key, value) { + if (value < 0) { + throw new Zmodem2.Error("validation", "\u201C" + key + "\u201D (" + value + ") must be nonnegative."); + } + if (value !== Math.floor(value)) { + throw new Zmodem2.Error("validation", "\u201C" + key + "\u201D (" + value + ") must be an integer."); + } + } + Zmodem2.Validation = { + /** + * Validates and normalizes a set of parameters for an offer to send. + * NOTE: This returns “mtime” as epoch seconds, not a Date. This is + * inconsistent with the get_details() method in Session, but it’s + * more useful for sending over the wire. + * + * @param {FileDetails} params - The file details. Some fairly trivial + * variances from the specification are allowed. + * + * @return {FileDetails} The parameters that should be sent. `mtime` + * will be a Date rather than a number. + */ + offer_parameters: function offer_parameters(params) { + if (!params.name) { + throw new Zmodem2.Error("validation", "Need \u201Cname\u201D!"); + } + if (typeof params.name !== "string") { + throw new Zmodem2.Error("validation", "\u201Cname\u201D (" + params.name + ") must be a string!"); + } + params = Object.assign({}, params); + if (LOOKS_LIKE_ZMODEM_HEADER.test(params.name)) { + console.warn("The filename " + JSON.stringify(name) + " contains characters that look like a ZMODEM header. This could corrupt the ZMODEM session; consider renaming it so that the filename doesn\u2019t contain control characters."); + } + if (params.serial !== null && params.serial !== void 0) { + throw new Zmodem2.Error("validation", "\u201Cserial\u201D is meaningless."); + } + params.serial = null; + ["size", "mode", "files_remaining", "bytes_remaining"].forEach( + function(k) { + var ok; + switch (typeof params[k]) { + case "object": + ok = params[k] === null; + break; + case "undefined": + params[k] = null; + ok = true; + break; + case "number": + _validate_number(k, params[k]); + ok = true; + break; + } + if (!ok) { + throw new Zmodem2.Error("validation", "\u201C" + k + "\u201D (" + params[k] + ") must be null, undefined, or a number."); + } + } + ); + if (typeof params.mode === "number") { + params.mode |= 32768; + } + if (params.files_remaining === 0) { + throw new Zmodem2.Error("validation", "\u201Cfiles_remaining\u201D, if given, must be positive."); + } + var mtime_ok; + switch (typeof params.mtime) { + case "object": + mtime_ok = true; + if (params.mtime instanceof Date) { + var date_obj = params.mtime; + params.mtime = Math.floor(date_obj.getTime() / 1e3); + if (params.mtime < 0) { + throw new Zmodem2.Error("validation", "\u201Cmtime\u201D (" + date_obj + ") must not be earlier than 1970."); + } + } else if (params.mtime !== null) { + mtime_ok = false; + } + break; + case "undefined": + params.mtime = null; + mtime_ok = true; + break; + case "number": + _validate_number("mtime", params.mtime); + mtime_ok = true; + break; + } + if (!mtime_ok) { + throw new Zmodem2.Error("validation", "\u201Cmtime\u201D (" + params.mtime + ") must be null, undefined, a Date, or a number."); + } + return params; + } + }; + } +}); + +// node_modules/zmodem.js/src/zsession.js +var require_zsession = __commonJS({ + "node_modules/zmodem.js/src/zsession.js"(exports2, module2) { + "use strict"; + var Zmodem2 = module2.exports; + Zmodem2.DEBUG = false; + Object.assign( + Zmodem2, + require_encode(), + require_text(), + require_zdle(), + require_zmlib(), + require_zheader(), + require_zsubpacket(), + require_zvalidation(), + require_zerror() + ); + var KEEPALIVE_INTERVAL = 5e3; + var ZRINIT_FLAGS = [ + "CANFDX", + //full duplex + "CANOVIO", + //overlap I/O + //lsz has a buffer overflow bug that shows itself when: + // + // - 16-bit CRC is used, and + // - lsz receives the abort sequence while sending a file + // + //To avoid this, we just tell lsz to use 32-bit CRC + //even though there is otherwise no reason. This ensures that + //unfixed lsz versions will avoid the buffer overflow. + "CANFC32" + ]; + var FORCE_ESCAPE_CTRL_CHARS = true; + var DEFAULT_RECEIVE_INPUT_MODE = "spool_uint8array"; + var MAX_CHUNK_LENGTH = 8192; + var BS = 8; + var OVER_AND_OUT = [79, 79]; + var ABORT_SEQUENCE = Zmodem2.ZMLIB.ABORT_SEQUENCE; + var _Eventer = class { + /** + * Not called directly. + */ + constructor() { + this._on_evt = {}; + this._evt_once_index = {}; + } + _Add_event(evt_name) { + this._on_evt[evt_name] = []; + this._evt_once_index[evt_name] = []; + } + _get_evt_queue(evt_name) { + if (!this._on_evt[evt_name]) { + throw "Bad event: " + evt_name; + } + return this._on_evt[evt_name]; + } + /** + * Register a callback for a given event. + * + * @param {string} evt_name - The name of the event. + * + * @param {Function} todo - The function to execute when the event happens. + */ + on(evt_name, todo) { + var queue = this._get_evt_queue(evt_name); + queue.push(todo); + return this; + } + /** + * Unregister a callback for a given event. + * + * @param {string} evt_name - The name of the event. + * + * @param {Function} [todo] - The function to execute when the event + * happens. If not given, the last event registered for the event + * is unregistered. + */ + off(evt_name, todo) { + var queue = this._get_evt_queue(evt_name); + if (todo) { + var at = queue.indexOf(todo); + if (at === -1) { + throw "\u201C" + todo + "\u201D is not in the \u201C" + evt_name + "\u201D queue."; + } + queue.splice(at, 1); + } else { + queue.pop(); + } + return this; + } + _Happen(evt_name) { + var queue = this._get_evt_queue(evt_name); + var args = Array.apply(null, arguments); + args.shift(); + var sess = this; + queue.forEach(function(cb) { + cb.apply(sess, args); + }); + return queue.length; + } + }; + Zmodem2.Session = class ZmodemSession extends _Eventer { + /** + * Parse out a hex header from the given array. + * If there’s a ZRQINIT or ZRINIT at the beginning, + * we’ll return it. If the input isn’t a header, + * for whatever reason, we return undefined. + * + * @param {number[]} octets - The bytes to parse. + * + * @return {Session|undefined} A Session object if the beginning + * of a session was parsable in “octets”; otherwise undefined. + */ + static parse(octets) { + var hdr; + try { + hdr = Zmodem2.Header.parse_hex(octets); + } catch (e) { + return; + } + if (!hdr) return; + switch (hdr.NAME) { + case "ZRQINIT": + return new Zmodem2.Session.Receive(); + case "ZRINIT": + return new Zmodem2.Session.Send(hdr); + } + } + /** + * Sets the sender function that a Session object will use. + * + * @param {Function} sender_func - The function to call. + * It will receive an Array with the relevant octets. + * + * @return {Session} The session object (for chaining). + */ + set_sender(sender_func) { + this._sender = sender_func; + return this; + } + /** + * Whether the current Session has ended. + * + * @returns {boolean} The ended state. + */ + has_ended() { + return this._has_ended(); + } + /** + * Consumes an array of octets as ZMODEM session input. + * + * @param {number[]} octets - The input octets. + */ + consume(octets) { + this._before_consume(octets); + if (this._aborted) throw new Zmodem2.Error("already_aborted"); + if (!octets.length) return; + this._strip_and_enqueue_input(octets); + if (!this._check_for_abort_sequence(octets)) { + this._consume_first(); + } + return; + } + /** + * Whether the current Session has been `abort()`ed. + * + * @returns {boolean} The aborted state. + */ + aborted() { + return !!this._aborted; + } + /** + * Not called directly. + */ + constructor() { + super(); + this._config = {}; + this._input_buffer = []; + this._Add_event("receive"); + this._Add_event("garbage"); + this._Add_event("session_end"); + } + /** + * Returns the Session object’s role. + * + * @returns {string} One of: + * - `receive` + * - `send` + */ + get_role() { + return this.type; + } + _trim_leading_garbage_until_header() { + var garbage = Zmodem2.Header.trim_leading_garbage(this._input_buffer); + if (garbage.length) { + if (this._Happen("garbage", garbage) === 0) { + console.debug( + "Garbage: ", + String.fromCharCode.apply(String, garbage), + garbage + ); + } + } + } + _parse_and_consume_header() { + this._trim_leading_garbage_until_header(); + var new_header_and_crc = Zmodem2.Header.parse(this._input_buffer); + if (!new_header_and_crc) return; + if (Zmodem2.DEBUG) { + this._log_header("RECEIVED HEADER", new_header_and_crc[0]); + } + this._consume_header(new_header_and_crc[0]); + this._last_header_name = new_header_and_crc[0].NAME; + this._last_header_crc = new_header_and_crc[1]; + return new_header_and_crc[0]; + } + _log_header(label, header) { + console.debug(this.type, label, header.NAME, header._bytes4.join()); + } + _consume_header(new_header) { + this._on_receive(new_header); + var handler = this._next_header_handler && this._next_header_handler[new_header.NAME]; + if (!handler) { + console.error("Unhandled header!", new_header, this._next_header_handler); + throw new Zmodem2.Error("Unhandled header: " + new_header.NAME); + } + this._next_header_handler = null; + handler.call(this, new_header); + } + //TODO: strip out the abort sequence + _check_for_abort_sequence() { + var abort_at = Zmodem2.ZMLIB.find_subarray(this._input_buffer, ABORT_SEQUENCE); + if (abort_at !== -1) { + this._input_buffer.splice(0, abort_at + ABORT_SEQUENCE.length); + this._aborted = true; + this._on_session_end(); + throw new Zmodem2.Error("peer_aborted"); + } + } + _send_header(name2) { + if (!this._sender) throw "Need sender!"; + var args = Array.apply(null, arguments); + var bytes_hdr = this._create_header_bytes(args); + if (Zmodem2.DEBUG) { + this._log_header("SENDING HEADER", bytes_hdr[1]); + } + this._sender(bytes_hdr[0]); + this._last_sent_header = bytes_hdr[1]; + } + _create_header_bytes(name_and_args) { + var hdr = Zmodem2.Header.build.apply(Zmodem2.Header, name_and_args); + var formatter = this._get_header_formatter(name_and_args[0]); + return [ + hdr[formatter](this._zencoder), + hdr + ]; + } + _strip_and_enqueue_input(input) { + Zmodem2.ZMLIB.strip_ignored_bytes(input); + this._input_buffer.push.apply(this._input_buffer, input); + } + /** + * **STOP!** You probably want to `skip()` an Offer rather than + * `abort()`. See below. + * + * Abort the current session by sending the ZMODEM abort sequence. + * This function will cause the Session object to refuse to send + * any further data. + * + * Zmodem.Sentry is configured to send all output to the terminal + * after a session’s `abort()`. That could result in lots of + * ZMODEM garble being sent to the JavaScript terminal, which you + * probably don’t want. + * + * `skip()` on an Offer is better because Session will continue to + * discard data until we reach either another file or the + * sender-initiated end of the ZMODEM session. So no ZMODEM garble, + * and the session will end successfully. + * + * The behavior of `abort()` is subject to change since it’s not + * very useful as currently implemented. + */ + abort() { + this._sender( + ABORT_SEQUENCE.concat([BS, BS, BS, BS, BS]) + ); + this._aborted = true; + this._sender = function() { + throw new Zmodem2.Error("already_aborted"); + }; + this._on_session_end(); + return; + } + //---------------------------------------------------------------------- + _on_session_end() { + this._Happen("session_end"); + } + _on_receive(hdr_or_pkt) { + this._Happen("receive", hdr_or_pkt); + } + _before_consume() { + } + }; + function _trim_OO(array) { + if (0 === Zmodem2.ZMLIB.find_subarray(array, OVER_AND_OUT)) { + array.splice(0, OVER_AND_OUT.length); + } else if (array[0] === OVER_AND_OUT[OVER_AND_OUT.length - 1]) { + array.splice(0, 1); + } + return array; + } + Zmodem2.Session.Receive = class ZmodemReceiveSession extends Zmodem2.Session { + //We only get 1 file at a time, so on each consume() either + //continue state for the current file or start a new one. + /** + * Not called directly. + */ + constructor() { + super(); + this._Add_event("offer"); + this._Add_event("data_in"); + this._Add_event("file_end"); + } + /** + * Consume input bytes from the sender. + * + * @private + * @param {number[]} octets - The bytes to consume. + */ + _before_consume(octets) { + if (this._bytes_after_OO) { + throw "PROTOCOL: Session is completed!"; + } + this._bytes_being_consumed = octets; + } + /** + * Return any bytes that have been `consume()`d but + * came after the end of the ZMODEM session. + * + * @returns {number[]} The trailing bytes. + */ + get_trailing_bytes() { + if (this._aborted) return []; + if (!this._bytes_after_OO) { + throw "PROTOCOL: Session is not completed!"; + } + return this._bytes_after_OO.slice(0); + } + _has_ended() { + return this.aborted() || !!this._bytes_after_OO; + } + //Receiver always sends hex headers. + _get_header_formatter() { + return "to_hex"; + } + _parse_and_consume_subpacket() { + var parse_func; + if (this._last_header_crc === 16) { + parse_func = "parse16"; + } else { + parse_func = "parse32"; + } + var subpacket = Zmodem2.Subpacket[parse_func](this._input_buffer); + if (subpacket) { + if (Zmodem2.DEBUG) { + console.debug(this.type, "RECEIVED SUBPACKET", subpacket); + } + this._consume_data(subpacket); + if (subpacket.frame_end()) { + this._next_subpacket_handler = null; + } + } + return subpacket; + } + _consume_first() { + if (this._got_ZFIN) { + if (this._input_buffer.length < 2) return; + if (Zmodem2.ZMLIB.find_subarray(this._input_buffer, OVER_AND_OUT) === 0) { + this._bytes_after_OO = _trim_OO(this._bytes_being_consumed.slice(0)); + this._on_session_end(); + return; + } else { + throw "PROTOCOL: Only thing after ZFIN should be \u201COO\u201D (79,79), not: " + this._input_buffer.join(); + } + } + var parsed; + do { + if (this._next_subpacket_handler) { + parsed = this._parse_and_consume_subpacket(); + } else { + parsed = this._parse_and_consume_header(); + } + } while (parsed && this._input_buffer.length); + } + _consume_data(subpacket) { + this._on_receive(subpacket); + if (!this._next_subpacket_handler) { + throw "PROTOCOL: Received unexpected data packet after " + this._last_header_name + " header: " + subpacket.get_payload().join(); + } + this._next_subpacket_handler.call(this, subpacket); + } + _octets_to_string(octets) { + if (!this._textdecoder) { + this._textdecoder = new Zmodem2.Text.Decoder(); + } + return this._textdecoder.decode(new Uint8Array(octets)); + } + _consume_ZFILE_data(hdr, subpacket) { + if (this._file_info) { + throw "PROTOCOL: second ZFILE data subpacket received"; + } + var packet_payload = subpacket.get_payload(); + var nul_at = packet_payload.indexOf(0); + var fname = this._octets_to_string(packet_payload.slice(0, nul_at)); + var the_rest = this._octets_to_string(packet_payload.slice(1 + nul_at)).split(" "); + var mtime = the_rest[1] && parseInt(the_rest[1], 8) || void 0; + if (mtime) { + mtime = new Date(mtime * 1e3); + } + this._file_info = { + name: fname, + size: the_rest[0] ? parseInt(the_rest[0], 10) : null, + mtime: mtime || null, + mode: the_rest[2] && parseInt(the_rest[2], 8) || null, + serial: the_rest[3] && parseInt(the_rest[3], 10) || null, + files_remaining: the_rest[4] ? parseInt(the_rest[4], 10) : null, + bytes_remaining: the_rest[5] ? parseInt(the_rest[5], 10) : null + }; + var xfer = new Offer( + hdr.get_options(), + this._file_info, + this._accept.bind(this), + this._skip.bind(this) + ); + this._current_transfer = xfer; + } + _consume_ZDATA_data(subpacket) { + if (!this._accepted_offer) { + throw "PROTOCOL: Received data without accepting!"; + } + if (!this._offset_ok) { + console.warn("offset not ok!"); + _send_ZRPOS(); + return; + } + this._file_offset += subpacket.get_payload().length; + this._on_data_in(subpacket); + if (subpacket.ack_expected() && !subpacket.frame_end()) { + this._send_header("ZACK", Zmodem2.ENCODELIB.pack_u32_le(this._file_offset)); + } + } + _make_promise_for_between_files() { + var sess = this; + return new Promise(function(res) { + var between_files_handler = { + ZFILE: function(hdr) { + this._next_subpacket_handler = function(subpacket) { + this._next_subpacket_handler = null; + this._consume_ZFILE_data(hdr, subpacket); + this._Happen("offer", this._current_transfer); + res(this._current_transfer); + }; + }, + //We use this as a keep-alive. Maybe other + //implementations do, too? + ZSINIT: function(hdr) { + sess._next_subpacket_handler = function(spkt) { + sess._next_subpacket_handler = null; + sess._consume_ZSINIT_data(spkt); + sess._send_header("ZACK"); + sess._next_header_handler = between_files_handler; + }; + }, + ZFIN: function() { + this._consume_ZFIN(); + res(); + } + }; + sess._next_header_handler = between_files_handler; + }); + } + _consume_ZSINIT_data(spkt) { + this._attn = spkt.get_payload(); + } + /** + * Start the ZMODEM session by signaling to the sender that + * we are ready for the first file offer. + * + * @returns {Promise} A promise that resolves with an Offer object + * or, if the sender closes the session immediately without offering + * anything, nothing. + */ + start() { + if (this._started) throw "Already started!"; + this._started = true; + var ret = this._make_promise_for_between_files(); + this._send_ZRINIT(); + return ret; + } + //Returns a promise that’s fulfilled when the file + //transfer is done. + // + // That ZEOF promise return is another promise that’s + // fulfilled when we get either ZFIN or another ZFILE. + _accept(offset) { + this._accepted_offer = true; + this._file_offset = offset || 0; + var sess = this; + var ret = new Promise(function(resolve_accept) { + var last_ZDATA; + sess._next_header_handler = { + ZDATA: function on_ZDATA(hdr) { + this._consume_ZDATA(hdr); + this._next_subpacket_handler = this._consume_ZDATA_data; + this._next_header_handler = { + ZEOF: function on_ZEOF(hdr2) { + this._consume_ZEOF(hdr2); + this._next_subpacket_handler = null; + this._make_promise_for_between_files(); + resolve_accept(); + this._send_ZRINIT(); + } + }; + } + }; + }); + this._send_ZRPOS(); + return ret; + } + _skip() { + var ret = this._make_promise_for_between_files(); + if (this._accepted_offer) { + if (!this._current_transfer) return; + var bound_make_promise_for_between_files = function() { + this._accepted_offer = false; + this._next_subpacket_handler = null; + this._make_promise_for_between_files(); + }.bind(this); + Object.assign( + this._next_header_handler, + { + ZEOF: bound_make_promise_for_between_files, + ZDATA: function() { + bound_make_promise_for_between_files(); + this._next_header_handler.ZEOF = bound_make_promise_for_between_files; + }.bind(this) + } + ); + } + this._file_info = null; + this._send_header("ZSKIP"); + return ret; + } + _send_ZRINIT() { + this._send_header("ZRINIT", ZRINIT_FLAGS); + } + _consume_ZFIN() { + this._got_ZFIN = true; + this._send_header("ZFIN"); + } + _consume_ZEOF(header) { + if (this._file_offset !== header.get_offset()) { + throw "ZEOF offset mismatch; unimplemented (local: " + this._file_offset + "; ZEOF: " + header.get_offset() + ")"; + } + this._on_file_end(); + this._file_info = null; + this._current_transfer = null; + } + _consume_ZDATA(header) { + if (this._file_offset === header.get_offset()) { + this._offset_ok = true; + } else { + throw "Error correction is unimplemented."; + } + } + _send_ZRPOS() { + this._send_header("ZRPOS", this._file_offset); + } + //---------------------------------------------------------------------- + //events + _on_file_end() { + this._Happen("file_end"); + if (this._current_transfer) { + this._current_transfer._Happen("complete"); + this._current_transfer = null; + } + } + _on_data_in(subpacket) { + this._Happen("data_in", subpacket); + if (this._current_transfer) { + this._current_transfer._Happen("input", subpacket.get_payload()); + } + } + }; + Object.assign( + Zmodem2.Session.Receive.prototype, + { + type: "receive" + } + ); + var Transfer_Offer_Mixin = { + /** + * Returns the file details object. + * @returns {FileDetails} `mtime` is a Date. + */ + get_details: function get_details() { + return Object.assign({}, this._file_info); + }, + /** + * Returns a parse of the ZFILE header’s payload. + * + * @returns {Object} Members are: + * + * - `conversion` (string | undefined) + * - `management` (string | undefined) + * - `transfer` (string | undefined) + * - `sparse` (boolean) + */ + get_options: function get_options() { + return Object.assign({}, this._zfile_opts); + }, + /** + * Returns the offset based on the last transferred chunk. + * @returns {number} The file offset (i.e., number of bytes after + * the start of the file). + */ + get_offset: function get_offset() { + return this._file_offset; + } + }; + var Transfer = class { + /** + * Not called directly. + */ + constructor(file_info, offset, send_func, end_func) { + this._file_info = file_info; + this._file_offset = offset || 0; + this._send = send_func; + this._end = end_func; + } + /** + * Send a (non-terminal) piece of the file. + * + * @param { number[] | Uint8Array } array_like - The bytes to send. + */ + send(array_like) { + this._send(array_like); + this._file_offset += array_like.length; + } + /** + * Complete the file transfer. + * + * @param { number[] | Uint8Array } [array_like] - The last bytes to send. + * + * @return { Promise } Resolves when the receiver has indicated + * acceptance of the end of the file transfer. + */ + end(array_like) { + var ret = this._end(array_like || []); + if (array_like) this._file_offset += array_like.length; + return ret; + } + }; + Object.assign(Transfer.prototype, Transfer_Offer_Mixin); + var Offer = class extends _Eventer { + /** + * Not called directly. + */ + constructor(zfile_opts, file_info, accept_func, skip_func) { + super(); + this._zfile_opts = zfile_opts; + this._file_info = file_info; + this._accept_func = accept_func; + this._skip_func = skip_func; + this._Add_event("input"); + this._Add_event("complete"); + this.on("input", this._input_handler); + } + _verify_not_skipped() { + if (this._skipped) { + throw new Zmodem2.Error("Already skipped!"); + } + } + /** + * Tell the sender that you don’t want the offered file. + * + * You can send this in lieu of `accept()` or after it, e.g., + * if you find that the transfer is taking too long. Note that, + * if you `skip()` after you `accept()`, you’ll likely have to + * wait for buffers to clear out. + * + */ + skip() { + this._verify_not_skipped(); + this._skipped = true; + return this._skip_func.apply(this, arguments); + } + /** + * Tell the sender to send the offered file. + * + * @param {Object} [opts] - Can be: + * @param {string} [opts.oninput=spool_uint8array] - Can be: + * + * - `spool_uint8array`: Stores the ZMODEM + * packet payloads as Uint8Array instances. + * This makes for an easy transition to a Blob, + * which JavaScript can use to save the file to disk. + * + * - `spool_array`: Stores the ZMODEM packet payloads + * as Array instances. Each value is an octet value. + * + * - (function): A handler that receives each payload + * as it arrives. The Offer object does not store + * the payloads internally when thus configured. + * + * @return { Promise } Resolves when the file is fully received. + * If the Offer has been spooling + * the packet payloads, the promise resolves with an Array + * that contains those payloads. + */ + accept(opts) { + this._verify_not_skipped(); + if (this._accepted) { + throw new Zmodem2.Error("Already accepted!"); + } + this._accepted = true; + if (!opts) opts = {}; + this._file_offset = opts.offset || 0; + switch (opts.on_input) { + case null: + case void 0: + case "spool_array": + case DEFAULT_RECEIVE_INPUT_MODE: + this._spool = []; + break; + default: + if (typeof opts.on_input !== "function") { + throw "Invalid \u201Con_input\u201D: " + opts.on_input; + } + } + this._input_handler_mode = opts.on_input || DEFAULT_RECEIVE_INPUT_MODE; + return this._accept_func(this._file_offset).then(this._get_spool.bind(this)); + } + _input_handler(payload) { + this._file_offset += payload.length; + if (typeof this._input_handler_mode === "function") { + this._input_handler_mode(payload); + } else { + if (this._input_handler_mode === DEFAULT_RECEIVE_INPUT_MODE) { + payload = new Uint8Array(payload); + } else if (this._input_handler_mode !== "spool_array") { + throw new Zmodem2.Error("WTF?? _input_handler_mode = " + this._input_handler_mode); + } + this._spool.push(payload); + } + } + _get_spool() { + return this._spool; + } + }; + Object.assign(Offer.prototype, Transfer_Offer_Mixin); + var SENDER_BINARY_HEADER = { + ZFILE: true, + ZDATA: true + }; + Zmodem2.Session.Send = class ZmodemSendSession extends Zmodem2.Session { + /** + * Not called directly. + */ + constructor(zrinit_hdr) { + super(); + if (!zrinit_hdr) { + throw "Need first header!"; + } else if (zrinit_hdr.NAME !== "ZRINIT") { + throw "First header should be ZRINIT, not " + zrinit_hdr.NAME; + } + this._last_header_name = "ZRINIT"; + this._subpacket_encode_func = "encode16"; + this._zencoder = new Zmodem2.ZDLE(); + this._consume_ZRINIT(zrinit_hdr); + this._file_offset = 0; + var zrqinit_count = 0; + this._start_keepalive_on_set_sender = true; + } + /** + * Sets the sender function. The first time this is called, + * it will also initiate a keepalive using ZSINIT until the + * first file is sent. + * + * @param {Function} func - The function to call. + * It will receive an Array with the relevant octets. + * + * @return {Session} The session object (for chaining). + */ + set_sender(func) { + super.set_sender(func); + if (this._start_keepalive_on_set_sender) { + this._start_keepalive_on_set_sender = false; + this._start_keepalive(); + } + return this; + } + //7.3.3 .. The sender also uses hex headers when they are + //not followed by binary data subpackets. + // + //FG: … or when the header is ZSINIT? That’s what lrzsz does, anyway. + //Then it sends a single NUL byte as the payload to an end_ack subpacket. + _get_header_formatter(name2) { + return SENDER_BINARY_HEADER[name2] ? "to_binary16" : "to_hex"; + } + //In order to keep lrzsz from timing out, we send ZSINIT every 5 seconds. + //Maybe make this configurable? + _start_keepalive() { + if (!this._keepalive_promise) { + var sess = this; + this._keepalive_promise = new Promise(function(resolve) { + sess._keepalive_timeout = setTimeout(resolve, KEEPALIVE_INTERVAL); + }).then(function() { + sess._next_header_handler = { + ZACK: function() { + sess._got_ZSINIT_ZACK = true; + } + }; + sess._send_ZSINIT(); + sess._keepalive_promise = null; + sess._start_keepalive(); + }); + } + } + _stop_keepalive() { + if (this._keepalive_promise) { + clearTimeout(this._keepalive_timeout); + this._keep_alive_promise = null; + } + } + _send_ZSINIT() { + var zsinit_flags = []; + if (this._zencoder.escapes_ctrl_chars()) { + zsinit_flags.push("ESCCTL"); + } + this._send_header_and_data( + ["ZSINIT", zsinit_flags], + [0], + "end_ack" + ); + } + _consume_ZRINIT(hdr) { + this._last_ZRINIT = hdr; + if (hdr.get_buffer_size()) { + throw "Buffer size (" + hdr.get_buffer_size() + ") is unsupported!"; + } + if (!hdr.can_full_duplex()) { + throw "Half-duplex I/O is unsupported!"; + } + if (!hdr.can_overlap_io()) { + throw "Non-overlap I/O is unsupported!"; + } + if (hdr.escape_8th_bit()) { + throw "8-bit escaping is unsupported!"; + } + if (FORCE_ESCAPE_CTRL_CHARS) { + this._zencoder.set_escape_ctrl_chars(true); + if (!hdr.escape_ctrl_chars()) { + console.debug("Peer didn\u2019t request escape of all control characters. Will send ZSINIT to force recognition of escaped control characters."); + } + } else { + this._zencoder.set_escape_ctrl_chars(hdr.escape_ctrl_chars()); + } + } + //https://stackoverflow.com/questions/23155939/missing-0xf-and-0x16-when-binary-data-through-virtual-serial-port-pair-created-b + //^^ Because of that, we always escape control characters. + //The alternative would be that lrz would never receive those + //two bytes from zmodem.js. + _ensure_receiver_escapes_ctrl_chars() { + var promise; + var needs_ZSINIT = !this._last_ZRINIT.escape_ctrl_chars() && !this._got_ZSINIT_ZACK; + if (needs_ZSINIT) { + var sess = this; + promise = new Promise(function(res) { + sess._next_header_handler = { + ZACK: (hdr) => { + res(); + } + }; + sess._send_ZSINIT(); + }); + } else { + promise = Promise.resolve(); + } + return promise; + } + _convert_params_to_offer_payload_array(params) { + params = Zmodem2.Validation.offer_parameters(params); + var subpacket_payload = params.name + "\0"; + var subpacket_space_pieces = [ + (params.size || 0).toString(10), + params.mtime ? params.mtime.toString(8) : "0", + params.mode ? (32768 | params.mode).toString(8) : "0", + "0" + //serial + ]; + if (params.files_remaining) { + subpacket_space_pieces.push(params.files_remaining); + if (params.bytes_remaining) { + subpacket_space_pieces.push(params.bytes_remaining); + } + } + subpacket_payload += subpacket_space_pieces.join(" "); + return this._string_to_octets(subpacket_payload); + } + /** + * Send an offer to the receiver. + * + * @param {FileDetails} params - All about the file you want to transfer. + * + * @returns {Promise} If the receiver accepts the offer, then the + * resolution is a Transfer object; otherwise the resolution is + * undefined. + */ + send_offer(params) { + if (Zmodem2.DEBUG) { + console.debug("SENDING OFFER", params); + } + if (!params) throw "need file params!"; + if (this._sending_file) throw "Already sending file!"; + var payload_array = this._convert_params_to_offer_payload_array(params); + this._stop_keepalive(); + var sess = this; + function zrpos_handler_setter_func() { + sess._next_header_handler = { + // The receiver may send ZRPOS in at least two cases: + // + // 1) A malformed subpacket arrived, so we need to + // “rewind” a bit and continue from the receiver’s + // last-successful location in the file. + // + // 2) The receiver hasn’t gotten any data for a bit, + // so it sends ZRPOS as a “ping”. + // + // Case #1 shouldn’t happen since zmodem.js requires a + // reliable transport. Case #2, though, can happen due + // to either normal network congestion or errors in + // implementation. In either case, there’s nothing for + // us to do but to ignore the ZRPOS, with an optional + // warning. + // + ZRPOS: function(hdr) { + if (Zmodem2.DEBUG) { + console.warn("Mid-transfer ZRPOS \u2026 implementation error?"); + } + zrpos_handler_setter_func(); + } + }; + } + ; + var doer_func = function() { + var handler_setter_promise = new Promise(function(res) { + sess._next_header_handler = { + ZSKIP: function() { + sess._start_keepalive(); + res(); + }, + ZRPOS: function(hdr) { + sess._sending_file = true; + zrpos_handler_setter_func(); + res( + new Transfer( + params, + hdr.get_offset(), + sess._send_interim_file_piece.bind(sess), + sess._end_file.bind(sess) + ) + ); + } + }; + }); + sess._send_header_and_data(["ZFILE"], payload_array, "end_ack"); + delete sess._sent_ZDATA; + return handler_setter_promise; + }; + if (FORCE_ESCAPE_CTRL_CHARS) { + return this._ensure_receiver_escapes_ctrl_chars().then(doer_func); + } + return doer_func(); + } + _send_header_and_data(hdr_name_and_args, data_arr, frameend) { + var bytes_hdr = this._create_header_bytes(hdr_name_and_args); + var data_bytes = this._build_subpacket_bytes(data_arr, frameend); + bytes_hdr[0].push.apply(bytes_hdr[0], data_bytes); + if (Zmodem2.DEBUG) { + this._log_header("SENDING HEADER", bytes_hdr[1]); + console.debug(this.type, "-- HEADER PAYLOAD:", frameend, data_bytes.length); + } + this._sender(bytes_hdr[0]); + this._last_sent_header = bytes_hdr[1]; + } + _build_subpacket_bytes(bytes_arr, frameend) { + var subpacket = Zmodem2.Subpacket.build(bytes_arr, frameend); + return subpacket[this._subpacket_encode_func](this._zencoder); + } + _build_and_send_subpacket(bytes_arr, frameend) { + this._sender(this._build_subpacket_bytes(bytes_arr, frameend)); + } + _string_to_octets(string) { + if (!this._textencoder) { + this._textencoder = new Zmodem2.Text.Encoder(); + } + var uint8arr = this._textencoder.encode(string); + return Array.prototype.slice.call(uint8arr); + } + /* + Potential future support for responding to ZRPOS: + send_file_offset(offset) { + } + */ + /* + Sending logic works thus: + - ASSUME the receiver can overlap I/O (CANOVIO) + (so fail if !CANFDX || !CANOVIO) + - Sender opens the firehose … all ZCRCG (!end/!ack) + until the end, when we send a ZCRCE (end/!ack) + NB: try 8k/32k/64k chunk sizes? Looks like there’s + no need to change the packet otherwise. + */ + //TODO: Put this on a Transfer object similar to what Receive uses? + _send_interim_file_piece(bytes_obj) { + this._send_file_part(bytes_obj, "no_end_no_ack"); + return Promise.resolve(); + } + _ensure_we_are_sending() { + if (!this._sending_file) throw "Not sending a file currently!"; + } + //This resolves once we receive ZEOF. + _end_file(bytes_obj) { + this._ensure_we_are_sending(); + this._send_file_part(bytes_obj, "end_no_ack"); + var sess = this; + var ret = new Promise(function(res) { + sess._sending_file = false; + sess._prepare_to_receive_ZRINIT(res); + }); + this._send_header("ZEOF", this._file_offset); + this._file_offset = 0; + return ret; + } + //Called at the beginning of our session + //and also when we’re done sending a file. + _prepare_to_receive_ZRINIT(after_consume) { + this._next_header_handler = { + ZRINIT: function(hdr) { + this._consume_ZRINIT(hdr); + if (after_consume) after_consume(); + } + }; + } + /** + * Signal to the receiver that the ZMODEM session is wrapping up. + * + * @returns {Promise} Resolves when the receiver has responded to + * our signal that the session is over. + */ + close() { + var ok_to_close = this._last_header_name === "ZRINIT"; + if (!ok_to_close) { + ok_to_close = this._last_header_name === "ZSKIP"; + } + if (!ok_to_close) { + ok_to_close = this._last_sent_header.name === "ZSINIT" && this._last_header_name === "ZACK"; + } + if (!ok_to_close) { + throw "Can\u2019t close; last received header was \u201C" + this._last_header_name + "\u201D"; + } + var sess = this; + var ret = new Promise(function(res, rej) { + sess._next_header_handler = { + ZFIN: function() { + sess._sender(OVER_AND_OUT); + sess._sent_OO = true; + sess._on_session_end(); + res(); + } + }; + }); + this._send_header("ZFIN"); + return ret; + } + _has_ended() { + return this.aborted() || !!this._sent_OO; + } + _send_file_part(bytes_obj, final_packetend) { + if (!this._sent_ZDATA) { + this._send_header("ZDATA", this._file_offset); + this._sent_ZDATA = true; + } + var obj_offset = 0; + var bytes_count = bytes_obj.length; + while (true) { + var chunk_size = Math.min(obj_offset + MAX_CHUNK_LENGTH, bytes_count) - obj_offset; + var at_end = chunk_size + obj_offset >= bytes_count; + var chunk = bytes_obj.slice(obj_offset, obj_offset + chunk_size); + if (!(chunk instanceof Array)) { + chunk = Array.prototype.slice.call(chunk); + } + this._build_and_send_subpacket( + chunk, + at_end ? final_packetend : "no_end_no_ack" + ); + this._file_offset += chunk_size; + obj_offset += chunk_size; + if (obj_offset >= bytes_count) break; + } + } + _consume_first() { + if (!this._parse_and_consume_header()) { + if (this._input_buffer.join() === "67") { + throw "Receiver has fallen back to YMODEM."; + } + } + } + _on_session_end() { + this._stop_keepalive(); + super._on_session_end(); + } + }; + Object.assign( + Zmodem2.Session.Send.prototype, + { + type: "send" + } + ); + } +}); + +// node_modules/zmodem.js/src/zsentry.js +var require_zsentry = __commonJS({ + "node_modules/zmodem.js/src/zsentry.js"(exports2, module2) { + "use strict"; + var Zmodem2 = module2.exports; + Object.assign( + Zmodem2, + require_zmlib(), + require_zsession() + ); + var MAX_ZM_HEX_START_LENGTH = 21; + var COMMON_ZM_HEX_START = [42, 42, 24, 66, 48]; + var SENTRY_CONSTRUCTOR_REQUIRED_ARGS = [ + "to_terminal", + "on_detect", + "on_retract", + "sender" + ]; + var Detection = class { + /** + * Not called directly. + */ + constructor(session_type, accepter, denier, checker) { + this._confirmer = accepter; + this._denier = denier; + this._is_valid = checker; + this._session_type = session_type; + } + /** + * Confirm that the detected ZMODEM sequence indicates the + * start of a ZMODEM session. + * + * @return {Session} The ZMODEM Session object (i.e., either a + * Send or Receive instance). + */ + confirm() { + return this._confirmer.apply(this, arguments); + } + /** + * Tell the Sentry that the detected bytes sequence is + * **NOT** intended to be the start of a ZMODEM session. + */ + deny() { + return this._denier.apply(this, arguments); + } + /** + * Tells whether the Detection is still valid; i.e., whether + * the Sentry has `consume()`d bytes that invalidate the + * Detection. + * + * @returns {boolean} Whether the Detection is valid. + */ + is_valid() { + return this._is_valid.apply(this, arguments); + } + /** + * Gives the session’s role. + * + * @returns {string} One of: + * - `receive` + * - `send` + */ + get_session_role() { + return this._session_type; + } + }; + Zmodem2.Sentry = class ZmodemSentry { + /** + * Invoked directly. Creates a new Sentry that inspects all + * traffic before it goes to the terminal. + * + * @param {Object} options - The Sentry parameters + * + * @param {Function} options.to_terminal - Handler that sends + * traffic to the terminal object. Receives an iterable object + * (e.g., an Array) that contains octet numbers. + * + * @param {Function} options.on_detect - Handler for new + * detection events. Receives a new Detection object. + * + * @param {Function} options.on_retract - Handler for retraction + * events. Receives no input. + * + * @param {Function} options.sender - Handler that sends traffic to + * the peer. If, for example, your application uses WebSocket to talk + * to the peer, use this to send data to the WebSocket instance. + */ + constructor(options) { + if (!options) throw "Need options!"; + var sentry = this; + SENTRY_CONSTRUCTOR_REQUIRED_ARGS.forEach(function(arg) { + if (!options[arg]) { + throw "Need \u201C" + arg + "\u201D!"; + } + sentry["_" + arg] = options[arg]; + }); + this._cache = []; + } + _after_session_end() { + this._zsession = null; + } + /** + * “Consumes” a piece of input: + * + * - If there is no active or pending ZMODEM session, the text is + * all output. (This is regardless of whether we’ve got a new + * Detection.) + * + * - If there is no active ZMODEM session and the input **ends** with + * a ZRINIT or ZRQINIT, then a new Detection object is created, + * and it is passed to the “on_detect” function. + * If there was another pending Detection object, it is retracted. + * + * - If there is no active ZMODEM session and the input does NOT end + * with a ZRINIT or ZRQINIT, then any pending Detection object is + * retracted. + * + * - If there is an active ZMODEM session, the input is passed to it. + * Any non-ZMODEM data (i.e., “garbage”) parsed from the input + * is sent to output. + * If the ZMODEM session ends, any post-ZMODEM part of the input + * is sent to output. + * + * @param {number[] | ArrayBuffer} input - Octets to parse as input. + */ + consume(input) { + if (!(input instanceof Array)) { + input = Array.prototype.slice.call(new Uint8Array(input)); + } + if (this._zsession) { + var session_before_consume = this._zsession; + session_before_consume.consume(input); + if (session_before_consume.has_ended()) { + if (session_before_consume.type === "receive") { + input = session_before_consume.get_trailing_bytes(); + } else { + input = []; + } + } else return; + } + var new_session = this._parse(input); + var to_terminal = input; + if (new_session) { + let checker = function() { + return sentry._parsed_session === new_session; + }, accepter = function() { + if (!this.is_valid()) { + throw "Stale ZMODEM session!"; + } + new_session.on("garbage", sentry._to_terminal); + new_session.on( + "session_end", + sentry._after_session_end.bind(sentry) + ); + new_session.set_sender(sentry._sender); + delete sentry._parsed_session; + return sentry._zsession = new_session; + }, denier = function() { + if (!this.is_valid()) return; + }; + let replacement_detect = !!this._parsed_session; + if (replacement_detect) { + if (this._parsed_session.type === new_session.type) { + to_terminal = []; + } + this._on_retract(); + } + this._parsed_session = new_session; + var sentry = this; + ; + ; + this._on_detect(new Detection( + new_session.type, + accepter, + this._send_abort.bind(this), + checker + )); + } else { + var expired_session = this._parsed_session; + this._parsed_session = null; + if (expired_session) { + if (to_terminal.length === 1 && to_terminal[0] === 67) { + this._send_abort(); + } + this._on_retract(); + } + } + this._to_terminal(to_terminal); + } + /** + * @return {Session|null} The sentry’s current Session object, or + * null if there is none. + */ + get_confirmed_session() { + return this._zsession || null; + } + _send_abort() { + this._sender(Zmodem2.ZMLIB.ABORT_SEQUENCE); + } + /** + * Parse an input stream and decide how much of it goes to the + * terminal or to a new Session object. + * + * This will accommodate input strings that are fragmented + * across calls to this function; e.g., if you send the first + * two bytes at the end of one parse() call then send the rest + * at the beginning of the next, parse() will recognize it as + * the beginning of a ZMODEM session. + * + * In order to keep from blocking any actual useful data to the + * terminal in real-time, this will send on the initial + * ZRINIT/ZRQINIT bytes to the terminal. They’re meant to go to the + * terminal anyway, so that should be fine. + * + * @private + * + * @param {Array|Uint8Array} array_like - The input bytes. + * Each member should be a number between 0 and 255 (inclusive). + * + * @return {Array} A two-member list: + * 0) the bytes that should be printed on the terminal + * 1) the created Session object (if any) + */ + _parse(array_like) { + var cache = this._cache; + cache.push.apply(cache, array_like); + while (true) { + let common_hex_at = Zmodem2.ZMLIB.find_subarray(cache, COMMON_ZM_HEX_START); + if (-1 === common_hex_at) break; + let before_common_hex = cache.splice(0, common_hex_at); + let zsession; + try { + zsession = Zmodem2.Session.parse(cache); + } catch (err) { + } + if (!zsession) break; + if (cache.length === 1 && cache[0] === Zmodem2.ZMLIB.XON) { + cache.shift(); + } + return cache.length ? null : zsession; + } + cache.splice(MAX_ZM_HEX_START_LENGTH); + return null; + } + }; + } +}); + +// node_modules/zmodem.js/index.js +var require_zmodem = __commonJS({ + "node_modules/zmodem.js/index.js"(exports2, module2) { + "use strict"; + Object.assign( + module2.exports, + require_zsentry() + ); + } +}); + +// src/main/zmodem.ts +var zmodem_exports = {}; +__export(zmodem_exports, { + attachZmodem: () => attachZmodem, + detachZmodem: () => detachZmodem, + feedZmodem: () => feedZmodem, + isZmodemActive: () => isZmodemActive, + respondZmodem: () => respondZmodem +}); +module.exports = __toCommonJS(zmodem_exports); +var import_path = require("path"); +var import_fs = require("fs"); +var import_promises = require("fs/promises"); +var Zmodem = __toESM(require_zmodem()); + +// src/shared/ipc.ts +var Ipc = { + // ---- PTY session lifecycle ---- + // NOTE: since M2 these channels are session-generic: `id` addresses both + // local pty sessions and ssh sessions; data plane is identical. + PTY_CREATE: "pty:create", + PTY_WRITE: "pty:write", + PTY_RESIZE: "pty:resize", + PTY_KILL: "pty:kill", + /** main -> renderer broadcast: { id, data } */ + PTY_DATA: "pty:data", + /** main -> renderer broadcast: { id, exitCode } */ + PTY_EXIT: "pty:exit", + // ---- settings ---- + SETTINGS_GET: "settings:get", + SETTINGS_SET: "settings:set", + /** main -> renderer broadcast after any save */ + SETTINGS_CHANGED: "settings:changed", + // ---- fonts ---- + FONTS_LIST: "fonts:list", + // ---- layouts ---- + LAYOUTS_LIST: "layouts:list", + LAYOUTS_GET: "layouts:get", + LAYOUTS_SAVE: "layouts:save", + LAYOUTS_DELETE: "layouts:delete", + // ---- sessions (openSession unifies local pty and ssh) ---- + SESSION_OPEN: "session:open", + /** main keeps a short replay buffer per session so late subscribers catch up */ + SESSION_REPLAY: "session:replay", + // ---- ssh connections (bookmarks) ---- + CONNECTIONS_LIST: "connections:list", + CONNECTIONS_SAVE: "connections:save", + CONNECTIONS_DELETE: "connections:delete", + // ---- host key verification ---- + /** main -> renderer: needs user decision before connect proceeds */ + HOSTKEY_PROMPT: "hostkey:prompt", + HOSTKEY_RESPOND: "hostkey:respond", + // ---- sysinfo (M3) ---- + SYSINFO_START: "sysinfo:start", + SYSINFO_STOP: "sysinfo:stop", + /** main -> renderer broadcast: { id, meta } (once per session) */ + SYSINFO_META: "sysinfo:meta", + /** main -> renderer broadcast: { id, sample } (poll interval) */ + SYSINFO_SAMPLE: "sysinfo:sample", + // ---- sftp (M4) ---- + SFTP_LIST: "sftp:list", + SFTP_MKDIR: "sftp:mkdir", + SFTP_RENAME: "sftp:rename", + SFTP_DELETE: "sftp:delete", + SFTP_CHMOD: "sftp:chmod", + SFTP_CHOWN: "sftp:chown", + SFTP_UPLOAD: "sftp:upload", + SFTP_DOWNLOAD: "sftp:download", + TRANSFER_CANCEL: "transfer:cancel", + /** main -> renderer broadcast: TransferProgressEvent */ + TRANSFER_PROGRESS: "transfer:progress", + DIALOG_PICK_FILES: "dialog:pickFiles", + DIALOG_PICK_DIR: "dialog:pickDirectory", + // ---- commands + logs (M5) ---- + COMMANDS_RECORD: "commands:record", + COMMANDS_HISTORY_LIST: "commands:historyList", + COMMANDS_HISTORY_CLEAR: "commands:historyClear", + COMMANDS_LIBRARY_LIST: "commands:libraryList", + COMMANDS_LIBRARY_SAVE: "commands:librarySave", + COMMANDS_LIBRARY_DELETE: "commands:libraryDelete", + LOG_START: "log:start", + LOG_STOP: "log:stop", + LOG_LIST: "log:list", + LOG_OPEN_DIR: "log:openDir", + // ---- zmodem (M6: main-process engine, ssh sessions only) ---- + /** main -> renderer: remote ran rz (mode=send) or sz (mode=receive); renderer must ask the user */ + ZMODEM_OFFER: "zmodem:offer", + /** renderer -> main: user answered the zmodem offer */ + ZMODEM_RESPOND: "zmodem:respond", + /** main -> renderer: zmodem session finished (ok, cancelled or failed) */ + ZMODEM_DONE: "zmodem:done", + // ---- misc ---- + APP_INFO: "app:info" +}; + +// src/main/zmodem.ts +var OFFER_TIMEOUT_MS = 12e4; +var STALL_TIMEOUT_MS = 9e4; +var ABORT_BUFFER = Buffer.from(Zmodem.ZMLIB.ABORT_SEQUENCE); +var engines = /* @__PURE__ */ new Map(); +function current(sessionId) { + return engines.get(sessionId); +} +function cancelTimers(engine) { + if (engine.offerTimer) clearTimeout(engine.offerTimer); + if (engine.stallTimer) clearTimeout(engine.stallTimer); + engine.offerTimer = null; + engine.stallTimer = null; +} +function emitProgress(engine, patch) { + const evt = { + transferId: engine.transferId, + kind: engine.mode === "receive" ? "zmodem-download" : "zmodem-upload", + state: "running", + file: "", + bytes: engine.bytes, + totalBytes: engine.totalBytes, + ...patch + }; + try { + engine.deps.broadcast(Ipc.TRANSFER_PROGRESS, evt); + } catch { + } +} +function emitDone(engine, ok, message) { + if (engine.doneEmitted) return; + engine.doneEmitted = true; + const evt = { id: engine.id, ok, message }; + try { + engine.deps.broadcast(Ipc.ZMODEM_DONE, evt); + } catch { + } +} +function finalize(engine, ok, message) { + engine.active = false; + cancelTimers(engine); + if (engine.receiveStream) { + try { + engine.receiveStream.end(); + } catch { + } + engine.receiveStream = null; + } + engine.session = null; + engine.detection = null; + if (!engine.progressEmitted) { + emitProgress(engine, { state: ok ? "done" : "cancelled", file: message ?? "" }); + } + engine.progressEmitted = true; + if (ok) emitDone(engine, true); + else emitDone(engine, false, message ?? "\u4F20\u8F93\u5931\u8D25"); +} +function makeSessionEnd(engine) { + return () => finalize(engine, true); +} +function touchActivity(engine) { + if (engine.stallTimer) clearTimeout(engine.stallTimer); + const timer = setTimeout(() => { + if (engine.stallTimer === null) return; + finalize(engine, false, "\u4F20\u8F93\u8D85\u65F6"); + }, STALL_TIMEOUT_MS); + engine.stallTimer = timer; +} +function wireSession(engine) { + const session = engine.session; + if (!session) return; + const sendToPeer = (octets) => { + try { + engine.deps.writeStream(engine.id, Buffer.from(octets)); + } catch { + } + }; + session.set_sender(sendToPeer); + session.on("session_end", makeSessionEnd(engine)); + touchActivity(engine); +} +async function sendOneFile(engine, session, file) { + const xfer = await session.send_offer({ + name: file.name, + size: file.size, + mtime: new Date(file.mtimeMs), + mode: void 0 + }); + if (!xfer) return; + emitProgress(engine, { file: file.name, bytes: engine.bytes, totalBytes: engine.totalBytes }); + await new Promise((resolve, reject) => { + const rs = (0, import_fs.createReadStream)(file.path); + rs.on("data", (chunk) => { + rs.pause(); + const buf = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk); + try { + xfer.send(new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength)); + } catch (err) { + rs.destroy(); + reject(err instanceof Error ? err : new Error("\u53D1\u9001\u5931\u8D25")); + return; + } + engine.bytes += buf.length; + emitProgress(engine, { file: file.name, bytes: engine.bytes, totalBytes: engine.totalBytes }); + touchActivity(engine); + rs.resume(); + }); + rs.on("end", () => { + xfer.end().then(() => resolve()).catch(reject); + }); + rs.on("error", reject); + }); +} +async function runSend(engine, paths) { + try { + const session = engine.session; + const files = await Promise.all( + paths.map(async (p) => { + const s = await (0, import_promises.stat)(p); + return { path: p, name: (0, import_path.basename)(p), size: s.size, mtimeMs: s.mtimeMs }; + }) + ); + engine.totalBytes = files.reduce((acc, f) => acc + f.size, 0); + for (const f of files) { + emitProgress(engine, { file: f.name, bytes: engine.bytes, totalBytes: engine.totalBytes }); + await sendOneFile(engine, session, f); + } + emitProgress(engine, { file: "", bytes: engine.totalBytes, totalBytes: engine.totalBytes }); + await session.close(); + } catch (err) { + finalize(engine, false, err instanceof Error ? err.message : "\u4E0A\u4F20\u5931\u8D25"); + } +} +function handleOffer(engine, offer) { + const details = offer.get_details(); + const name2 = (0, import_path.basename)(String(details.name ?? "file")); + const dest = (0, import_path.join)(engine.dir ?? ".", name2); + if (details.size) engine.totalBytes += details.size; + const stream = (0, import_fs.createWriteStream)(dest); + engine.receiveStream = stream; + emitProgress(engine, { file: name2, bytes: engine.bytes, totalBytes: engine.totalBytes }); + offer.accept({ + on_input: (payload) => { + const buf = Array.isArray(payload) ? Buffer.from(payload) : Buffer.from(payload.buffer, payload.byteOffset, payload.byteLength); + if (!stream.destroyed && !stream.closed) { + stream.write(buf); + } + engine.bytes += buf.byteLength; + emitProgress(engine, { file: name2, bytes: engine.bytes, totalBytes: engine.totalBytes }); + touchActivity(engine); + } + }).then(() => { + stream.end(() => { + if (engine.receiveStream === stream) engine.receiveStream = null; + emitProgress(engine, { file: name2, bytes: engine.bytes, totalBytes: engine.totalBytes }); + }); + }).catch((err) => { + finalize(engine, false, err instanceof Error ? err.message : "\u63A5\u6536\u5931\u8D25"); + }); +} +function attachZmodem(sessionId, deps) { + const engine = { + id: sessionId, + deps, + sentry: new Zmodem.Sentry({ + to_terminal: (octets) => { + if (engine.active) return; + try { + deps.toTerminal(sessionId, Buffer.from(octets).toString("utf8")); + } catch { + } + }, + on_detect: (detection) => { + const role = detection.get_session_role(); + engine.mode = role === "receive" ? "receive" : "send"; + engine.detection = detection; + engine.active = true; + engine.transferId = `zm-${sessionId}`; + emitProgress(engine, { file: "", bytes: 0, totalBytes: 0 }); + try { + deps.broadcast(Ipc.ZMODEM_OFFER, { id: sessionId, mode: engine.mode }); + } catch { + } + touchActivity(engine); + engine.offerTimer = setTimeout(() => { + if (engine.detection && !engine.confirmed) abortWithoutSession(engine, "\u672A\u9009\u62E9\u6587\u4EF6\uFF0C\u5DF2\u53D6\u6D88"); + }, OFFER_TIMEOUT_MS); + }, + on_retract: () => { + engine.detection = null; + engine.mode = null; + engine.active = false; + cancelTimers(engine); + }, + sender: (octets) => { + try { + deps.writeStream(sessionId, Buffer.from(octets)); + } catch { + } + } + }), + detection: null, + session: null, + active: false, + mode: null, + dir: null, + confirmed: false, + transferId: `zm-${sessionId}`, + progressEmitted: false, + doneEmitted: false, + offerTimer: null, + stallTimer: null, + receiveStream: null, + bytes: 0, + totalBytes: 0 + }; + engines.set(sessionId, engine); +} +function abortWithoutSession(engine, message) { + try { + engine.deps.writeStream(engine.id, ABORT_BUFFER); + } catch { + } + engine.active = false; + cancelTimers(engine); + if (!engine.progressEmitted) emitProgress(engine, { state: "cancelled", file: message }); + engine.progressEmitted = true; + emitDone(engine, false, message); +} +function abortSession(engine, message) { + try { + engine.session?.abort(); + } catch { + } + try { + engine.deps.writeStream(engine.id, ABORT_BUFFER); + } catch { + } + finalize(engine, false, message); +} +function feedZmodem(sessionId, data) { + const engine = current(sessionId); + if (!engine) return; + try { + engine.sentry.consume(data); + } catch (err) { + if (!engine.doneEmitted) finalize(engine, false, err instanceof Error ? err.message : "ZMODEM \u4F20\u8F93\u5F02\u5E38"); + } +} +function isZmodemActive(sessionId) { + return current(sessionId)?.active ?? false; +} +function respondZmodem(resp) { + const engine = current(resp.id); + if (!engine || engine.confirmed) return; + if (resp.cancelled) { + abortWithoutSession(engine, "\u5DF2\u53D6\u6D88"); + return; + } + const detection = engine.detection; + if (!detection || !detection.is_valid()) { + abortWithoutSession(engine, "\u4F1A\u8BDD\u5DF2\u5931\u6548"); + return; + } + engine.confirmed = true; + engine.session = detection.confirm(); + if (!engine.session) { + abortWithoutSession(engine, "\u65E0\u6CD5\u5EFA\u7ACB ZMODEM \u4F1A\u8BDD"); + return; + } + wireSession(engine); + if (engine.mode === "receive") { + if (!resp.dir) { + abortSession(engine, "\u672A\u6307\u5B9A\u4FDD\u5B58\u76EE\u5F55"); + return; + } + engine.dir = resp.dir; + const session = engine.session; + session.on("offer", (offer) => handleOffer(engine, offer)); + void session.start().catch((err) => finalize(engine, false, err instanceof Error ? err.message : "\u63A5\u6536\u5931\u8D25")); + } else { + const paths = resp.paths ?? []; + if (paths.length === 0) { + abortSession(engine, "\u672A\u9009\u62E9\u6587\u4EF6"); + return; + } + void runSend(engine, paths); + } +} +function detachZmodem(sessionId) { + const engine = current(sessionId); + if (!engine) return; + engine.active = false; + cancelTimers(engine); + if (engine.receiveStream) { + try { + engine.receiveStream.end(); + } catch { + } + engine.receiveStream = null; + } + engine.session = null; + engine.detection = null; + engines.delete(sessionId); +} +// Annotate the CommonJS export names for ESM import in node: +0 && (module.exports = { + attachZmodem, + detachZmodem, + feedZmodem, + isZmodemActive, + respondZmodem +}); +/*! Bundled license information: + +crc-32/crc32.js: + (*! crc32.js (C) 2014-present SheetJS -- http://sheetjs.com *) +*/ diff --git a/tests/commands-store.mjs b/tests/commands-store.mjs index 785daf9..8251dce 100644 --- a/tests/commands-store.mjs +++ b/tests/commands-store.mjs @@ -72,6 +72,7 @@ ok(store.listHistory().length === 0, 'clearHistory empties history') // ---- 4. Library CRUD + ordering ------------------------------------------------ const a = store.saveLibraryItem({ command: 'ps aux', name: 'procs', note: 'show processes' }) +await wait(2) // createdAt has ms resolution; avoid a timestamp tie breaking the order assertion const b = store.saveLibraryItem({ command: 'df -h', name: 'disk', group: 'ops' }) ok(typeof a.id === 'string' && a.id.length > 0, 'saveLibraryItem assigns an id + createdAt') let lib = store.listLibrary() diff --git a/tests/zmodem-e2e.mjs b/tests/zmodem-e2e.mjs new file mode 100644 index 0000000..145232d --- /dev/null +++ b/tests/zmodem-e2e.mjs @@ -0,0 +1,257 @@ +/** + * ZMODEM engine e2e (M6). In-process, no SSH server and no real credentials: + * our engine (src/main/zmodem.ts) is attached to one end of an in-process wire, + * and a SEPARATE zmodem.js Sentry plays the remote rz/sz on the other end. Two + * full duplex directions are exercised over the real ZMODEM framing: + * + * 场景 1 (download): remote `sz` sends a file -> our engine receives it into + * a temp dir; assert content matches + done(ok) + progress. + * 场景 2 (upload): our engine uploads a local temp file -> remote `rz` + * receives it; assert remote content matches + done(ok). + * + * Pre-req (the bundle is built automatically on first run): + * npx esbuild src/main/zmodem.ts --bundle --platform=node --format=cjs \ + * --external:ssh2 --alias:electron=./tests/electron-stub.cjs \ + * --outfile=tests/.zmodem-e2e.cjs + * zmodem.js stays bundled (NOT external). + * + * Run: node tests/zmodem-e2e.mjs (must exit 0) + */ +import { createRequire } from 'module' +import { + mkdtempSync, + writeFileSync, + readFileSync, + mkdirSync, + rmSync, + existsSync +} from 'fs' +import { join, dirname, resolve, basename } from 'path' +import { fileURLToPath } from 'url' +import { execFileSync } from 'child_process' +import { randomBytes } from 'crypto' + +const __dirname = dirname(fileURLToPath(import.meta.url)) +const require_ = createRequire(import.meta.url) +const fail = (msg) => { + console.error(`FAIL: ${msg}`) + process.exit(1) +} + +// ---- 0. ensure bundle --------------------------------------------------------- +const bundlePath = join(__dirname, '.zmodem-e2e.cjs') +if (!existsSync(bundlePath)) { + console.log('[zmodem-e2e] building bundle ...') + const cmd = process.platform === 'win32' ? 'npx.cmd' : 'npx' + execFileSync( + cmd, + [ + 'esbuild', resolve(__dirname, '../src/main/zmodem.ts'), + '--bundle', '--platform=node', '--format=cjs', + '--external:ssh2', + '--alias:electron=./tests/electron-stub.cjs', + `--outfile=${bundlePath}` + ], + { stdio: 'inherit', shell: process.platform === 'win32' } + ) +} + +const zmodem = require_('zmodem.js') +const engine = require_(bundlePath) + +const sleep = (ms) => new Promise((r) => setTimeout(r, ms)) + +/** + * Drive a zmodem.js SEND session with a real file (the fs-port of the browser + * send_files helper, used by the remote half of the download scenario). + */ +async function driveRemoteSend(session, filePath) { + const data = readFileSync(filePath) + const name = basename(filePath) + const xfer = await session.send_offer({ name, size: data.length, mtime: new Date() }) + if (!xfer) throw new Error('offer was skipped') + const CHUNK = 8192 + let off = 0 + if (data.length === 0) { + await xfer.end(new Uint8Array(0)) + } else { + while (off < data.length) { + const end = Math.min(off + CHUNK, data.length) + if (end < data.length) { + xfer.send(new Uint8Array(data.buffer, data.byteOffset + off, end - off)) + } else { + await xfer.end(new Uint8Array(data.buffer, data.byteOffset + off, end - off)) + } + off = end + } + } + await session.close() +} + +/** Read all bytes a remote receive side has drained so far. */ +function collectRemote(self) { + return Buffer.concat(self._incoming) +} + +/** + * Build a remote (peer) Sentry. `remoteOnDetect(confirm)` wires the remote's + * on_detect: it should confirm + drive its side. `routeOut` is called with the + * octets the remote sent so we can feed them into our engine. + */ +function makeRemoteSentry(remoteOnDetect, routeOut) { + const self = { _incoming: [] } + const remoteSentry = new zmodem.Sentry({ + to_terminal: () => {}, + on_retract: () => {}, + on_detect: (detection) => remoteOnDetect(detection, self), + sender: (octets) => routeOut(Buffer.from(octets)) + }) + self.sentry = remoteSentry + self.consume = (buf) => { + self._incoming.push(Buffer.from(buf)) + } + return self +} + +// ---- SCENARIO 1: download (remote `sz` -> our engine receives) ---------------- +async function scenarioDownload() { + console.log('\n=== 场景 1: 远端 sz + 本地接收 (download) ===') + const sessionId = `sess-${randomBytes(3).toString('hex')}` + const srcDir = mkdtempSync(join(__dirname, '.zm-src-dl-')) + const srcFile = join(srcDir, 'transferred.bin') + const payload = randomBytes(200_000) + writeFileSync(srcFile, payload) + const outDir = mkdtempSync(join(__dirname, '.zm-out-dl-')) + + const events = [] + const remote = makeRemoteSentry((detection) => { + const s = detection.confirm() + if (s.type === 'send') { + // remote plays sz: send the source file, then finish + driveRemoteSend(s, srcFile).catch((e) => fail(`remote send error: ${e}`)) + } + }, (buf) => setTimeout(() => engine.feedZmodem(sessionId, buf), 0)) + + engine.attachZmodem(sessionId, { + broadcast: (channel, payload) => events.push({ channel, payload }), + toTerminal: () => {}, + writeStream: (id, data) => setTimeout(() => remote.sentry.consume(data), 0) + }) + + // remote sz initiates with ZRQINIT + engine.feedZmodem(sessionId, Buffer.from(zmodem.Header.build('ZRQINIT').to_hex())) + + // wait for the receive offer + for (let i = 0; i < 200 && !events.some((e) => e.channel === 'zmodem:offer'); i++) await sleep(10) + const offer = events.find((e) => e.channel === 'zmodem:offer')?.payload + if (!offer) fail('download: no ZMODEM_OFFER received') + if (offer.mode !== 'receive') fail(`download: expected receive offer, got ${offer.mode}`) + console.log(`[download] offer received (mode=receive) id=${offer.id}`) + + engine.respondZmodem({ id: offer.id, cancelled: false, dir: outDir }) + + // wait for done + for (let i = 0; i < 200 && !events.some((e) => e.channel === 'zmodem:done'); i++) await sleep(10) + const done = events.find((e) => e.channel === 'zmodem:done')?.payload + if (!done) fail('download: no ZMODEM_DONE received') + if (!done.ok) fail(`download: expected ok, got ${JSON.stringify(done)}`) + + // assert progress events flowed + const prog = events.filter((e) => e.channel === 'transfer:progress').map((e) => e.payload) + if (prog.length === 0) fail('download: no TRANSFER_PROGRESS emitted') + if (prog[0].kind !== 'zmodem-download') fail(`download: expected zmodem-download kind, got ${prog[0].kind}`) + const finalProg = prog[prog.length - 1] + if (finalProg.state !== 'done') fail(`download: last progress not done (${finalProg.state})`) + + // assert content + const got = readFileSync(join(outDir, 'transferred.bin')) + if (got.length !== payload.length || !got.equals(payload)) { + fail(`download: content mismatch (len ${got.length} vs ${payload.length})`) + } + console.log(`[download] file received: ${got.length} bytes, content matches`) + console.log(`[download] progress events: ${prog.length} (state->done), done(ok)=true`) + + rmSync(srcDir, { recursive: true, force: true }) + rmSync(outDir, { recursive: true, force: true }) +} + +// ---- SCENARIO 2: upload (our engine -> remote `rz`) ---------------------------- +async function scenarioUpload() { + console.log('\n=== 场景 2: 本地上传 + 远端 rz (upload) ===') + const sessionId = `sess-${randomBytes(3).toString('hex')}` + const srcDir = mkdtempSync(join(__dirname, '.zm-src-up-')) + const srcFile = join(srcDir, 'sendme.bin') + const payload = randomBytes(150_000) + writeFileSync(srcFile, payload) + const outDir = mkdtempSync(join(__dirname, '.zm-out-up-')) + + const events = [] + let remoteGot = null + const remote = makeRemoteSentry((detection, self) => { + const s = detection.confirm() + if (s.type === 'receive') { + // remote plays rz: accept the offered file, spool to disk. + s.on('offer', (offer) => { + const name = basename(offer.get_details().name) + const chunks = [] + offer.on('input', (p) => chunks.push(Buffer.from(p))) + offer + .accept() + .then(async () => { + const data = Buffer.concat(chunks) + writeFileSync(join(outDir, name), data) + remoteGot = data + }) + .catch((e) => fail(`remote receive error: ${e}`)) + }) + // start() once; zmodem.js re-sends ZRINIT itself after each accepted file. + s.start().catch((e) => fail(`remote receive start error: ${e}`)) + } + }, (buf) => setTimeout(() => engine.feedZmodem(sessionId, buf), 0)) + + engine.attachZmodem(sessionId, { + broadcast: (channel, payload) => events.push({ channel, payload }), + toTerminal: () => {}, + writeStream: (id, data) => setTimeout(() => remote.sentry.consume(data), 0) + }) + + // remote rz receiver initiates: we feed it ZRQINIT so it detects "receive", + // confirms and start()s, which sends ZRINIT toward our engine. + remote.sentry.consume(Buffer.from(zmodem.Header.build('ZRQINIT').to_hex())) + + // engine should detect send and offer + for (let i = 0; i < 200 && !events.some((e) => e.channel === 'zmodem:offer'); i++) await sleep(10) + const offer = events.find((e) => e.channel === 'zmodem:offer')?.payload + if (!offer) fail('upload: no ZMODEM_OFFER received') + if (offer.mode !== 'send') fail(`upload: expected send offer, got ${offer.mode}`) + console.log(`[upload] offer received (mode=send) id=${offer.id}`) + + engine.respondZmodem({ id: offer.id, cancelled: false, paths: [srcFile] }) + + for (let i = 0; i < 200 && !events.some((e) => e.channel === 'zmodem:done'); i++) await sleep(10) + const done = events.find((e) => e.channel === 'zmodem:done')?.payload + if (!done) fail('upload: no ZMODEM_DONE received') + if (!done.ok) fail(`upload: expected ok, got ${JSON.stringify(done)}`) + + const prog = events.filter((e) => e.channel === 'transfer:progress').map((e) => e.payload) + if (prog.length === 0) fail('upload: no TRANSFER_PROGRESS emitted') + if (prog[0].kind !== 'zmodem-upload') fail(`upload: expected zmodem-upload kind, got ${prog[0].kind}`) + + if (!remoteGot) fail('upload: remote never received the file data') + if (remoteGot.length !== payload.length || !payload.equals(remoteGot)) { + fail(`upload: content mismatch (remote ${remoteGot.length} vs src ${payload.length})`) + } + console.log(`[upload] remote received ${remoteGot.length} bytes, content matches`) + console.log(`[upload] progress events: ${prog.length}, done(ok)=true`) + + rmSync(srcDir, { recursive: true, force: true }) + rmSync(outDir, { recursive: true, force: true }) +} + +// ---- run ---------------------------------------------------------------------- +const timer = setTimeout(() => fail('zmodem-e2e timed out'), 30000) +await scenarioDownload() +await scenarioUpload() +clearTimeout(timer) +console.log('\n[zmodem-e2e] ALL CHECKS PASSED') +process.exit(0) \ No newline at end of file