From d1cca1afbe7f55babbc34c0d588febf469afe38d Mon Sep 17 00:00:00 2001 From: Cellivar <1441553+Cellivar@users.noreply.github.com> Date: Thu, 2 Jan 2025 03:52:02 -0800 Subject: [PATCH 1/8] Update packages and configs --- package-lock.json | 3238 +++++++++++++++++++------------- package.json | 23 +- src/ReceiptLine/RECEIPTLINE.js | 10 +- tsconfig.json | 4 +- vite.config.ts | 16 +- 5 files changed, 2016 insertions(+), 1275 deletions(-) diff --git a/package-lock.json b/package-lock.json index 6477b2c..b572ae7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,23 +9,24 @@ "version": "0.2.0-rc5", "license": "Apache-2.0", "dependencies": { - "web-device-mux": "^0.2.3" + "web-device-mux": "^0.5.0" }, "devDependencies": { "@eslint/js": "^9.9.0", "@types/eslint__js": "^8.42.3", - "@types/node": "^20.8.9", + "@types/node": "^20.14.8", "@types/w3c-web-usb": "^1.0.10", - "@vitest/coverage-v8": "^2.0.5", + "@vitest/coverage-v8": "^2.1.6", "eslint": "^9.9.0", "globals": "^15.9.0", + "happy-dom": "^15.11.7", "https-localhost": "^4.7.1", - "typescript": "^5.5.4", - "typescript-eslint": "^8.0.1", - "vite": "^5.4.0", - "vite-plugin-dts": "^4.0.2", + "typescript": "^5.7.2", + "typescript-eslint": "^8.16.0", + "vite": "^6.0.0", + "vite-plugin-dts": "^4.3.0", "vite-plugin-eslint": "^1.8.1", - "vitest": "^2.0.5" + "vitest": "^2.1.6" } }, "node_modules/@aashutoshrathi/word-wrap": { @@ -51,30 +52,30 @@ } }, "node_modules/@babel/helper-string-parser": { - "version": "7.24.8", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.8.tgz", - "integrity": "sha512-pO9KhhRcuUyGnJWwyEgnRJTSIZHiT+vMD0kPeD+so0l7mxkMT19g3pjY9GTnHySck/hDzq+dtW/4VgnMkippsQ==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz", + "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz", - "integrity": "sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz", + "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/parser": { - "version": "7.25.3", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.25.3.tgz", - "integrity": "sha512-iLTJKDbJ4hMvFPgQwwsVoxtHyWpKKPBrxkANrSYewDPaPpT5py5yeVkgPIJ7XYXhndxJpaA3PyALSXQ7u8e/Dw==", + "version": "7.26.3", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.3.tgz", + "integrity": "sha512-WJ/CvmY8Mea8iDXo6a7RK2wbmJITT5fN3BEkRuFlxVyNx8jOKIIhmC4fSkTcPcf8JyavbBwIe6OpiCOBXt/IcA==", "dev": true, "dependencies": { - "@babel/types": "^7.25.2" + "@babel/types": "^7.26.3" }, "bin": { "parser": "bin/babel-parser.js" @@ -84,14 +85,13 @@ } }, "node_modules/@babel/types": { - "version": "7.25.2", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.25.2.tgz", - "integrity": "sha512-YTnYtra7W9e6/oAZEHj0bJehPRUlLH9/fbpT5LfB0NhQXyALCRkRs3zH9v07IYhkgpqX6Z78FnuccZr/l4Fs4Q==", + "version": "7.26.3", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.3.tgz", + "integrity": "sha512-vN5p+1kl59GVKMvTHt55NzzmYVxprfJD+ql7U9NFIfKCBkYE55LYtS+WtPlaYOyzydrKI8Nezd+aZextrd+FMA==", "dev": true, "dependencies": { - "@babel/helper-string-parser": "^7.24.8", - "@babel/helper-validator-identifier": "^7.24.7", - "to-fast-properties": "^2.0.0" + "@babel/helper-string-parser": "^7.25.9", + "@babel/helper-validator-identifier": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -104,9 +104,9 @@ "dev": true }, "node_modules/@esbuild/aix-ppc64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", - "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.24.0.tgz", + "integrity": "sha512-WtKdFM7ls47zkKHFVzMz8opM7LkcsIp9amDUBIAWirg70RM71WRSjdILPsY5Uv1D42ZpUfaPILDlfactHgsRkw==", "cpu": [ "ppc64" ], @@ -116,13 +116,13 @@ "aix" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/android-arm": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", - "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.24.0.tgz", + "integrity": "sha512-arAtTPo76fJ/ICkXWetLCc9EwEHKaeya4vMrReVlEIUCAUncH7M4bhMQ+M9Vf+FFOZJdTNMXNBrWwW+OXWpSew==", "cpu": [ "arm" ], @@ -132,13 +132,13 @@ "android" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/android-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", - "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.24.0.tgz", + "integrity": "sha512-Vsm497xFM7tTIPYK9bNTYJyF/lsP590Qc1WxJdlB6ljCbdZKU9SY8i7+Iin4kyhV/KV5J2rOKsBQbB77Ab7L/w==", "cpu": [ "arm64" ], @@ -148,13 +148,13 @@ "android" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/android-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", - "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.24.0.tgz", + "integrity": "sha512-t8GrvnFkiIY7pa7mMgJd7p8p8qqYIz1NYiAoKc75Zyv73L3DZW++oYMSHPRarcotTKuSs6m3hTOa5CKHaS02TQ==", "cpu": [ "x64" ], @@ -164,13 +164,13 @@ "android" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", - "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.24.0.tgz", + "integrity": "sha512-CKyDpRbK1hXwv79soeTJNHb5EiG6ct3efd/FTPdzOWdbZZfGhpbcqIpiD0+vwmpu0wTIL97ZRPZu8vUt46nBSw==", "cpu": [ "arm64" ], @@ -180,13 +180,13 @@ "darwin" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", - "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.24.0.tgz", + "integrity": "sha512-rgtz6flkVkh58od4PwTRqxbKH9cOjaXCMZgWD905JOzjFKW+7EiUObfd/Kav+A6Gyud6WZk9w+xu6QLytdi2OA==", "cpu": [ "x64" ], @@ -196,13 +196,13 @@ "darwin" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", - "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.24.0.tgz", + "integrity": "sha512-6Mtdq5nHggwfDNLAHkPlyLBpE5L6hwsuXZX8XNmHno9JuL2+bg2BX5tRkwjyfn6sKbxZTq68suOjgWqCicvPXA==", "cpu": [ "arm64" ], @@ -212,13 +212,13 @@ "freebsd" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", - "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.24.0.tgz", + "integrity": "sha512-D3H+xh3/zphoX8ck4S2RxKR6gHlHDXXzOf6f/9dbFt/NRBDIE33+cVa49Kil4WUjxMGW0ZIYBYtaGCa2+OsQwQ==", "cpu": [ "x64" ], @@ -228,13 +228,13 @@ "freebsd" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-arm": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", - "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.24.0.tgz", + "integrity": "sha512-gJKIi2IjRo5G6Glxb8d3DzYXlxdEj2NlkixPsqePSZMhLudqPhtZ4BUrpIuTjJYXxvF9njql+vRjB2oaC9XpBw==", "cpu": [ "arm" ], @@ -244,13 +244,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", - "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.24.0.tgz", + "integrity": "sha512-TDijPXTOeE3eaMkRYpcy3LarIg13dS9wWHRdwYRnzlwlA370rNdZqbcp0WTyyV/k2zSxfko52+C7jU5F9Tfj1g==", "cpu": [ "arm64" ], @@ -260,13 +260,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", - "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.24.0.tgz", + "integrity": "sha512-K40ip1LAcA0byL05TbCQ4yJ4swvnbzHscRmUilrmP9Am7//0UjPreh4lpYzvThT2Quw66MhjG//20mrufm40mA==", "cpu": [ "ia32" ], @@ -276,13 +276,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", - "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.24.0.tgz", + "integrity": "sha512-0mswrYP/9ai+CU0BzBfPMZ8RVm3RGAN/lmOMgW4aFUSOQBjA31UP8Mr6DDhWSuMwj7jaWOT0p0WoZ6jeHhrD7g==", "cpu": [ "loong64" ], @@ -292,13 +292,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", - "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.24.0.tgz", + "integrity": "sha512-hIKvXm0/3w/5+RDtCJeXqMZGkI2s4oMUGj3/jM0QzhgIASWrGO5/RlzAzm5nNh/awHE0A19h/CvHQe6FaBNrRA==", "cpu": [ "mips64el" ], @@ -308,13 +308,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", - "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.24.0.tgz", + "integrity": "sha512-HcZh5BNq0aC52UoocJxaKORfFODWXZxtBaaZNuN3PUX3MoDsChsZqopzi5UupRhPHSEHotoiptqikjN/B77mYQ==", "cpu": [ "ppc64" ], @@ -324,13 +324,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", - "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.24.0.tgz", + "integrity": "sha512-bEh7dMn/h3QxeR2KTy1DUszQjUrIHPZKyO6aN1X4BCnhfYhuQqedHaa5MxSQA/06j3GpiIlFGSsy1c7Gf9padw==", "cpu": [ "riscv64" ], @@ -340,13 +340,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", - "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.24.0.tgz", + "integrity": "sha512-ZcQ6+qRkw1UcZGPyrCiHHkmBaj9SiCD8Oqd556HldP+QlpUIe2Wgn3ehQGVoPOvZvtHm8HPx+bH20c9pvbkX3g==", "cpu": [ "s390x" ], @@ -356,13 +356,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", - "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.24.0.tgz", + "integrity": "sha512-vbutsFqQ+foy3wSSbmjBXXIJ6PL3scghJoM8zCL142cGaZKAdCZHyf+Bpu/MmX9zT9Q0zFBVKb36Ma5Fzfa8xA==", "cpu": [ "x64" ], @@ -372,13 +372,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", - "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.24.0.tgz", + "integrity": "sha512-hjQ0R/ulkO8fCYFsG0FZoH+pWgTTDreqpqY7UnQntnaKv95uP5iW3+dChxnx7C3trQQU40S+OgWhUVwCjVFLvg==", "cpu": [ "x64" ], @@ -388,13 +388,29 @@ "netbsd" ], "engines": { - "node": ">=12" + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.24.0.tgz", + "integrity": "sha512-MD9uzzkPQbYehwcN583yx3Tu5M8EIoTD+tUgKF982WYL9Pf5rKy9ltgD0eUgs8pvKnmizxjXZyLt0z6DC3rRXg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", - "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.24.0.tgz", + "integrity": "sha512-4ir0aY1NGUhIC1hdoCzr1+5b43mw99uNwVzhIq1OY3QcEwPDO3B7WNXBzaKY5Nsf1+N11i1eOfFcq+D/gOS15Q==", "cpu": [ "x64" ], @@ -404,13 +420,13 @@ "openbsd" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", - "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.24.0.tgz", + "integrity": "sha512-jVzdzsbM5xrotH+W5f1s+JtUy1UWgjU0Cf4wMvffTB8m6wP5/kx0KiaLHlbJO+dMgtxKV8RQ/JvtlFcdZ1zCPA==", "cpu": [ "x64" ], @@ -420,13 +436,13 @@ "sunos" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", - "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.24.0.tgz", + "integrity": "sha512-iKc8GAslzRpBytO2/aN3d2yb2z8XTVfNV0PjGlCxKo5SgWmNXx82I/Q3aG1tFfS+A2igVCY97TJ8tnYwpUWLCA==", "cpu": [ "arm64" ], @@ -436,13 +452,13 @@ "win32" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", - "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.24.0.tgz", + "integrity": "sha512-vQW36KZolfIudCcTnaTpmLQ24Ha1RjygBo39/aLkM2kmjkWmZGEJ5Gn9l5/7tzXA42QGIoWbICfg6KLLkIw6yw==", "cpu": [ "ia32" ], @@ -452,13 +468,13 @@ "win32" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/win32-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", - "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.24.0.tgz", + "integrity": "sha512-7IAFPrjSQIJrGsK6flwg7NFmwBoSTyF3rl7If0hNUFQU4ilTsEPL6GuMuU9BfIWVVGuRnuIidkSMC+c0Otu8IA==", "cpu": [ "x64" ], @@ -468,7 +484,7 @@ "win32" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@eslint-community/eslint-utils": { @@ -686,9 +702,9 @@ } }, "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", - "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz", + "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==", "dev": true, "dependencies": { "@jridgewell/set-array": "^1.2.1", @@ -734,18 +750,18 @@ } }, "node_modules/@microsoft/api-extractor": { - "version": "7.47.4", - "resolved": "https://registry.npmjs.org/@microsoft/api-extractor/-/api-extractor-7.47.4.tgz", - "integrity": "sha512-HKm+P4VNzWwvq1Ey+Jfhhj/3MjsD+ka2hbt8L5AcRM95lu1MFOYnz3XlU7Gr79Q/ZhOb7W/imAKeYrOI0bFydg==", + "version": "7.48.0", + "resolved": "https://registry.npmjs.org/@microsoft/api-extractor/-/api-extractor-7.48.0.tgz", + "integrity": "sha512-FMFgPjoilMUWeZXqYRlJ3gCVRhB7WU/HN88n8OLqEsmsG4zBdX/KQdtJfhq95LQTQ++zfu0Em1LLb73NqRCLYQ==", "dev": true, "dependencies": { - "@microsoft/api-extractor-model": "7.29.4", - "@microsoft/tsdoc": "~0.15.0", - "@microsoft/tsdoc-config": "~0.17.0", - "@rushstack/node-core-library": "5.5.1", + "@microsoft/api-extractor-model": "7.30.0", + "@microsoft/tsdoc": "~0.15.1", + "@microsoft/tsdoc-config": "~0.17.1", + "@rushstack/node-core-library": "5.10.0", "@rushstack/rig-package": "0.5.3", - "@rushstack/terminal": "0.13.3", - "@rushstack/ts-command-line": "4.22.3", + "@rushstack/terminal": "0.14.3", + "@rushstack/ts-command-line": "4.23.1", "lodash": "~4.17.15", "minimatch": "~3.0.3", "resolve": "~1.22.1", @@ -758,14 +774,14 @@ } }, "node_modules/@microsoft/api-extractor-model": { - "version": "7.29.4", - "resolved": "https://registry.npmjs.org/@microsoft/api-extractor-model/-/api-extractor-model-7.29.4.tgz", - "integrity": "sha512-LHOMxmT8/tU1IiiiHOdHFF83Qsi+V8d0kLfscG4EvQE9cafiR8blOYr8SfkQKWB1wgEilQgXJX3MIA4vetDLZw==", + "version": "7.30.0", + "resolved": "https://registry.npmjs.org/@microsoft/api-extractor-model/-/api-extractor-model-7.30.0.tgz", + "integrity": "sha512-26/LJZBrsWDKAkOWRiQbdVgcfd1F3nyJnAiJzsAgpouPk7LtOIj7PK9aJtBaw/pUXrkotEg27RrT+Jm/q0bbug==", "dev": true, "dependencies": { - "@microsoft/tsdoc": "~0.15.0", - "@microsoft/tsdoc-config": "~0.17.0", - "@rushstack/node-core-library": "5.5.1" + "@microsoft/tsdoc": "~0.15.1", + "@microsoft/tsdoc-config": "~0.17.1", + "@rushstack/node-core-library": "5.10.0" } }, "node_modules/@microsoft/api-extractor/node_modules/brace-expansion": { @@ -790,6 +806,21 @@ "node": "*" } }, + "node_modules/@microsoft/api-extractor/node_modules/semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/@microsoft/api-extractor/node_modules/typescript": { "version": "5.4.2", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.2.tgz", @@ -804,18 +835,18 @@ } }, "node_modules/@microsoft/tsdoc": { - "version": "0.15.0", - "resolved": "https://registry.npmjs.org/@microsoft/tsdoc/-/tsdoc-0.15.0.tgz", - "integrity": "sha512-HZpPoABogPvjeJOdzCOSJsXeL/SMCBgBZMVC3X3d7YYp2gf31MfxhUoYUNwf1ERPJOnQc0wkFn9trqI6ZEdZuA==", + "version": "0.15.1", + "resolved": "https://registry.npmjs.org/@microsoft/tsdoc/-/tsdoc-0.15.1.tgz", + "integrity": "sha512-4aErSrCR/On/e5G2hDP0wjooqDdauzEbIq8hIkIe5pXV0rtWJZvdCEKL0ykZxex+IxIwBp0eGeV48hQN07dXtw==", "dev": true }, "node_modules/@microsoft/tsdoc-config": { - "version": "0.17.0", - "resolved": "https://registry.npmjs.org/@microsoft/tsdoc-config/-/tsdoc-config-0.17.0.tgz", - "integrity": "sha512-v/EYRXnCAIHxOHW+Plb6OWuUoMotxTN0GLatnpOb1xq0KuTNw/WI3pamJx/UbsoJP5k9MCw1QxvvhPcF9pH3Zg==", + "version": "0.17.1", + "resolved": "https://registry.npmjs.org/@microsoft/tsdoc-config/-/tsdoc-config-0.17.1.tgz", + "integrity": "sha512-UtjIFe0C6oYgTnad4q1QP4qXwLhe6tIpNTRStJ2RZEPIkqQPREAwE5spzVxsdn9UaEMUqhh0AqSx3X4nWAKXWw==", "dev": true, "dependencies": { - "@microsoft/tsdoc": "0.15.0", + "@microsoft/tsdoc": "0.15.1", "ajv": "~8.12.0", "jju": "~1.4.0", "resolve": "~1.22.2" @@ -911,9 +942,9 @@ } }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.20.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.20.0.tgz", - "integrity": "sha512-TSpWzflCc4VGAUJZlPpgAJE1+V60MePDQnBd7PPkpuEmOy8i87aL6tinFGKBFKuEDikYpig72QzdT3QPYIi+oA==", + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.28.1.tgz", + "integrity": "sha512-2aZp8AES04KI2dy3Ss6/MDjXbwBzj+i0GqKtWXgw2/Ma6E4jJvujryO6gJAghIRVz7Vwr9Gtl/8na3nDUKpraQ==", "cpu": [ "arm" ], @@ -924,9 +955,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.20.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.20.0.tgz", - "integrity": "sha512-u00Ro/nok7oGzVuh/FMYfNoGqxU5CPWz1mxV85S2w9LxHR8OoMQBuSk+3BKVIDYgkpeOET5yXkx90OYFc+ytpQ==", + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.28.1.tgz", + "integrity": "sha512-EbkK285O+1YMrg57xVA+Dp0tDBRB93/BZKph9XhMjezf6F4TpYjaUSuPt5J0fZXlSag0LmZAsTmdGGqPp4pQFA==", "cpu": [ "arm64" ], @@ -937,9 +968,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.20.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.20.0.tgz", - "integrity": "sha512-uFVfvzvsdGtlSLuL0ZlvPJvl6ZmrH4CBwLGEFPe7hUmf7htGAN+aXo43R/V6LATyxlKVC/m6UsLb7jbG+LG39Q==", + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.28.1.tgz", + "integrity": "sha512-prduvrMKU6NzMq6nxzQw445zXgaDBbMQvmKSJaxpaZ5R1QDM8w+eGxo6Y/jhT/cLoCvnZI42oEqf9KQNYz1fqQ==", "cpu": [ "arm64" ], @@ -950,9 +981,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.20.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.20.0.tgz", - "integrity": "sha512-xbrMDdlev53vNXexEa6l0LffojxhqDTBeL+VUxuuIXys4x6xyvbKq5XqTXBCEUA8ty8iEJblHvFaWRJTk/icAQ==", + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.28.1.tgz", + "integrity": "sha512-WsvbOunsUk0wccO/TV4o7IKgloJ942hVFK1CLatwv6TJspcCZb9umQkPdvB7FihmdxgaKR5JyxDjWpCOp4uZlQ==", "cpu": [ "x64" ], @@ -962,10 +993,36 @@ "darwin" ] }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.28.1.tgz", + "integrity": "sha512-HTDPdY1caUcU4qK23FeeGxCdJF64cKkqajU0iBnTVxS8F7H/7BewvYoG+va1KPSL63kQ1PGNyiwKOfReavzvNA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.28.1.tgz", + "integrity": "sha512-m/uYasxkUevcFTeRSM9TeLyPe2QDuqtjkeoTpP9SW0XxUWfcYrGDMkO/m2tTw+4NMAF9P2fU3Mw4ahNvo7QmsQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ] + }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.20.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.20.0.tgz", - "integrity": "sha512-jMYvxZwGmoHFBTbr12Xc6wOdc2xA5tF5F2q6t7Rcfab68TT0n+r7dgawD4qhPEvasDsVpQi+MgDzj2faOLsZjA==", + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.28.1.tgz", + "integrity": "sha512-QAg11ZIt6mcmzpNE6JZBpKfJaKkqTm1A9+y9O+frdZJEuhQxiugM05gnCWiANHj4RmbgeVJpTdmKRmH/a+0QbA==", "cpu": [ "arm" ], @@ -976,9 +1033,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.20.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.20.0.tgz", - "integrity": "sha512-1asSTl4HKuIHIB1GcdFHNNZhxAYEdqML/MW4QmPS4G0ivbEcBr1JKlFLKsIRqjSwOBkdItn3/ZDlyvZ/N6KPlw==", + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.28.1.tgz", + "integrity": "sha512-dRP9PEBfolq1dmMcFqbEPSd9VlRuVWEGSmbxVEfiq2cs2jlZAl0YNxFzAQS2OrQmsLBLAATDMb3Z6MFv5vOcXg==", "cpu": [ "arm" ], @@ -989,9 +1046,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.20.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.20.0.tgz", - "integrity": "sha512-COBb8Bkx56KldOYJfMf6wKeYJrtJ9vEgBRAOkfw6Ens0tnmzPqvlpjZiLgkhg6cA3DGzCmLmmd319pmHvKWWlQ==", + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.28.1.tgz", + "integrity": "sha512-uGr8khxO+CKT4XU8ZUH1TTEUtlktK6Kgtv0+6bIFSeiSlnGJHG1tSFSjm41uQ9sAO/5ULx9mWOz70jYLyv1QkA==", "cpu": [ "arm64" ], @@ -1002,9 +1059,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.20.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.20.0.tgz", - "integrity": "sha512-+it+mBSyMslVQa8wSPvBx53fYuZK/oLTu5RJoXogjk6x7Q7sz1GNRsXWjn6SwyJm8E/oMjNVwPhmNdIjwP135Q==", + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.28.1.tgz", + "integrity": "sha512-QF54q8MYGAqMLrX2t7tNpi01nvq5RI59UBNx+3+37zoKX5KViPo/gk2QLhsuqok05sSCRluj0D00LzCwBikb0A==", "cpu": [ "arm64" ], @@ -1014,10 +1071,23 @@ "linux" ] }, + "node_modules/@rollup/rollup-linux-loongarch64-gnu": { + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.28.1.tgz", + "integrity": "sha512-vPul4uodvWvLhRco2w0GcyZcdyBfpfDRgNKU+p35AWEbJ/HPs1tOUrkSueVbBS0RQHAf/A+nNtDpvw95PeVKOA==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { - "version": "4.20.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.20.0.tgz", - "integrity": "sha512-yAMvqhPfGKsAxHN8I4+jE0CpLWD8cv4z7CK7BMmhjDuz606Q2tFKkWRY8bHR9JQXYcoLfopo5TTqzxgPUjUMfw==", + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.28.1.tgz", + "integrity": "sha512-pTnTdBuC2+pt1Rmm2SV7JWRqzhYpEILML4PKODqLz+C7Ou2apEV52h19CR7es+u04KlqplggmN9sqZlekg3R1A==", "cpu": [ "ppc64" ], @@ -1028,9 +1098,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.20.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.20.0.tgz", - "integrity": "sha512-qmuxFpfmi/2SUkAw95TtNq/w/I7Gpjurx609OOOV7U4vhvUhBcftcmXwl3rqAek+ADBwSjIC4IVNLiszoj3dPA==", + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.28.1.tgz", + "integrity": "sha512-vWXy1Nfg7TPBSuAncfInmAI/WZDd5vOklyLJDdIRKABcZWojNDY0NJwruY2AcnCLnRJKSaBgf/GiJfauu8cQZA==", "cpu": [ "riscv64" ], @@ -1041,9 +1111,9 @@ ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.20.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.20.0.tgz", - "integrity": "sha512-I0BtGXddHSHjV1mqTNkgUZLnS3WtsqebAXv11D5BZE/gfw5KoyXSAXVqyJximQXNvNzUo4GKlCK/dIwXlz+jlg==", + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.28.1.tgz", + "integrity": "sha512-/yqC2Y53oZjb0yz8PVuGOQQNOTwxcizudunl/tFs1aLvObTclTwZ0JhXF2XcPT/zuaymemCDSuuUPXJJyqeDOg==", "cpu": [ "s390x" ], @@ -1054,9 +1124,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.20.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.20.0.tgz", - "integrity": "sha512-y+eoL2I3iphUg9tN9GB6ku1FA8kOfmF4oUEWhztDJ4KXJy1agk/9+pejOuZkNFhRwHAOxMsBPLbXPd6mJiCwew==", + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.28.1.tgz", + "integrity": "sha512-fzgeABz7rrAlKYB0y2kSEiURrI0691CSL0+KXwKwhxvj92VULEDQLpBYLHpF49MSiPG4sq5CK3qHMnb9tlCjBw==", "cpu": [ "x64" ], @@ -1067,9 +1137,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.20.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.20.0.tgz", - "integrity": "sha512-hM3nhW40kBNYUkZb/r9k2FKK+/MnKglX7UYd4ZUy5DJs8/sMsIbqWK2piZtVGE3kcXVNj3B2IrUYROJMMCikNg==", + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.28.1.tgz", + "integrity": "sha512-xQTDVzSGiMlSshpJCtudbWyRfLaNiVPXt1WgdWTwWz9n0U12cI2ZVtWe/Jgwyv/6wjL7b66uu61Vg0POWVfz4g==", "cpu": [ "x64" ], @@ -1080,9 +1150,9 @@ ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.20.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.20.0.tgz", - "integrity": "sha512-psegMvP+Ik/Bg7QRJbv8w8PAytPA7Uo8fpFjXyCRHWm6Nt42L+JtoqH8eDQ5hRP7/XW2UiIriy1Z46jf0Oa1kA==", + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.28.1.tgz", + "integrity": "sha512-wSXmDRVupJstFP7elGMgv+2HqXelQhuNf+IS4V+nUpNVi/GUiBgDmfwD0UGN3pcAnWsgKG3I52wMOBnk1VHr/A==", "cpu": [ "arm64" ], @@ -1093,9 +1163,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.20.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.20.0.tgz", - "integrity": "sha512-GabekH3w4lgAJpVxkk7hUzUf2hICSQO0a/BLFA11/RMxQT92MabKAqyubzDZmMOC/hcJNlc+rrypzNzYl4Dx7A==", + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.28.1.tgz", + "integrity": "sha512-ZkyTJ/9vkgrE/Rk9vhMXhf8l9D+eAhbAVbsGsXKy2ohmJaWg0LPQLnIxRdRp/bKyr8tXuPlXhIoGlEB5XpJnGA==", "cpu": [ "ia32" ], @@ -1106,9 +1176,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.20.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.20.0.tgz", - "integrity": "sha512-aJ1EJSuTdGnM6qbVC4B5DSmozPTqIag9fSzXRNNo+humQLG89XpPgdt16Ia56ORD7s+H8Pmyx44uczDQ0yDzpg==", + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.28.1.tgz", + "integrity": "sha512-ZvK2jBafvttJjoIdKm/Q/Bh7IJ1Ose9IBOwpOXcOvW3ikGTQGmKDgxTC6oCAzW6PynbkKP8+um1du81XJHZ0JA==", "cpu": [ "x64" ], @@ -1119,9 +1189,9 @@ ] }, "node_modules/@rushstack/node-core-library": { - "version": "5.5.1", - "resolved": "https://registry.npmjs.org/@rushstack/node-core-library/-/node-core-library-5.5.1.tgz", - "integrity": "sha512-ZutW56qIzH8xIOlfyaLQJFx+8IBqdbVCZdnj+XT1MorQ1JqqxHse8vbCpEM+2MjsrqcbxcgDIbfggB1ZSQ2A3g==", + "version": "5.10.0", + "resolved": "https://registry.npmjs.org/@rushstack/node-core-library/-/node-core-library-5.10.0.tgz", + "integrity": "sha512-2pPLCuS/3x7DCd7liZkqOewGM0OzLyCacdvOe8j6Yrx9LkETGnxul1t7603bIaB8nUAooORcct9fFDOQMbWAgw==", "dev": true, "dependencies": { "ajv": "~8.13.0", @@ -1178,6 +1248,21 @@ "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", "dev": true }, + "node_modules/@rushstack/node-core-library/node_modules/semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/@rushstack/rig-package": { "version": "0.5.3", "resolved": "https://registry.npmjs.org/@rushstack/rig-package/-/rig-package-0.5.3.tgz", @@ -1189,12 +1274,12 @@ } }, "node_modules/@rushstack/terminal": { - "version": "0.13.3", - "resolved": "https://registry.npmjs.org/@rushstack/terminal/-/terminal-0.13.3.tgz", - "integrity": "sha512-fc3zjXOw8E0pXS5t9vTiIPx9gHA0fIdTXsu9mT4WbH+P3mYvnrX0iAQ5a6NvyK1+CqYWBTw/wVNx7SDJkI+WYQ==", + "version": "0.14.3", + "resolved": "https://registry.npmjs.org/@rushstack/terminal/-/terminal-0.14.3.tgz", + "integrity": "sha512-csXbZsAdab/v8DbU1sz7WC2aNaKArcdS/FPmXMOXEj/JBBZMvDK0+1b4Qao0kkG0ciB1Qe86/Mb68GjH6/TnMw==", "dev": true, "dependencies": { - "@rushstack/node-core-library": "5.5.1", + "@rushstack/node-core-library": "5.10.0", "supports-color": "~8.1.1" }, "peerDependencies": { @@ -1222,12 +1307,12 @@ } }, "node_modules/@rushstack/ts-command-line": { - "version": "4.22.3", - "resolved": "https://registry.npmjs.org/@rushstack/ts-command-line/-/ts-command-line-4.22.3.tgz", - "integrity": "sha512-edMpWB3QhFFZ4KtSzS8WNjBgR4PXPPOVrOHMbb7kNpmQ1UFS9HdVtjCXg1H5fG+xYAbeE+TMPcVPUyX2p84STA==", + "version": "4.23.1", + "resolved": "https://registry.npmjs.org/@rushstack/ts-command-line/-/ts-command-line-4.23.1.tgz", + "integrity": "sha512-40jTmYoiu/xlIpkkRsVfENtBq4CW3R4azbL0Vmda+fMwHWqss6wwf/Cy/UJmMqIzpfYc2OTnjYP1ZLD3CmyeCA==", "dev": true, "dependencies": { - "@rushstack/terminal": "0.13.3", + "@rushstack/terminal": "0.14.3", "@types/argparse": "1.0.38", "argparse": "~1.0.9", "string-argv": "~0.3.1" @@ -1268,9 +1353,9 @@ } }, "node_modules/@types/estree": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", - "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", + "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", "dev": true }, "node_modules/@types/json-schema": { @@ -1280,12 +1365,12 @@ "dev": true }, "node_modules/@types/node": { - "version": "20.9.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.9.1.tgz", - "integrity": "sha512-HhmzZh5LSJNS5O8jQKpJ/3ZcrrlG6L70hpGqMIAoM9YVD0YBRNWYsfwcXq8VnSjlNpCpgLzMXdiPo+dxcvSmiA==", + "version": "20.17.10", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.10.tgz", + "integrity": "sha512-/jrvh5h6NXhEauFFexRin69nA0uHJ5gwk4iDivp/DeoEua3uwCUto6PC86IpRITBOs4+6i2I56K5x5b6WYGXHA==", "dev": true, "dependencies": { - "undici-types": "~5.26.4" + "undici-types": "~6.19.2" } }, "node_modules/@types/w3c-web-usb": { @@ -1294,15 +1379,20 @@ "integrity": "sha512-CHgUI5kTc/QLMP8hODUHhge0D4vx+9UiAwIGiT0sTy/B2XpdX1U5rJt6JSISgr6ikRT7vxV9EVAFeYZqUnl1gQ==", "dev": true }, - "node_modules/@typescript-eslint/type-utils": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.0.1.tgz", - "integrity": "sha512-+/UT25MWvXeDX9YaHv1IS6KI1fiuTto43WprE7pgSMswHbn1Jm9GEM4Txp+X74ifOWV8emu2AWcbLhpJAvD5Ng==", + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.18.0.tgz", + "integrity": "sha512-NR2yS7qUqCL7AIxdJUQf2MKKNDVNaig/dEB0GBLU7D+ZdHgK1NoH/3wsgO3OnPVipn51tG3MAwaODEGil70WEw==", "dev": true, "dependencies": { - "@typescript-eslint/typescript-estree": "8.0.1", - "@typescript-eslint/utils": "8.0.1", - "debug": "^4.3.4", + "@eslint-community/regexpp": "^4.10.0", + "@typescript-eslint/scope-manager": "8.18.0", + "@typescript-eslint/type-utils": "8.18.0", + "@typescript-eslint/utils": "8.18.0", + "@typescript-eslint/visitor-keys": "8.18.0", + "graphemer": "^1.4.0", + "ignore": "^5.3.1", + "natural-compare": "^1.4.0", "ts-api-utils": "^1.3.0" }, "engines": { @@ -1312,39 +1402,44 @@ "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "peerDependencies": { + "@typescript-eslint/parser": "^8.0.0 || ^8.0.0-alpha.0", + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.8.0" } }, - "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/types": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.0.1.tgz", - "integrity": "sha512-PpqTVT3yCA/bIgJ12czBuE3iBlM3g4inRSC5J0QOdQFAn07TYrYEQBBKgXH1lQpglup+Zy6c1fxuwTk4MTNKIw==", + "node_modules/@typescript-eslint/parser": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.18.0.tgz", + "integrity": "sha512-hgUZ3kTEpVzKaK3uNibExUYm6SKKOmTU2BOxBSvOYwtJEPdVQ70kZJpPjstlnhCHcuc2WGfSbpKlb/69ttyN5Q==", "dev": true, + "dependencies": { + "@typescript-eslint/scope-manager": "8.18.0", + "@typescript-eslint/types": "8.18.0", + "@typescript-eslint/typescript-estree": "8.18.0", + "@typescript-eslint/visitor-keys": "8.18.0", + "debug": "^4.3.4" + }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.8.0" } }, - "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/typescript-estree": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.0.1.tgz", - "integrity": "sha512-8V9hriRvZQXPWU3bbiUV4Epo7EvgM6RTs+sUmxp5G//dBGy402S7Fx0W0QkB2fb4obCF8SInoUzvTYtc3bkb5w==", + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.18.0.tgz", + "integrity": "sha512-PNGcHop0jkK2WVYGotk/hxj+UFLhXtGPiGtiaWgVBVP1jhMoMCHlTyJA+hEj4rszoSdLTK3fN4oOatrL0Cp+Xw==", "dev": true, "dependencies": { - "@typescript-eslint/types": "8.0.1", - "@typescript-eslint/visitor-keys": "8.0.1", - "debug": "^4.3.4", - "globby": "^11.1.0", - "is-glob": "^4.0.3", - "minimatch": "^9.0.4", - "semver": "^7.6.0", - "ts-api-utils": "^1.3.0" + "@typescript-eslint/types": "8.18.0", + "@typescript-eslint/visitor-keys": "8.18.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1352,21 +1447,18 @@ "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } } }, - "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/visitor-keys": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.0.1.tgz", - "integrity": "sha512-W5E+o0UfUcK5EgchLZsyVWqARmsM7v54/qEq6PY3YI5arkgmCzHiuk0zKSJJbm71V0xdRna4BGomkCTXz2/LkQ==", + "node_modules/@typescript-eslint/type-utils": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.18.0.tgz", + "integrity": "sha512-er224jRepVAVLnMF2Q7MZJCq5CsdH2oqjP4dT7K6ij09Kyd+R21r7UVJrF0buMVdZS5QRhDzpvzAxHxabQadow==", "dev": true, "dependencies": { - "@typescript-eslint/types": "8.0.1", - "eslint-visitor-keys": "^3.4.3" + "@typescript-eslint/typescript-estree": "8.18.0", + "@typescript-eslint/utils": "8.18.0", + "debug": "^4.3.4", + "ts-api-utils": "^1.3.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1374,45 +1466,61 @@ "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.8.0" } }, - "node_modules/@typescript-eslint/type-utils/node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "node_modules/@typescript-eslint/types": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.18.0.tgz", + "integrity": "sha512-FNYxgyTCAnFwTrzpBGq+zrnoTO4x0c1CKYY5MuUTzpScqmY5fmsh2o3+57lqdI3NZucBDCzDgdEbIaNfAjAHQA==", "dev": true, - "dependencies": { - "brace-expansion": "^2.0.1" - }, "engines": { - "node": ">=16 || 14 >=14.17" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { - "url": "https://github.com/sponsors/isaacs" + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" } }, - "node_modules/@typescript-eslint/type-utils/node_modules/semver": { - "version": "7.6.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", - "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.18.0.tgz", + "integrity": "sha512-rqQgFRu6yPkauz+ms3nQpohwejS8bvgbPyIDq13cgEDbkXt4LH4OkDMT0/fN1RUtzG8e8AKJyDBoocuQh8qNeg==", "dev": true, - "bin": { - "semver": "bin/semver.js" + "dependencies": { + "@typescript-eslint/types": "8.18.0", + "@typescript-eslint/visitor-keys": "8.18.0", + "debug": "^4.3.4", + "fast-glob": "^3.3.2", + "is-glob": "^4.0.3", + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^1.3.0" }, "engines": { - "node": ">=10" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <5.8.0" } }, "node_modules/@typescript-eslint/utils": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.0.1.tgz", - "integrity": "sha512-CBFR0G0sCt0+fzfnKaciu9IBsKvEKYwN9UZ+eeogK1fYHg4Qxk1yf/wLQkLXlq8wbU2dFlgAesxt8Gi76E8RTA==", + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.18.0.tgz", + "integrity": "sha512-p6GLdY383i7h5b0Qrfbix3Vc3+J2k6QWw6UMUeY5JGfm3C5LbZ4QIZzJNoNOfgyRe0uuYKjvVOsO/jD4SJO+xg==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", - "@typescript-eslint/scope-manager": "8.0.1", - "@typescript-eslint/types": "8.0.1", - "@typescript-eslint/typescript-estree": "8.0.1" + "@typescript-eslint/scope-manager": "8.18.0", + "@typescript-eslint/types": "8.18.0", + "@typescript-eslint/typescript-estree": "8.18.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1422,17 +1530,18 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0" + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.8.0" } }, - "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/scope-manager": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.0.1.tgz", - "integrity": "sha512-NpixInP5dm7uukMiRyiHjRKkom5RIFA4dfiHvalanD2cF0CLUuQqxfg8PtEUo9yqJI2bBhF+pcSafqnG3UBnRQ==", + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.18.0.tgz", + "integrity": "sha512-pCh/qEA8Lb1wVIqNvBke8UaRjJ6wrAWkJO5yyIbs8Yx6TNGYyfNjOo61tLv+WwLvoLPp4BQ8B7AHKijl8NGUfw==", "dev": true, "dependencies": { - "@typescript-eslint/types": "8.0.1", - "@typescript-eslint/visitor-keys": "8.0.1" + "@typescript-eslint/types": "8.18.0", + "eslint-visitor-keys": "^4.2.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1442,107 +1551,34 @@ "url": "https://opencollective.com/typescript-eslint" } }, - "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/types": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.0.1.tgz", - "integrity": "sha512-PpqTVT3yCA/bIgJ12czBuE3iBlM3g4inRSC5J0QOdQFAn07TYrYEQBBKgXH1lQpglup+Zy6c1fxuwTk4MTNKIw==", + "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", + "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", "dev": true, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" + "url": "https://opencollective.com/eslint" } }, - "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/typescript-estree": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.0.1.tgz", - "integrity": "sha512-8V9hriRvZQXPWU3bbiUV4Epo7EvgM6RTs+sUmxp5G//dBGy402S7Fx0W0QkB2fb4obCF8SInoUzvTYtc3bkb5w==", - "dev": true, - "dependencies": { - "@typescript-eslint/types": "8.0.1", - "@typescript-eslint/visitor-keys": "8.0.1", - "debug": "^4.3.4", - "globby": "^11.1.0", - "is-glob": "^4.0.3", - "minimatch": "^9.0.4", - "semver": "^7.6.0", - "ts-api-utils": "^1.3.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/visitor-keys": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.0.1.tgz", - "integrity": "sha512-W5E+o0UfUcK5EgchLZsyVWqARmsM7v54/qEq6PY3YI5arkgmCzHiuk0zKSJJbm71V0xdRna4BGomkCTXz2/LkQ==", - "dev": true, - "dependencies": { - "@typescript-eslint/types": "8.0.1", - "eslint-visitor-keys": "^3.4.3" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/utils/node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", - "dev": true, - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/@typescript-eslint/utils/node_modules/semver": { - "version": "7.6.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", - "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@vitest/coverage-v8": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-2.0.5.tgz", - "integrity": "sha512-qeFcySCg5FLO2bHHSa0tAZAOnAUbp4L6/A5JDuj9+bt53JREl8hpLjLHEWF0e/gWc8INVpJaqA7+Ene2rclpZg==", + "node_modules/@vitest/coverage-v8": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-2.1.8.tgz", + "integrity": "sha512-2Y7BPlKH18mAZYAW1tYByudlCYrQyl5RGvnnDYJKW5tCiO5qg3KSAy3XAxcxKz900a0ZXxWtKrMuZLe3lKBpJw==", "dev": true, "dependencies": { "@ampproject/remapping": "^2.3.0", "@bcoe/v8-coverage": "^0.2.3", - "debug": "^4.3.5", + "debug": "^4.3.7", "istanbul-lib-coverage": "^3.2.2", "istanbul-lib-report": "^3.0.1", "istanbul-lib-source-maps": "^5.0.6", "istanbul-reports": "^3.1.7", - "magic-string": "^0.30.10", - "magicast": "^0.3.4", - "std-env": "^3.7.0", + "magic-string": "^0.30.12", + "magicast": "^0.3.5", + "std-env": "^3.8.0", "test-exclude": "^7.0.1", "tinyrainbow": "^1.2.0" }, @@ -1550,18 +1586,24 @@ "url": "https://opencollective.com/vitest" }, "peerDependencies": { - "vitest": "2.0.5" + "@vitest/browser": "2.1.8", + "vitest": "2.1.8" + }, + "peerDependenciesMeta": { + "@vitest/browser": { + "optional": true + } } }, "node_modules/@vitest/expect": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-2.0.5.tgz", - "integrity": "sha512-yHZtwuP7JZivj65Gxoi8upUN2OzHTi3zVfjwdpu2WrvCZPLwsJ2Ey5ILIPccoW23dd/zQBlJ4/dhi7DWNyXCpA==", + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-2.1.8.tgz", + "integrity": "sha512-8ytZ/fFHq2g4PJVAtDX57mayemKgDR6X3Oa2Foro+EygiOJHUXhCqBAAKQYYajZpFoIfvBCF1j6R6IYRSIUFuw==", "dev": true, "dependencies": { - "@vitest/spy": "2.0.5", - "@vitest/utils": "2.0.5", - "chai": "^5.1.1", + "@vitest/spy": "2.1.8", + "@vitest/utils": "2.1.8", + "chai": "^5.1.2", "tinyrainbow": "^1.2.0" }, "funding": { @@ -1569,9 +1611,9 @@ } }, "node_modules/@vitest/pretty-format": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-2.0.5.tgz", - "integrity": "sha512-h8k+1oWHfwTkyTkb9egzwNMfJAEx4veaPSnMeKbVSjp4euqGSbQlm5+6VHwTr7u4FJslVVsUG5nopCaAYdOmSQ==", + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-2.1.8.tgz", + "integrity": "sha512-9HiSZ9zpqNLKlbIDRWOnAWqgcA7xu+8YxXSekhr0Ykab7PAYFkhkwoqVArPOtJhPmYeE2YHgKZlj3CP36z2AJQ==", "dev": true, "dependencies": { "tinyrainbow": "^1.2.0" @@ -1581,12 +1623,12 @@ } }, "node_modules/@vitest/runner": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-2.0.5.tgz", - "integrity": "sha512-TfRfZa6Bkk9ky4tW0z20WKXFEwwvWhRY+84CnSEtq4+3ZvDlJyY32oNTJtM7AW9ihW90tX/1Q78cb6FjoAs+ig==", + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-2.1.8.tgz", + "integrity": "sha512-17ub8vQstRnRlIU5k50bG+QOMLHRhYPAna5tw8tYbj+jzjcspnwnwtPtiOlkuKC4+ixDPTuLZiqiWWQ2PSXHVg==", "dev": true, "dependencies": { - "@vitest/utils": "2.0.5", + "@vitest/utils": "2.1.8", "pathe": "^1.1.2" }, "funding": { @@ -1594,13 +1636,13 @@ } }, "node_modules/@vitest/snapshot": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-2.0.5.tgz", - "integrity": "sha512-SgCPUeDFLaM0mIUHfaArq8fD2WbaXG/zVXjRupthYfYGzc8ztbFbu6dUNOblBG7XLMR1kEhS/DNnfCZ2IhdDew==", + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-2.1.8.tgz", + "integrity": "sha512-20T7xRFbmnkfcmgVEz+z3AU/3b0cEzZOt/zmnvZEctg64/QZbSDJEVm9fLnnlSi74KibmRsO9/Qabi+t0vCRPg==", "dev": true, "dependencies": { - "@vitest/pretty-format": "2.0.5", - "magic-string": "^0.30.10", + "@vitest/pretty-format": "2.1.8", + "magic-string": "^0.30.12", "pathe": "^1.1.2" }, "funding": { @@ -1608,88 +1650,78 @@ } }, "node_modules/@vitest/spy": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-2.0.5.tgz", - "integrity": "sha512-c/jdthAhvJdpfVuaexSrnawxZz6pywlTPe84LUB2m/4t3rl2fTo9NFGBG4oWgaD+FTgDDV8hJ/nibT7IfH3JfA==", + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-2.1.8.tgz", + "integrity": "sha512-5swjf2q95gXeYPevtW0BLk6H8+bPlMb4Vw/9Em4hFxDcaOxS+e0LOX4yqNxoHzMR2akEB2xfpnWUzkZokmgWDg==", "dev": true, "dependencies": { - "tinyspy": "^3.0.0" + "tinyspy": "^3.0.2" }, "funding": { "url": "https://opencollective.com/vitest" } }, "node_modules/@vitest/utils": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-2.0.5.tgz", - "integrity": "sha512-d8HKbqIcya+GR67mkZbrzhS5kKhtp8dQLcmRZLGTscGVg7yImT82cIrhtn2L8+VujWcy6KZweApgNmPsTAO/UQ==", + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-2.1.8.tgz", + "integrity": "sha512-dwSoui6djdwbfFmIgbIjX2ZhIoG7Ex/+xpxyiEgIGzjliY8xGkcpITKTlp6B4MgtGkF2ilvm97cPM96XZaAgcA==", "dev": true, "dependencies": { - "@vitest/pretty-format": "2.0.5", - "estree-walker": "^3.0.3", - "loupe": "^3.1.1", + "@vitest/pretty-format": "2.1.8", + "loupe": "^3.1.2", "tinyrainbow": "^1.2.0" }, "funding": { "url": "https://opencollective.com/vitest" } }, - "node_modules/@vitest/utils/node_modules/estree-walker": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", - "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", - "dev": true, - "dependencies": { - "@types/estree": "^1.0.0" - } - }, "node_modules/@volar/language-core": { - "version": "2.3.4", - "resolved": "https://registry.npmjs.org/@volar/language-core/-/language-core-2.3.4.tgz", - "integrity": "sha512-wXBhY11qG6pCDAqDnbBRFIDSIwbqkWI7no+lj5+L7IlA7HRIjRP7YQLGzT0LF4lS6eHkMSsclXqy9DwYJasZTQ==", + "version": "2.4.10", + "resolved": "https://registry.npmjs.org/@volar/language-core/-/language-core-2.4.10.tgz", + "integrity": "sha512-hG3Z13+nJmGaT+fnQzAkS0hjJRa2FCeqZt6Bd+oGNhUkQ+mTFsDETg5rqUTxyzIh5pSOGY7FHCWUS8G82AzLCA==", "dev": true, "dependencies": { - "@volar/source-map": "2.3.4" + "@volar/source-map": "2.4.10" } }, "node_modules/@volar/source-map": { - "version": "2.3.4", - "resolved": "https://registry.npmjs.org/@volar/source-map/-/source-map-2.3.4.tgz", - "integrity": "sha512-C+t63nwcblqLIVTYXaVi/+gC8NukDaDIQI72J3R7aXGvtgaVB16c+J8Iz7/VfOy7kjYv7lf5GhBny6ACw9fTGQ==", + "version": "2.4.10", + "resolved": "https://registry.npmjs.org/@volar/source-map/-/source-map-2.4.10.tgz", + "integrity": "sha512-OCV+b5ihV0RF3A7vEvNyHPi4G4kFa6ukPmyVocmqm5QzOd8r5yAtiNvaPEjl8dNvgC/lj4JPryeeHLdXd62rWA==", "dev": true }, "node_modules/@volar/typescript": { - "version": "2.3.4", - "resolved": "https://registry.npmjs.org/@volar/typescript/-/typescript-2.3.4.tgz", - "integrity": "sha512-acCvt7dZECyKcvO5geNybmrqOsu9u8n5XP1rfiYsOLYGPxvHRav9BVmEdRyZ3vvY6mNyQ1wLL5Hday4IShe17w==", + "version": "2.4.10", + "resolved": "https://registry.npmjs.org/@volar/typescript/-/typescript-2.4.10.tgz", + "integrity": "sha512-F8ZtBMhSXyYKuBfGpYwqA5rsONnOwAVvjyE7KPYJ7wgZqo2roASqNWUnianOomJX5u1cxeRooHV59N0PhvEOgw==", "dev": true, "dependencies": { - "@volar/language-core": "2.3.4", + "@volar/language-core": "2.4.10", "path-browserify": "^1.0.1", "vscode-uri": "^3.0.8" } }, "node_modules/@vue/compiler-core": { - "version": "3.4.37", - "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.4.37.tgz", - "integrity": "sha512-ZDDT/KiLKuCRXyzWecNzC5vTcubGz4LECAtfGPENpo0nrmqJHwuWtRLxk/Sb9RAKtR9iFflFycbkjkY+W/PZUQ==", + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.13.tgz", + "integrity": "sha512-oOdAkwqUfW1WqpwSYJce06wvt6HljgY3fGeM9NcVA1HaYOij3mZG9Rkysn0OHuyUAGMbEbARIpsG+LPVlBJ5/Q==", "dev": true, "dependencies": { - "@babel/parser": "^7.24.7", - "@vue/shared": "3.4.37", - "entities": "^5.0.0", + "@babel/parser": "^7.25.3", + "@vue/shared": "3.5.13", + "entities": "^4.5.0", "estree-walker": "^2.0.2", "source-map-js": "^1.2.0" } }, "node_modules/@vue/compiler-dom": { - "version": "3.4.37", - "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.4.37.tgz", - "integrity": "sha512-rIiSmL3YrntvgYV84rekAtU/xfogMUJIclUMeIKEtVBFngOL3IeZHhsH3UaFEgB5iFGpj6IW+8YuM/2Up+vVag==", + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.13.tgz", + "integrity": "sha512-ZOJ46sMOKUjO3e94wPdCzQ6P1Lx/vhp2RSvfaab88Ajexs0AHeV0uasYhi99WPaogmBlRHNRuly8xV75cNTMDA==", "dev": true, "dependencies": { - "@vue/compiler-core": "3.4.37", - "@vue/shared": "3.4.37" + "@vue/compiler-core": "3.5.13", + "@vue/shared": "3.5.13" } }, "node_modules/@vue/compiler-vue2": { @@ -1703,12 +1735,12 @@ } }, "node_modules/@vue/language-core": { - "version": "2.0.29", - "resolved": "https://registry.npmjs.org/@vue/language-core/-/language-core-2.0.29.tgz", - "integrity": "sha512-o2qz9JPjhdoVj8D2+9bDXbaI4q2uZTHQA/dbyZT4Bj1FR9viZxDJnLcKVHfxdn6wsOzRgpqIzJEEmSSvgMvDTQ==", + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/@vue/language-core/-/language-core-2.1.6.tgz", + "integrity": "sha512-MW569cSky9R/ooKMh6xa2g1D0AtRKbL56k83dzus/bx//RDJk24RHWkMzbAlXjMdDNyxAaagKPRquBIxkxlCkg==", "dev": true, "dependencies": { - "@volar/language-core": "~2.4.0-alpha.18", + "@volar/language-core": "~2.4.1", "@vue/compiler-dom": "^3.4.0", "@vue/compiler-vue2": "^2.7.16", "@vue/shared": "^3.4.0", @@ -1726,25 +1758,10 @@ } } }, - "node_modules/@vue/language-core/node_modules/@volar/language-core": { - "version": "2.4.0-alpha.18", - "resolved": "https://registry.npmjs.org/@volar/language-core/-/language-core-2.4.0-alpha.18.tgz", - "integrity": "sha512-JAYeJvYQQROmVRtSBIczaPjP3DX4QW1fOqW1Ebs0d3Y3EwSNRglz03dSv0Dm61dzd0Yx3WgTW3hndDnTQqgmyg==", - "dev": true, - "dependencies": { - "@volar/source-map": "2.4.0-alpha.18" - } - }, - "node_modules/@vue/language-core/node_modules/@volar/source-map": { - "version": "2.4.0-alpha.18", - "resolved": "https://registry.npmjs.org/@volar/source-map/-/source-map-2.4.0-alpha.18.tgz", - "integrity": "sha512-MTeCV9MUwwsH0sNFiZwKtFrrVZUK6p8ioZs3xFzHc2cvDXHWlYN3bChdQtwKX+FY2HG6H3CfAu1pKijolzIQ8g==", - "dev": true - }, "node_modules/@vue/shared": { - "version": "3.4.37", - "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.37.tgz", - "integrity": "sha512-nIh8P2fc3DflG8+5Uw8PT/1i17ccFn0xxN/5oE9RfV5SVnd7G0XEFRwakrnNFE/jlS95fpGXDVG5zDETS26nmg==", + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.13.tgz", + "integrity": "sha512-/hnE/qP5ZoGpol0a5mDi45bOd7t3tjYJBjsgCsivow7D48cJeV5l05RD82lPqi7gRiphZM37rnhW1l6ZoCNNnQ==", "dev": true }, "node_modules/accepts": { @@ -1875,15 +1892,6 @@ "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", "dev": true }, - "node_modules/array-union": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", - "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/assertion-error": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", @@ -1900,9 +1908,9 @@ "dev": true }, "node_modules/body-parser": { - "version": "1.20.2", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", - "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==", + "version": "1.20.3", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", + "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", "dev": true, "dependencies": { "bytes": "3.1.2", @@ -1913,7 +1921,7 @@ "http-errors": "2.0.0", "iconv-lite": "0.4.24", "on-finished": "2.4.1", - "qs": "6.11.0", + "qs": "6.13.0", "raw-body": "2.5.2", "type-is": "~1.6.18", "unpipe": "1.0.0" @@ -1987,16 +1995,44 @@ } }, "node_modules/call-bind": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", - "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", + "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", "dev": true, "dependencies": { + "call-bind-apply-helpers": "^1.0.0", "es-define-property": "^1.0.0", - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", "get-intrinsic": "^1.2.4", - "set-function-length": "^1.2.1" + "set-function-length": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.1.tgz", + "integrity": "sha512-BhYE+WDaywFg2TBWYNXAE+8B1ATnThNBqXHP5nQu0jWJdVvY2hvkpyB3qOmtmDePiS5/BDQ8wASEWGMWRG148g==", + "dev": true, + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.2.tgz", + "integrity": "sha512-0lk0PHFe/uz0vl527fG9CgdE9WdafjDbCXvBbs+LUv000TVt2Jjhqbs4Jwm8gz070w8xXyEAxrPOMullsxXeGg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.8", + "get-intrinsic": "^1.2.5" }, "engines": { "node": ">= 0.4" @@ -2015,9 +2051,9 @@ } }, "node_modules/chai": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/chai/-/chai-5.1.1.tgz", - "integrity": "sha512-pT1ZgP8rPNqUgieVaEY+ryQr6Q4HXNg8Ei9UnLUrjN4IA7dvQC5JB+/kxVcPNDHyBcc/26CXPkbNzq3qwrOEKA==", + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/chai/-/chai-5.1.2.tgz", + "integrity": "sha512-aGtmf24DW6MLHHG5gCx4zaI3uBq3KRtxeVs0DjFH6Z0rDNbsvTxFASFvdj79pxjxZ8/5u3PIiN3IwEIQkiiuPw==", "dev": true, "dependencies": { "assertion-error": "^2.0.1", @@ -2199,9 +2235,9 @@ } }, "node_modules/cookie": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", - "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", + "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", "dev": true, "engines": { "node": ">= 0.6" @@ -2233,9 +2269,9 @@ } }, "node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", "dev": true, "dependencies": { "path-key": "^3.1.0", @@ -2253,12 +2289,12 @@ "dev": true }, "node_modules/debug": { - "version": "4.3.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.6.tgz", - "integrity": "sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==", + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", "dev": true, "dependencies": { - "ms": "2.1.2" + "ms": "^2.1.3" }, "engines": { "node": ">=6.0" @@ -2326,16 +2362,18 @@ "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==", "dev": true }, - "node_modules/dir-glob": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", - "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "node_modules/dunder-proto": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.0.tgz", + "integrity": "sha512-9+Sj30DIu+4KvHqMfLUGLFYL2PkURSYMVXJyXe92nFRvlYq5hBjLEhblKB+vkd/WVlUYMWigiY07T91Fkk0+4A==", "dev": true, "dependencies": { - "path-type": "^4.0.0" + "call-bind-apply-helpers": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" }, "engines": { - "node": ">=8" + "node": ">= 0.4" } }, "node_modules/eastasianwidth": { @@ -2357,18 +2395,18 @@ "dev": true }, "node_modules/encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", "dev": true, "engines": { "node": ">= 0.8" } }, "node_modules/entities": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-5.0.0.tgz", - "integrity": "sha512-BeJFvFRJddxobhvEdm5GqHzRV/X+ACeuw0/BuuxsCh1EUZcAIz8+kYmBp/LrQuloy6K1f3a0M7+IhmZ7QnkISA==", + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", "dev": true, "engines": { "node": ">=0.12" @@ -2378,13 +2416,10 @@ } }, "node_modules/es-define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", - "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", "dev": true, - "dependencies": { - "get-intrinsic": "^1.2.4" - }, "engines": { "node": ">= 0.4" } @@ -2398,42 +2433,61 @@ "node": ">= 0.4" } }, + "node_modules/es-module-lexer": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.5.4.tgz", + "integrity": "sha512-MVNK56NiMrOwitFB7cqDwq0CQutbw+0BvLshJSse0MUNU+y1FC3bUS/AQg7oUng+/wKrrki7JfmwtVHkVfPLlw==", + "dev": true + }, + "node_modules/es-object-atoms": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.0.0.tgz", + "integrity": "sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==", + "dev": true, + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/esbuild": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", - "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.24.0.tgz", + "integrity": "sha512-FuLPevChGDshgSicjisSooU0cemp/sGXR841D5LHMB7mTVOmsEHcAxaH3irL53+8YDIeVNQEySh4DaYU/iuPqQ==", "dev": true, "hasInstallScript": true, "bin": { "esbuild": "bin/esbuild" }, "engines": { - "node": ">=12" + "node": ">=18" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.21.5", - "@esbuild/android-arm": "0.21.5", - "@esbuild/android-arm64": "0.21.5", - "@esbuild/android-x64": "0.21.5", - "@esbuild/darwin-arm64": "0.21.5", - "@esbuild/darwin-x64": "0.21.5", - "@esbuild/freebsd-arm64": "0.21.5", - "@esbuild/freebsd-x64": "0.21.5", - "@esbuild/linux-arm": "0.21.5", - "@esbuild/linux-arm64": "0.21.5", - "@esbuild/linux-ia32": "0.21.5", - "@esbuild/linux-loong64": "0.21.5", - "@esbuild/linux-mips64el": "0.21.5", - "@esbuild/linux-ppc64": "0.21.5", - "@esbuild/linux-riscv64": "0.21.5", - "@esbuild/linux-s390x": "0.21.5", - "@esbuild/linux-x64": "0.21.5", - "@esbuild/netbsd-x64": "0.21.5", - "@esbuild/openbsd-x64": "0.21.5", - "@esbuild/sunos-x64": "0.21.5", - "@esbuild/win32-arm64": "0.21.5", - "@esbuild/win32-ia32": "0.21.5", - "@esbuild/win32-x64": "0.21.5" + "@esbuild/aix-ppc64": "0.24.0", + "@esbuild/android-arm": "0.24.0", + "@esbuild/android-arm64": "0.24.0", + "@esbuild/android-x64": "0.24.0", + "@esbuild/darwin-arm64": "0.24.0", + "@esbuild/darwin-x64": "0.24.0", + "@esbuild/freebsd-arm64": "0.24.0", + "@esbuild/freebsd-x64": "0.24.0", + "@esbuild/linux-arm": "0.24.0", + "@esbuild/linux-arm64": "0.24.0", + "@esbuild/linux-ia32": "0.24.0", + "@esbuild/linux-loong64": "0.24.0", + "@esbuild/linux-mips64el": "0.24.0", + "@esbuild/linux-ppc64": "0.24.0", + "@esbuild/linux-riscv64": "0.24.0", + "@esbuild/linux-s390x": "0.24.0", + "@esbuild/linux-x64": "0.24.0", + "@esbuild/netbsd-x64": "0.24.0", + "@esbuild/openbsd-arm64": "0.24.0", + "@esbuild/openbsd-x64": "0.24.0", + "@esbuild/sunos-x64": "0.24.0", + "@esbuild/win32-arm64": "0.24.0", + "@esbuild/win32-ia32": "0.24.0", + "@esbuild/win32-x64": "0.24.0" } }, "node_modules/escape-html": { @@ -2661,61 +2715,47 @@ "node": ">= 0.6" } }, - "node_modules/execa": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz", - "integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==", + "node_modules/expect-type": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.1.0.tgz", + "integrity": "sha512-bFi65yM+xZgk+u/KRIpekdSYkTB5W1pEf0Lt8Q8Msh7b+eQ7LXVtIB1Bkm4fvclDEL1b2CZkMhv2mOeF8tMdkA==", "dev": true, - "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^8.0.1", - "human-signals": "^5.0.0", - "is-stream": "^3.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^5.1.0", - "onetime": "^6.0.0", - "signal-exit": "^4.1.0", - "strip-final-newline": "^3.0.0" - }, "engines": { - "node": ">=16.17" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" + "node": ">=12.0.0" } }, "node_modules/express": { - "version": "4.19.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz", - "integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==", + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", + "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", "dev": true, "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", - "body-parser": "1.20.2", + "body-parser": "1.20.3", "content-disposition": "0.5.4", "content-type": "~1.0.4", - "cookie": "0.6.0", + "cookie": "0.7.1", "cookie-signature": "1.0.6", "debug": "2.6.9", "depd": "2.0.0", - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", - "finalhandler": "1.2.0", + "finalhandler": "1.3.1", "fresh": "0.5.2", "http-errors": "2.0.0", - "merge-descriptors": "1.0.1", + "merge-descriptors": "1.0.3", "methods": "~1.1.2", "on-finished": "2.4.1", "parseurl": "~1.3.3", - "path-to-regexp": "0.1.7", + "path-to-regexp": "0.1.12", "proxy-addr": "~2.0.7", - "qs": "6.11.0", + "qs": "6.13.0", "range-parser": "~1.2.1", "safe-buffer": "5.2.1", - "send": "0.18.0", - "serve-static": "1.15.0", + "send": "0.19.0", + "serve-static": "1.16.2", "setprototypeof": "1.2.0", "statuses": "2.0.1", "type-is": "~1.6.18", @@ -2724,6 +2764,10 @@ }, "engines": { "node": ">= 0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/express/node_modules/debug": { @@ -2808,9 +2852,9 @@ "dev": true }, "node_modules/fast-uri": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.1.tgz", - "integrity": "sha512-MWipKbbYiYI0UC7cl8m/i/IWTqfC8YXsqjzybjddLsFjStroQzsHXkc73JutMvBiXmOvapk+axIl79ig5t55Bw==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.3.tgz", + "integrity": "sha512-aLrHthzCjH5He4Z2H9YZ+v6Ujb9ocRuW6ZzkJQOrTxleEijANq4v1TsaPaVG1PZcuurEzrLcWRyYBYXD5cEiaw==", "dev": true }, "node_modules/fastq": { @@ -2847,13 +2891,13 @@ } }, "node_modules/finalhandler": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", - "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", + "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", "dev": true, "dependencies": { "debug": "2.6.9", - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "on-finished": "2.4.1", "parseurl": "~1.3.3", @@ -2985,26 +3029,22 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/get-func-name": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz", - "integrity": "sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==", - "dev": true, - "engines": { - "node": "*" - } - }, "node_modules/get-intrinsic": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", - "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.6.tgz", + "integrity": "sha512-qxsEs+9A+u85HhllWJJFicJfPDhRmjzoYdl64aMWW9yRIJmSyxdn8IEkuIM530/7T+lv0TIHd8L6Q/ra0tEoeA==", "dev": true, "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "dunder-proto": "^1.0.0", + "es-define-property": "^1.0.1", "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", "function-bind": "^1.1.2", - "has-proto": "^1.0.1", - "has-symbols": "^1.0.3", - "hasown": "^2.0.0" + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.0.0" }, "engines": { "node": ">= 0.4" @@ -3013,18 +3053,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/get-stream": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz", - "integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==", - "dev": true, - "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/glob": { "version": "10.4.5", "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", @@ -3057,21 +3085,6 @@ "node": ">=10.13.0" } }, - "node_modules/glob/node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", - "dev": true, - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/globals": { "version": "15.9.0", "resolved": "https://registry.npmjs.org/globals/-/globals-15.9.0.tgz", @@ -3084,33 +3097,13 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/globby": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", - "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", - "dev": true, - "dependencies": { - "array-union": "^2.1.0", - "dir-glob": "^3.0.1", - "fast-glob": "^3.2.9", - "ignore": "^5.2.0", - "merge2": "^1.4.1", - "slash": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/gopd": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", - "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", "dev": true, - "dependencies": { - "get-intrinsic": "^1.1.3" + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -3134,6 +3127,20 @@ "integrity": "sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg==", "dev": true }, + "node_modules/happy-dom": { + "version": "15.11.7", + "resolved": "https://registry.npmjs.org/happy-dom/-/happy-dom-15.11.7.tgz", + "integrity": "sha512-KyrFvnl+J9US63TEzwoiJOQzZBJY7KgBushJA8X61DMbNsH+2ONkDuLDnCnwUiPTF42tLoEmrPyoqbenVA5zrg==", + "dev": true, + "dependencies": { + "entities": "^4.5.0", + "webidl-conversions": "^7.0.0", + "whatwg-mimetype": "^3.0.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -3155,22 +3162,10 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/has-proto": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", - "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", - "dev": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/has-symbols": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", "dev": true, "engines": { "node": ">= 0.4" @@ -3281,15 +3276,6 @@ "serve": "index.js" } }, - "node_modules/human-signals": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz", - "integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==", - "dev": true, - "engines": { - "node": ">=16.17.0" - } - }, "node_modules/iconv-lite": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", @@ -3361,9 +3347,9 @@ } }, "node_modules/is-core-module": { - "version": "2.15.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.0.tgz", - "integrity": "sha512-Dd+Lb2/zvk9SKy1TGCt1wFJFo/MWBPMX5x7KcvLajWTGuomczdQX61PvY5yK6SVACwpoexWo81IfFyoKY2QnTA==", + "version": "2.15.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.1.tgz", + "integrity": "sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==", "dev": true, "dependencies": { "hasown": "^2.0.2" @@ -3423,18 +3409,6 @@ "node": ">=8" } }, - "node_modules/is-stream": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", - "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", - "dev": true, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", @@ -3629,13 +3603,10 @@ "dev": true }, "node_modules/loupe": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.1.1.tgz", - "integrity": "sha512-edNu/8D5MKVfGVFRhFf8aAxiTM6Wumfz5XsaatSxlD3w4R1d/WEKUTydCdPGbl9K7QG/Ca3GnDV2sIKIpXRQcw==", - "dev": true, - "dependencies": { - "get-func-name": "^2.0.1" - } + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.1.2.tgz", + "integrity": "sha512-23I4pFZHmAemUnz8WZXbYRSKYj801VDaNv9ETuMh7IrMc7VuVVSo+Z9iLE3ni30+U48iDWfi30d3twAXBYmnCg==", + "dev": true }, "node_modules/lru-cache": { "version": "6.0.0", @@ -3650,22 +3621,22 @@ } }, "node_modules/magic-string": { - "version": "0.30.11", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.11.tgz", - "integrity": "sha512-+Wri9p0QHMy+545hKww7YAu5NyzF8iomPL/RQazugQ9+Ez4Ic3mERMd8ZTX5rfK944j+560ZJi8iAwgak1Ac7A==", + "version": "0.30.15", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.15.tgz", + "integrity": "sha512-zXeaYRgZ6ldS1RJJUrMrYgNJ4fdwnyI6tVqoiIhyCyv5IVTK9BU8Ic2l253GGETQHxI4HNUwhJ3fjDhKqEoaAw==", "dev": true, "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0" } }, "node_modules/magicast": { - "version": "0.3.4", - "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.3.4.tgz", - "integrity": "sha512-TyDF/Pn36bBji9rWKHlZe+PZb6Mx5V8IHCSxk7X4aljM4e/vyDvZZYwHewdVaqiA0nb3ghfHU/6AUpDxWoER2Q==", + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.3.5.tgz", + "integrity": "sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ==", "dev": true, "dependencies": { - "@babel/parser": "^7.24.4", - "@babel/types": "^7.24.0", + "@babel/parser": "^7.25.4", + "@babel/types": "^7.25.4", "source-map-js": "^1.2.0" } }, @@ -3684,6 +3655,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/math-intrinsics": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.0.0.tgz", + "integrity": "sha512-4MqMiKP90ybymYvsut0CH2g4XWbfLtmlCkXmtmdcDCxNB+mQcu1w/1+L/VD7vi/PSv7X2JYV7SCcR+jiPXnQtA==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", @@ -3694,16 +3674,13 @@ } }, "node_modules/merge-descriptors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", - "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==", - "dev": true - }, - "node_modules/merge-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "dev": true + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, "node_modules/merge2": { "version": "1.4.1", @@ -3724,12 +3701,12 @@ } }, "node_modules/micromatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", - "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", "dev": true, "dependencies": { - "braces": "^3.0.2", + "braces": "^3.0.3", "picomatch": "^2.3.1" }, "engines": { @@ -3769,18 +3746,6 @@ "node": ">= 0.6" } }, - "node_modules/mimic-fn": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", - "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/minimalistic-assert": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", @@ -3788,9 +3753,9 @@ "dev": true }, "node_modules/minimatch": { - "version": "9.0.3", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", - "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", "dev": true, "dependencies": { "brace-expansion": "^2.0.1" @@ -3824,9 +3789,9 @@ } }, "node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "dev": true }, "node_modules/muggle-string": { @@ -3836,9 +3801,9 @@ "dev": true }, "node_modules/nanoid": { - "version": "3.3.7", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", - "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "version": "3.3.8", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz", + "integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==", "dev": true, "funding": [ { @@ -3868,33 +3833,6 @@ "node": ">= 0.6" } }, - "node_modules/npm-run-path": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.3.0.tgz", - "integrity": "sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==", - "dev": true, - "dependencies": { - "path-key": "^4.0.0" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/npm-run-path/node_modules/path-key": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", - "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -3905,9 +3843,9 @@ } }, "node_modules/object-inspect": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.2.tgz", - "integrity": "sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==", + "version": "1.13.3", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.3.tgz", + "integrity": "sha512-kDCGIbxkDSXE3euJZZXzc6to7fCrKHNI/hSRQnRuQ+BWjFNzZwiFF8fj/6o2t2G9/jTj8PSIYTfCLelLZEeRpA==", "dev": true, "engines": { "node": ">= 0.4" @@ -3943,21 +3881,6 @@ "node": ">= 0.8" } }, - "node_modules/onetime": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", - "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", - "dev": true, - "dependencies": { - "mimic-fn": "^4.0.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/optionator": { "version": "0.9.3", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", @@ -4097,20 +4020,11 @@ "dev": true }, "node_modules/path-to-regexp": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", - "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==", + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", + "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", "dev": true }, - "node_modules/path-type": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/pathe": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz", @@ -4127,9 +4041,9 @@ } }, "node_modules/picocolors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz", - "integrity": "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", "dev": true }, "node_modules/picomatch": { @@ -4156,9 +4070,9 @@ } }, "node_modules/postcss": { - "version": "8.4.41", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.41.tgz", - "integrity": "sha512-TesUflQ0WKZqAvg52PWL6kHgLKP6xB6heTOdoYM0Wt2UHyxNa4K25EZZMgKns3BH1RLVbZCREPpLY0rhnNoHVQ==", + "version": "8.4.49", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.49.tgz", + "integrity": "sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==", "dev": true, "funding": [ { @@ -4176,8 +4090,8 @@ ], "dependencies": { "nanoid": "^3.3.7", - "picocolors": "^1.0.1", - "source-map-js": "^1.2.0" + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" }, "engines": { "node": "^10 || ^12 || >=14" @@ -4221,12 +4135,12 @@ } }, "node_modules/qs": { - "version": "6.11.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", - "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", "dev": true, "dependencies": { - "side-channel": "^1.0.4" + "side-channel": "^1.0.6" }, "engines": { "node": ">=0.6" @@ -4348,12 +4262,12 @@ } }, "node_modules/rollup": { - "version": "4.20.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.20.0.tgz", - "integrity": "sha512-6rbWBChcnSGzIlXeIdNIZTopKYad8ZG8ajhl78lGRLsI2rX8IkaotQhVas2Ma+GPxJav19wrSzvRvuiv0YKzWw==", + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.28.1.tgz", + "integrity": "sha512-61fXYl/qNVinKmGSTHAZ6Yy8I3YIJC/r2m9feHo6SwVAVcLT5MPwOUFe7EuURA/4m0NR8lXG4BBXuo/IZEsjMg==", "dev": true, "dependencies": { - "@types/estree": "1.0.5" + "@types/estree": "1.0.6" }, "bin": { "rollup": "dist/bin/rollup" @@ -4363,22 +4277,25 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.20.0", - "@rollup/rollup-android-arm64": "4.20.0", - "@rollup/rollup-darwin-arm64": "4.20.0", - "@rollup/rollup-darwin-x64": "4.20.0", - "@rollup/rollup-linux-arm-gnueabihf": "4.20.0", - "@rollup/rollup-linux-arm-musleabihf": "4.20.0", - "@rollup/rollup-linux-arm64-gnu": "4.20.0", - "@rollup/rollup-linux-arm64-musl": "4.20.0", - "@rollup/rollup-linux-powerpc64le-gnu": "4.20.0", - "@rollup/rollup-linux-riscv64-gnu": "4.20.0", - "@rollup/rollup-linux-s390x-gnu": "4.20.0", - "@rollup/rollup-linux-x64-gnu": "4.20.0", - "@rollup/rollup-linux-x64-musl": "4.20.0", - "@rollup/rollup-win32-arm64-msvc": "4.20.0", - "@rollup/rollup-win32-ia32-msvc": "4.20.0", - "@rollup/rollup-win32-x64-msvc": "4.20.0", + "@rollup/rollup-android-arm-eabi": "4.28.1", + "@rollup/rollup-android-arm64": "4.28.1", + "@rollup/rollup-darwin-arm64": "4.28.1", + "@rollup/rollup-darwin-x64": "4.28.1", + "@rollup/rollup-freebsd-arm64": "4.28.1", + "@rollup/rollup-freebsd-x64": "4.28.1", + "@rollup/rollup-linux-arm-gnueabihf": "4.28.1", + "@rollup/rollup-linux-arm-musleabihf": "4.28.1", + "@rollup/rollup-linux-arm64-gnu": "4.28.1", + "@rollup/rollup-linux-arm64-musl": "4.28.1", + "@rollup/rollup-linux-loongarch64-gnu": "4.28.1", + "@rollup/rollup-linux-powerpc64le-gnu": "4.28.1", + "@rollup/rollup-linux-riscv64-gnu": "4.28.1", + "@rollup/rollup-linux-s390x-gnu": "4.28.1", + "@rollup/rollup-linux-x64-gnu": "4.28.1", + "@rollup/rollup-linux-x64-musl": "4.28.1", + "@rollup/rollup-win32-arm64-msvc": "4.28.1", + "@rollup/rollup-win32-ia32-msvc": "4.28.1", + "@rollup/rollup-win32-x64-msvc": "4.28.1", "fsevents": "~2.3.2" } }, @@ -4424,13 +4341,10 @@ "dev": true }, "node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, "bin": { "semver": "bin/semver.js" }, @@ -4439,9 +4353,9 @@ } }, "node_modules/send": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", - "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", + "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", "dev": true, "dependencies": { "debug": "2.6.9", @@ -4477,22 +4391,25 @@ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "dev": true }, - "node_modules/send/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true + "node_modules/send/node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "dev": true, + "engines": { + "node": ">= 0.8" + } }, "node_modules/serve-static": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", - "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", + "version": "1.16.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", + "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", "dev": true, "dependencies": { - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "parseurl": "~1.3.3", - "send": "0.18.0" + "send": "0.19.0" }, "engines": { "node": ">= 0.8.0" @@ -4543,15 +4460,69 @@ } }, "node_modules/side-channel": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", - "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", "dev": true, "dependencies": { - "call-bind": "^1.0.7", "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.4", - "object-inspect": "^1.13.1" + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "dev": true, + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "dev": true, + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "dev": true, + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" }, "engines": { "node": ">= 0.4" @@ -4578,15 +4549,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -4597,9 +4559,9 @@ } }, "node_modules/source-map-js": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", - "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", "dev": true, "engines": { "node": ">=0.10.0" @@ -4657,9 +4619,9 @@ } }, "node_modules/std-env": { - "version": "3.7.0", - "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.7.0.tgz", - "integrity": "sha512-JPbdCEQLj1w5GilpiHAx3qJvFndqybBysA3qUOnznweH4QbNYUsW/ea8QzSrnh0vNsezMMw5bcVool8lM0gwzg==", + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.8.0.tgz", + "integrity": "sha512-Bc3YwwCB+OzldMxOXJIIvC6cPRWr/LxOp48CdQTOkPyk/t4JWWJbrilwBd7RJzKV8QW7tJkcgAmeuLLJugl5/w==", "dev": true }, "node_modules/string_decoder": { @@ -4790,18 +4752,6 @@ "node": ">=8" } }, - "node_modules/strip-final-newline": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", - "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/strip-json-comments": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", @@ -4852,21 +4802,6 @@ "node": ">=18" } }, - "node_modules/test-exclude/node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", - "dev": true, - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", @@ -4879,10 +4814,16 @@ "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", "dev": true }, + "node_modules/tinyexec": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.1.tgz", + "integrity": "sha512-WiCJLEECkO18gwqIp6+hJg0//p23HXp4S+gGtAKu3mI2F2/sXC4FvHvXvB0zJVVaTPhx1/tOwdbRsa1sOBIKqQ==", + "dev": true + }, "node_modules/tinypool": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.0.0.tgz", - "integrity": "sha512-KIKExllK7jp3uvrNtvRBYBWBOAXSX8ZvoaD8T+7KB/QHIuoJW3Pmr60zucywjAlMb5TeXUkcs/MWeWLu0qvuAQ==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.0.2.tgz", + "integrity": "sha512-al6n+QEANGFOMf/dmUMsuS5/r9B06uwlyNjZZql/zv8J7ybHCgoihBNORZCY2mzUuAnomQa2JdhyHKzZxPCrFA==", "dev": true, "engines": { "node": "^18.0.0 || >=20.0.0" @@ -4898,23 +4839,14 @@ } }, "node_modules/tinyspy": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-3.0.0.tgz", - "integrity": "sha512-q5nmENpTHgiPVd1cJDDc9cVoYN5x4vCvwT3FMilvKPKneCBZAxn2YWQjDF0UMcE9k0Cay1gBiDfTMU0g+mPMQA==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-3.0.2.tgz", + "integrity": "sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==", "dev": true, "engines": { "node": ">=14.0.0" } }, - "node_modules/to-fast-properties": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", - "dev": true, - "engines": { - "node": ">=4" - } - }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -4937,9 +4869,9 @@ } }, "node_modules/ts-api-utils": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.3.0.tgz", - "integrity": "sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==", + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.4.3.tgz", + "integrity": "sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw==", "dev": true, "engines": { "node": ">=16" @@ -4974,9 +4906,9 @@ } }, "node_modules/typescript": { - "version": "5.5.4", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.4.tgz", - "integrity": "sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==", + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.2.tgz", + "integrity": "sha512-i5t66RHxDvVN40HfDd1PsEThGNnlMCMT3jMUuoh9/0TaqWevNontacunWyN02LA9/fIbEWlcHZcgTKb9QoaLfg==", "dev": true, "bin": { "tsc": "bin/tsc", @@ -4987,14 +4919,14 @@ } }, "node_modules/typescript-eslint": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.0.1.tgz", - "integrity": "sha512-V3Y+MdfhawxEjE16dWpb7/IOgeXnLwAEEkS7v8oDqNcR1oYlqWhGH/iHqHdKVdpWme1VPZ0SoywXAkCqawj2eQ==", + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.18.0.tgz", + "integrity": "sha512-Xq2rRjn6tzVpAyHr3+nmSg1/9k9aIHnJ2iZeOH7cfGOWqTkXTm3kwpQglEuLGdNrYvPF+2gtAs+/KF5rjVo+WQ==", "dev": true, "dependencies": { - "@typescript-eslint/eslint-plugin": "8.0.1", - "@typescript-eslint/parser": "8.0.1", - "@typescript-eslint/utils": "8.0.1" + "@typescript-eslint/eslint-plugin": "8.18.0", + "@typescript-eslint/parser": "8.18.0", + "@typescript-eslint/utils": "8.18.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -5003,221 +4935,57 @@ "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.8.0" } }, - "node_modules/typescript-eslint/node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.0.1.tgz", - "integrity": "sha512-5g3Y7GDFsJAnY4Yhvk8sZtFfV6YNF2caLzjrRPUBzewjPCaj0yokePB4LJSobyCzGMzjZZYFbwuzbfDHlimXbQ==", + "node_modules/ufo": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.5.4.tgz", + "integrity": "sha512-UsUk3byDzKd04EyoZ7U4DOlxQaD14JUKQl6/P7wiX4FNvUfm3XL246n9W5AmqwW5RSFJ27NAuM0iLscAOYUiGQ==", + "dev": true + }, + "node_modules/uglify-js": { + "version": "3.17.4", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.17.4.tgz", + "integrity": "sha512-T9q82TJI9e/C1TAxYvfb16xO120tMVFZrGA3f9/P4424DNu6ypK103y0GPFVa17yotwSyZW5iYXgjYHkGrJW/g==", "dev": true, - "dependencies": { - "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.0.1", - "@typescript-eslint/type-utils": "8.0.1", - "@typescript-eslint/utils": "8.0.1", - "@typescript-eslint/visitor-keys": "8.0.1", - "graphemer": "^1.4.0", - "ignore": "^5.3.1", - "natural-compare": "^1.4.0", - "ts-api-utils": "^1.3.0" + "bin": { + "uglifyjs": "bin/uglifyjs" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "@typescript-eslint/parser": "^8.0.0 || ^8.0.0-alpha.0", - "eslint": "^8.57.0 || ^9.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "node": ">=0.8.0" } }, - "node_modules/typescript-eslint/node_modules/@typescript-eslint/parser": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.0.1.tgz", - "integrity": "sha512-5IgYJ9EO/12pOUwiBKFkpU7rS3IU21mtXzB81TNwq2xEybcmAZrE9qwDtsb5uQd9aVO9o0fdabFyAmKveXyujg==", + "node_modules/undici-types": { + "version": "6.19.8", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", + "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", + "dev": true + }, + "node_modules/universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", "dev": true, - "dependencies": { - "@typescript-eslint/scope-manager": "8.0.1", - "@typescript-eslint/types": "8.0.1", - "@typescript-eslint/typescript-estree": "8.0.1", - "@typescript-eslint/visitor-keys": "8.0.1", - "debug": "^4.3.4" - }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "node": ">= 4.0.0" } }, - "node_modules/typescript-eslint/node_modules/@typescript-eslint/scope-manager": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.0.1.tgz", - "integrity": "sha512-NpixInP5dm7uukMiRyiHjRKkom5RIFA4dfiHvalanD2cF0CLUuQqxfg8PtEUo9yqJI2bBhF+pcSafqnG3UBnRQ==", + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", "dev": true, - "dependencies": { - "@typescript-eslint/types": "8.0.1", - "@typescript-eslint/visitor-keys": "8.0.1" - }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" + "node": ">= 0.8" } }, - "node_modules/typescript-eslint/node_modules/@typescript-eslint/types": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.0.1.tgz", - "integrity": "sha512-PpqTVT3yCA/bIgJ12czBuE3iBlM3g4inRSC5J0QOdQFAn07TYrYEQBBKgXH1lQpglup+Zy6c1fxuwTk4MTNKIw==", - "dev": true, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/typescript-eslint/node_modules/@typescript-eslint/typescript-estree": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.0.1.tgz", - "integrity": "sha512-8V9hriRvZQXPWU3bbiUV4Epo7EvgM6RTs+sUmxp5G//dBGy402S7Fx0W0QkB2fb4obCF8SInoUzvTYtc3bkb5w==", - "dev": true, - "dependencies": { - "@typescript-eslint/types": "8.0.1", - "@typescript-eslint/visitor-keys": "8.0.1", - "debug": "^4.3.4", - "globby": "^11.1.0", - "is-glob": "^4.0.3", - "minimatch": "^9.0.4", - "semver": "^7.6.0", - "ts-api-utils": "^1.3.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/typescript-eslint/node_modules/@typescript-eslint/visitor-keys": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.0.1.tgz", - "integrity": "sha512-W5E+o0UfUcK5EgchLZsyVWqARmsM7v54/qEq6PY3YI5arkgmCzHiuk0zKSJJbm71V0xdRna4BGomkCTXz2/LkQ==", - "dev": true, - "dependencies": { - "@typescript-eslint/types": "8.0.1", - "eslint-visitor-keys": "^3.4.3" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/typescript-eslint/node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", - "dev": true, - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/typescript-eslint/node_modules/semver": { - "version": "7.6.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", - "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/ufo": { - "version": "1.5.4", - "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.5.4.tgz", - "integrity": "sha512-UsUk3byDzKd04EyoZ7U4DOlxQaD14JUKQl6/P7wiX4FNvUfm3XL246n9W5AmqwW5RSFJ27NAuM0iLscAOYUiGQ==", - "dev": true - }, - "node_modules/uglify-js": { - "version": "3.17.4", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.17.4.tgz", - "integrity": "sha512-T9q82TJI9e/C1TAxYvfb16xO120tMVFZrGA3f9/P4424DNu6ypK103y0GPFVa17yotwSyZW5iYXgjYHkGrJW/g==", - "dev": true, - "bin": { - "uglifyjs": "bin/uglifyjs" - }, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/undici-types": { - "version": "5.26.5", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", - "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", - "dev": true - }, - "node_modules/universalify": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", - "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", - "dev": true, - "engines": { - "node": ">= 4.0.0" - } - }, - "node_modules/unpipe": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", - "dev": true, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", "dev": true, "dependencies": { "punycode": "^2.1.0" @@ -5248,20 +5016,20 @@ } }, "node_modules/vite": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.0.tgz", - "integrity": "sha512-5xokfMX0PIiwCMCMb9ZJcMyh5wbBun0zUzKib+L65vAZ8GY9ePZMXxFrHbr/Kyll2+LSCY7xtERPpxkBDKngwg==", + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.0.3.tgz", + "integrity": "sha512-Cmuo5P0ENTN6HxLSo6IHsjCLn/81Vgrp81oaiFFMRa8gGDj5xEjIcEpf2ZymZtZR8oU0P2JX5WuUp/rlXcHkAw==", "dev": true, "dependencies": { - "esbuild": "^0.21.3", - "postcss": "^8.4.40", - "rollup": "^4.13.0" + "esbuild": "^0.24.0", + "postcss": "^8.4.49", + "rollup": "^4.23.0" }, "bin": { "vite": "bin/vite.js" }, "engines": { - "node": "^18.0.0 || >=20.0.0" + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" }, "funding": { "url": "https://github.com/vitejs/vite?sponsor=1" @@ -5270,19 +5038,25 @@ "fsevents": "~2.3.3" }, "peerDependencies": { - "@types/node": "^18.0.0 || >=20.0.0", + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "jiti": ">=1.21.0", "less": "*", "lightningcss": "^1.21.0", "sass": "*", "sass-embedded": "*", "stylus": "*", "sugarss": "*", - "terser": "^5.4.0" + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" }, "peerDependenciesMeta": { "@types/node": { "optional": true }, + "jiti": { + "optional": true + }, "less": { "optional": true }, @@ -5303,19 +5077,25 @@ }, "terser": { "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true } } }, "node_modules/vite-node": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-2.0.5.tgz", - "integrity": "sha512-LdsW4pxj0Ot69FAoXZ1yTnA9bjGohr2yNBU7QKRxpz8ITSkhuDl6h3zS/tvgz4qrNjeRnvrWeXQ8ZF7Um4W00Q==", + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-2.1.8.tgz", + "integrity": "sha512-uPAwSr57kYjAUux+8E2j0q0Fxpn8M9VoyfGiRI8Kfktz9NcYMCenwY5RnZxnF1WTu3TGiYipirIzacLL3VVGFg==", "dev": true, "dependencies": { "cac": "^6.7.14", - "debug": "^4.3.5", + "debug": "^4.3.7", + "es-module-lexer": "^1.5.4", "pathe": "^1.1.2", - "tinyrainbow": "^1.2.0", "vite": "^5.0.0" }, "bin": { @@ -5328,192 +5108,1114 @@ "url": "https://opencollective.com/vitest" } }, - "node_modules/vite-plugin-dts": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/vite-plugin-dts/-/vite-plugin-dts-4.0.2.tgz", - "integrity": "sha512-Ni3EPG8yeLc5ivEzT4szreJ0rXpEQgvdYq3PaZ7OMoHc8uET4/HRUfzVPejJaUAojbxsKgaZbp6Zgm41sxb86Q==", + "node_modules/vite-node/node_modules/@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "cpu": [ + "ppc64" + ], "dev": true, - "dependencies": { - "@microsoft/api-extractor": "7.47.4", - "@rollup/pluginutils": "^5.1.0", - "@volar/typescript": "^2.3.4", - "@vue/language-core": "2.0.29", - "compare-versions": "^6.1.1", - "debug": "^4.3.6", - "kolorist": "^1.8.0", - "local-pkg": "^0.5.0", - "magic-string": "^0.30.11", - "vue-tsc": "2.0.29" - }, + "optional": true, + "os": [ + "aix" + ], "engines": { - "node": "^14.18.0 || >=16.0.0" - }, - "peerDependencies": { - "typescript": "*", - "vite": "*" - }, - "peerDependenciesMeta": { - "vite": { - "optional": true - } + "node": ">=12" } }, - "node_modules/vite-plugin-eslint": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/vite-plugin-eslint/-/vite-plugin-eslint-1.8.1.tgz", - "integrity": "sha512-PqdMf3Y2fLO9FsNPmMX+//2BF5SF8nEWspZdgl4kSt7UvHDRHVVfHvxsD7ULYzZrJDGRxR81Nq7TOFgwMnUang==", + "node_modules/vite-node/node_modules/@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "cpu": [ + "arm" + ], "dev": true, - "dependencies": { - "@rollup/pluginutils": "^4.2.1", - "@types/eslint": "^8.4.5", - "rollup": "^2.77.2" - }, - "peerDependencies": { - "eslint": ">=7", - "vite": ">=2" + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" } }, - "node_modules/vite-plugin-eslint/node_modules/@rollup/pluginutils": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-4.2.1.tgz", - "integrity": "sha512-iKnFXr7NkdZAIHiIWE+BX5ULi/ucVFYWD6TbAV+rZctiRTY2PL6tsIKhoIOaoskiWAkgu+VsbXgUVDNLHf+InQ==", + "node_modules/vite-node/node_modules/@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "cpu": [ + "arm64" + ], "dev": true, - "dependencies": { - "estree-walker": "^2.0.1", - "picomatch": "^2.2.2" - }, + "optional": true, + "os": [ + "android" + ], "engines": { - "node": ">= 8.0.0" + "node": ">=12" } }, - "node_modules/vite-plugin-eslint/node_modules/rollup": { - "version": "2.79.1", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.79.1.tgz", - "integrity": "sha512-uKxbd0IhMZOhjAiD5oAFp7BqvkA4Dv47qpOCtaNvng4HBwdbWtdOh8f5nZNuk2rp51PMGk3bzfWu5oayNEuYnw==", + "node_modules/vite-node/node_modules/@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "cpu": [ + "x64" + ], "dev": true, - "bin": { - "rollup": "dist/bin/rollup" - }, + "optional": true, + "os": [ + "android" + ], "engines": { - "node": ">=10.0.0" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" + "node": ">=12" } }, - "node_modules/vitest": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-2.0.5.tgz", - "integrity": "sha512-8GUxONfauuIdeSl5f9GTgVEpg5BTOlplET4WEDaeY2QBiN8wSm68vxN/tb5z405OwppfoCavnwXafiaYBC/xOA==", + "node_modules/vite-node/node_modules/@esbuild/darwin-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "cpu": [ + "arm64" + ], "dev": true, - "dependencies": { - "@ampproject/remapping": "^2.3.0", - "@vitest/expect": "2.0.5", - "@vitest/pretty-format": "^2.0.5", - "@vitest/runner": "2.0.5", - "@vitest/snapshot": "2.0.5", - "@vitest/spy": "2.0.5", - "@vitest/utils": "2.0.5", - "chai": "^5.1.1", - "debug": "^4.3.5", - "execa": "^8.0.1", - "magic-string": "^0.30.10", - "pathe": "^1.1.2", - "std-env": "^3.7.0", - "tinybench": "^2.8.0", - "tinypool": "^1.0.0", - "tinyrainbow": "^1.2.0", - "vite": "^5.0.0", - "vite-node": "2.0.5", - "why-is-node-running": "^2.3.0" - }, - "bin": { - "vitest": "vitest.mjs" - }, + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": "^18.0.0 || >=20.0.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - }, - "peerDependencies": { - "@edge-runtime/vm": "*", - "@types/node": "^18.0.0 || >=20.0.0", - "@vitest/browser": "2.0.5", - "@vitest/ui": "2.0.5", - "happy-dom": "*", - "jsdom": "*" - }, - "peerDependenciesMeta": { - "@edge-runtime/vm": { - "optional": true - }, - "@types/node": { - "optional": true - }, - "@vitest/browser": { - "optional": true - }, + "node": ">=12" + } + }, + "node_modules/vite-node/node_modules/@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite-node/node_modules/@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite-node/node_modules/@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite-node/node_modules/@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite-node/node_modules/@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite-node/node_modules/@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite-node/node_modules/@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite-node/node_modules/@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite-node/node_modules/@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite-node/node_modules/@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite-node/node_modules/@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite-node/node_modules/@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite-node/node_modules/@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite-node/node_modules/@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite-node/node_modules/@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite-node/node_modules/@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite-node/node_modules/@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite-node/node_modules/@esbuild/win32-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite-node/node_modules/esbuild": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "dev": true, + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" + } + }, + "node_modules/vite-node/node_modules/vite": { + "version": "5.4.11", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.11.tgz", + "integrity": "sha512-c7jFQRklXua0mTzneGW9QVyxFjUgwcihC4bXEtujIo2ouWCe1Ajt/amn2PCxYnhYfd5k09JX3SB7OYWFKYqj8Q==", + "dev": true, + "dependencies": { + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/vite-plugin-dts": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/vite-plugin-dts/-/vite-plugin-dts-4.3.0.tgz", + "integrity": "sha512-LkBJh9IbLwL6/rxh0C1/bOurDrIEmRE7joC+jFdOEEciAFPbpEKOLSAr5nNh5R7CJ45cMbksTrFfy52szzC5eA==", + "dev": true, + "dependencies": { + "@microsoft/api-extractor": "^7.47.11", + "@rollup/pluginutils": "^5.1.0", + "@volar/typescript": "^2.4.4", + "@vue/language-core": "2.1.6", + "compare-versions": "^6.1.1", + "debug": "^4.3.6", + "kolorist": "^1.8.0", + "local-pkg": "^0.5.0", + "magic-string": "^0.30.11" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "peerDependencies": { + "typescript": "*", + "vite": "*" + }, + "peerDependenciesMeta": { + "vite": { + "optional": true + } + } + }, + "node_modules/vite-plugin-eslint": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/vite-plugin-eslint/-/vite-plugin-eslint-1.8.1.tgz", + "integrity": "sha512-PqdMf3Y2fLO9FsNPmMX+//2BF5SF8nEWspZdgl4kSt7UvHDRHVVfHvxsD7ULYzZrJDGRxR81Nq7TOFgwMnUang==", + "dev": true, + "dependencies": { + "@rollup/pluginutils": "^4.2.1", + "@types/eslint": "^8.4.5", + "rollup": "^2.77.2" + }, + "peerDependencies": { + "eslint": ">=7", + "vite": ">=2" + } + }, + "node_modules/vite-plugin-eslint/node_modules/@rollup/pluginutils": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-4.2.1.tgz", + "integrity": "sha512-iKnFXr7NkdZAIHiIWE+BX5ULi/ucVFYWD6TbAV+rZctiRTY2PL6tsIKhoIOaoskiWAkgu+VsbXgUVDNLHf+InQ==", + "dev": true, + "dependencies": { + "estree-walker": "^2.0.1", + "picomatch": "^2.2.2" + }, + "engines": { + "node": ">= 8.0.0" + } + }, + "node_modules/vite-plugin-eslint/node_modules/rollup": { + "version": "2.79.2", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.79.2.tgz", + "integrity": "sha512-fS6iqSPZDs3dr/y7Od6y5nha8dW1YnbgtsyotCVvoFGKbERG++CVRFv1meyGDE1SNItQA8BrnCw7ScdAhRJ3XQ==", + "dev": true, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=10.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/vitest": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-2.1.8.tgz", + "integrity": "sha512-1vBKTZskHw/aosXqQUlVWWlGUxSJR8YtiyZDJAFeW2kPAeX6S3Sool0mjspO+kXLuxVWlEDDowBAeqeAQefqLQ==", + "dev": true, + "dependencies": { + "@vitest/expect": "2.1.8", + "@vitest/mocker": "2.1.8", + "@vitest/pretty-format": "^2.1.8", + "@vitest/runner": "2.1.8", + "@vitest/snapshot": "2.1.8", + "@vitest/spy": "2.1.8", + "@vitest/utils": "2.1.8", + "chai": "^5.1.2", + "debug": "^4.3.7", + "expect-type": "^1.1.0", + "magic-string": "^0.30.12", + "pathe": "^1.1.2", + "std-env": "^3.8.0", + "tinybench": "^2.9.0", + "tinyexec": "^0.3.1", + "tinypool": "^1.0.1", + "tinyrainbow": "^1.2.0", + "vite": "^5.0.0", + "vite-node": "2.1.8", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@types/node": "^18.0.0 || >=20.0.0", + "@vitest/browser": "2.1.8", + "@vitest/ui": "2.1.8", + "happy-dom": "*", + "jsdom": "*" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser": { + "optional": true + }, "@vitest/ui": { "optional": true }, "happy-dom": { "optional": true }, - "jsdom": { + "jsdom": { + "optional": true + } + } + }, + "node_modules/vitest/node_modules/@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vitest/node_modules/@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vitest/node_modules/@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vitest/node_modules/@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vitest/node_modules/@esbuild/darwin-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vitest/node_modules/@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vitest/node_modules/@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vitest/node_modules/@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vitest/node_modules/@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vitest/node_modules/@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vitest/node_modules/@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vitest/node_modules/@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vitest/node_modules/@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vitest/node_modules/@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vitest/node_modules/@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vitest/node_modules/@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vitest/node_modules/@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vitest/node_modules/@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vitest/node_modules/@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vitest/node_modules/@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vitest/node_modules/@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vitest/node_modules/@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vitest/node_modules/@esbuild/win32-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vitest/node_modules/@vitest/mocker": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-2.1.8.tgz", + "integrity": "sha512-7guJ/47I6uqfttp33mgo6ga5Gr1VnL58rcqYKyShoRK9ebu8T5Rs6HN3s1NABiBeVTdWNrwUMcHH54uXZBN4zA==", + "dev": true, + "dependencies": { + "@vitest/spy": "2.1.8", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.12" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^5.0.0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { "optional": true } } }, - "node_modules/vscode-uri": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.0.8.tgz", - "integrity": "sha512-AyFQ0EVmsOZOlAnxoFOGOq1SQDWAB7C6aqMGS23svWAllfOaxbuFvcT8D1i8z3Gyn8fraVeZNNmN6e9bxxXkKw==", - "dev": true - }, - "node_modules/vue-tsc": { - "version": "2.0.29", - "resolved": "https://registry.npmjs.org/vue-tsc/-/vue-tsc-2.0.29.tgz", - "integrity": "sha512-MHhsfyxO3mYShZCGYNziSbc63x7cQ5g9kvijV7dRe1TTXBRLxXyL0FnXWpUF1xII2mJ86mwYpYsUmMwkmerq7Q==", + "node_modules/vitest/node_modules/esbuild": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", "dev": true, - "dependencies": { - "@volar/typescript": "~2.4.0-alpha.18", - "@vue/language-core": "2.0.29", - "semver": "^7.5.4" - }, + "hasInstallScript": true, "bin": { - "vue-tsc": "bin/vue-tsc.js" + "esbuild": "bin/esbuild" }, - "peerDependencies": { - "typescript": ">=5.0.0" + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" } }, - "node_modules/vue-tsc/node_modules/@volar/language-core": { - "version": "2.4.0-alpha.18", - "resolved": "https://registry.npmjs.org/@volar/language-core/-/language-core-2.4.0-alpha.18.tgz", - "integrity": "sha512-JAYeJvYQQROmVRtSBIczaPjP3DX4QW1fOqW1Ebs0d3Y3EwSNRglz03dSv0Dm61dzd0Yx3WgTW3hndDnTQqgmyg==", + "node_modules/vitest/node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", "dev": true, "dependencies": { - "@volar/source-map": "2.4.0-alpha.18" + "@types/estree": "^1.0.0" } }, - "node_modules/vue-tsc/node_modules/@volar/source-map": { - "version": "2.4.0-alpha.18", - "resolved": "https://registry.npmjs.org/@volar/source-map/-/source-map-2.4.0-alpha.18.tgz", - "integrity": "sha512-MTeCV9MUwwsH0sNFiZwKtFrrVZUK6p8ioZs3xFzHc2cvDXHWlYN3bChdQtwKX+FY2HG6H3CfAu1pKijolzIQ8g==", - "dev": true - }, - "node_modules/vue-tsc/node_modules/@volar/typescript": { - "version": "2.4.0-alpha.18", - "resolved": "https://registry.npmjs.org/@volar/typescript/-/typescript-2.4.0-alpha.18.tgz", - "integrity": "sha512-sXh5Y8sqGUkgxpMWUGvRXggxYHAVxg0Pa1C42lQZuPDrW6vHJPR0VCK8Sr7WJsAW530HuNQT/ZIskmXtxjybMQ==", + "node_modules/vitest/node_modules/vite": { + "version": "5.4.11", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.11.tgz", + "integrity": "sha512-c7jFQRklXua0mTzneGW9QVyxFjUgwcihC4bXEtujIo2ouWCe1Ajt/amn2PCxYnhYfd5k09JX3SB7OYWFKYqj8Q==", "dev": true, "dependencies": { - "@volar/language-core": "2.4.0-alpha.18", - "path-browserify": "^1.0.1", - "vscode-uri": "^3.0.8" + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } } }, + "node_modules/vscode-uri": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.0.8.tgz", + "integrity": "sha512-AyFQ0EVmsOZOlAnxoFOGOq1SQDWAB7C6aqMGS23svWAllfOaxbuFvcT8D1i8z3Gyn8fraVeZNNmN6e9bxxXkKw==", + "dev": true + }, "node_modules/wbuf": { "version": "1.7.3", "resolved": "https://registry.npmjs.org/wbuf/-/wbuf-1.7.3.tgz", @@ -5524,9 +6226,27 @@ } }, "node_modules/web-device-mux": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/web-device-mux/-/web-device-mux-0.2.3.tgz", - "integrity": "sha512-Q1C7Swjr2t5BfgSni/GsfPUrCX0XFi/x0r8P8DBDbitNet6tYG4UgyfqF6FlIr9WDIl3QqZFLlYaM7Vsv3e6DQ==" + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/web-device-mux/-/web-device-mux-0.5.0.tgz", + "integrity": "sha512-LIMhsojxpz7reK4LmdUA21Etmd68Fsh1C/s6+/PjhGm0CfOzQ+YoaPMeR6cv3L8iByqkzsrCa89YOFlwnhFxKQ==" + }, + "node_modules/webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/whatwg-mimetype": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz", + "integrity": "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==", + "dev": true, + "engines": { + "node": ">=12" + } }, "node_modules/which": { "version": "2.0.2", diff --git a/package.json b/package.json index b23a9b9..e08e2a7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "web-receiptline-printer", - "version": "0.2.0-rc5", + "version": "0.3.0-rc1", "description": "A small library for printing ReceiptLine documents to various receipt printers from a browser.", "type": "module", "repository": { @@ -21,11 +21,11 @@ "tm-t88v" ], "scripts": { - "dev": "vite", "build": "tsc && vite build", - "preview": "vite preview", "lint": "eslint .", "test": "vitest --run", + "test:snapshot": "vitest --run -u", + "serve-local": "export PORT=4444 || set PORT=4444 && serve .", "all": "npm run build && npm run lint && npm run test" }, "exports": { @@ -43,20 +43,21 @@ "devDependencies": { "@eslint/js": "^9.9.0", "@types/eslint__js": "^8.42.3", - "@types/node": "^20.8.9", + "@types/node": "^20.14.8", "@types/w3c-web-usb": "^1.0.10", - "@vitest/coverage-v8": "^2.0.5", + "@vitest/coverage-v8": "^2.1.6", "eslint": "^9.9.0", "globals": "^15.9.0", + "happy-dom": "^15.11.7", "https-localhost": "^4.7.1", - "typescript": "^5.5.4", - "typescript-eslint": "^8.0.1", - "vite": "^5.4.0", - "vite-plugin-dts": "^4.0.2", + "typescript": "^5.7.2", + "typescript-eslint": "^8.16.0", + "vite": "^6.0.0", + "vite-plugin-dts": "^4.3.0", "vite-plugin-eslint": "^1.8.1", - "vitest": "^2.0.5" + "vitest": "^2.1.6" }, "dependencies": { - "web-device-mux": "^0.2.3" + "web-device-mux": "^0.5.0" } } diff --git a/src/ReceiptLine/RECEIPTLINE.js b/src/ReceiptLine/RECEIPTLINE.js index 8ed5198..36602a5 100644 --- a/src/ReceiptLine/RECEIPTLINE.js +++ b/src/ReceiptLine/RECEIPTLINE.js @@ -2015,7 +2015,11 @@ limitations under the License. return r; }, // generate CODE128 data: - code128: data => data.replace(/((?!^[\x00-\x7f]+$).)*/, '').replace(/%/g, '%0').replace(/[\x00-\x1f]/g, m => '%' + String.fromCharCode(m.charCodeAt(0) + 64)).replace(/\x7f/g, '%5') + code128: data => data + .replace(/((?!^[\x00-\x7f]+$).)*/, '') + .replace(/%/g, '%0') + .replace(/[\x00-\x1f]/g, m => '%' + String.fromCharCode(m.charCodeAt(0) + 64)) + .replace(/\x7f/g, '%5') }; // @@ -2660,14 +2664,18 @@ limitations under the License. base: Object.assign({}, _base), svg: Object.assign({}, _base, _svg), text: Object.assign({}, _base, _text), + escpos: Object.assign({}, _base, _escpos, _thermal), epson: Object.assign({}, _base, _escpos, _thermal), + sii: Object.assign({}, _base, _escpos, _thermal, _sii), citizen: Object.assign({}, _base, _escpos, _thermal, _citizen), fit: Object.assign({}, _base, _escpos, _thermal, _fit), impact: Object.assign({}, _base, _escpos, _impact), impactb: Object.assign({}, _base, _escpos, _impact, _fontb), + generic: Object.assign({}, _base, _escpos, _thermal, _generic), + starsbcs: Object.assign({}, _base, _star, _sbcs), starmbcs: Object.assign({}, _base, _star, _mbcs), starmbcs2: Object.assign({}, _base, _star, _mbcs2), diff --git a/tsconfig.json b/tsconfig.json index 4aed18a..67cfa33 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -20,7 +20,9 @@ "noUnusedParameters": true, "forceConsistentCasingInFileNames": true, "verbatimModuleSyntax": true, - "incremental": true + "noImplicitOverride": true, + "incremental": true, + "resolveJsonModule": true }, "include": [ "src" diff --git a/vite.config.ts b/vite.config.ts index 8f26f56..d7a30ef 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -16,10 +16,19 @@ export default defineConfig({ define: { 'import.meta.vitest': 'undefined', }, - plugins: [dts(), eslint({ - failOnError: false - })], + plugins: [ + dts({ + exclude: [ + '**/node_modules', + '**/*.test.ts' + ] + }), + eslint({ + failOnError: false + }) + ], test: { + environment: 'happy-dom', includeSource: ['src/**/*.{js,ts}'], coverage: { enabled: true, @@ -27,6 +36,7 @@ export default defineConfig({ reporter: ['text', 'json-summary', 'json'], // If you want a coverage reports even if your tests are failing, include the reportOnFailure option reportOnFailure: true, + include: ['src/**'] } } }); From 196f57716dd04c4f62fb8ac0bc9291c420c3f6d8 Mon Sep 17 00:00:00 2001 From: Cellivar <1441553+Cellivar@users.noreply.github.com> Date: Thu, 2 Jan 2025 03:54:10 -0800 Subject: [PATCH 2/8] Migrate util module --- src/NumericRange.test.ts | 40 --------- src/Printers/Codepages/index.ts | 53 ----------- src/Util/ASCII.test.ts | 41 +++++++++ src/{Printers/Codepages => Util}/ASCII.ts | 26 ++++++ .../Codepages => Util}/CodepageEncoder.ts | 53 ++++++++++- src/Util/EnumUtils.test.ts | 21 +++++ src/Util/EnumUtils.ts | 10 +++ src/Util/NumericRange.test.ts | 36 ++++++++ src/{ => Util}/NumericRange.ts | 18 +++- src/Util/StringUtils.test.ts | 75 ++++++++++++++++ src/Util/StringUtils.ts | 90 +++++++++++++++++++ src/{ => Util}/WebReceiptLineError.ts | 0 src/Util/index.ts | 5 ++ src/WebReceiptlinePrinterError.ts | 7 -- 14 files changed, 369 insertions(+), 106 deletions(-) delete mode 100644 src/NumericRange.test.ts delete mode 100644 src/Printers/Codepages/index.ts create mode 100644 src/Util/ASCII.test.ts rename src/{Printers/Codepages => Util}/ASCII.ts (78%) rename src/{Printers/Codepages => Util}/CodepageEncoder.ts (96%) create mode 100644 src/Util/EnumUtils.test.ts create mode 100644 src/Util/EnumUtils.ts create mode 100644 src/Util/NumericRange.test.ts rename src/{ => Util}/NumericRange.ts (64%) create mode 100644 src/Util/StringUtils.test.ts create mode 100644 src/Util/StringUtils.ts rename src/{ => Util}/WebReceiptLineError.ts (100%) create mode 100644 src/Util/index.ts delete mode 100644 src/WebReceiptlinePrinterError.ts diff --git a/src/NumericRange.test.ts b/src/NumericRange.test.ts deleted file mode 100644 index de9afc9..0000000 --- a/src/NumericRange.test.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { test, expect, describe } from 'vitest'; -import { clampToRange, numberInRange, repeat } from './NumericRange.js'; - -describe("clampToRange tests", () => { - test('high number is clamped down', () => { - expect(clampToRange(5, 0, 2)).toBe(2); - }); - - test('low number is clamped up', () => { - expect(clampToRange(-1, 0, 5)).toBe(0); - }); - - test('in-range number is not modified', () => { - expect(clampToRange(3, 0, 5)).toBe(3); - }); -}); - -describe('numberInRange', () => { - test('discard invalid', () => { - expect(numberInRange(undefined!)).toBe(undefined); - expect(numberInRange('')).toBe(undefined); - expect(numberInRange('hello')).toBe(undefined); - expect(numberInRange('5')).toBe(5); - }); - - test('clamp to range', () => { - expect(numberInRange('8', -5, 5)).toBe(undefined); - expect(numberInRange('4', -5, 5)).toBe(4); - expect(numberInRange('-4', -5, 5)).toBe(-4); - expect(numberInRange('-10', -5, 5)).toBe(undefined); - }); -}); - -describe("repeat tests", () => { - test('Repeat repeats', () => { - const val = repeat(5, 5); - expect(val.length).toBe(5); - expect(val).toEqual([5, 5, 5, 5, 5]); - }) -}) diff --git a/src/Printers/Codepages/index.ts b/src/Printers/Codepages/index.ts deleted file mode 100644 index a7304b3..0000000 --- a/src/Printers/Codepages/index.ts +++ /dev/null @@ -1,53 +0,0 @@ -export * from './CodepageEncoder.js' -export * from './ASCII.js' - -// Help me -export type Codepage - = 'CP437' // USA! USA! USA! - | 'CP720' - | 'CP737' // GREEK - | 'CP775' - | 'CP850' // MULTILINGUAL - | 'CP851' // GREEK - | 'CP852' - | 'CP853' // TURKISH - | 'CP855' - | 'CP857' // TURKISH - | 'CP858' - | 'CP860' // PORTUGUESE - | 'CP861' - | 'CP862' - | 'CP863' - | 'CP863' // CANADIAN-FRENCH - | 'CP864' - | 'CP865' - | 'CP865' // NORDIC - | 'CP866' // Cyrillic 2 - | 'CP869' - //| 'CP932' // Actually shiftjis? - //| 'CP936' // Simplified Chinese? - //| 'CP949' // Unified Hangul? - //| 'CP950' // Traditional Chinese? - | 'CP1098' - | 'CP1118' - | 'CP1119' - | 'CP1125' - | 'ISO885915' - | 'ISO88592' - | 'ISO88597' - | 'RK1048' - | 'WINDOWS1250' - | 'WINDOWS1251' - | 'WINDOWS1252' - | 'WINDOWS1253' - | 'WINDOWS1254' - | 'WINDOWS1255' - | 'WINDOWS1256' - | 'WINDOWS1257' - | 'WINDOWS1258' - // TODO: These ones are hard lol - // | 'SHIFTJIS' - // | 'GB18030' - // | 'KSC5601' - // | 'BIG5' - // | 'TIS620' diff --git a/src/Util/ASCII.test.ts b/src/Util/ASCII.test.ts new file mode 100644 index 0000000..e355eb6 --- /dev/null +++ b/src/Util/ASCII.test.ts @@ -0,0 +1,41 @@ +import { expect, describe, it } from 'vitest'; +import { asciiToDisplay, DecodeAscii, EncodeAscii, hex } from './ASCII.js'; + +describe('asciiToDisplay', () => { + it('Encodes ascii as human legible output', () => { + expect(asciiToDisplay(2, 69, 69, 3, 13, 10)) + .toMatchInlineSnapshot(`"0x02[STX], 0x45[E], 0x45[E], 0x03[ETX], 0x0d[CR], 0x0a[LF]"`); + }); + + it('Hex displays value as hex value', () => { + expect(hex(155)).toMatchInlineSnapshot(`"0x9b"`); + }); +}); + +describe('Encode and Decode', () => { + it('Encodes ASCII', () => { + expect(EncodeAscii("hello\r\n")).toMatchInlineSnapshot(` + Uint8Array [ + 104, + 101, + 108, + 108, + 111, + 13, + 10, + ] + `); + }); + + it('Complains about non-ASCII', () => { + expect(() => {EncodeAscii("🐁")}).toThrow(); + }); + + it('Decodes ASCII', () => { + expect(DecodeAscii(new Uint8Array([2, 69, 69, 3, 13, 10]))) + .toMatchInlineSnapshot(` + "EE + " + `); + }); +}); diff --git a/src/Printers/Codepages/ASCII.ts b/src/Util/ASCII.ts similarity index 78% rename from src/Printers/Codepages/ASCII.ts rename to src/Util/ASCII.ts index 415e7ba..d32b563 100644 --- a/src/Printers/Codepages/ASCII.ts +++ b/src/Util/ASCII.ts @@ -120,3 +120,29 @@ export function asciiToDisplay(...codes: number[]) { return `${hex(c)}[${controlcode}]`; }).join(', '); } + +/** + * Convert an ASCII string to a raw byte array. + * + * Will throw an error for any characters not in the 256 character ASCII table. + * Don't send this unicode and expect a happy ending. + */ +export function EncodeAscii(str: string): Uint8Array { + const out = new Uint8Array(str.length); + for (let charIdx = 0; charIdx < str.length; charIdx++) { + const char = str.charCodeAt(charIdx); + if (char > 0xFF) { + throw new Error(`Character at index ${charIdx} of "${str}" is not ASCII`); + } + out[charIdx] = char; + } + return out; +} + +/** + * Convert a byte array of raw ASCII codepoints to a string. + * @param array + */ +export function DecodeAscii(array: Uint8Array): string { + return new TextDecoder('ascii').decode(array); +} diff --git a/src/Printers/Codepages/CodepageEncoder.ts b/src/Util/CodepageEncoder.ts similarity index 96% rename from src/Printers/Codepages/CodepageEncoder.ts rename to src/Util/CodepageEncoder.ts index 2bf1080..1407399 100644 --- a/src/Printers/Codepages/CodepageEncoder.ts +++ b/src/Util/CodepageEncoder.ts @@ -4,14 +4,63 @@ // This file is from Niels LeenHeer's project // https://github.com/NielsLeenheer/CodepageEncoder -import type { Codepage } from "./index.js"; - // TODO: Migrate to iconv-lite and reimplement Niels' autoEncode function, which // I want to use. // This file is (c) Niels, MIT licensed and thank you Niels <3 // I have stripped it down to just autoencode and added TS type info. +// Help me +export type Codepage + = 'CP437' // USA! USA! USA! + | 'CP720' + | 'CP737' // GREEK + | 'CP775' + | 'CP850' // MULTILINGUAL + | 'CP851' // GREEK + | 'CP852' + | 'CP853' // TURKISH + | 'CP855' + | 'CP857' // TURKISH + | 'CP858' + | 'CP860' // PORTUGUESE + | 'CP861' + | 'CP862' + | 'CP863' + | 'CP863' // CANADIAN-FRENCH + | 'CP864' + | 'CP865' + | 'CP865' // NORDIC + | 'CP866' // Cyrillic 2 + | 'CP869' + //| 'CP932' // Actually shiftjis? + //| 'CP936' // Simplified Chinese? + //| 'CP949' // Unified Hangul? + //| 'CP950' // Traditional Chinese? + | 'CP1098' + | 'CP1118' + | 'CP1119' + | 'CP1125' + | 'ISO885915' + | 'ISO88592' + | 'ISO88597' + | 'RK1048' + | 'WINDOWS1250' + | 'WINDOWS1251' + | 'WINDOWS1252' + | 'WINDOWS1253' + | 'WINDOWS1254' + | 'WINDOWS1255' + | 'WINDOWS1256' + | 'WINDOWS1257' + | 'WINDOWS1258' + // TODO: These ones are hard lol + // | 'SHIFTJIS' + // | 'GB18030' + // | 'KSC5601' + // | 'BIG5' + // | 'TIS620' + export interface CodepageDefinition { name: string, languages: string[], diff --git a/src/Util/EnumUtils.test.ts b/src/Util/EnumUtils.test.ts new file mode 100644 index 0000000..15d0db0 --- /dev/null +++ b/src/Util/EnumUtils.test.ts @@ -0,0 +1,21 @@ +import { expect, describe, it } from 'vitest'; +import { exhaustiveMatchGuard, hasFlag } from './EnumUtils.js'; + +describe('hasFlag', () => { + enum testEnum { + zero = 0, + one, + } + it('Has the flag', () => { + expect(hasFlag(testEnum.one, testEnum.one)).toBe(true); + }); + it('Does not have flag', () => { + expect(hasFlag(testEnum.one, 5)).toBe(false); + }); +}); + +describe('exhaustiveMatchGuard', () => { + it('Only allows never', () => { + expect(() => {exhaustiveMatchGuard(false as never); }).toThrow(); + }) +}) diff --git a/src/Util/EnumUtils.ts b/src/Util/EnumUtils.ts new file mode 100644 index 0000000..2c15d8b --- /dev/null +++ b/src/Util/EnumUtils.ts @@ -0,0 +1,10 @@ + +/** Determine if an enum has a given flag. */ +export function hasFlag(val: number, flag: number) { + // TODO: Figure out type safety in the face of TS's weird enum type support. + return (val & flag) === flag; +} + +export function exhaustiveMatchGuard(_: never): never { + throw new Error('Invalid case received!' + _); +} diff --git a/src/Util/NumericRange.test.ts b/src/Util/NumericRange.test.ts new file mode 100644 index 0000000..2baa796 --- /dev/null +++ b/src/Util/NumericRange.test.ts @@ -0,0 +1,36 @@ +import { it, expect, describe } from 'vitest'; +import { clampToRange, numberInRange, repeat } from './NumericRange.js'; + +describe("clampToRange", () => { + it('high number is clamped down', () => { + expect(clampToRange(5, 0, 2)).toBe(2); + }); + + it('low number is clamped up', () => { + expect(clampToRange(-1, 0, 5)).toBe(0); + }); + + it('in-range number is not modified', () => { + expect(clampToRange(3, 0, 5)).toBe(3); + }); +}); + +describe('numberInRange', () => { + it('Returns numbers in the range', () => { + expect(numberInRange("1", 0, 2)).toBe(1); + expect(numberInRange("-1", -5, 5)).toBe(-1); + expect(numberInRange("0", 0, 0)).toBe(0); + }); + it('Returns undefined for out of range', () => { + expect(numberInRange("nope")).toBe(undefined); + expect(numberInRange("-1", 0)).toBe(undefined); + expect(numberInRange("0", -2, -1)).toBe(undefined); + expect(numberInRange("5", 6, 4)).toBe(undefined); + }); +}); + +describe('repeat', () => { + it('Repeats', () => { + expect(repeat("a", 5)).toStrictEqual(["a", "a", "a", "a", "a"]); + }) +}) diff --git a/src/NumericRange.ts b/src/Util/NumericRange.ts similarity index 64% rename from src/NumericRange.ts rename to src/Util/NumericRange.ts index 97953b5..85a7959 100644 --- a/src/NumericRange.ts +++ b/src/Util/NumericRange.ts @@ -6,6 +6,10 @@ export type NumericRange = Exclude; +export function range(start: number, stop: number, step = 1) { + return Array.from({ length: (stop - start) / step + 1 }, (_, i) => start + i * step); +} + /** Clamp a number to a given range of values. */ export function clampToRange(value: number, min: number, max: number) { return Math.min(Math.max(value, min), max); @@ -15,10 +19,9 @@ export function clampToRange(value: number, min: number, max: number) { export function numberInRange( str: string, min?: number, - max?: number, - defaultValue?: number) { - if (!/^-?\d+$/.test(str)) { - return defaultValue; + max?: number) { + if (!/^[+-]?\d+$/.test(str)) { + return; } const val = Number(str); if (min !== undefined && val < min) { @@ -30,6 +33,13 @@ export function numberInRange( return val; } +/** Create an array of a value with count number of items. */ export function repeat(val: T, count: number) { return new Array(count).fill(val) as T[]; } + +/** Round a raw value to the nearest step. */ +export function roundToNearestStep(value: number, step: number): number { + const inverse = 1.0 / step; + return Math.round(value * inverse) / inverse; +} diff --git a/src/Util/StringUtils.test.ts b/src/Util/StringUtils.test.ts new file mode 100644 index 0000000..2922fcd --- /dev/null +++ b/src/Util/StringUtils.test.ts @@ -0,0 +1,75 @@ +import { expect, describe, it } from 'vitest'; +import { AsciiCodeNumbers } from './ASCII.js'; +import { sliceToCRLF, sliceToNewline } from './StringUtils.js'; + +describe('sliceToNewline', () => { + it('Finds a newline', () => { + const msg = new Uint8Array([ + AsciiCodeNumbers.STX, + AsciiCodeNumbers.LF, + AsciiCodeNumbers.ETX + ]); + expect(sliceToNewline(msg)).toMatchInlineSnapshot(` + { + "remainder": Uint8Array [ + 3, + ], + "sliced": Uint8Array [ + 2, + 10, + ], + } + `); + }); + it('Handles no newlines gracefully', () => { + expect(sliceToNewline(new Uint8Array())).toMatchInlineSnapshot(` + { + "remainder": Uint8Array [], + "sliced": Uint8Array [], + } + `); + expect(sliceToNewline(undefined!)).toMatchInlineSnapshot(` + { + "remainder": Uint8Array [], + "sliced": Uint8Array [], + } + `); + }); +}); + +describe('sliceToCRLF', () => { + it('Finds a newline', () => { + expect(sliceToCRLF("hello\r\ngoodbye")).toMatchInlineSnapshot(` + { + "remainder": "goodbye", + "sliced": "hello", + } + `); + expect(sliceToCRLF("hello\ngoodbye")).toMatchInlineSnapshot(` + { + "remainder": "goodbye", + "sliced": "hello", + } + `); + }); + it('Handles no newlines', () => { + expect(sliceToCRLF("hello")).toMatchInlineSnapshot(` + { + "remainder": "hello", + "sliced": "", + } + `); + expect(sliceToCRLF("")).toMatchInlineSnapshot(` + { + "remainder": "", + "sliced": "", + } + `); + expect(sliceToCRLF(undefined!)).toMatchInlineSnapshot(` + { + "remainder": "", + "sliced": "", + } + `); + }); +}); diff --git a/src/Util/StringUtils.ts b/src/Util/StringUtils.ts new file mode 100644 index 0000000..834abe3 --- /dev/null +++ b/src/Util/StringUtils.ts @@ -0,0 +1,90 @@ +import { AsciiCodeNumbers } from "./ASCII.js"; + +/** + * Slice an array from the start to the first NUL character, returning both pieces. + * + * If no NUL character is found sliced will have a length of 0. + */ +export function sliceToNull(msg: Uint8Array): { + sliced: Uint8Array, + remainder: Uint8Array, +} { + const idx = msg.indexOf(AsciiCodeNumbers.NUL); + if (idx === -1) { + return { + sliced: new Uint8Array(), + remainder: msg + } + } + + return { + sliced: msg.slice(0, idx + 1), + remainder: msg.slice(idx + 1), + }; +} + +/** + * Slice an array from the start to the first LF character, returning both pieces. + * + * If no LF character is found sliced will have a length of 0. + * + * CR characters are not removed if present! + */ +export function sliceToNewline(msg: Uint8Array): { + sliced: Uint8Array, + remainder: Uint8Array, +} { + if (msg === undefined) { + return { + sliced: new Uint8Array(), + remainder: new Uint8Array() + } + } + + const idx = msg.indexOf(AsciiCodeNumbers.LF); + if (idx === -1) { + return { + sliced: new Uint8Array(), + remainder: msg + } + } + + return { + sliced: msg.slice(0, idx + 1), + remainder: msg.slice(idx + 1), + }; +} + +/** Slice a string from the start to the first CRLF or LF, returning both pieces. */ +export function sliceToCRLF(msg: string): { + sliced: string, + remainder: string, +} { + if (msg === undefined) { + return { + sliced: "", + remainder: "" + } + } + + const cr = msg.indexOf('\r\n'); + if (cr !== -1) { + return { + sliced: msg.substring(0, cr), + remainder: msg.substring(cr + 2) + } + } + + const lf = msg.indexOf('\n'); + if (lf !== -1) { + return { + sliced: msg.substring(0, lf), + remainder: msg.substring(lf + 1) + } + } + + return { + sliced: "", + remainder: msg + } +} diff --git a/src/WebReceiptLineError.ts b/src/Util/WebReceiptLineError.ts similarity index 100% rename from src/WebReceiptLineError.ts rename to src/Util/WebReceiptLineError.ts diff --git a/src/Util/index.ts b/src/Util/index.ts new file mode 100644 index 0000000..138be06 --- /dev/null +++ b/src/Util/index.ts @@ -0,0 +1,5 @@ +export * from './ASCII.js'; +export * from './CodepageEncoder.js'; +export * from './EnumUtils.js'; +export * from './NumericRange.js'; +export * from './WebReceiptLineError.js'; diff --git a/src/WebReceiptlinePrinterError.ts b/src/WebReceiptlinePrinterError.ts deleted file mode 100644 index 8d11f9b..0000000 --- a/src/WebReceiptlinePrinterError.ts +++ /dev/null @@ -1,7 +0,0 @@ -/** Exception thrown from the WebReceptline library. */ -export class WebReceptlinePrinterError extends Error { - constructor(message: string) { - super(message); - this.name = this.constructor.name; - } -} From 418821fb988cdc7666bc2f6aad5117f6cffc76ac Mon Sep 17 00:00:00 2001 From: Cellivar <1441553+Cellivar@users.noreply.github.com> Date: Thu, 2 Jan 2025 03:57:36 -0800 Subject: [PATCH 3/8] Migrate basic printer configs --- src/Configs/BasePrinterConfig.ts | 82 ++++++++++++++++++++++ src/Configs/ConfigurationTypes.ts | 78 ++++++++++++++++++++ src/Configs/Languages.ts | 16 +++++ src/Configs/index.ts | 3 + src/Printers/Languages/LanguageDetector.ts | 16 ----- src/Printers/Options/Models.ts | 4 -- src/Printers/Options/index.ts | 1 - 7 files changed, 179 insertions(+), 21 deletions(-) create mode 100644 src/Configs/BasePrinterConfig.ts create mode 100644 src/Configs/ConfigurationTypes.ts create mode 100644 src/Configs/Languages.ts create mode 100644 src/Configs/index.ts delete mode 100644 src/Printers/Languages/LanguageDetector.ts delete mode 100644 src/Printers/Options/Models.ts delete mode 100644 src/Printers/Options/index.ts diff --git a/src/Configs/BasePrinterConfig.ts b/src/Configs/BasePrinterConfig.ts new file mode 100644 index 0000000..8c7f5d3 --- /dev/null +++ b/src/Configs/BasePrinterConfig.ts @@ -0,0 +1,82 @@ +import * as Util from '../Util/index.js'; +import { PrintCutter, PrintOrientation, type DarknessPercent, type IPrinterHardware, type IPrinterMedia } from "./ConfigurationTypes.js"; + +/** Configured options for a label printer */ +export abstract class BasePrinterConfig implements IPrinterHardware, IPrinterMedia { + + // Read-only printer config info + protected _serial = 'no_serial_nm'; + get serialNumber() { return this._serial; } + + protected _model = 'Unknown Model'; + get model() { return this._model; } + + protected _manufacturer = 'Unknown Manufacturer'; + get manufacturer() { return this._manufacturer; } + + protected _firmware = ''; + get firmware() { return this._firmware; } + + protected _darkness: DarknessPercent = 50; + get darknessPercent() { return this._darkness; } + + protected _printOrientation = PrintOrientation.normal; + get printOrientation() { return this._printOrientation; } + + public constructor() {} + + protected _cpl: number = 42; + get charactersPerLine() { return this._cpl; } + + protected _hasMultiByteSupport = false; + get hasMultiByteSupport() { return this._hasMultiByteSupport; } + + protected _fontLanguageSupport = ''; + get fontLanguageSupport() { return this._fontLanguageSupport; } + + protected _hasDmdConnected = false; + get hasDmdConnected() { return this._hasDmdConnected; } + + protected _codepages: ReadonlySet = new Set([ + "CP437", + "CP720", + "CP737", + "CP775", + "CP850", + "CP851", + "CP852", + "CP853", + "CP855", + "CP857", + "CP858", + "CP860", + "CP861", + "CP862", + "CP863", + "CP864", + "CP865", + "CP866", + "CP869", + "CP1098", + "CP1118", + "CP1119", + "CP1125", + "ISO88592", + "ISO88597", + "ISO885915", + "RK1048", + "WINDOWS1250", + "WINDOWS1251", + "WINDOWS1252", + "WINDOWS1253", + "WINDOWS1254", + "WINDOWS1255", + "WINDOWS1256", + "WINDOWS1257", + "WINDOWS1258", + ]); + get codepages(): ReadonlySet { return this._codepages; } + + protected _cutter = PrintCutter.none; + get cutter() { return this._cutter; } +} diff --git a/src/Configs/ConfigurationTypes.ts b/src/Configs/ConfigurationTypes.ts new file mode 100644 index 0000000..7882196 --- /dev/null +++ b/src/Configs/ConfigurationTypes.ts @@ -0,0 +1,78 @@ +import * as Util from '../Util/index.js'; + +/** Utility type to create an 'update' object, making all properties optional and not readonly. */ +export type UpdateFor = { + -readonly [Property in keyof Type]?: Type[Property]; +}; + +/** The darkness of the printer setting, higher being printing darker. */ +export type DarknessPercent = Util.Percent; + +/** Coordinates on a 2D plane. */ +export interface Coordinate { + /** Offset from the left side of the plane, incrementing to the right. --> */ + left: number; + /** Offset from the top side of the plane, incrementing down. */ + top: number; +} + +/** The orientation of a document as it comes out of the printer. */ +export enum PrintOrientation { + /** Right-side up when the printer faces the user. */ + normal, + /** Upside-down when the printer faces the user. */ + inverted +} + +export enum PrintCutter { + /** No cutter available. */ + none, + /** Only supports partial cuts. */ + partial, + /** Only supports full cuts. */ + full, + /** Supports switching between cutter modes. */ + multiple +} + +/** Hardware information about the printer that can't be modified. */ +export interface IPrinterHardware { + /** The firmware version information for the printer. */ + readonly firmware: string; + + /** The manufacturer of the printer. */ + readonly manufacturer: string; + + /** The model name of the printer. */ + readonly model: string; + + /** The raw serial number of the printer. */ + readonly serialNumber: string; + + /** The available codepages this printer supports */ + readonly codepages: ReadonlySet; + + /** The cutter mode the printer supports. */ + readonly cutter: PrintCutter; + + /** Printer supports multi-byte codepages. */ + readonly hasMultiByteSupport: boolean; + + /** Printer font language support. */ + readonly fontLanguageSupport: string; + + /** Whether the printerh as a DMD display connected. */ + readonly hasDmdConnected: boolean; +} + +/** Printer options related to the media being printed */ +export interface IPrinterMedia { + /** Number of characters printed per line. Commonly 42. */ + charactersPerLine: number; + + /** How dark to print. 0 is blank, 99 is max darkness */ + darknessPercent: DarknessPercent; + + /** Whether the document prints right-side-up or upside-down. */ + printOrientation: PrintOrientation; +} diff --git a/src/Configs/Languages.ts b/src/Configs/Languages.ts new file mode 100644 index 0000000..910ba93 --- /dev/null +++ b/src/Configs/Languages.ts @@ -0,0 +1,16 @@ +// [flags] I miss C#. +/** Command languages a printer could support. One printer may support multiple. */ +export enum PrinterCommandLanguage { + /** Error condition indicating autodetect failed. */ + none = 0, + /** Printer can be set to ESC/POS. */ + escPos = 1 << 0, +} + +/** Types that can be used for comm channels to printers. */ +export type MessageArrayLike = string | Uint8Array +export type MessageArrayLikeType = "string" | "Uint8Array" +export interface MessageArrayLikeMap { + "string": string; + "Uint8Array": Uint8Array; +} diff --git a/src/Configs/index.ts b/src/Configs/index.ts new file mode 100644 index 0000000..28c00c8 --- /dev/null +++ b/src/Configs/index.ts @@ -0,0 +1,3 @@ +export * from './ConfigurationTypes.js'; +export * from './Languages.js'; +export * from './BasePrinterConfig.js'; diff --git a/src/Printers/Languages/LanguageDetector.ts b/src/Printers/Languages/LanguageDetector.ts deleted file mode 100644 index e14d193..0000000 --- a/src/Printers/Languages/LanguageDetector.ts +++ /dev/null @@ -1,16 +0,0 @@ -import type { IDeviceInformation } from "web-device-mux"; -import { type PrinterCommandLanguage } from "./index.js"; - -export interface ILanguageDetector { - detectLanguage(deviceInfo: IDeviceInformation): PrinterCommandLanguage | undefined; -} - -export function detectLanguage( - deviceInfo: IDeviceInformation, - candidates: ILanguageDetector[] -): PrinterCommandLanguage | undefined { - return candidates - .map(c => c.detectLanguage(deviceInfo)) - .filter(l => l !== undefined) - .at(0); -} diff --git a/src/Printers/Options/Models.ts b/src/Printers/Options/Models.ts deleted file mode 100644 index 0a24d6d..0000000 --- a/src/Printers/Options/Models.ts +++ /dev/null @@ -1,4 +0,0 @@ -/** A class describing identifiers to determine printer model and capabilities */ -export interface IPrinterModelDetector { - detector: string; -} diff --git a/src/Printers/Options/index.ts b/src/Printers/Options/index.ts deleted file mode 100644 index fb285b9..0000000 --- a/src/Printers/Options/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./PrinterOptions.js" From 8d91e1cd9442604bc6b0d4120b4e289c452c1d73 Mon Sep 17 00:00:00 2001 From: Cellivar <1441553+Cellivar@users.noreply.github.com> Date: Thu, 2 Jan 2025 03:58:36 -0800 Subject: [PATCH 4/8] Migrate common command structure --- .../Commands.ts => Commands/BasicCommands.ts} | 132 ++----- src/Commands/CommandSet.ts | 256 +++++++++++++ src/Commands/Commands.ts | 125 +++++++ src/Commands/Messages.test.ts | 38 ++ src/Commands/Messages.ts | 350 ++++++++++++++++++ src/Commands/PrinterConfig.ts | 38 ++ src/Commands/TranspileCommand.ts | 61 +++ src/Commands/index.ts | 6 + src/Documents/CommandSet.ts | 77 ---- src/Printers/Communication/Messages.ts | 77 ---- src/Printers/Communication/index.ts | 1 - src/Printers/Options/PrinterOptions.ts | 146 -------- 12 files changed, 910 insertions(+), 397 deletions(-) rename src/{Documents/Commands.ts => Commands/BasicCommands.ts} (67%) create mode 100644 src/Commands/CommandSet.ts create mode 100644 src/Commands/Commands.ts create mode 100644 src/Commands/Messages.test.ts create mode 100644 src/Commands/Messages.ts create mode 100644 src/Commands/PrinterConfig.ts create mode 100644 src/Commands/TranspileCommand.ts create mode 100644 src/Commands/index.ts delete mode 100644 src/Documents/CommandSet.ts delete mode 100644 src/Printers/Communication/Messages.ts delete mode 100644 src/Printers/Communication/index.ts delete mode 100644 src/Printers/Options/PrinterOptions.ts diff --git a/src/Documents/Commands.ts b/src/Commands/BasicCommands.ts similarity index 67% rename from src/Documents/Commands.ts rename to src/Commands/BasicCommands.ts index 9331735..6056e3f 100644 --- a/src/Documents/Commands.ts +++ b/src/Commands/BasicCommands.ts @@ -1,87 +1,27 @@ -import type { Codepage } from "../Printers/Codepages/index.js"; -import { clampToRange } from "../NumericRange.js"; -import type { PrinterCommandLanguages } from "../Printers/Languages/index.js"; - -/** General categories of side-effects commands can cause to a device. */ -export type PrinterCommandEffectTypes - = "unknown" - | "altersConfig" - | "feedsPaper" - | "lossOfConnection" - | "actuatesCutter" - | "pulsesOutputPins" - | "waitsForResponse"; - -/** Flags to indicate special operations a command might cause. */ -export class CommandEffectFlags extends Set { } -export const NoEffect = new CommandEffectFlags(); - -/** Union type of all possible commands that must be handled by command sets. */ -export type CommandType - // Users/PCLs may supply printer commands. This uses a different lookup table. - = "CustomCommand" - // General printer commands - | "Reset" - | "TestPrint" - | "Raw" - | "PulseOutput" - | "Newline" - | "Cut" - // Status and Config - | "GetConfiguration" - | "GetStatus" - // Print Format Commands - | "OffsetPrintPosition" - // Image commands - | "Image" - | "Barcode" - | "TwoDCode" - // Text formatting commands - | "TextFormatting" - | "TextDraw" - | "Text" - | "Codepage" - //| "Box" - | "HorizontalRule" - // Configuration commands - | "SetLineSpacing" - | "SetPrintArea" - -/** A command that can be sent to a printer. */ -export interface IPrinterCommand { - /** Get the display name of this command. */ - readonly name: string; - /** Get the command type of this command. */ - readonly type: CommandType; - /** Any effects this command may cause the printer to undergo. */ - readonly effectFlags: CommandEffectFlags; - - /** Get the human-readable output of this command. */ - toDisplay(): string; -} +import * as Util from '../Util/index.js'; +import { BasicCommand, CommandEffectFlags, NoEffect, type CommandTypeBasic, type IPrinterBasicCommand } from './Commands.js'; -/** A custom command beyond the standard command set, with command-language-specific behavior. */ -export interface IPrinterExtendedCommand extends IPrinterCommand { - /** The unique identifier for this command. */ - typeExtended: symbol; +export class NoOp extends BasicCommand { + name = 'No operation placeholder'; + type: CommandTypeBasic = 'NoOp'; + constructor() { super(); } +} - /** Gets the command languages this extended command can apply to. */ - commandLanguageApplicability: PrinterCommandLanguages; +export class StartReceipt extends BasicCommand { + name = 'Explicitly start a new receipt.'; + type = 'StartReceipt' as const; + constructor() { super([]); } } -abstract class BasicCommand implements IPrinterCommand { - abstract name: string; - abstract type: CommandType; - effectFlags: CommandEffectFlags; - toDisplay() { return this.name; } - constructor(effects: PrinterCommandEffectTypes[]) { - this.effectFlags = new CommandEffectFlags(effects); - } +export class EndReceipt extends BasicCommand { + name = 'Explicitly end a receipt.'; + type: CommandTypeBasic = 'EndReceipt'; + constructor() { super([]); } } export class Newline extends BasicCommand { name = 'Print a newline'; - type: CommandType = 'Newline'; + type: CommandTypeBasic = 'Newline'; constructor() { super( ['feedsPaper']); } } @@ -89,27 +29,27 @@ export type TestPrintType = 'hexadecimal' | 'rolling' | 'printerStatus' export class TestPrint extends BasicCommand { name = 'Run a test print'; - type: CommandType = 'TestPrint'; + type: CommandTypeBasic = 'TestPrint'; constructor(public readonly printType: TestPrintType = 'rolling') { super(['feedsPaper', 'actuatesCutter', 'lossOfConnection']); } } -export class GetConfiguration extends BasicCommand { +export class QueryConfiguration extends BasicCommand { name = 'Get the printer configuration' - type: CommandType = 'GetConfiguration'; + type: CommandTypeBasic = 'QueryConfiguration'; constructor() { super(['waitsForResponse']); } } export class GetStatus extends BasicCommand { name = 'Get the printer status' - type: CommandType = 'GetStatus'; + type: CommandTypeBasic = 'GetStatus'; constructor() { super(['waitsForResponse']); } } export type CutType = "Partial" | "Complete" -export class Cut implements IPrinterCommand { +export class Cut implements IPrinterBasicCommand { name = 'Cut paper'; type = "Cut" as const; effectFlags = new CommandEffectFlags(["actuatesCutter", "feedsPaper"]); @@ -124,7 +64,7 @@ export class Cut implements IPrinterCommand { export type PulsePin = "Drawer1" | "Drawer2" -export class PulseCommand implements IPrinterCommand { +export class PulseCommand implements IPrinterBasicCommand { name = "Pulse the drawer kick output."; type = 'PulseOutput' as const; effectFlags = new CommandEffectFlags(["pulsesOutputPins"]); @@ -149,7 +89,7 @@ export class PulseCommand implements IPrinterCommand { } } -export class ImageCommand implements IPrinterCommand { +export class ImageCommand implements IPrinterBasicCommand { name = 'Prints an image' type = "Image" as const; effectFlags = new CommandEffectFlags(["feedsPaper"]); @@ -158,7 +98,7 @@ export class ImageCommand implements IPrinterCommand { constructor(public readonly imgData: string) {} } -export class Barcode implements IPrinterCommand { +export class Barcode implements IPrinterBasicCommand { name = 'Prints a barcode'; type = 'Barcode' as const; effectFlags = new CommandEffectFlags(["feedsPaper"]); @@ -167,7 +107,7 @@ export class Barcode implements IPrinterCommand { constructor(public readonly barcodeData: object) {} } -export class TwoDCode implements IPrinterCommand { +export class TwoDCode implements IPrinterBasicCommand { name = 'Prints a 2D code'; type = 'TwoDCode' as const; effectFlags = new CommandEffectFlags(["feedsPaper"]); @@ -176,7 +116,7 @@ export class TwoDCode implements IPrinterCommand { constructor(public readonly codeData: object) {} } -export class RawCommand implements IPrinterCommand { +export class RawCommand implements IPrinterBasicCommand { name = 'Adds raw command' type = "Raw" as const; effectFlags = new CommandEffectFlags(["unknown"]); @@ -202,7 +142,7 @@ export interface TextFormat { height?: Height } -export class TextFormatting implements IPrinterCommand { +export class TextFormatting implements IPrinterBasicCommand { name = 'Set text formatting' type = "TextFormatting" as const; effectFlags = NoEffect; @@ -243,7 +183,7 @@ export type OnlyBoxDrawingCharacters = ? OnlyBoxDrawingCharacters : never; -export class TextDraw implements IPrinterCommand { +export class TextDraw implements IPrinterBasicCommand { name = 'Write text as drawing' type = 'TextDraw' as const; effectFlags = NoEffect; @@ -256,7 +196,7 @@ export class TextDraw implements IPrinterCommand { } } -export class Text implements IPrinterCommand { +export class Text implements IPrinterBasicCommand { name = 'Write text' type = 'Text' as const; effectFlags = NoEffect; @@ -265,18 +205,18 @@ export class Text implements IPrinterCommand { constructor(public readonly text: string = '') {} } -export class SetCodepage implements IPrinterCommand { +export class SetCodepage implements IPrinterBasicCommand { name = 'Set character codepage' type = 'Codepage' as const; effectFlags = NoEffect; toDisplay() { return `Set codepage ${this.codepage}` } - constructor(public readonly codepage: Codepage) {} + constructor(public readonly codepage: Util.Codepage) {} } export type PrintPositionOrigin = "absolute" | "relative"; -export class OffsetPrintPosition implements IPrinterCommand { +export class OffsetPrintPosition implements IPrinterBasicCommand { name = 'Change print position' type = 'OffsetPrintPosition' as const; effectFlags = NoEffect; @@ -293,7 +233,7 @@ export class OffsetPrintPosition implements IPrinterCommand { } } -export class SetPrintArea implements IPrinterCommand { +export class SetPrintArea implements IPrinterBasicCommand { name = 'Set print area' type = 'SetPrintArea' as const; effectFlags = NoEffect; @@ -311,7 +251,7 @@ export class SetPrintArea implements IPrinterCommand { export type LineStyle = 'single' | 'double' -export class HorizontalRule implements IPrinterCommand { +export class HorizontalRule implements IPrinterBasicCommand { name = 'Add horizontal rule' type = 'HorizontalRule' as const; effectFlags = NoEffect; @@ -322,7 +262,7 @@ export class HorizontalRule implements IPrinterCommand { public readonly lineStyle: LineStyle = 'single') {} } -export class SetLineSpacing implements IPrinterCommand { +export class SetLineSpacing implements IPrinterBasicCommand { name = 'Set the vertical line spacing' type = 'SetLineSpacing' as const; effectFlags = NoEffect; @@ -331,6 +271,6 @@ export class SetLineSpacing implements IPrinterCommand { public readonly spacing: number; constructor(spacing: number = 1) { - this.spacing = clampToRange(spacing, 0, 255); + this.spacing = Util.clampToRange(spacing, 0, 255); } } diff --git a/src/Commands/CommandSet.ts b/src/Commands/CommandSet.ts new file mode 100644 index 0000000..0e75df7 --- /dev/null +++ b/src/Commands/CommandSet.ts @@ -0,0 +1,256 @@ +import * as Conf from '../Configs/index.js'; +import * as Commands from './Commands.js'; +import { TranspileDocumentError, type TranspiledDocumentState } from "./TranspileCommand.js"; +import { MessageParsingError, RawMessageTransformer, StringMessageTransformer, type CommandSetMessageHandlerDelegate, type IMessageHandlerResult, type MessageTransformer } from './Messages.js'; +import type { PrinterConfig } from './PrinterConfig.js'; + +/** How a command should be wrapped into a form, if at all */ +export enum CommandFormInclusionMode { + /** Command can appear in a shared form with other commands. */ + sharedForm = 0, + /** Command should not be wrapped in a form at all. */ + noForm +} + +/** Describes a class capable of managing command implementations. */ +export interface CommandSet { + + /** Handle the dispatch of a message received from the printer. */ + handleMessage( + msg: TReceived, + config: PrinterConfig, + sentCommand?: Commands.IPrinterCommand + ): IMessageHandlerResult; + + /** Gets the command language this command set implements */ + get commandLanguage(): Conf.PrinterCommandLanguage; + + /** Gets the prefix to start a new document. */ + get documentStartPrefix(): TMsgType; + /** Gets the suffix to end a document. */ + get documentEndSuffix(): TMsgType; + + /** Get expanded commands for a given command, if applicable. */ + expandCommand(cmd: Commands.IPrinterCommand): Commands.IPrinterCommand[]; + /** Determine if a given command must appear outside of a form. */ + isCommandNonFormCommand(cmd: Commands.IPrinterCommand): boolean; + /** Combine separate commands into one. */ + combineCommands(...commands: TMsgType[]): TMsgType; + /** Dispatch a message to the appropriate handler for it. */ + callMessageHandler( + message: TMsgType, + sentCommand?: Commands.IPrinterCommand + ): IMessageHandlerResult + + /** Expand a printer config to a language-specific config. */ + getConfig(config: PrinterConfig): PrinterConfig; + + /** Transpile a single command, tracking its effects to a document. */ + transpileCommand( + cmd: Commands.IPrinterCommand, + docMetadata: TranspiledDocumentState + ): TMsgType | TranspileDocumentError; +} + +/** A method for transpiling a given command to its native command. */ +export type TranspileCommandDelegate< + TCmd extends Commands.IPrinterCommand, + TMsgType extends Conf.MessageArrayLike +> = ( + cmd: TCmd, + docState: TranspiledDocumentState, + commandSet: CommandSet +) => TMsgType | TranspileDocumentError; + +/** A method for expanding one command into multiple other commands. */ +export type CommandExpandDelegate = ( + cmd?: TCmd +) => Commands.IPrinterCommand[]; + +/** A method for handling a response message to a command. */ +export type MessageHandlerDelegate = ( + msg: TMsgType, + sentCommand: Commands.IPrinterCommand +) => IMessageHandlerResult; + +/** A manifest for a printer command's behavior. */ +export interface IPrinterCommandMapping { + /** The printer command being mapped. */ + commandType: Commands.CommandAnyType, + /** Method to transpile this command to its native command. */ + transpile?: TranspileCommandDelegate, + /** Method to replace a command with multiple other commands. */ + expand?: CommandExpandDelegate, + /** Method to handle a message from the device in response to this command. */ + readMessage?: MessageHandlerDelegate, + /** Compatibility of this command with being included in a form. Defaults to true. */ + formInclusionMode?: CommandFormInclusionMode, +} + +export abstract class PrinterCommandSet implements CommandSet { + private cmdLanguage: Conf.PrinterCommandLanguage; + get commandLanguage() { + return this.cmdLanguage; + } + + protected abstract get noop(): TMsgType; + + protected messageTransformer: MessageTransformer; + + protected messageHandlerDelegate: CommandSetMessageHandlerDelegate; + + protected commandMap = new Map>; + + protected constructor( + transformer : MessageTransformer, + messageHandlerDelegate: CommandSetMessageHandlerDelegate, + implementedLanguage : Conf.PrinterCommandLanguage, + basicCommands : Record>, + extendedCommands : IPrinterCommandMapping[] = [], + ) { + this.cmdLanguage = implementedLanguage; + this.messageTransformer = transformer; + this.messageHandlerDelegate = messageHandlerDelegate; + for (const cmdType of Commands.basicCommandTypes) { + this.commandMap.set(cmdType, basicCommands[cmdType]); + } + // Support overriding behaviors + extendedCommands.forEach(c => this.commandMap.set(c.commandType, c)); + } + + abstract get documentStartPrefix(): TMsgType; + abstract get documentEndSuffix(): TMsgType; + + public transpileCommand( + cmd: Commands.IPrinterCommand, + docMetadata: TranspiledDocumentState + ): TMsgType | TranspileDocumentError{ + const mappedCmd = this.getMappedCmd(cmd); + if (mappedCmd === undefined) { + return new TranspileDocumentError(`Command could not be mapped, is the command mapping correcT?`); + } + const handler = mappedCmd.transpile ?? (() => this.noop); + return handler(cmd, docMetadata, this); + } + + public handleMessage( + msg: TReceived, + config: PrinterConfig, + sentCommand?: Commands.IPrinterCommand, + ): IMessageHandlerResult { + return this.messageHandlerDelegate( + this, + msg, + config, + sentCommand + ); + } + + protected getMappedCmd(cmd: Commands.IPrinterCommand) { + return this.commandMap.get(Commands.getCommandAnyType(cmd)); + } + + public isCommandNonFormCommand(cmd: Commands.IPrinterCommand): boolean { + return this.getMappedCmd(cmd)?.formInclusionMode === CommandFormInclusionMode.noForm; + } + + public expandCommand(cmd: Commands.IPrinterCommand): Commands.IPrinterCommand[] { + return (this.getMappedCmd(cmd)?.expand ?? (() => []))(cmd); + } + + public combineCommands(...commands: TMsgType[]) { + return this.messageTransformer.combineMessages(...commands); + } + + public getConfig(config: PrinterConfig): PrinterConfig { + return config; + } + + public callMessageHandler( + message: TMsgType, + sentCommand?: Commands.IPrinterCommand + ): IMessageHandlerResult { + if (sentCommand === undefined) { + throw new MessageParsingError( + `Received a command reply message without 'sentCommand' being provided, can't handle this message.`, + message + ); + } + + const handler = this.getMappedCmd(sentCommand)?.readMessage; + if (handler === undefined) { + throw new MessageParsingError( + `Command '${sentCommand.name}' has no message handler and should not have been awaited for this message. This is a bug in the library.`, + message + ) + } + + return handler(message, sentCommand); + } + + protected getExtendedCommand( + cmd: Commands.IPrinterCommand + ) { + const lookup = (cmd as Commands.IPrinterExtendedCommand).typeExtended; + if (!lookup) { + throw new TranspileDocumentError( + `Command '${cmd.constructor.name}' did not have a value for typeExtended. If you're trying to implement a custom command check the documentation.` + ) + } + + const handler = this.getMappedCmd(cmd)?.transpile; + + if (handler === undefined) { + throw new TranspileDocumentError( + `Unknown command '${cmd.constructor.name}' was not found in the command map for ${this.commandLanguage} command language. If you're trying to implement a custom command check the documentation for correctly adding mappings.` + ); + } + return handler; + } +} + +export abstract class RawCommandSet extends PrinterCommandSet { + + protected static readonly _noop = new Uint8Array(); + public get noop() { + return RawCommandSet._noop; + } + + protected constructor( + implementedLanguage : Conf.PrinterCommandLanguage, + messageHandlerDelegate: CommandSetMessageHandlerDelegate, + basicCommands : Record>, + extendedCommands : IPrinterCommandMapping[] = [] + ) { + super( + new RawMessageTransformer(), + messageHandlerDelegate, + implementedLanguage, + basicCommands, + extendedCommands + ); + } +} + +export abstract class StringCommandSet extends PrinterCommandSet { + + protected static readonly _noop = ""; + public get noop() { + return StringCommandSet._noop; + } + + protected constructor( + implementedLanguage : Conf.PrinterCommandLanguage, + messageHandlerDelegate: CommandSetMessageHandlerDelegate, + basicCommands : Record>, + extendedCommands : IPrinterCommandMapping[] = [] + ) { + super( + new StringMessageTransformer(), + messageHandlerDelegate, + implementedLanguage, + basicCommands, + extendedCommands + ); + } +} diff --git a/src/Commands/Commands.ts b/src/Commands/Commands.ts new file mode 100644 index 0000000..e0e2663 --- /dev/null +++ b/src/Commands/Commands.ts @@ -0,0 +1,125 @@ +import * as Conf from '../Configs/index.js'; + +/** General categories of side-effects commands can cause to a device. */ +export type PrinterCommandEffectTypes + = "unknown" + /** Changes the printer config, necessitating an update of the cached config. */ + | "altersConfig" + /** Causes the printer motor to engage, even if nothing is printed. */ + | "feedsPaper" + /** Causes the printer to print labels, regardless of peel settings. */ + | "feedsPaperIgnoringPeeler" + /** Causes the printer to disconnect or otherwise need reconnecting. */ + | "lossOfConnection" + /** Causes something sharp to move. */ + | "actuatesCutter" + /** Causes output pins to apply voltage */ + | "pulsesOutputPins" + /** Expects a response from the printer. */ + | "waitsForResponse"; + +/** Flags to indicate special operations a command might cause. */ +export class CommandEffectFlags extends Set { } +export const NoEffect = new CommandEffectFlags(); +export const AwaitsEffect = new CommandEffectFlags(['waitsForResponse']); + +/** A command that can be sent to a printer. */ +export type IPrinterCommand = IPrinterCommandBase & (IPrinterBasicCommand | IPrinterExtendedCommand) + +interface IPrinterCommandBase { + /** Get the display name of this command. */ + readonly name: string; + /** Any effects this command may cause the printer to undergo. */ + readonly effectFlags: CommandEffectFlags; + /** Get the human-readable output of this command. */ + toDisplay(): string; +} + +export type CommandTypeBasic = Exclude; +/** A basic printer command, common to all printer languages. */ +export interface IPrinterBasicCommand extends IPrinterCommandBase { + /** Get the command type of this command. */ + readonly type: CommandTypeBasic; +} + +/** A custom command beyond the standard command set, with language-specific behavior. */ +export interface IPrinterExtendedCommand extends IPrinterCommandBase { + /** Get the command type of this command. */ + readonly type: Extract; + /** The unique identifier for this command. */ + readonly typeExtended: symbol; + /** Gets the command languages this extended command can apply to. */ + readonly commandLanguageApplicability: Conf.PrinterCommandLanguage; +} + +/** Behavior to take for commands that belong inside or outside of a form. */ +export enum CommandReorderBehavior { + /** Perform no reordering, non-form commands will be interpreted as form closing. */ + closeForm = 0, + /** Reorder non-form commands to the end, retaining order. */ + afterAllForms, + /** Reorder non-form commands before all forms, retaining order. */ + beforeAllForms, + /** + * Throw an exception if a non-form command is within a form. + * You will need to explicitly close and open forms to use this option. + */ + throwError, +} + +/** Union type of all possible commands that must be handled by command sets. */ +export const basicCommandTypes = [ + // Users/PCLs may supply printer commands. This uses a different lookup table. + "CustomCommand", + // General printer commands + "Identify", + "Reset", + "Raw", + "NoOp", + // Status commands + "GetStatus", + "PrintConfiguration", + "QueryConfiguration", + "TestPrint", + "PulseOutput", + // Document handling + "OffsetPrintPosition", + "Cut", + "Newline", + "StartReceipt", + "EndReceipt", + // Content + "Barcode", + //| "Box" + "Codepage", + "HorizontalRule", + "Image", + "SetLineSpacing", + "SetPrintArea", + "Text", + "TextDraw", + "TextFormatting", + "TwoDCode", +] as const; +/** Union type of all possible commands that must be handled by command sets. */ +export type CommandType = typeof basicCommandTypes[number]; + +/**A regular command or an extended command type. */ +export type CommandAnyType = CommandType | symbol; +export function getCommandAnyType(cmd: IPrinterCommand | IPrinterExtendedCommand): CommandAnyType { + if (cmd.type === 'CustomCommand') { + return cmd.typeExtended; + } else { + return cmd.type; + } +} + +export abstract class BasicCommand implements IPrinterBasicCommand { + abstract name: string; + abstract type: CommandTypeBasic; + effectFlags: CommandEffectFlags; + toDisplay() { return this.name; } + constructor(effects: PrinterCommandEffectTypes[] = []) { + this.effectFlags = new CommandEffectFlags(effects); + } +} diff --git a/src/Commands/Messages.test.ts b/src/Commands/Messages.test.ts new file mode 100644 index 0000000..c1bf306 --- /dev/null +++ b/src/Commands/Messages.test.ts @@ -0,0 +1,38 @@ +import { expect, describe, it } from 'vitest'; +import * as Msgs from './Messages.js'; +import { AsciiCodeNumbers } from '../Util/ASCII.js'; + +describe('RawMessageTransformer', () => { + it('combineMessages', () => { + const t = new Msgs.RawMessageTransformer(); + expect(t.combineMessages(new Uint8Array([AsciiCodeNumbers.CR]), new Uint8Array([AsciiCodeNumbers.LF]))) + .toStrictEqual(new Uint8Array([AsciiCodeNumbers.CR, AsciiCodeNumbers.LF])); + expect(t.combineMessages(new Uint8Array([]), new Uint8Array([AsciiCodeNumbers.LF]))) + .toStrictEqual(new Uint8Array([AsciiCodeNumbers.LF])); + }); +}); + +describe('StringMessageTransformer', () => { + it('combineMessages', () => { + const t = new Msgs.StringMessageTransformer(); + expect(t.combineMessages('\r', '\n')) + .toStrictEqual('\r\n'); + expect(t.combineMessages('', '\n')) + .toStrictEqual('\n'); + }) +}); + +describe('Converters', () => { + it('Uint8Array to String', () => { + const expected = "This is my expected message!"; + const arr = Msgs.asUint8Array(expected); + const result = Msgs.asString(arr); + expect(result).toStrictEqual(expected); + }); + it('String to Uint8Array', () => { + const expected = new Uint8Array([AsciiCodeNumbers.CR, AsciiCodeNumbers.LF]); + const arr = Msgs.asString(expected); + const result = Msgs.asUint8Array(arr); + expect(result).toStrictEqual(expected); + }) +}) diff --git a/src/Commands/Messages.ts b/src/Commands/Messages.ts new file mode 100644 index 0000000..cc33856 --- /dev/null +++ b/src/Commands/Messages.ts @@ -0,0 +1,350 @@ +import * as Util from '../Util/index.js'; +import * as Conf from '../Configs/index.js'; +import type { IDeviceInformation } from "web-device-mux"; +import type { IPrinterCommand } from "./Commands.js"; +import type { CommandSet } from './CommandSet.js'; +import type { PrinterConfig } from './PrinterConfig.js'; + +export type PrinterMessage + = ISettingUpdateMessage + | IStatusMessage + | IErrorMessage + +export type MessageType = 'SettingUpdateMessage' | 'StatusMessage' | 'ErrorMessage' + +export interface MessageTransformer { + transformerType: Conf.MessageArrayLikeType; + combineMessages(...messages: TMessage[]): TMessage; + + messageToString(message: TMessage): string; + messageToUint8Array(message: TMessage): Uint8Array; +} + +export class RawMessageTransformer implements MessageTransformer { + transformerType: Conf.MessageArrayLikeType = "Uint8Array"; + + combineMessages(...messages: Uint8Array[]): Uint8Array { + const bufferLen = messages.reduce((sum, arr) => sum + arr.byteLength, 0); + return messages.reduce( + (accumulator, arr) => { + accumulator.buffer.set(arr, accumulator.offset); + return { ...accumulator, offset: arr.byteLength + accumulator.offset }; + }, + { buffer: new Uint8Array(bufferLen), offset: 0 } + ).buffer; + } + + messageToString(message: Uint8Array): string { + return asString(message); + } + + messageToUint8Array(message: Uint8Array): Uint8Array { + return message; + } +} + +export class StringMessageTransformer implements MessageTransformer { + transformerType: Conf.MessageArrayLikeType = "string"; + + combineMessages(...messages: string[]): string { + return messages.join(''); + } + messageToString(message: string): string { + return message; + } + messageToUint8Array(message: string): Uint8Array { + return asUint8Array(message); + } +} + +export function asUint8Array(commands: Conf.MessageArrayLike): Uint8Array { + if (typeof commands === "string") { + return new TextEncoder().encode(commands); + } else if (commands instanceof Uint8Array) { + return commands; + } else { + throw new Error("Unknown message type not implemented!"); + } +} + +export function asString(commands: Conf.MessageArrayLike): string { + if (typeof commands === "string") { + return commands; + } else if (commands instanceof Uint8Array) { + return Util.DecodeAscii(commands); + } else { + throw new Error("Unknown message type not implemented!"); + } +} + +export function asTargetMessageType( + msg: Conf.MessageArrayLike, + targetType: TMessage, +): TMessage { + if (typeof targetType === "string") { + return asString(msg) as TMessage; + } else if (targetType instanceof Uint8Array) { + return asUint8Array(msg) as TMessage; + } else { + throw new Error("Unknown message type not implemented!"); + } +} + +export type AwaitedCommand = { + cmd: IPrinterCommand, + promise: Promise, + resolve?: (value: boolean) => void, + reject?: (reason?: unknown) => void, +} + +/** A printer settings message, describing printer configuration status. */ +export interface ISettingUpdateMessage { + messageType: 'SettingUpdateMessage'; + + printerHardware?: Conf.UpdateFor; + printerMedia ?: Conf.UpdateFor; +} + +export enum StatusState { + PrinterOnline = "PrinterOnline", + PaperButtonFeedingPaper = "PaperButtonFeedingPaper", + DrawerOpen = "DrawerOpen", +} +export type StatusStates = keyof typeof StatusState; +export class StatusStateSet extends Set {} + +/** A status message sent by the printer. */ +export interface IStatusMessage { + messageType: 'StatusMessage' + + /** Any status notes provided by the printer. */ + statuses: StatusStateSet, +} + +export enum ErrorState { + NoError = "NoError", + UnknownError = "UnknownError", + + // User-generated errors + CommandSyntaxError = "CommandSyntaxError", + ObjectExceededLabelBorder = "ObjectExceededLabelBorder", + BarCodeDataLengthError = "BarCodeDataLengthError", + InsufficientMemoryToStoreData = "InsufficientMemoryToStoreData", + DuplicateNameFormGraphicOrSoftFont = "DuplicateNameFormGraphicOrSoftFont", + NameNotFoundFormGraphicOrSoftFont = "NameNotFoundFormGraphicOrSoftFont", + NotInDataEntryMode = "NotInDataEntryMode", + PDF417CodedDataTooLargeToFit = "PDF417CodedDataTooLargeToFit", + ReceiveBufferFull = "ReceiveBufferFull", + PresenterNotRunning = "PresenterNotRunning", + + // Physical problems with the device + MemoryConfigurationError = "MemoryConfigurationError", + RS232InterfaceError = "RS232InterfaceError", + CorruptRamConfigLost = "CorruptRamConfigLost", + InvalidFirmwareConfig = "InvalidFirmwareConfig", + PrintheadThermistorOpen = "PrintheadThermistorOpen", + PrintheadDetectionError = "PrintheadDetectionError", + BadPrintheadElement = "BadPrintheadElement", + IllegalInterruptOccurred = "IllegalInterruptOccurred", + + // Errors that need user action to resolve + PrintheadUp = "PrintheadUp", + + MediaEmpty = "MediaEmptyError", + MediaNearEnd = "MediaNearEnd", + RibbonEmptyError = "RibbonEmptyError", + + PrintheadTooHot = "PrintheadTooHot", + PrintheadTooCold = "PrintheadTooCold", + MotorTooHot = "MotorTooHot", + MotorTooCold = "MotorTooCold", + //MotorJuuuuuuuustRight + + BatteryLowWarning40Percent = "BatteryLowWarning40Percent", + BatteryLowLimit20Percent = "BatteryLowLimit20Percent", + + CutterJammedOrNotInstalled = "CutterJammedOrNotInstalled", + PressFeedButtonToRecover = "PressFeedButtonToRecover", + PaperFeedError = "PaperFeedError", + PaperJamDuringRetract = "PaperJamDuringRetract", + UnrecoverableError = "UnrecoverableError", + + PrintheadNeedsCleaning = "PrintheadNeedsCleaning", + PrintheadNeedsReplacing = "PrintheadNeedsReplacing", + + // Media calibration errors + MediaErrorOrBlacklineNotDetectedOrExcessiveMediaFeeding = "MediaErrorOrBlacklineNotDetectedOrExcessiveMediaFeeding", + + BlackMarkNotFound = "BlackMarkNotFound", + BlackMarkCalirateError = "BlackMarkCalirateError", + AutoSenseOrSensorFailure = "AutoSenseOrSensorFailure", + ExcessiveMediaFeeding = "ExcessiveMediaFeeding", + RetractFunctionTimeout = "RetractFunctionTimeout", + NeedToCalibrateMedia = "NeedToCalibrateMedia", + + // Statuses + PrinterBusyProcessingPrintJob = "PrinterBusyProcessingPrintJob", + PrinterPaused = "PrinterPaused", + PartialFormatInProgress = "PartialFormatInProgress", + CommDiagnosticModeActive = "CommDiagnosticModeActive", + LabelWaitingToBeTaken = "LabelWaitingToBeTaken", + + // General library errors + MessageReceiveException = "MessageReceiveException", +} +export type ErrorStates = keyof typeof ErrorState; +export class ErrorStateSet extends Set {} + +/** An error message sent by the printer. */ +export interface IErrorMessage { + messageType: 'ErrorMessage', + + /** Any error notes that prevent the printer from printing. */ + errors: ErrorStateSet, + + exceptions?: Error[], +} + +/** The output of a function for parsing a message. */ +export interface IMessageHandlerResult { + messageIncomplete: boolean, + messageMatchedExpectedCommand: boolean, + messages: PrinterMessage[], + remainder: TInput +} + +export type CommandSetMessageHandlerDelegate = + ( + cmdSet: CommandSet, + message: TReceived, + config: PrinterConfig, + sentCommand?: IPrinterCommand + ) => IMessageHandlerResult; + +/** An error indicating a problem parsing a received message. */ +export class MessageParsingError extends Util.WebReceiptLineError { + public readonly receivedMessage: Conf.MessageArrayLike; + constructor(message: string, receivedMessage: Conf.MessageArrayLike) { + super(message); + this.receivedMessage = receivedMessage; + } +} + +export function deviceInfoToOptionsUpdate(deviceInfo: IDeviceInformation): ISettingUpdateMessage { + return { + messageType: 'SettingUpdateMessage', + printerHardware: { + serialNumber: deviceInfo.serialNumber, + model: deviceInfo.productName, + manufacturer: deviceInfo.manufacturerName + }, + printerMedia: {} + } +} + +export async function parseRaw( + input: TInput, + commandSet: CommandSet, + config: PrinterConfig, + awaitedCommands: AwaitedCommand[] +): Promise<{ remainderMsg: TInput; remainderCommands: AwaitedCommand[], messages: PrinterMessage[]; }> { + let remainderMsg = input; + if (remainderMsg.length === 0) { return { messages: [], remainderCommands: awaitedCommands, remainderMsg}; } + let incomplete = false; + const messages: PrinterMessage[] = []; + + let remainderCommands = awaitedCommands.slice(); + + do { + if (remainderCommands.length === 0) { + // No candidate commands, treat as raw! + const parseResult = commandSet.handleMessage(remainderMsg, config); + remainderMsg = parseResult.remainder; + incomplete = parseResult.messageIncomplete; + parseResult.messages.forEach(m => messages.push(m)); + + } else { + remainderCommands = remainderCommands.filter(c => { + if (incomplete) { + // Something else indicated it's incomplete, keep this candidate too. + return true; + } + + const parseResult = commandSet.handleMessage(remainderMsg, config, c.cmd); + if (parseResult.messageMatchedExpectedCommand) { + // The command found its response! Mark it accordingly. + if (parseResult.messageIncomplete) { + // But the command expects a longer response. Bail IMMEDIATELY. + incomplete = true; + return true; + } + + // Otherwise it's safe to remove that message chunk and remove the candidate. + remainderMsg = parseResult.remainder; + parseResult.messages.forEach(m => messages.push(m)); + + if (c?.resolve === undefined) { + console.error('Resolve callback was undefined for awaited command, this may cause a deadlock! This is a bug in the library.'); + } else { + c.resolve(true); + } + return false; + } + }); + } + } while (incomplete === false && remainderMsg.length > 0 && remainderCommands.length > 0) + + return { remainderMsg, remainderCommands, messages } +} + +/** + * Slice an array from the start to the first LF character, returning both pieces. + * + * If no LF character is found sliced will have a length of 0. + * + * CR characters are not removed if present! + */ +export function sliceToNewline(msg: Uint8Array): { + sliced: Uint8Array, + remainder: Uint8Array, +} { + const idx = msg.indexOf(Util.AsciiCodeNumbers.LF); + if (idx === -1) { + return { + sliced: new Uint8Array(), + remainder: msg + } + } + + return { + sliced: msg.slice(0, idx + 1), + remainder: msg.slice(idx + 1), + }; +} + +/** Slice a string from the start to the first CRLF or LF, returning both pieces. */ +export function sliceToCRLF(msg: string): { + sliced: string, + remainder: string, +} { + const cr = msg.indexOf('\r\n'); + if (cr !== -1) { + return { + sliced: msg.substring(0, cr), + remainder: msg.substring(cr + 2) + } + } + + const lf = msg.indexOf('\n'); + if (lf !== -1) { + return { + sliced: msg.substring(0, lf), + remainder: msg.substring(lf + 1) + } + } + + return { + sliced: "", + remainder: msg + } +} diff --git a/src/Commands/PrinterConfig.ts b/src/Commands/PrinterConfig.ts new file mode 100644 index 0000000..862d8a4 --- /dev/null +++ b/src/Commands/PrinterConfig.ts @@ -0,0 +1,38 @@ +import * as Conf from '../Configs/index.js'; +import type { ISettingUpdateMessage } from './Messages.js'; + +/** Configured options for a label printer */ +export class PrinterConfig extends Conf.BasePrinterConfig { + public constructor() { + super(); + } + + /** Update these options with newly transmitted settings. */ + public update(msg: ISettingUpdateMessage) { + const h = msg.printerHardware; + this._codepages = h?.codepages ?? this._codepages; + this._cutter = h?.cutter ?? this._cutter; + this._firmware = h?.firmware ?? this._firmware; + this._manufacturer = h?.manufacturer ?? this._manufacturer; + this._model = h?.model ?? this._model; + this._serial = h?.serialNumber ?? this._serial; + + this._hasMultiByteSupport = h?.hasMultiByteSupport ?? this._hasMultiByteSupport; + this._hasDmdConnected = h?.hasDmdConnected ?? this._hasDmdConnected; + this._fontLanguageSupport = h?.fontLanguageSupport ?? this._fontLanguageSupport; + + const m = msg.printerMedia; + this._cpl = m?.charactersPerLine ?? this._cpl + this._darkness = m?.darknessPercent ?? this._darkness; + this._printOrientation = m?.printOrientation ?? this._printOrientation; + } + + public toUpdate(): ISettingUpdateMessage { + return { + messageType: 'SettingUpdateMessage', + + printerHardware: this, + printerMedia: this, + } + } +} diff --git a/src/Commands/TranspileCommand.ts b/src/Commands/TranspileCommand.ts new file mode 100644 index 0000000..2fc915d --- /dev/null +++ b/src/Commands/TranspileCommand.ts @@ -0,0 +1,61 @@ +import * as Util from '../Util/index.js'; +import * as Conf from '../Configs/index.js'; +import type { PrinterConfig } from './PrinterConfig.js'; +import type { TextFormat } from './BasicCommands.js'; +import { CommandEffectFlags } from './Commands.js'; + +/** Interface of document state effects carried between individual commands. */ +export interface TranspiledDocumentState { + /** The current formatted character font size. */ + characterSize: Conf.Coordinate; + /** The current formatted codepage. */ + codepage: Util.Codepage; + /** The aggregate effects of this document, when printed. */ + commandEffectFlags: CommandEffectFlags; + /** The current formatted document print width. */ + currentPrintWidth: number; + + /** The read-only config at the start of the transpile operation. */ + initialConfig: PrinterConfig; + lineSpacing: number; + + margin: { + leftChars: number; + rightChars: number; + } + + textFormat: TextFormat; +} + +export function getNewTranspileState(config: PrinterConfig): TranspiledDocumentState { + return { + // TODO: Pull more of these from printer options. + characterSize: { + left: 12, + top: 24, + }, + commandEffectFlags: new CommandEffectFlags(), + currentPrintWidth: config.charactersPerLine, + lineSpacing: 1, + margin: { + leftChars: 0, + rightChars: 0, + }, + textFormat: {}, + codepage: "CP437", + initialConfig: config, + } +} + +/** Represents an error when validating a document against a printer's capabilities. */ +export class TranspileDocumentError extends Util.WebReceiptLineError { + private _innerErrors: TranspileDocumentError[] = []; + get innerErrors() { + return this._innerErrors; + } + + constructor(message: string, innerErrors?: TranspileDocumentError[]) { + super(message); + this._innerErrors = innerErrors ?? []; + } +} diff --git a/src/Commands/index.ts b/src/Commands/index.ts new file mode 100644 index 0000000..22f3471 --- /dev/null +++ b/src/Commands/index.ts @@ -0,0 +1,6 @@ +export * from './BasicCommands.js'; +export * from './Commands.js'; +export * from './CommandSet.js'; +export * from './Messages.js'; +export * from './PrinterConfig.js'; +export * from './TranspileCommand.js'; diff --git a/src/Documents/CommandSet.ts b/src/Documents/CommandSet.ts deleted file mode 100644 index f6dff3f..0000000 --- a/src/Documents/CommandSet.ts +++ /dev/null @@ -1,77 +0,0 @@ -import type { PrinterCommandLanguage } from "../Printers/Languages/index.js"; -import * as Cmds from "./Commands.js"; -import type { Codepage } from "../Printers/Codepages/index.js"; -import type { Coordinate, IMediaOptions } from "../Printers/Options/index.js"; -import type { IMessageHandlerResult } from "../Printers/Communication/index.js"; -import type { TranspileDocumentError } from "./TranspileCommandError.js"; - -/** Describes a class capable of managing command implementations. */ -export interface CommandSet { - /** Encode a raw string command into a raw command according to the command language rules. */ - encodeCommand(str?: string, withNewline?: boolean): TOutput; - - /** Gets the command language this command set implements */ - get commandLanguage(): PrinterCommandLanguage; - /** Get an empty command to do nothing at all. */ - get noop(): TOutput; - /** Gets the commands to start a new document. */ - get documentStartCommands(): Cmds.IPrinterCommand[]; - /** Gets the commands to end a document. */ - get documentEndCommands(): Cmds.IPrinterCommand[]; - /** Get a new document metadata tracking object. */ - getNewTranspileState(media: IMediaOptions): TranspiledDocumentState; - - /** Parse a message object received from the printer. */ - parseMessage( - msg: TOutput, - sentCommand?: Cmds.IPrinterCommand - ): IMessageHandlerResult; - - /** Get expanded commands for a given command, if applicable. */ - expandCommand(cmd: Cmds.IPrinterCommand): Cmds.IPrinterCommand[]; - - /** Transpile a single command, tracking its effects to a document. */ - transpileCommand( - cmd: Cmds.IPrinterCommand, - docMetadata: TranspiledDocumentState - ): TOutput | TranspileDocumentError; - - /** Combine separate commands into one series of commands. */ - combineCommands(...commands: TOutput[]): TOutput; -} - -export function exhaustiveMatchGuard(_: never): never { - throw new Error('Invalid case received!' + _); -} - -/** Interface of document state effects carried between individual commands. */ -export interface TranspiledDocumentState { - lineSpacing: number; - charactersPerLine: number; - - margin: { - leftChars: number; - rightChars: number; - } - printWidth: number; - - characterSize: Coordinate; - - commandEffectFlags: Cmds.CommandEffectFlags; - codepage: Codepage; - - textFormat: Cmds.TextFormat; -} - -/** A method for transpiling a given command to its native command. */ -export type TranspileCommandDelegate = ( - cmd: Cmds.IPrinterCommand, - docState: TranspiledDocumentState, - commandSet: CommandSet -) => TOutput; - -/** A manifest for a custom extended printer command. */ -export interface IPrinterExtendedCommandMapping { - extendedTypeSymbol: symbol, - delegate: TranspileCommandDelegate, -} diff --git a/src/Printers/Communication/Messages.ts b/src/Printers/Communication/Messages.ts deleted file mode 100644 index c618154..0000000 --- a/src/Printers/Communication/Messages.ts +++ /dev/null @@ -1,77 +0,0 @@ -import { WebReceiptLineError } from "../../index.js"; -import type { IDeviceInformation } from "web-device-mux"; - -export type PrinterMessage - = ISettingUpdateMessage - | IStatusMessage - | IErrorMessage - -export type MessageType = 'SettingUpdateMessage' | 'StatusMessage' | 'ErrorMessage' - -/** A printer settings message, describing printer configuration status. */ -export interface ISettingUpdateMessage { - messageType: 'SettingUpdateMessage'; - - // Yep it's a giant bundle of optional values to update! - hasMultiByteSupport?: boolean; - hasAutocutter?: boolean; - hasDmdConnected?: boolean; - - firmwareVersion?: number[]; - manufacturerName?: string; - modelName?: string; - serialNumber?: string; - fontLanguageSupport?: string; -} - -/** A status message sent by the printer. */ -export interface IStatusMessage { - messageType: 'StatusMessage' - - printerOnline?: boolean, - drawerKickStatus?: boolean, - coverOpen?: boolean, - paperLow?: boolean, - paperButtonFeedingPaper?: boolean, -} - -/** An error message sent by the printer. */ -export interface IErrorMessage { - messageType: 'ErrorMessage', - displayText: string, - isErrored: boolean, - - paperOut?: boolean, - cutterError?: boolean, - waitForOnlineRecovery?: boolean, - recoverableError?: boolean, - autorecoverableError?: boolean, - - turnOffPowerImmediately?: boolean, -} - -/** The output of a function for parsing a message. */ -export interface IMessageHandlerResult { - messageIncomplete: boolean, - messageMatchedExpectedCommand: boolean, - messages: PrinterMessage[], - remainder: TInput -} - -/** An error indicating a problem parsing a received message. */ -export class MessageParsingError extends WebReceiptLineError { - public readonly receivedMessage: Uint8Array; - constructor(message: string, receivedMessage: Uint8Array) { - super(message); - this.receivedMessage = receivedMessage; - } -} - -export function deviceInfoToOptionsUpdate(deviceInfo: IDeviceInformation): ISettingUpdateMessage { - return { - messageType: 'SettingUpdateMessage', - modelName: deviceInfo.productName, - serialNumber: deviceInfo.serialNumber, - manufacturerName: deviceInfo.manufacturerName, - } -} diff --git a/src/Printers/Communication/index.ts b/src/Printers/Communication/index.ts deleted file mode 100644 index b6317c0..0000000 --- a/src/Printers/Communication/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './Messages.js' diff --git a/src/Printers/Options/PrinterOptions.ts b/src/Printers/Options/PrinterOptions.ts deleted file mode 100644 index adef896..0000000 --- a/src/Printers/Options/PrinterOptions.ts +++ /dev/null @@ -1,146 +0,0 @@ -import type { Codepage } from "../Codepages/index.js"; -import type { ISettingUpdateMessage } from "../Communication/Messages.js"; -import { EscPos, hex, type PrinterCommandLanguage } from "../Languages/index.js"; - -/** Coordinates on a 2D plane. */ -export interface Coordinate { - /** Offset from the left side of the plane, incrementing to the right. --> */ - left: number; - /** Offset from the top side of the plane, incrementing down. */ - top: number; -} - -/** The orientation of a label as it comes out of the printer. */ -export enum PrintOrientation { - /** Right-side up when the printer faces the user. */ - normal, - /** Upside-down when the printer faces the user. */ - inverted -} - -export enum PrintCutter { - /** No cutter available. */ - none, - /** Only supports partial cuts. */ - partial, - /** Only supports full cuts. */ - full, - /** Supports switching between cutter modes. */ - multiple -} - -export interface IPrinterFeatures { - /** The cutter mode the printer supports. */ - get cutter(): PrintCutter; -} - -/** Firmware information about the printer that can't be modified. */ -export interface IPrinterFactoryInformation { - /** The raw serial number of the printer. */ - get serialNumber(): string; - /** The model of the printer. */ - get model(): string; - /** The manufacturer of the printer */ - get manufacturer(): string; - /** The firmware version information for the printer. */ - get firmware(): string; - /** The command languages the printer supports. */ - get language(): PrinterCommandLanguage; -} - -export interface IPrinterEncoding { - /** The available codepages this printer supports */ - get codepages(): Set; -} - -export interface IMediaOptions { - /** Number of characters printed per line. Commonly 42. */ - charactersPerLine: number; - - /** The orientation of page contents. */ - orientation: PrintOrientation; -} - -export class PrinterOptions implements IMediaOptions, IPrinterEncoding, IPrinterFactoryInformation { - charactersPerLine: number = 42; // TODO: dynamic! - orientation: PrintOrientation = PrintOrientation.normal; - private _codepages = new Set([ - "CP437", - "CP720", - "CP737", - "CP775", - "CP850", - "CP851", - "CP852", - "CP853", - "CP855", - "CP857", - "CP858", - "CP860", - "CP861", - "CP862", - "CP863", - "CP864", - "CP865", - "CP866", - "CP869", - "CP1098", - "CP1118", - "CP1119", - "CP1125", - "ISO88592", - "ISO88597", - "ISO885915", - "RK1048", - "WINDOWS1250", - "WINDOWS1251", - "WINDOWS1252", - "WINDOWS1253", - "WINDOWS1254", - "WINDOWS1255", - "WINDOWS1256", - "WINDOWS1257", - "WINDOWS1258", - ]); - get codepages() { return this._codepages; } - - private _serial?: string; - get serialNumber() { return this._serial ?? ''} - - private _model?: string; - get model() { return this._model ?? '' } - - private _manufacturer?: string; - get manufacturer() { return this._manufacturer ?? '' } - - private _hasMultiByteSupport?: boolean; - get hasMultByteSupport() { return this._hasMultiByteSupport ?? false } - private _fontLanguageSupport?: string; - get fontLanugageSupport() { return this._fontLanguageSupport ?? '' } - - private _hasAutocutter?: boolean; - get hasAutocutter() { return this._hasAutocutter ?? false } - - private _firmware?: string; - get firmware() { return this._firmware ?? '' } - - get language() { return EscPos } - - /** Update these options with newly transmitted settings. */ - update(msg: ISettingUpdateMessage) { - - this._firmware = msg.firmwareVersion?.map(hex).join('') ?? this.firmware; - this._fontLanguageSupport = msg.fontLanguageSupport ?? this._fontLanguageSupport; - this._hasAutocutter = msg.hasAutocutter ?? this._hasAutocutter; - //this._hasDmdConnected = msg.hasDmdConnected ?? this._hasDmdConnected; - this._hasMultiByteSupport = msg.hasMultiByteSupport ?? this._hasMultiByteSupport; - this._manufacturer = msg.manufacturerName ?? this._manufacturer; - this._model = msg.modelName ?? this._model; - this._serial = msg.serialNumber ?? this._serial; - } - - copy(): PrinterOptions { - return structuredClone(this); - } -} - From 1b0351e67e97621237df99d7473f6d8d629d349b Mon Sep 17 00:00:00 2001 From: Cellivar <1441553+Cellivar@users.noreply.github.com> Date: Thu, 2 Jan 2025 03:59:32 -0800 Subject: [PATCH 5/8] Migrate document structures --- src/Documents/Document.ts | 24 +- src/Documents/DocumentTranspiler.ts | 291 ++++++++++++++++----- src/Documents/TranspileCommandError.ts | 14 - src/Documents/index.ts | 7 +- src/Printers/Languages/RawCommandSet.ts | 78 ------ src/Printers/Languages/StringCommandSet.ts | 79 ------ src/ReceiptLine/Parser.ts | 26 +- 7 files changed, 246 insertions(+), 273 deletions(-) delete mode 100644 src/Documents/TranspileCommandError.ts delete mode 100644 src/Printers/Languages/RawCommandSet.ts delete mode 100644 src/Printers/Languages/StringCommandSet.ts diff --git a/src/Documents/Document.ts b/src/Documents/Document.ts index f23ff36..b67129a 100644 --- a/src/Documents/Document.ts +++ b/src/Documents/Document.ts @@ -1,25 +1,25 @@ -import type { PrinterCommandLanguage } from "../Printers/Languages/index.js"; -import type { CommandEffectFlags, IPrinterCommand } from "./Commands.js"; +import * as Conf from '../Configs/index.js'; +import * as Cmds from '../Commands/index.js'; -/** A document of printer commands, to be compiled for a specific printer. */ +/** A prepared document, ready to be compiled and sent. */ export interface IDocument { /** Gets the series of commands this document contains. */ - commands: ReadonlyArray; + commands: ReadonlyArray; } -/** Stream of commands, optionally ended by an awaited command. */ -export class Transaction{ +/** Stream of commands, with zero or more commands expected to return messages. */ +export class Transaction{ constructor( - public readonly commands: T, - public readonly awaitedCommand: IPrinterCommand | undefined, + public readonly commands: Conf.MessageArrayLike, + public readonly awaitedCommands: Cmds.IPrinterCommand[], ) {} } /** Compiled document of commands ready to be sent to a printer which supports the PCL. */ -export class CompiledDocument { +export class CompiledDocument { constructor( - public readonly language: PrinterCommandLanguage, - public readonly effects: CommandEffectFlags, - public readonly transactions: Transaction[] + public readonly language: Conf.PrinterCommandLanguage, + public readonly effects: Cmds.CommandEffectFlags, + public readonly transactions: Transaction[] ) {} } diff --git a/src/Documents/DocumentTranspiler.ts b/src/Documents/DocumentTranspiler.ts index ca19910..fca162f 100644 --- a/src/Documents/DocumentTranspiler.ts +++ b/src/Documents/DocumentTranspiler.ts @@ -1,38 +1,48 @@ -import * as Cmds from "./index.js"; -import type { CommandSet, TranspiledDocumentState } from "./CommandSet.js"; -import { TranspileDocumentError } from "./TranspileCommandError.js"; +import * as Util from '../Util/index.js'; +import * as Conf from '../Configs/index.js'; +import * as Cmds from '../Commands/index.js'; +import { CompiledDocument, Transaction, type IDocument } from './Document.js'; type PrecompiledTransaction = { - commands: Array; - waitCommand?: Cmds.IPrinterCommand; + commands: Cmds.IPrinterCommand[]; + waitCommands: Cmds.IPrinterCommand[]; }; -export function transpileDocument( - doc: Cmds.IDocument, - commandSet: CommandSet, - documentMetadata: TranspiledDocumentState, -): Readonly> { +interface RawCommandForm { + effects: Cmds.CommandEffectFlags; + transactions: PrecompiledTransaction[]; + withinForm: boolean; +} + +export function transpileDocument( + doc: IDocument, + commandSet: Cmds.CommandSet, + documentMetadata: Cmds.TranspiledDocumentState, + commandReorderBehavior: Cmds.CommandReorderBehavior = Cmds.CommandReorderBehavior.afterAllForms +): Readonly { - const cmdsWithinDoc = [ - ...commandSet.documentStartCommands, - ...doc.commands, - ...commandSet.documentEndCommands, - ]; - const { transactions, effects } = splitTransactions(cmdsWithinDoc, commandSet); + const forms = splitTransactionsAndForms(doc.commands, commandSet, commandReorderBehavior); + + // Wait why do we throw away the form data here? + // ZPL has some advanced form processing concepts that aren't implemented in + // this library yet, and that code was annoying to figure out. It hangs out + // here until the advanced form processing can be implemented later. + // TODO: Handle separate forms instead of mooshing them together. + const { transactions, effects } = combineForms(forms); const commandsWithMaybeErrors = transactions .map((trans) => compileTransaction(trans, commandSet, documentMetadata)); const errs = commandsWithMaybeErrors.flatMap(ce => ce.errors); if (errs.length > 0) { - throw new TranspileDocumentError( + throw new Cmds.TranspileDocumentError( 'One or more validation errors occurred transpiling the document.', errs ); } return Object.freeze( - new Cmds.CompiledDocument( + new CompiledDocument( commandSet.commandLanguage, effects, commandsWithMaybeErrors.map(c => c.transaction) @@ -40,47 +50,23 @@ export function transpileDocument( ); } -function compileTransaction( - trans: PrecompiledTransaction, - commandSet: CommandSet, - docState: TranspiledDocumentState, -): { - transaction: Cmds.Transaction, - errors: TranspileDocumentError[] -} { - const {cmds, errors} = trans.commands - .map((cmd) => commandSet.transpileCommand(cmd, docState)) - .reduce((a, cmd) => { - if (cmd instanceof TranspileDocumentError) { - a.errors.push(cmd); - } else { - a.cmds.push(cmd); - } - return a; - }, { - cmds: new Array, - errors: new Array, - }); +function splitTransactionsAndForms( + commands: ReadonlyArray, + commandSet: Cmds.CommandSet, + reorderBehavior: Cmds.CommandReorderBehavior +): RawCommandForm[] { + const forms: Array = []; + const reorderedCommands: Array = []; - return { - transaction: new Cmds.Transaction( - commandSet.combineCommands(...cmds), - trans.waitCommand - ), - errors + let currentTrans: PrecompiledTransaction = { + commands: [], + waitCommands: [] }; -} - -function splitTransactions( - commands: ReadonlyArray, - commandSet: CommandSet, -): { - transactions: PrecompiledTransaction[]; - effects: Cmds.CommandEffectFlags -} { - const effects = new Cmds.CommandEffectFlags(); - const transactions: PrecompiledTransaction[] = []; - let currentTrans: Cmds.IPrinterCommand[] = []; + let currentForm: RawCommandForm = { + transactions: [], + withinForm: false, + effects: new Cmds.CommandEffectFlags(), + } // We may need to add new commands while iterating, create a stack. const commandStack = commands.toReversed(); @@ -98,27 +84,188 @@ function splitTransactions( continue; } + if (currentForm.withinForm) { + if (command.type === "StartReceipt") { + // Assume they meant to close the previous form and start a new one. + // Insert the missing EndLabel and go back through. + commandStack.push(command); + commandStack.push(new Cmds.EndReceipt()); + continue; + } else if (command.type === "EndReceipt") { + currentForm.withinForm = false; + } else if (commandSet.isCommandNonFormCommand(command)) { + // Some commands won't work within a form, move them out according to the + // selected behavior. + switch (reorderBehavior) { + default: + Util.exhaustiveMatchGuard(reorderBehavior); + break; + case Cmds.CommandReorderBehavior.afterAllForms: + case Cmds.CommandReorderBehavior.beforeAllForms: + reorderedCommands.push(command); + // Replace with a placeholder in case this is the last command + commandStack.push(new Cmds.NoOp()); + continue; + case Cmds.CommandReorderBehavior.closeForm: + if (currentForm.withinForm) { + // The label wasn't closed so we must do it ourselves. Add a command + // to close the label on the stack and send it back around. + commandStack.push(command); + commandStack.push(new Cmds.EndReceipt()); + continue; + } + break; + case Cmds.CommandReorderBehavior.throwError: + if (currentForm.withinForm) { + throw new Cmds.TranspileDocumentError("Non-form command present within a document form and Command Reorder Behavior was set to throw errors."); + } + break; + } + } + + } else { + if (command.type === "StartReceipt") { + currentForm.withinForm = true; + } else if (command.type === "EndReceipt") { + // Spurious EndLabel? + commandStack.push(new Cmds.NoOp()); + continue; + } else if (!commandSet.isCommandNonFormCommand(command)) { + // Conversely, if we're outside a form and the command should be in a form + // we start a form for it. + commandStack.push(command); + commandStack.push(new Cmds.StartReceipt()); + continue; + } + } + // Record the command in the transpile buffer. - currentTrans.push(command); - command.effectFlags.forEach(f => effects.add(f)); + if (command.type !== "NoOp") { + currentTrans.commands.push(command); + command.effectFlags.forEach(f => currentForm.effects.add(f)); + } if (command.effectFlags.has("waitsForResponse")) { - // This command expects the printer to provide feedback. We should pause - // sending more commands until we get its response, which could take some - // amount of time. - // This is the end of our transaction. - transactions.push({ - commands: currentTrans, - waitCommand: command, - }); - currentTrans = []; + // This command expects the printer to provide feedback. + // Mark it as awaited in Valhalla. + currentTrans.waitCommands.push(command); + if (!currentForm.withinForm) { + // We have an awaited command and we're outside a form, this is a great + // spot to segment a transaction so we can wait for the printer's response. + currentForm.transactions.push(currentTrans); + currentTrans = { + commands: [], + waitCommands: [] + }; + } + } + + if (command.type === "EndReceipt") { + if (currentTrans.commands.length > 0) { + currentForm.transactions.push(currentTrans); + currentTrans = { + commands: [], + waitCommands: [] + }; + } + forms.push(currentForm); + currentForm = { + transactions: [], + withinForm: false, + effects: new Cmds.CommandEffectFlags(), + } + } + + // If we're about to close up shop because this is the last command let's + // check a few bookkeeping items are in order. + if (commandStack.length === 0) { + // If we didn't close out the current form we should now. + if (currentForm.withinForm) { + commandStack.push(new Cmds.EndReceipt()); + continue; + } + // If we have commands in a transaction buffer close that too. + if (currentTrans.commands.length > 0) { + currentForm.transactions.push(currentTrans); + currentTrans = { + commands: [], + waitCommands: [] + }; + } + // And if the current form has any contents close it out. + if (currentForm.transactions.length > 0) { + forms.push(currentForm); + } } } while (commandStack.length > 0) - if (currentTrans.length > 0) { - currentTrans.push(); - transactions.push({ commands: currentTrans }); + if (reorderedCommands.length > 0) { + // The reordered commands should only be non-form commands, so it should be + // safe to throw an error about it. + const reorderedForms = splitTransactionsAndForms( + reorderedCommands, + commandSet, + Cmds.CommandReorderBehavior.throwError); + switch (reorderBehavior) { + default: + Util.exhaustiveMatchGuard(reorderBehavior); + break; + case Cmds.CommandReorderBehavior.beforeAllForms: + reorderedForms.reverse().forEach(f => forms.unshift(f)); + break; + case Cmds.CommandReorderBehavior.afterAllForms: + case Cmds.CommandReorderBehavior.closeForm: + case Cmds.CommandReorderBehavior.throwError: + reorderedForms.forEach(f => forms.push(f)); + break; + } } - return { transactions, effects } + return forms; +} + +function compileTransaction( + trans: PrecompiledTransaction, + commandSet: Cmds.CommandSet, + docState: Cmds.TranspiledDocumentState, +): { + transaction: Transaction, + errors: Cmds.TranspileDocumentError[] +} { + const {cmds, errors} = trans.commands + .map((cmd) => commandSet.transpileCommand(cmd, docState)) + .reduce((a, cmd) => { + if (cmd instanceof Cmds.TranspileDocumentError) { + a.errors.push(cmd); + } else { + a.cmds.push(cmd); + } + return a; + }, { + cmds: new Array, + errors: new Array, + }); + + return { + transaction: new Transaction( + commandSet.combineCommands(commandSet.documentStartPrefix, ...cmds, commandSet.documentEndSuffix), + trans.waitCommands + ), + errors + }; +} + +function combineForms(forms: RawCommandForm[]): RawCommandForm { + const result: RawCommandForm = { + transactions: [], + withinForm: false, + effects: new Cmds.CommandEffectFlags(), + } + + forms.forEach(f => { + result.transactions.push(...f.transactions); + f.effects.forEach(e => result.effects.add(e)); + }); + + return result; } diff --git a/src/Documents/TranspileCommandError.ts b/src/Documents/TranspileCommandError.ts deleted file mode 100644 index 4bc6695..0000000 --- a/src/Documents/TranspileCommandError.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { WebReceiptLineError } from "../WebReceiptLineError.js"; - -/** Represents an error when validating a document against a printer's capabilities. */ -export class TranspileDocumentError extends WebReceiptLineError { - private _innerErrors: TranspileDocumentError[] = []; - get innerErrors() { - return this._innerErrors; - } - - constructor(message: string, innerErrors?: TranspileDocumentError[]) { - super(message); - this._innerErrors = innerErrors ?? []; - } -} diff --git a/src/Documents/index.ts b/src/Documents/index.ts index 8b83de4..26ac6e7 100644 --- a/src/Documents/index.ts +++ b/src/Documents/index.ts @@ -1,5 +1,2 @@ -export * from "./Commands.js" -export * from "./Document.js" -export * from "./CommandSet.js" -export * from "./DocumentTranspiler.js" -export * from "./TranspileCommandError.js" +export * from './Document.js'; +export * from './DocumentTranspiler.js'; diff --git a/src/Printers/Languages/RawCommandSet.ts b/src/Printers/Languages/RawCommandSet.ts deleted file mode 100644 index 2490184..0000000 --- a/src/Printers/Languages/RawCommandSet.ts +++ /dev/null @@ -1,78 +0,0 @@ -import * as Cmds from "../../Documents/index.js"; -import type { IMessageHandlerResult } from "../Communication/index.js"; -import type { IMediaOptions } from "../Options/index.js"; -import type { PrinterCommandLanguage } from "./index.js"; - -/** A class to transpile commands as raw 8-bit commands to a printer. */ -export abstract class RawCommandSet implements Cmds.CommandSet { - /** Encode a raw string command into a Uint8Array according to the command language rules. */ - public abstract encodeCommand(str?: string, withNewline?: boolean): Uint8Array; - - private readonly _noop = new Uint8Array(); - public get noop() { - return this._noop; - } - - public abstract get documentStartCommands(): Cmds.IPrinterCommand[]; - public abstract get documentEndCommands(): Cmds.IPrinterCommand[]; - private cmdLanguage: PrinterCommandLanguage; - get commandLanguage() { - return this.cmdLanguage; - } - - public abstract getNewTranspileState(media: IMediaOptions): Cmds.TranspiledDocumentState; - - protected extendedCommandMap = new Map>; - - protected constructor( - implementedLanguage: PrinterCommandLanguage, - extendedCommands: Array> = [] - ) { - this.cmdLanguage = implementedLanguage; - extendedCommands.forEach(c => this.extendedCommandMap.set(c.extendedTypeSymbol, c.delegate)); - } - - public abstract parseMessage( - msg: Uint8Array, - sentCommand?: Cmds.IPrinterCommand - ): IMessageHandlerResult; - - public abstract expandCommand(cmd: Cmds.IPrinterCommand): Cmds.IPrinterCommand[]; - - public abstract transpileCommand( - cmd: Cmds.IPrinterCommand, - docState: Cmds.TranspiledDocumentState - ): Uint8Array | Cmds.TranspileDocumentError; - - protected extendedCommandHandler( - cmd: Cmds.IPrinterCommand, - docState: Cmds.TranspiledDocumentState - ) { - const lookup = (cmd as Cmds.IPrinterExtendedCommand).typeExtended; - if (!lookup) { - throw new Cmds.TranspileDocumentError( - `Command '${cmd.constructor.name}' did not have a value for typeExtended. If you're trying to implement a custom command check the documentation.` - ) - } - - const cmdHandler = this.extendedCommandMap.get(lookup); - - if (cmdHandler === undefined) { - throw new Cmds.TranspileDocumentError( - `Unknown command '${cmd.constructor.name}' was not found in the command map for ${this.commandLanguage} command language. If you're trying to implement a custom command check the documentation for correctly adding mappings.` - ); - } - return cmdHandler(cmd, docState, this); - } - - public combineCommands(...commands: Uint8Array[]) { - const bufferLen = commands.reduce((sum, arr) => sum + arr.byteLength, 0); - return commands.reduce( - (accumulator, arr) => { - accumulator.buffer.set(arr, accumulator.offset); - return { ...accumulator, offset: arr.byteLength + accumulator.offset }; - }, - { buffer: new Uint8Array(bufferLen), offset: 0 } - ).buffer; - } -} diff --git a/src/Printers/Languages/StringCommandSet.ts b/src/Printers/Languages/StringCommandSet.ts deleted file mode 100644 index 5370b22..0000000 --- a/src/Printers/Languages/StringCommandSet.ts +++ /dev/null @@ -1,79 +0,0 @@ -// import * as Commands from "../../Documents/index.js"; -// import type { CommandSet, IPrinterExtendedCommandMapping, TranspileCommandDelegate, TranspiledFormState } from "./CommandSet.js"; -// import { TranspileCommandError } from "./TranspileCommandError.js"; -// import type { PrinterCommandLanguage } from "./index.js"; - -// export type StringForm = TranspiledFormState; - -// /** A class to transpile commands into string-based commands for a printer. */ -// export abstract class StringCommandSet implements CommandSet { -// public abstract encodeCommand(str?: string, withNewline?: boolean): string; - -// private readonly _noop = ''; -// public get noop() { -// return this._noop; -// } - -// public abstract get formStartCommand(): string; -// public abstract get formEndCommand(): string; -// private cmdLanguage: PrinterCommandLanguage; -// get commandLanguage() { -// return this.cmdLanguage; -// } - -// public abstract getNewFormMetadata(): StringForm; - -// protected extendedCommandMap = new Map>; - -// protected constructor( -// implementedLanguage: PrinterCommandLanguage, -// extendedCommands: Array> = [] -// ) { -// this.cmdLanguage = implementedLanguage; - -// for (const newCmd of extendedCommands) { -// if (newCmd.applicableLanguages.has(this.commandLanguage)) { -// this.extendedCommandMap.set(newCmd.command.typeExtended, newCmd.delegate); -// } -// } -// } - -// public abstract transpileCommand( -// cmd: Commands.IPrinterCommand, -// formMetadata: StringForm): string; - -// protected extendedCommandHandler( -// cmd: Commands.IPrinterCommand, -// formMetadata: StringForm -// ) { -// const lookup = (cmd as Commands.IPrinterExtendedCommand).typeExtended; -// if (!lookup) { -// throw new TranspileCommandError( -// `Command '${cmd.constructor.name}' did not have a value for typeExtended. If you're trying to implement a custom command check the documentation.` -// ) -// } - -// const cmdHandler = this.extendedCommandMap.get(lookup); - -// if (cmdHandler === undefined) { -// throw new TranspileCommandError( -// // eslint-disable-next-line prettier/prettier -// `Unknown command '${cmd.constructor.name}' was not found in the command map for ${this.commandLanguage} command language. If you're trying to implement a custom command check the documentation for correctly adding mappings.` -// ); -// } -// return cmdHandler(cmd, formMetadata, this); -// } - -// public combineCommands(...commands: string[]): string { -// return commands.reduce((all, cmd) => all += cmd, ''); -// } - -// abstract parseConfigurationResponse( -// rawText: string, -// commOpts: PrinterCommunicationOptions -// ): PrinterOptions; -// } - -// export class TextCmdFormMetadata implements TranspiledFormState { - -// } diff --git a/src/ReceiptLine/Parser.ts b/src/ReceiptLine/Parser.ts index 2c915b4..4750ae3 100644 --- a/src/ReceiptLine/Parser.ts +++ b/src/ReceiptLine/Parser.ts @@ -1,7 +1,7 @@ /* eslint-disable no-control-regex */ -import * as Cmds from "../Documents/index.js" -import { clampToRange, numberInRange, repeat } from "../NumericRange.js"; -import type { IMediaOptions } from "../Printers/Options/index.js"; +import * as Cmds from '../Commands/index.js'; +import { repeat, numberInRange, clampToRange } from '../Util/NumericRange.js'; +import type { IDocument } from '../Documents/index.js'; type LineRuleNext = "stopped" | "addVertical" | "addHorizontal" | "initialize" @@ -428,10 +428,10 @@ function columnsToLine( /** * Transform ReceiptLine document to command document. * @param {string} doc ReceiptLine document - * @param {IMediaOptions} mediaOptions Label media configuration + * @param {Cmds.PrinterConfig} printerConfig Printer configuration * @returns {IDocument} PCL document to give to a printer. */ -export function parseReceiptLineToDocument(doc: string, mediaOptions: IMediaOptions): Cmds.IDocument { +export function parseReceiptLineToDocument(doc: string, printerConfig: Cmds.PrinterConfig): IDocument { // initialize state variables const state: parseState = { wrap: true, @@ -453,7 +453,7 @@ export function parseReceiptLineToDocument(doc: string, mediaOptions: IMediaOpti const res: Cmds.IPrinterCommand[] = doc .normalize() .split(/\n|\r\n|\r/) - .flatMap(line => createLine(parseLine(line, state), mediaOptions, state)); + .flatMap(line => createLine(parseLine(line, state), printerConfig, state)); // Clean up any lingering table formatting switch (state.nextRuleOperation) { @@ -575,13 +575,13 @@ function parseEscape(str: string) { /** * Generate commands from line objects. * @param {object} line parsed line object - * @param {object} mediaOptions printer configuration + * @param {object} printerConfig printer configuration * @param {object} state state variables * @returns {string} printer command fragment or SVG image fragment */ function createLine( line: lineElement[], - mediaOptions: IMediaOptions, + printerConfig: Cmds.PrinterConfig, state: parseState ): Cmds.IPrinterCommand[] { const lineCmds: Cmds.IPrinterCommand[] = []; @@ -598,8 +598,8 @@ function createLine( 0, Math.floor( firstColumn.border < 0 - ? (mediaOptions.charactersPerLine - 1) / 2 - : (mediaOptions.charactersPerLine + firstColumn.border) / (firstColumn.border + 1) + ? (printerConfig.charactersPerLine - 1) / 2 + : (printerConfig.charactersPerLine + firstColumn.border) / (firstColumn.border + 1) ) ); } @@ -609,7 +609,7 @@ function createLine( // Print space explicitly occupied const reservedWidth = fixedSizeColumns.reduce((a, el) => a + el.width, 0); // Remaining space for auto-sizing - let freeWidth = mediaOptions.charactersPerLine - reservedWidth; + let freeWidth = printerConfig.charactersPerLine - reservedWidth; // Borders occupy free space if (isTextLine && columns.length > 0) { freeWidth -= firstColumn.border < 0 @@ -630,7 +630,7 @@ function createLine( // Calculate margins for entire line. const left = Math.floor(freeWidth * getLeftAlignmentMultiplier(firstColumn.lineAlignment)); - const width = mediaOptions.charactersPerLine - freeWidth; + const width = printerConfig.charactersPerLine - freeWidth; const right = freeWidth - left; console.warn(`Line margins are | ${left} [ ${width} ] ${right} |`); @@ -659,7 +659,7 @@ function createLine( const widthDiff = width - state.rules.width; const minLeftMargin = Math.min(left, state.rules.left); const minRightMargin = Math.min(right, state.rules.right); - const maxSpace = mediaOptions.charactersPerLine - minLeftMargin - minRightMargin; + const maxSpace = printerConfig.charactersPerLine - minLeftMargin - minRightMargin; lineCmds.push( ...resetFormattingCmds(minLeftMargin, maxSpace, minRightMargin), new Cmds.TextDraw( From ef6345f98a9ce6a896b91a5a6145c922aee9661e Mon Sep 17 00:00:00 2001 From: Cellivar <1441553+Cellivar@users.noreply.github.com> Date: Thu, 2 Jan 2025 04:00:00 -0800 Subject: [PATCH 6/8] Migrate EscPos language --- src/Languages/EscPos/BasicCommands.ts | 263 +++++++++++ src/Languages/EscPos/CmdSetAutoStatusBack.ts | 166 +++++++ .../EscPos/CmdTransmitPrinterId.ts} | 77 ++-- .../EscPos/CmdTransmitPrinterStatus.ts} | 75 ++-- .../Languages/EscPos/Codepages.ts | 4 +- src/Languages/EscPos/EscPos.ts | 145 ++++++ src/Languages/EscPos/Messages.test.ts | 29 ++ src/Languages/EscPos/Messages.ts | 163 +++++++ src/Languages/EscPos/index.ts | 9 + src/Languages/PrinterCommandLanguage.ts | 21 + .../Languages/Svg/SvgGenerator.ts | 0 src/{Printers => }/Languages/Svg/index.ts | 0 src/Languages/index.ts | 3 + .../Languages/EscPos/AutoStatusBack.ts | 122 ----- src/Printers/Languages/EscPos/EscPos.ts | 423 ------------------ src/Printers/Languages/EscPos/Messages.ts | 259 ----------- src/Printers/Languages/EscPos/index.ts | 12 - src/Printers/Languages/index.ts | 10 - 18 files changed, 884 insertions(+), 897 deletions(-) create mode 100644 src/Languages/EscPos/BasicCommands.ts create mode 100644 src/Languages/EscPos/CmdSetAutoStatusBack.ts rename src/{Printers/Languages/EscPos/PrinterIdCmd.ts => Languages/EscPos/CmdTransmitPrinterId.ts} (71%) rename src/{Printers/Languages/EscPos/PrinterStatusCmd.ts => Languages/EscPos/CmdTransmitPrinterStatus.ts} (57%) rename src/{Printers => }/Languages/EscPos/Codepages.ts (96%) create mode 100644 src/Languages/EscPos/EscPos.ts create mode 100644 src/Languages/EscPos/Messages.test.ts create mode 100644 src/Languages/EscPos/Messages.ts create mode 100644 src/Languages/EscPos/index.ts create mode 100644 src/Languages/PrinterCommandLanguage.ts rename src/{Printers => }/Languages/Svg/SvgGenerator.ts (100%) rename src/{Printers => }/Languages/Svg/index.ts (100%) create mode 100644 src/Languages/index.ts delete mode 100644 src/Printers/Languages/EscPos/AutoStatusBack.ts delete mode 100644 src/Printers/Languages/EscPos/EscPos.ts delete mode 100644 src/Printers/Languages/EscPos/Messages.ts delete mode 100644 src/Printers/Languages/EscPos/index.ts delete mode 100644 src/Printers/Languages/index.ts diff --git a/src/Languages/EscPos/BasicCommands.ts b/src/Languages/EscPos/BasicCommands.ts new file mode 100644 index 0000000..2a2358a --- /dev/null +++ b/src/Languages/EscPos/BasicCommands.ts @@ -0,0 +1,263 @@ +import * as Util from '../../Util/index.js'; +import * as Cmds from '../../Commands/index.js'; +import { codepageNumberForEscPos, codepageSwitchCmd } from './Codepages.js'; + +/** Encode a single character, for readability of command sequences. */ +export function enc(char = '') { + return Cmds.asUint8Array(char)[0]; +} + +export function testPrint(cmd: Cmds.TestPrint) { // GS ( A + let page = 0x03; + switch (cmd.printType) { + case 'hexadecimal': page = 0x01; break; + case 'printerStatus': page = 0x02; break; + case 'rolling': page = 0x03; break; + } + return new Uint8Array([Util.AsciiCodeNumbers.GS, enc('('), enc('A'), 0x02, 0x00, 0x01, page]); +} + +export function cutHandler(cmd: Cmds.Cut, docState: Cmds.TranspiledDocumentState) { + const bladeOffset = cmd.bladeOffsetLines * 2.5; + let cut = 0x00; + switch (cmd.cutType) { + case "Complete": cut = 0x00; break; + case "Partial": cut = 0x01; break; + } + + // The cutter and the print head are separated by about 4 lines. + // Set the line spacing to 4x, newline, cut, then reset line spacing. + const currentLineSpacing = docState.lineSpacing; + return new Uint8Array([ + ...setLineSpacing(bladeOffset, docState), + Util.AsciiCodeNumbers.LF, + Util.AsciiCodeNumbers.GS, enc('V'), cut, + ...setLineSpacing(currentLineSpacing, docState) + ]); +} + +export function pulseHandler(cmd: Cmds.PulseCommand) { + let drawer: number + switch (cmd.pulsePin) { + case "Drawer1": drawer = 0x00; break; + case "Drawer2": drawer = 0x01; break; + } + const onMS = Math.floor(cmd.onMS / 2); + const offMS = Math.floor(cmd.offMS / 2); + return new Uint8Array([Util.AsciiCodeNumbers.ESC, enc('p'), drawer, onMS, offMS]); +} + +export function setTextFormatting(f: Cmds.TextFormat, docState: Cmds.TranspiledDocumentState) { + const buffer: number[] = []; + + if (f.underline !== undefined || f.resetToDefault) { // ESC - // FS - + docState.textFormat.underline = f.underline; + let op: number; + switch (f.underline) { + default: + case 'None' : op = 0x00; break; + case 'Single': op = 0x01; break; + case 'Double': op = 0x02; break; + } + buffer.push(Util.AsciiCodeNumbers.ESC, 0x2d, op, Util.AsciiCodeNumbers.FS, 0x2d, op); + } + + if (f.bold !== undefined || f.resetToDefault) { // ESC E + docState.textFormat.bold = f.bold ?? 'None'; + const op = f.bold === 'Enable' ? 0x01 : 0x00; + buffer.push(Util.AsciiCodeNumbers.ESC, enc('E'), op); + } + + if (f.invert !== undefined || f.resetToDefault) { // GS B + docState.textFormat.invert = f.invert ?? 'None'; + const op = f.invert === 'Enable' ? 0x01 : 0x00; + buffer.push(Util.AsciiCodeNumbers.GS, enc('B'), op); + } + + if (f.alignment !== undefined || f.resetToDefault) { // ESC a + docState.textFormat.alignment = f.alignment; + let op: number; + switch (f.alignment) { + case 'Left': op = 0x00; break; + default: + case 'Center': op = 0x01; break; + case 'Right': op = 0x02; break; + } + buffer.push(Util.AsciiCodeNumbers.ESC, enc('a'), op); + } + + if (f.width !== undefined || f.height !== undefined || f.resetToDefault) { // GS ! + const resetHeight = f.resetToDefault === true ? 1 : undefined; + const resetWidth = f.resetToDefault === true ? 1 : undefined; + const newHeight = f.height ?? resetHeight ?? docState.textFormat.height ?? 1; + const newWidth = f.width ?? resetWidth ?? docState.textFormat.width ?? 1; + docState.textFormat.height = newHeight; + docState.textFormat.width = newWidth; + buffer.push(Util.AsciiCodeNumbers.GS, enc('!'), (newHeight - 1) | (newWidth - 1) << 4); + } + + return new Uint8Array(buffer); +} + +export function setCodepage( + codepage: Util.Codepage, + docState: Cmds.TranspiledDocumentState, + persistToDocState = true +) { + let gotCode = codepage; + let code = codepageSwitchCmd.get(codepage); + if (code === undefined) { + code = new Uint8Array([codepageNumberForEscPos.CP437]); + gotCode = "CP437"; + } + + if (persistToDocState) { + docState.codepage = gotCode; + } + + return [new Uint8Array([ + // ESC t , then any other weird commands we might need to set. + Util.AsciiCodeNumbers.ESC, enc('t')]), code + ]; +} + +export function offsetPrintPosition( + cmd: Cmds.OffsetPrintPosition, + docState: Cmds.TranspiledDocumentState +) { + // TODO: Add offset to print position in form + const offset = (cmd.characters) * docState.characterSize.left; + switch (cmd.origin) { + case 'absolute': { + const abs = Util.clampToRange(offset, 0, 65535); + // ESC $ lowbyte, highbyte + return new Uint8Array([Util.AsciiCodeNumbers.ESC, enc('$'), (abs & 255), (abs >> 8 & 255)]); + } + case 'relative': { + const rel = Util.clampToRange(offset, -32768, 32767); + // ESC \ lowbyte highbyte + return new Uint8Array([Util.AsciiCodeNumbers.ESC, enc('\\'), (rel & 255), (rel >> 8 & 255)]); + } + } +} + +export function textDraw( + textDraw: Cmds.BoxDrawingCharacter[], + docState: Cmds.TranspiledDocumentState, +) { + return [ + setTextFormatting({width: 1, height: 1}, docState), + setFormattingCodepage(), + ...text(textDraw.join(''), docState), + ]; +} + +// TODO: Pull dynamically instead of assuming the printer supports these! +// This list is common to most TM- series printers so will be safe for now.. +// https://download4.epson.biz/sec_pubs/pos/reference_en/charcode/supported_codepage.html +const candidateCodepages: Util.Codepage[] = [ + "CP437", + "CP720", + "CP737", + "CP775", + "CP850", + "CP851", + "CP852", + "CP853", + "CP855", + "CP857", + "CP858", + "CP860", + "CP861", + "CP862", + "CP863", + "CP864", + "CP865", + "CP866", + "CP869", + "CP1098", + "CP1118", + "CP1119", + "CP1125", + "ISO88592", + "ISO88597", + "ISO885915", + "RK1048", + "WINDOWS1250", + "WINDOWS1251", + "WINDOWS1252", + "WINDOWS1253", + "WINDOWS1254", + "WINDOWS1255", + "WINDOWS1256", + "WINDOWS1257", + "WINDOWS1258", +]; + +export function text( + text: string, + docState: Cmds.TranspiledDocumentState, +): Uint8Array[] { + // Auto-switch between encodings for special characters. + // TODO: Dynamic list of supported encodings based on printer model. + const fragments = Util.CodepageEncoder + .autoEncode(text, candidateCodepages) + .flatMap(f => [ + ...setCodepage(f.codepage, docState), + f.bytes + ]); + return fragments; +} + +export function setFormattingCodepage() { + // FS C 0 to disable kanji + // FS . to reset katakana mode? + // FS t 0 to select 'CP437' for line formatting glyphs. + return new Uint8Array([ + Util.AsciiCodeNumbers.FS, enc('C'), 0x00, + Util.AsciiCodeNumbers.FS, enc('.'), + Util.AsciiCodeNumbers.ESC, enc('t'), codepageNumberForEscPos.CP437, + ]); +} + +export function horizontalRule( + cmd: Cmds.HorizontalRule, + docState: Cmds.TranspiledDocumentState, +) { + const width = cmd.width ?? docState.initialConfig.charactersPerLine; + const char = cmd.lineStyle === 'single' ? '─' : '═'; + return textDraw( + Util.repeat(char as Cmds.BoxDrawingCharacter, width), + docState); +} + +export function setPrintArea( + cmd: Cmds.SetPrintArea, + docState: Cmds.TranspiledDocumentState, +) { + docState.margin.leftChars = cmd.leftMargin; + docState.currentPrintWidth = cmd.width; + docState.margin.rightChars = cmd.rightMargin; + const leftMarginMotionUnits = cmd.leftMargin * docState.characterSize.left; + const printSizeMotionsUnits = cmd.width * docState.characterSize.left; + // ESC/POS can't set a right margin, it doesn't really need to. We set this: + // |---> text area --> | + // GS L sets the area start, GS W sets the area end. + return new Uint8Array([ + // GS L lowbit, hightbit + Util.AsciiCodeNumbers.GS, enc('L'), leftMarginMotionUnits & 255, leftMarginMotionUnits >> 8 & 255, + Util.AsciiCodeNumbers.GS, enc('W'), printSizeMotionsUnits & 255, printSizeMotionsUnits >> 8 & 255, + ]); +} + +export function setLineSpacing( + spacing: number, + docState: Cmds.TranspiledDocumentState, +) { + const spacingInMotionUnits = Util.clampToRange(spacing * docState.characterSize.top, 0, 255); + docState.lineSpacing = spacing; + return new Uint8Array([ + // ESC 3 + Util.AsciiCodeNumbers.ESC, enc('3'), spacingInMotionUnits, + ]) +} diff --git a/src/Languages/EscPos/CmdSetAutoStatusBack.ts b/src/Languages/EscPos/CmdSetAutoStatusBack.ts new file mode 100644 index 0000000..d365054 --- /dev/null +++ b/src/Languages/EscPos/CmdSetAutoStatusBack.ts @@ -0,0 +1,166 @@ +import * as Util from '../../Util/index.js'; +import * as Conf from '../../Configs/index.js'; +import * as Cmds from '../../Commands/index.js'; +import { MessageCandidates } from './Messages.js'; + +export interface AutoStatusBackSetting { + drawerKickStatus: boolean, + onlineStatus: boolean, + errorStatus: boolean, + rollPaperStatus: boolean, + panelSwitchStatus: boolean, +} + +export class CmdSetAutoStatusBack implements Cmds.IPrinterExtendedCommand { + public static typeE = Symbol("CmdSetAutoStatusBack"); + typeExtended = CmdSetAutoStatusBack.typeE; + commandLanguageApplicability = Conf.PrinterCommandLanguage.escPos; + name = 'Set Automatic Status Back setting' + type = 'CustomCommand' as const; + effectFlags = Cmds.NoEffect; + toDisplay() { return this.name; } + + constructor(public readonly settings: AutoStatusBackSetting = { + drawerKickStatus : true, + errorStatus : true, + onlineStatus : true, + panelSwitchStatus: true, + rollPaperStatus : true + }) {} +} + +export const mappingCmdSetAutoStatusBack: Cmds.IPrinterCommandMapping = { + commandType: CmdSetAutoStatusBack.typeE, + transpile: handleCmdSetAutoStatusBack, + readMessage: parseCmdSetAutoStatusBack +} + +export function handleCmdSetAutoStatusBack( + cmd: Cmds.IPrinterCommand, +): Uint8Array { + const settings = (cmd as CmdSetAutoStatusBack).settings; + let setting = 0x00; + setting |= (settings.drawerKickStatus ? 0x01 : 0x00); + setting |= (settings.onlineStatus ? 0x02 : 0x00); + setting |= (settings.errorStatus ? 0x04 : 0x00); + setting |= (settings.rollPaperStatus ? 0x08 : 0x00); + setting |= (settings.panelSwitchStatus ? 0x40 : 0x00); + return new Uint8Array([ + // GS a + Util.AsciiCodeNumbers.GS, 0x61, setting, + ]) +} + +enum FirstAsbByte { + DrawerKickStatus = 0x04, + PrinterOnline = 0x08, + CoverOpen = 0x20, + PaperFedByButton = 0x40, +} + +enum SecondAsbByte { + WaitingForOnlineRecovery = 0x01, + PaperFeedButtonPushed = 0x02, + RecoverableError = 0x04, + AutocutterError = 0x08, + UnrecoverableError = 0x20, + AutomaticallyRecoverableError = 0x40, +} + +enum ThirdAsbByte { + RollPaperNearEnd = 0x03, + RollPaperEnd = 0x0c, +} + +export function parseCmdSetAutoStatusBack( + msg: Uint8Array, +): Cmds.IMessageHandlerResult { + // https://download4.epson.biz/sec_pubs/pos/reference_en/escpos/gs_la.html + const result: Cmds.IMessageHandlerResult = { + messageIncomplete: false, + messageMatchedExpectedCommand: false, + messages: [], + remainder: msg, + } + + // ASB is always 4 bytes, header of 0**1**00, trailer of 0**0****. + // We need the next 3 bytes, make sure they're there. + if (msg.length < 4) { + result.messageIncomplete = true; + return result; + } + + result.remainder = msg.slice(4); + + // Confirm the next 3 bytes are trailers. + const [first, second, third, fourth] = msg; + if ( (second & 0x90) !== MessageCandidates.ASB2to4 + || (third & 0x90) !== MessageCandidates.ASB2to4 + || (fourth & 0x90) !== MessageCandidates.ASB2to4 + ) { + // We got the trailers, but they're wrong! Discard the whole lot since + // we can't recover them. + result.messages.push({ + messageType: 'ErrorMessage', + errors: new Cmds.ErrorStateSet([Cmds.ErrorState.MessageReceiveException]), + exceptions: [ + new Cmds.MessageParsingError( + `First byte is an ASB (${Util.hex(first)}) but following bytes aren't (${Util.hex(second)} ${Util.hex(third)} ${Util.hex(fourth)}). Discarding invalid message!`, + msg, + ) + ], + }); + } + + const statuses = new Cmds.StatusStateSet(); + + if (Util.hasFlag(first, FirstAsbByte.PrinterOnline)) { + statuses.add(Cmds.StatusState.PrinterOnline); + } + if (Util.hasFlag(first, FirstAsbByte.PaperFedByButton)) { + statuses.add(Cmds.StatusState.PaperButtonFeedingPaper); + } + if (Util.hasFlag(first, FirstAsbByte.DrawerKickStatus)) { + statuses.add(Cmds.StatusState.DrawerOpen); + } + if (statuses.size > 0) { + result.messages.push({ + messageType: 'StatusMessage', + statuses, + }); + } + + const errors = new Cmds.ErrorStateSet(); + if (Util.hasFlag(first, FirstAsbByte.CoverOpen)) { + errors.add(Cmds.ErrorState.PrintheadUp); + } + if (Util.hasFlag(third, ThirdAsbByte.RollPaperNearEnd)) { + errors.add(Cmds.ErrorState.MediaNearEnd); + } + if (Util.hasFlag(third, ThirdAsbByte.RollPaperEnd)) { + errors.add(Cmds.ErrorState.MediaEmpty); + } + if (Util.hasFlag(second, SecondAsbByte.AutocutterError)) { + errors.add(Cmds.ErrorState.CutterJammedOrNotInstalled); + } + if (Util.hasFlag(second, SecondAsbByte.RecoverableError)) { + errors.add(Cmds.ErrorState.PressFeedButtonToRecover); + } + if (Util.hasFlag(second, SecondAsbByte.WaitingForOnlineRecovery)) { + errors.add(Cmds.ErrorState.PressFeedButtonToRecover); + } + if (Util.hasFlag(second, SecondAsbByte.UnrecoverableError)) { + errors.add(Cmds.ErrorState.UnrecoverableError); + } + if (Util.hasFlag(second, SecondAsbByte.AutomaticallyRecoverableError)) { + errors.add(Cmds.ErrorState.PressFeedButtonToRecover); + } + if (errors.size > 0) { + result.messages.push({ + messageType: 'ErrorMessage', + errors, + }); + } + + return result; +} diff --git a/src/Printers/Languages/EscPos/PrinterIdCmd.ts b/src/Languages/EscPos/CmdTransmitPrinterId.ts similarity index 71% rename from src/Printers/Languages/EscPos/PrinterIdCmd.ts rename to src/Languages/EscPos/CmdTransmitPrinterId.ts index eceb78f..dbfd904 100644 --- a/src/Printers/Languages/EscPos/PrinterIdCmd.ts +++ b/src/Languages/EscPos/CmdTransmitPrinterId.ts @@ -1,10 +1,7 @@ -import * as Cmds from "../../../Documents/index.js"; -import { MessageParsingError, type IMessageHandlerResult, type ISettingUpdateMessage } from "../../Communication/index.js"; -import { AsciiCodeNumbers } from "../../Codepages/index.js"; -import { EscPos, awaitsEffect } from "./EscPos.js"; -import { hasFlag, sliceToNull, type EscPosDocState } from "./index.js"; -import type { CommandSet } from "../../../Documents/CommandSet.js"; -import { PrinterCommandLanguages } from "../index.js"; +import * as Util from '../../Util/index.js'; +import * as Conf from '../../Configs/index.js'; +import * as Cmds from '../../Commands/index.js'; +import { sliceToNull } from '../../Util/StringUtils.js'; export type TransmitPrinterIdCmd = 'ModelID' // 1, 49 @@ -35,28 +32,32 @@ export const transmitPrinterIdCmdMap: Record = { //InfoBModelSpecific111: 111 } -export class TransmitPrinterId implements Cmds.IPrinterExtendedCommand { - public static typeE = Symbol("TransmitPrinterId"); - typeExtended = TransmitPrinterId.typeE; - commandLanguageApplicability = new PrinterCommandLanguages([EscPos]); +export class CmdTransmitPrinterId implements Cmds.IPrinterExtendedCommand { + public static typeE = Symbol("CmdTransmitPrinterId"); + typeExtended = CmdTransmitPrinterId.typeE; + commandLanguageApplicability = Conf.PrinterCommandLanguage.escPos; name = 'Transmit Printer ID' type = "CustomCommand" as const; - effectFlags = awaitsEffect; + effectFlags = Cmds.AwaitsEffect; toDisplay() { return this.name; } constructor(public readonly subcommand: TransmitPrinterIdCmd) {} } -export function handleTransmitPrinterId( - cmd: Cmds.IPrinterCommand, - _docState: EscPosDocState, - commandSet: CommandSet +export const mappingCmdTransmitPrinterId: Cmds.IPrinterCommandMapping = { + commandType: CmdTransmitPrinterId.typeE, + transpile: handleCmdTransmitPrinterId, + readMessage: parseCmdTransmitPrinterId, +} + +export function handleCmdTransmitPrinterId( + cmd: Cmds.IPrinterCommand ): Uint8Array { - const command = cmd as TransmitPrinterId; + const command = cmd as CmdTransmitPrinterId; const argnum = transmitPrinterIdCmdMap[command.subcommand]; return new Uint8Array([ // GS I - AsciiCodeNumbers.GS, ...commandSet.encodeCommand('I'), argnum, + Util.AsciiCodeNumbers.GS, 0x49, argnum, ]); } @@ -81,7 +82,7 @@ const PrinterInfoHeaderB = 0x5f; function setSingleByteData( firstByte: number, subcommand: TransmitPrinterIdCmd, - config: ISettingUpdateMessage + config: Cmds.ISettingUpdateMessage ) { switch (subcommand) { // Printer ID @@ -94,9 +95,13 @@ function setSingleByteData( // config.printerModelId = firstByte; break; case 'TypeID': - config.hasMultiByteSupport = hasFlag(firstByte, PrinterId.hasMultiByteSupport); - config.hasAutocutter = hasFlag(firstByte, PrinterId.hasAutocutter); - config.hasDmdConnected = hasFlag(firstByte, PrinterId.hasDmdConnected); + if (Util.hasFlag(firstByte, PrinterId.hasAutocutter)) { + // TODO: figure out partial vs full cut + config.printerHardware!.cutter = Conf.PrintCutter.partial; + } + + config.printerHardware!.hasMultiByteSupport = Util.hasFlag(firstByte, PrinterId.hasMultiByteSupport); + config.printerHardware!.hasDmdConnected = Util.hasFlag(firstByte, PrinterId.hasDmdConnected); break; case 'VersionID': // There is a one to one correspondence between the version ID (n = 3, 51) and the firmware version. @@ -110,10 +115,10 @@ function setSingleByteData( function setPrinterInfoB( msg: Uint8Array, subcommand: TransmitPrinterIdCmd, - config: ISettingUpdateMessage, + config: Cmds.ISettingUpdateMessage, ) { // sanity check - if (msg.at(0) !== PrinterInfoHeaderB || msg.at(-1) !== AsciiCodeNumbers.NUL) { + if (msg.at(0) !== PrinterInfoHeaderB || msg.at(-1) !== Util.AsciiCodeNumbers.NUL) { return; } @@ -129,44 +134,44 @@ function setPrinterInfoB( // Printer Info B case 'InfoBFirmwareVersion': // "Firmware version", not a string? - config.firmwareVersion = [...packetData]; + config.printerHardware!.firmware = String.fromCharCode(...packetData); break; case 'InfoBMakerName': - config.manufacturerName = String.fromCharCode(...packetData); + config.printerHardware!.manufacturer = String.fromCharCode(...packetData); break; case 'InfoBModelName': - config.modelName = String.fromCharCode(...packetData); + config.printerHardware!.model = String.fromCharCode(...packetData); break; case 'InfoBSerialNo': - config.serialNumber = String.fromCharCode(...packetData); + config.printerHardware!.serialNumber = String.fromCharCode(...packetData); break; case 'InfoBFontLanguage': - config.fontLanguageSupport = String.fromCharCode(...packetData); + config.printerHardware!.fontLanguageSupport = String.fromCharCode(...packetData); break; } } -export function parseTransmitPrinterId( +export function parseCmdTransmitPrinterId( msg: Uint8Array, cmd: Cmds.IPrinterCommand, -): IMessageHandlerResult { +): Cmds.IMessageHandlerResult { // https://download4.epson.biz/sec_pubs/pos/reference_en/escpos/gs_ci.html - if (cmd.type !== "CustomCommand" || (cmd as TransmitPrinterId).typeExtended !== TransmitPrinterId.typeE) { - throw new MessageParsingError( + if ((cmd as CmdTransmitPrinterId).typeExtended !== CmdTransmitPrinterId.typeE) { + throw new Cmds.MessageParsingError( `Incorrect command '${cmd.name}' passed to parseTransmitPrinterId, expected 'TransmitPrinterId' instead.`, msg ); } - const command = (cmd as TransmitPrinterId); + const command = (cmd as CmdTransmitPrinterId); - const result: IMessageHandlerResult = { + const result: Cmds.IMessageHandlerResult = { messageIncomplete: false, messageMatchedExpectedCommand: false, messages: [], remainder: msg, } - const config: ISettingUpdateMessage = { + const config: Cmds.ISettingUpdateMessage = { messageType: "SettingUpdateMessage" } diff --git a/src/Printers/Languages/EscPos/PrinterStatusCmd.ts b/src/Languages/EscPos/CmdTransmitPrinterStatus.ts similarity index 57% rename from src/Printers/Languages/EscPos/PrinterStatusCmd.ts rename to src/Languages/EscPos/CmdTransmitPrinterStatus.ts index ff1a6dc..e0b8b88 100644 --- a/src/Printers/Languages/EscPos/PrinterStatusCmd.ts +++ b/src/Languages/EscPos/CmdTransmitPrinterStatus.ts @@ -1,8 +1,6 @@ -import * as Cmds from "../../../Documents/index.js"; -import { MessageParsingError, type IErrorMessage, type IMessageHandlerResult, type IStatusMessage } from "../../Communication/index.js"; -import type { CommandSet } from "../../../Documents/CommandSet.js"; -import { AsciiCodeNumbers, PrinterCommandLanguages } from "../index.js"; -import { hasFlag, type EscPosDocState, EscPos } from "./index.js"; +import * as Util from '../../Util/index.js'; +import * as Conf from '../../Configs/index.js'; +import * as Cmds from '../../Commands/index.js'; const awaitsEffect = new Cmds.CommandEffectFlags(['waitsForResponse']); @@ -17,10 +15,10 @@ export const transmitPrinterStatusCmdMap: Record = { + commandType: CmdTransmitPrinterStatus.typeE, + transpile: handleCmdTransmitPrinterStatus, + readMessage: parseCmdTransmitPrinterStatus, +} + +export function handleCmdTransmitPrinterStatus( cmd: Cmds.IPrinterCommand, - _docState: EscPosDocState, - commandSet: CommandSet, ): Uint8Array { - const command = cmd as TransmitPrinterStatus; + const command = cmd as CmdTransmitPrinterStatus; const argnum = transmitPrinterStatusCmdMap[command.subcommand]; return new Uint8Array([ // GS r - AsciiCodeNumbers.GS, ...commandSet.encodeCommand('r'), argnum, + Util.AsciiCodeNumbers.GS, 0x72, argnum, ]); } @@ -64,26 +66,33 @@ enum DrawerKickByte { // ColorTwoNearEnd = 0x02, // } -export function parseTransmitPrinterStatus( +export function parseCmdTransmitPrinterStatus( msg: Uint8Array, cmd: Cmds.IPrinterCommand, -): IMessageHandlerResult { +): Cmds.IMessageHandlerResult { // https://download4.epson.biz/sec_pubs/pos/reference_en/escpos/gs_lr.html - if (cmd.type !== "CustomCommand" || (cmd as TransmitPrinterStatus).typeExtended !== TransmitPrinterStatus.typeE) { - throw new MessageParsingError( + if ((cmd as CmdTransmitPrinterStatus).typeExtended !== CmdTransmitPrinterStatus.typeE) { + throw new Cmds.MessageParsingError( `Incorrect command '${cmd.name}' passed to parseTransmitPrinterStatus, expected 'TransmitPrinterStatus' instead.`, msg ); } - const command = (cmd as TransmitPrinterStatus); + const result: Cmds.IMessageHandlerResult = { + messageIncomplete: false, + messageMatchedExpectedCommand: true, + messages: [], + remainder: msg.slice(1) + } - const status: IStatusMessage = { + const command = (cmd as CmdTransmitPrinterStatus); + + const status: Cmds.IStatusMessage = { messageType: "StatusMessage", + statuses: new Cmds.StatusStateSet(), } - const error: IErrorMessage = { + const error: Cmds.IErrorMessage = { messageType: "ErrorMessage", - displayText: "Status update reported an error", - isErrored: false, + errors: new Cmds.ErrorStateSet(), } // Each status is 1 byte. @@ -91,12 +100,19 @@ export function parseTransmitPrinterStatus( switch (command.subcommand) { case 'PaperSensorStatus': - status.paperLow = hasFlag(byte, PaperSensorByte.RollPaperNearEnd); - error.paperOut = hasFlag(byte, PaperSensorByte.RollPaperEnd); - error.isErrored = error.paperOut; + if (Util.hasFlag(byte, PaperSensorByte.RollPaperNearEnd)) { + error.errors.add(Cmds.ErrorState.MediaNearEnd); + } + if (Util.hasFlag(byte, PaperSensorByte.RollPaperEnd)) { + error.errors.add(Cmds.ErrorState.MediaEmpty); + } + if (error.errors.size > 0) { result.messages.push(error); } break; case 'DrawerKickStatus': - status.drawerKickStatus = hasFlag(byte, DrawerKickByte.DrawerKickOut); + if (Util.hasFlag(byte, DrawerKickByte.DrawerKickOut)) { + status.statuses.add(Cmds.StatusState.DrawerOpen); + } + if (status.statuses.size > 0) { result.messages.push(status); } break; // case 'InkSensorStatus': // status.colorOneLow = hasFlag(byte, InkStatusByte.ColorOneNearEnd); @@ -104,12 +120,5 @@ export function parseTransmitPrinterStatus( // break; } - const result: IMessageHandlerResult = { - messageIncomplete: false, - messageMatchedExpectedCommand: true, - messages: [status], - remainder: msg.slice(1) - } - if (error.isErrored) { result.messages.push(error); } return result; } diff --git a/src/Printers/Languages/EscPos/Codepages.ts b/src/Languages/EscPos/Codepages.ts similarity index 96% rename from src/Printers/Languages/EscPos/Codepages.ts rename to src/Languages/EscPos/Codepages.ts index a8b1389..6e5fa4b 100644 --- a/src/Printers/Languages/EscPos/Codepages.ts +++ b/src/Languages/EscPos/Codepages.ts @@ -1,4 +1,4 @@ -import type { Codepage } from "../../Codepages/index.js"; +import * as Util from '../../Util/index.js'; export const enum codepageNumberForEscPos { CP437 = 0, @@ -64,7 +64,7 @@ export const enum codepageNumberForEscPos { } /** Unusual commands to set codepages with ESC t */ -export const codepageSwitchCmd = new Map([ +export const codepageSwitchCmd = new Map<(Util.Codepage | codepageNumberForEscPos), Uint8Array>([ ['CP437', new Uint8Array([codepageNumberForEscPos.CP437])], ['CP720', new Uint8Array([codepageNumberForEscPos.CP720])], ['CP737', new Uint8Array([codepageNumberForEscPos.CP737])], diff --git a/src/Languages/EscPos/EscPos.ts b/src/Languages/EscPos/EscPos.ts new file mode 100644 index 0000000..5a9706c --- /dev/null +++ b/src/Languages/EscPos/EscPos.ts @@ -0,0 +1,145 @@ +import * as Util from '../../Util/index.js'; +import * as Conf from '../../Configs/index.js'; +import * as Cmds from '../../Commands/index.js'; +import * as Basic from './BasicCommands.js'; + +import { handleMessage } from './Messages.js'; +import { CmdTransmitPrinterId, mappingCmdTransmitPrinterId } from "./CmdTransmitPrinterId.js"; +import { CmdTransmitPrinterStatus, mappingCmdTransmitPrinterStatus } from "./CmdTransmitPrinterStatus.js"; +import { mappingCmdSetAutoStatusBack } from "./CmdSetAutoStatusBack.js"; + +/** PCL handler for ESC/POS */ +export class EscPos extends Cmds.RawCommandSet { + override get documentStartPrefix() { return new Uint8Array([Util.AsciiCodeNumbers.LF]); }; + override get documentEndSuffix() { return new Uint8Array([Util.AsciiCodeNumbers.LF]); }; + + constructor( + extendedCommands: Cmds.IPrinterCommandMapping[] = [] + ) { + super( + Conf.PrinterCommandLanguage.escPos, + handleMessage, + { + NoOp: { commandType: 'NoOp' }, + CustomCommand: { + commandType: 'CustomCommand', + transpile: (c, d) => this.getExtendedCommand(c)(c, d, this), + }, + Identify: { + commandType: 'Identify', + expand: () => [new CmdTransmitPrinterId('TypeID')], + }, + Reset: { + commandType: 'Reset', + transpile: () =>new Uint8Array([Util.AsciiCodeNumbers.ESC, Basic.enc('@')]), + }, + Raw: { + commandType: 'Raw', + transpile: (c) => (c as Cmds.RawCommand).command, + }, + GetStatus: { + commandType: 'GetStatus', + expand: () => [ + new CmdTransmitPrinterStatus('PaperSensorStatus'), + new CmdTransmitPrinterStatus('DrawerKickStatus'), + ], + }, + PrintConfiguration: { + commandType: 'PrintConfiguration', + transpile: () => Basic.testPrint(new Cmds.TestPrint('printerStatus')) + }, + QueryConfiguration: { + commandType: 'QueryConfiguration', + expand: () => [ + // TODO: Dynamically figure out what subcommands to send to the printer + // TODO: Add support for model-specific info? + // Getting the complete active config from ESC/POS requires multiple + // back-and-forth steps. + new CmdTransmitPrinterId('TypeID'), + new CmdTransmitPrinterId('InfoBMakerName'), + new CmdTransmitPrinterId('InfoBModelName'), + new CmdTransmitPrinterId('InfoBSerialNo'), + new CmdTransmitPrinterId('InfoBFirmwareVersion'), + ] + }, + TestPrint: { + commandType: 'TestPrint', + transpile: (c) => Basic.testPrint(c as Cmds.TestPrint), + }, + PulseOutput: { + commandType: 'PulseOutput', + transpile: (c) => Basic.pulseHandler(c as Cmds.PulseCommand), + }, + OffsetPrintPosition: { + commandType: 'OffsetPrintPosition', + transpile: (c, d) => Basic.offsetPrintPosition((c as Cmds.OffsetPrintPosition), d), + }, + Cut: { + commandType: 'Cut', + transpile: (c, d) => Basic.cutHandler(c as Cmds.Cut, d), + }, + Newline: { + commandType: 'Newline', + transpile: () => new Uint8Array([Util.AsciiCodeNumbers.LF]), + }, + StartReceipt: { commandType: 'StartReceipt' }, + EndReceipt: { + commandType: 'EndReceipt', + // ESC/POS doesn't have any special form end handling. Assume the document + // provided a cut command or whatever the user intended, don't guess. + + // The manual indicates always getting the paper status is a good practice + // as it tells you when the printer is done printing. This also ensures we + // always have something to await at the end of a document. + expand: () => [new CmdTransmitPrinterStatus('PaperSensorStatus')] + }, + Barcode: { + commandType: 'Barcode', + transpile: (c) => { throw new Cmds.TranspileDocumentError(`Command not implemented: ${c.constructor.name}`) }, + }, + Codepage: { + commandType: 'Codepage', + transpile: (c, d) => this.combineCommands(...Basic.setCodepage((c as Cmds.SetCodepage).codepage, d)), + }, + HorizontalRule: { + commandType: 'HorizontalRule', + transpile: (c, d) => this.combineCommands(...Basic.horizontalRule((c as Cmds.HorizontalRule), d)) + }, + Image: { + commandType: 'Image', + transpile: (c) => { throw new Cmds.TranspileDocumentError(`Command not implemented: ${c.constructor.name}`) }, + }, + SetLineSpacing: { + commandType: 'SetLineSpacing', + transpile: (c, d) => Basic.setLineSpacing((c as Cmds.SetLineSpacing).spacing, d), + }, + SetPrintArea: { + commandType: 'SetPrintArea', + transpile: (c, d) => Basic.setPrintArea((c as Cmds.SetPrintArea), d), + }, + Text: { + commandType: 'Text', + transpile: (c, d) => this.combineCommands(...Basic.text((c as Cmds.Text).text, d)), + }, + TextDraw: { + commandType: 'TextDraw', + transpile: (c, d) => this.combineCommands(...Basic.textDraw((c as Cmds.TextDraw).text, d)), + }, + TextFormatting: { + commandType: 'TextFormatting', + transpile: (c, d) => Basic.setTextFormatting((c as Cmds.TextFormatting).format, d), + }, + TwoDCode: { + commandType: 'TwoDCode', + transpile: (c) => { throw new Cmds.TranspileDocumentError(`Command not implemented: ${c.constructor.name}`) }, + } + }, + [ + mappingCmdTransmitPrinterStatus, + mappingCmdTransmitPrinterId, + mappingCmdSetAutoStatusBack, + ...extendedCommands, + ] + ); + } +} diff --git a/src/Languages/EscPos/Messages.test.ts b/src/Languages/EscPos/Messages.test.ts new file mode 100644 index 0000000..b71bbd1 --- /dev/null +++ b/src/Languages/EscPos/Messages.test.ts @@ -0,0 +1,29 @@ +import { expect, describe, it } from 'vitest'; +import { getMessageCandidate, MessageCandidates } from './Messages.js'; + +describe('escpos message candidates', () => { + it('xon and xoff are exact', () => { + expect(getMessageCandidate(MessageCandidates.XON)).toBe('xon'); + expect(getMessageCandidate(MessageCandidates.XOFF)).toBe('xoff'); + }); + + it('null byte is a response', () => { + expect(getMessageCandidate(0x00)).toBe('response'); + }); + + it('realtime byte is realtime', () => { + expect(getMessageCandidate(MessageCandidates.Realtime)).toBe('realtime'); + }); + + it('asb byte is asb', () => { + expect(getMessageCandidate(MessageCandidates.AutoStat)).toBe('asb'); + }); + + it('Header byte is Header', () => { + expect(getMessageCandidate(0x37)).toBe('header'); + }); + + it('FF to be unknown', () => { + expect(getMessageCandidate(0xff)).toBe('unknown'); + }); +}); diff --git a/src/Languages/EscPos/Messages.ts b/src/Languages/EscPos/Messages.ts new file mode 100644 index 0000000..3907179 --- /dev/null +++ b/src/Languages/EscPos/Messages.ts @@ -0,0 +1,163 @@ +import * as Util from '../../Util/index.js'; +import * as Conf from '../../Configs/index.js'; +import * as Cmds from '../../Commands/index.js'; + +import { CmdSetAutoStatusBack } from './CmdSetAutoStatusBack.js'; + +/* eslint-disable @typescript-eslint/no-duplicate-enum-values */ +export enum MessageCandidates { + Response = 0x00, + ASB2to4 = 0x00, + Realtime = 0x12, + AutoStat = 0x10, + Header = 0x11, + // Serial only + XON = 0x11, + XOFF = 0x13, +} +/* eslint-enable @typescript-eslint/no-duplicate-enum-values */ + +type MessageCandidate = 'unknown' | 'response' | 'asb' | 'realtime' | 'header' |'xon' | 'xoff' + +function checkBits(value: number, xor: number, mask: number) { + return ((value ^ xor) & mask) === mask; +} + +export function getMessageCandidate(firstByte: number): MessageCandidate { + // The basic single-byte responses can be differentiated according to this table: + + // Command | Status (bits) | Mask | XOR + // | 7 6 5 4 3 2 1 0 | | + // -------------------------------------------------- + // GS I | 0 * * 0 * * * * | 0x90 | 0x90 + // GS r | 0 * * 0 * * * * | 0x90 | 0x90 + // DLE EOT | 0 * * 1 * * 1 0 | 0x93 | 0x81 + // ASB (byte1) | 0 * * 1 * * 0 0 | 0x93 | 0x83 + // ASB (b2-4) | 0 * * 0 * * * * | 0x90 | 0x90 + // Header | 0 * * 1 * * * 1 | 0x91 | 0x80 + // -------------SERIAL ONLY ------------------ + // X ON | 0 0 0 1 0 0 0 1 | 0xFF + // X OFF | 0 0 0 1 0 0 1 1 | 0xFF + + // Fancier commands have a header byte first instead, which will always conflict + // with other first-bytes. + // Header 0**1***1 + // 0x37 - 00110111 - A whole bunch of commands under GS ( and FS ( + // 0x39 - 00111001 - FS ( e - Extended ASB + // 0x3d - 00111101 - GS I Printer Info A + // 0x5f - 01011111 - Maintenance counters / Printer Info B + + // The ESC/POS manual has notes like this: + // Basic ASB status can be differentiated by other transmission data by + // Bit 0, 1, 4, and 7 of the first byte. Process the transmitted data from the + // printer as ASB status which is consecutive 3 byte if it is + // "0xx1xx00" [x = 0 or 1]. + + // Compare what we constructed to expected patterns, paying attention to order + // we check in to ensure we don't confuse candidates. + switch (true) { + // XON/XOFF must be an exact match, guaranteed to conflict with all other first bytes. + case (firstByte === MessageCandidates.XON): + return 'xon'; + case (firstByte === MessageCandidates.XOFF): + return 'xoff'; + // Everything else is a mixture of 1s and 0s, so we can't do basic AND operations. + // First XOR the value against the inverse of the message we're looking for. + // This should give us all of the bits set for the mask. AND it with the mask. + // If the result matches the mask then we found our packet. + // Check bit 4 + case checkBits(firstByte, 0x90, 0x90): + return 'response'; + // Check bit 0 + case checkBits(firstByte, 0x80, 0x91): + return 'header' + // Check bit 1 + case checkBits(firstByte, 0x83, 0x93): + return 'asb'; + case checkBits(firstByte, 0x81, 0x93): + return 'realtime'; + default: + return 'unknown'; + } +} + +export function handleMessage( + cmdSet: Cmds.CommandSet, + message: TReceived, + _config: Cmds.PrinterConfig, + sentCommand?: Cmds.IPrinterCommand, +): Cmds.IMessageHandlerResult { + const result: Cmds.IMessageHandlerResult = { + messageIncomplete: false, + messageMatchedExpectedCommand: false, + messages: [], + remainder: message, + } + const msg = Cmds.asUint8Array(message); + let remainder = msg; + if (msg === undefined || msg.length === 0) { return result; } + // There are several categories of messages ESC/POS can send. Broadly: + // * Automatic Status Back (ASB) - Sent whenever the printer wants to. + // * Real-time commands - Processed asap and responded to asap, blocking otherwise. + // * One byte response - Immediate response to a command in a single byte. + // * Multibyte response - Immediate response to a command, multiple bytes. + + // To make things even more fun, the message package could contain SEVERAL + // separate messages. So we must determine what message(s) we've gotten and + // reply with them individually. + + // The messages we can receive unexpectedly will have a first byte header + // we can identify reliably. Get that candidate. + const firstByte = msg.at(0); + if (firstByte === undefined) { return result; } + + switch (getMessageCandidate(firstByte)) { + case 'asb': { + const handled = cmdSet.callMessageHandler(msg, new CmdSetAutoStatusBack()); + result.messages.push(...handled.messages); + result.messageIncomplete = handled.messageIncomplete; + result.messageMatchedExpectedCommand = handled.messageMatchedExpectedCommand; + remainder = handled.remainder; + + break; + } + case 'header': + case 'realtime': + case 'response': { + // We got a response to a sent command. To proceed we need to know what + // question we asked. Response bytes don't contain enough info to tell. + // If we don't know what we asked we can't process this command. + const handled = cmdSet.callMessageHandler(msg, sentCommand); + result.messages.push(...handled.messages); + result.messageIncomplete = handled.messageIncomplete; + result.messageMatchedExpectedCommand = handled.messageMatchedExpectedCommand; + remainder = handled.remainder; + + break; + } + case 'xoff': + case 'xon': + // TODO: Until serial support is figured out if we ever get these drop them. + remainder = msg.slice(1); + break; + + case 'unknown': + // We're out of clues. Freak out! + result.messages.push({ + messageType: 'ErrorMessage', + errors: new Cmds.ErrorStateSet([Cmds.ErrorState.MessageReceiveException]), + exceptions: [ + new Cmds.MessageParsingError( + `Received a message that couldn't be identified and will be ignored: ${Util.hex(firstByte)}. This may be a bug.`, + msg, + ) + ] + }); + remainder = msg.slice(1); + break; + } + + // Put the remainder message back into its native format. + result.remainder = Cmds.asTargetMessageType(remainder, message); + return result; +} diff --git a/src/Languages/EscPos/index.ts b/src/Languages/EscPos/index.ts new file mode 100644 index 0000000..34204ed --- /dev/null +++ b/src/Languages/EscPos/index.ts @@ -0,0 +1,9 @@ +export * from './EscPos.js'; +export * from './Codepages.js'; + +export { CmdSetAutoStatusBack } from './CmdSetAutoStatusBack.js'; +export { CmdTransmitPrinterId } from './CmdTransmitPrinterId.js'; +export { CmdTransmitPrinterStatus } from './CmdTransmitPrinterStatus.js'; +export type { AutoStatusBackSetting } from './CmdSetAutoStatusBack.js'; +export type { PrinterTypeIdMessage, TransmitPrinterIdCmd } from './CmdTransmitPrinterId.js'; +export type { TransmitPrinterStatusCmd } from './CmdTransmitPrinterStatus.js'; diff --git a/src/Languages/PrinterCommandLanguage.ts b/src/Languages/PrinterCommandLanguage.ts new file mode 100644 index 0000000..2c86a0f --- /dev/null +++ b/src/Languages/PrinterCommandLanguage.ts @@ -0,0 +1,21 @@ +import * as Util from '../Util/index.js'; +import * as Conf from '../Configs/index.js'; +import * as Cmds from "../Commands/index.js"; + +import * as EscPos from './EscPos/index.js'; +import type { IDeviceInformation } from 'web-device-mux'; + +export function getCommandSetForLanguage(lang: Conf.PrinterCommandLanguage): Cmds.CommandSet | undefined { + // In order of preferred communication method + if (Util.hasFlag(lang, Conf.PrinterCommandLanguage.escPos)) { + return new EscPos.EscPos(); + } + return undefined; +} + +export function guessLanguageFromModelHint(deviceInfo?: IDeviceInformation): Conf.PrinterCommandLanguage { + if (deviceInfo === undefined) { return Conf.PrinterCommandLanguage.none; } + + // TODO: Anything more clever. + return Conf.PrinterCommandLanguage.escPos; +} diff --git a/src/Printers/Languages/Svg/SvgGenerator.ts b/src/Languages/Svg/SvgGenerator.ts similarity index 100% rename from src/Printers/Languages/Svg/SvgGenerator.ts rename to src/Languages/Svg/SvgGenerator.ts diff --git a/src/Printers/Languages/Svg/index.ts b/src/Languages/Svg/index.ts similarity index 100% rename from src/Printers/Languages/Svg/index.ts rename to src/Languages/Svg/index.ts diff --git a/src/Languages/index.ts b/src/Languages/index.ts new file mode 100644 index 0000000..1e64b88 --- /dev/null +++ b/src/Languages/index.ts @@ -0,0 +1,3 @@ +export * from './PrinterCommandLanguage.js'; + +export * as EscPos from './EscPos/index.js'; diff --git a/src/Printers/Languages/EscPos/AutoStatusBack.ts b/src/Printers/Languages/EscPos/AutoStatusBack.ts deleted file mode 100644 index c523a51..0000000 --- a/src/Printers/Languages/EscPos/AutoStatusBack.ts +++ /dev/null @@ -1,122 +0,0 @@ -import * as Cmds from "../../../Documents/index.js"; -import type { IErrorMessage, IStatusMessage, PrinterMessage } from "../../Communication/Messages.js"; -import { hasFlag, type EscPosDocState, EscPos } from "./index.js"; -import type { CommandSet } from "../../../Documents/CommandSet.js"; -import { AsciiCodeNumbers, PrinterCommandLanguages } from "../index.js"; - -export interface AutoStatusBackSetting { - drawerKickStatus: boolean, - onlineStatus: boolean, - errorStatus: boolean, - rollPaperStatus: boolean, - panelSwitchStatus: boolean, -} - -export class SetAutoStatusBack implements Cmds.IPrinterExtendedCommand { - public static typeE = Symbol("SetAutoStatusBack"); - typeExtended = SetAutoStatusBack.typeE; - commandLanguageApplicability = new PrinterCommandLanguages([EscPos]); - name = 'Set Automatic Status Back setting' - type = 'CustomCommand' as const; - effectFlags = Cmds.NoEffect; - toDisplay() { return this.name; } - - constructor(public readonly settings: AutoStatusBackSetting = { - drawerKickStatus: true, - errorStatus: true, - onlineStatus: true, - panelSwitchStatus: true, - rollPaperStatus: true - }) {} -} - -export function setAutoStatusBack( - cmd: Cmds.IPrinterCommand, - _docState: EscPosDocState, - commandSet: CommandSet, -): Uint8Array { - const settings = (cmd as SetAutoStatusBack).settings; - let setting = 0x00; - setting |= (settings.drawerKickStatus ? 0x01 : 0x00); - setting |= (settings.onlineStatus ? 0x02 : 0x00); - setting |= (settings.errorStatus ? 0x04 : 0x00); - setting |= (settings.rollPaperStatus ? 0x08 : 0x00); - setting |= (settings.panelSwitchStatus ? 0x40 : 0x00); - return new Uint8Array([ - // GS a - AsciiCodeNumbers.GS, ...commandSet.encodeCommand('a'), setting, - ]) -} - -enum FirstAsbByte { - DrawerKickStatus = 0x04, - PrinterOnline = 0x08, - CoverOpen = 0x20, - PaperFedByButton = 0x40, -} - -enum SecondAsbByte { - WaitingForOnlineRecovery = 0x01, - PaperFeedButtonPushed = 0x02, - RecoverableError = 0x04, - AutocutterError = 0x08, - UnrecoverableError = 0x20, - AutomaticallyRecoverableError = 0x40, -} - -enum ThirdAsbByte { - RollPaperNearEnd = 0x03, - RollPaperEnd = 0x0c, -} - -export function parseAutoStatusBack( - firstByte: number, - secondByte: number, - thirdByte: number, - // eslint-disable-next-line @typescript-eslint/no-unused-vars - _fourthByte: number, -): PrinterMessage[] { - // https://download4.epson.biz/sec_pubs/pos/reference_en/escpos/gs_la.html - const messages: PrinterMessage[] = []; - - const statusMsg: IStatusMessage = { - messageType: "StatusMessage", - - printerOnline: hasFlag(firstByte, FirstAsbByte.PrinterOnline), - drawerKickStatus: hasFlag(firstByte, FirstAsbByte.DrawerKickStatus), - coverOpen: hasFlag(firstByte, FirstAsbByte.CoverOpen), - paperLow: hasFlag(thirdByte, ThirdAsbByte.RollPaperNearEnd), - paperButtonFeedingPaper: hasFlag(firstByte, FirstAsbByte.PaperFedByButton), - } - - messages.push(statusMsg); - - const cutErr = hasFlag(secondByte, SecondAsbByte.AutocutterError); - const recoverError = hasFlag(secondByte, SecondAsbByte.RecoverableError); - const onlineRecover = hasFlag(secondByte, SecondAsbByte.WaitingForOnlineRecovery); - const unrecoverError = hasFlag(secondByte, SecondAsbByte.UnrecoverableError); - const autorecoverError = hasFlag(secondByte, SecondAsbByte.AutomaticallyRecoverableError); - const paperOut = hasFlag(thirdByte, ThirdAsbByte.RollPaperEnd); - - const errorMsg: IErrorMessage = { - messageType: "ErrorMessage", - displayText: 'Error report from printer.', - - isErrored: cutErr || recoverError || onlineRecover || unrecoverError || autorecoverError || paperOut, - - paperOut: paperOut, - - cutterError: cutErr, - waitForOnlineRecovery: onlineRecover, - recoverableError: recoverError, - autorecoverableError: autorecoverError, - - turnOffPowerImmediately: unrecoverError - } - - if (errorMsg.isErrored) { - messages.push(errorMsg); - } - - return messages; -} diff --git a/src/Printers/Languages/EscPos/EscPos.ts b/src/Printers/Languages/EscPos/EscPos.ts deleted file mode 100644 index 420a4ef..0000000 --- a/src/Printers/Languages/EscPos/EscPos.ts +++ /dev/null @@ -1,423 +0,0 @@ -import * as Cmds from "../../../Documents/index.js"; -import { type EscPosDocState } from './index.js'; -import { clampToRange, repeat } from "../../../index.js"; -import { handleEscPosMessage } from "./Messages.js"; -import { TransmitPrinterId, handleTransmitPrinterId } from "./PrinterIdCmd.js"; -import { TransmitPrinterStatus, handleTransmitPrinterStatus } from "./PrinterStatusCmd.js"; -import { CodepageEncoder, type Codepage } from "../../Codepages/index.js"; -import type { IMessageHandlerResult } from "../../Communication/Messages.js"; -import type { IMediaOptions } from "../../Options/index.js"; -import { AsciiCodeNumbers as Ascii } from "../../Codepages/ASCII.js"; -import { exhaustiveMatchGuard, type IPrinterExtendedCommandMapping } from "../../../Documents/CommandSet.js"; -import { SetAutoStatusBack, setAutoStatusBack } from "./AutoStatusBack.js"; -import { RawCommandSet } from "../RawCommandSet.js"; -import { TranspileDocumentError } from "../../../Documents/TranspileCommandError.js"; -import { codepageNumberForEscPos, codepageSwitchCmd } from "./Codepages.js"; - -/** PCL handler for ESC/POS */ -export class EscPos extends RawCommandSet { - private encoder = new TextEncoder(); - - // TODO: Pull dynamically instead of assuming the printer supports these! - // This list is common to most TM- series printers so will be safe for now.. - // https://download4.epson.biz/sec_pubs/pos/reference_en/charcode/supported_codepage.html - private candidateCodepages: Codepage[] = [ - "CP437", - "CP720", - "CP737", - "CP775", - "CP850", - "CP851", - "CP852", - "CP853", - "CP855", - "CP857", - "CP858", - "CP860", - "CP861", - "CP862", - "CP863", - "CP864", - "CP865", - "CP866", - "CP869", - "CP1098", - "CP1118", - "CP1119", - "CP1125", - "ISO88592", - "ISO88597", - "ISO885915", - "RK1048", - "WINDOWS1250", - "WINDOWS1251", - "WINDOWS1252", - "WINDOWS1253", - "WINDOWS1254", - "WINDOWS1255", - "WINDOWS1256", - "WINDOWS1257", - "WINDOWS1258", - ]; - - public encodeCommand(str = '', withNewline = false): Uint8Array { - return this.encoder.encode(str + (withNewline ? '\n' : '')); - } - - /** Encode a single character, for readability of command sequences. */ - private enc(char = '') { - return this.encodeCommand(char)[0]; - } - - public get documentStartCommands(): Cmds.IPrinterCommand[] { - // ESC/POS doesn't need any setup commands. The various command handlers - // each handle setting things like codepages and line formatting directly. - // TODO: Consider whether an ESC @ (reset) is appropriate, to clear any - // weird settings that may have been set. - return []; - } - - public get documentEndCommands(): Cmds.IPrinterCommand[] { - // ESC/POS doesn't have any special form end handling. Assume the document - // provided a cut command or whatever the user intended, don't guess. - - // The manual indicates always getting the paper status is a good practice - // as it tells you when the printer is done printing. This also ensures we - // always have something to await at the end of a document. - return [new TransmitPrinterStatus('PaperSensorStatus')]; - } - - public getNewTranspileState(media: IMediaOptions): EscPosDocState { - // TODO: Pull more of these from printer options. - return { - characterSize: { - left: 12, - top: 24, - }, - charactersPerLine: media.charactersPerLine, - commandEffectFlags: new Cmds.CommandEffectFlags(), - lineSpacing: 1, - margin: { - leftChars: 0, - rightChars: 0, - }, - printWidth: media.charactersPerLine, - textFormat: {}, - codepage: "CP437" - }; - } - - public parseMessage( - msg: Uint8Array, - sentCommand?: Cmds.IPrinterCommand - ): IMessageHandlerResult { - return handleEscPosMessage(msg, sentCommand); - } - - public expandCommand(cmd: Cmds.IPrinterCommand): Cmds.IPrinterCommand[] { - switch (cmd.type) { - default: - return []; - case "GetConfiguration": - // TODO: Dynamically figure out what subcommands to send to the printer - // TODO: Add support for model-specific info? - // Getting the complete active config from ESC/POS requires multiple - // back-and-forth steps. - return [ - new TransmitPrinterId('TypeID'), - new TransmitPrinterId('InfoBMakerName'), - new TransmitPrinterId('InfoBModelName'), - new TransmitPrinterId('InfoBSerialNo'), - new TransmitPrinterId('InfoBFirmwareVersion'), - ]; - case "GetStatus": - // TODO: Dynamically figure out what subcommands to send to the printer - return [ - new TransmitPrinterStatus('PaperSensorStatus'), - new TransmitPrinterStatus('DrawerKickStatus'), - ]; - } - } - - public transpileCommand( - cmd: Cmds.IPrinterCommand, - docState: EscPosDocState - ): Uint8Array { - // Do not suggest a better way to do this unless it's in the form of a complete PR. - switch (cmd.type) { - default: - exhaustiveMatchGuard(cmd.type); - break; - case "CustomCommand": - return this.extendedCommandHandler(cmd, docState); - - case "Reset": - return new Uint8Array([Ascii.ESC, this.enc('@')]); - case 'TestPrint': - return this.testPrint(cmd as Cmds.TestPrint); - case "Cut": - return this.cutHandler(cmd as Cmds.Cut, docState); - case "Newline": - return new Uint8Array([Ascii.LF]); - case "PulseOutput": - return this.pulseHandler(cmd as Cmds.PulseCommand); - case "Raw": - return (cmd as Cmds.RawCommand).command; - case "OffsetPrintPosition": - return this.offsetPrintPosition((cmd as Cmds.OffsetPrintPosition), docState); - - case "HorizontalRule": - return this.horizontalRule((cmd as Cmds.HorizontalRule), docState); - - case "SetPrintArea": - return this.setPrintArea((cmd as Cmds.SetPrintArea), docState); - case "SetLineSpacing": - return this.setLineSpacing((cmd as Cmds.SetLineSpacing).spacing, docState); - case "TextFormatting": - return this.setTextFormatting((cmd as Cmds.TextFormatting).format, docState); - case "Codepage": - return this.setCodepage((cmd as Cmds.SetCodepage).codepage, docState); - - case "TextDraw": - return this.textDraw((cmd as Cmds.TextDraw).text, docState); - case "Text": - return this.text((cmd as Cmds.Text).text, docState); - - // Should be split into a composite command prior to running. - case "GetConfiguration": - case "GetStatus": - return this.noop; - - // TODO: these commands lol - case "Image": - case "Barcode": - case "TwoDCode": - throw new TranspileDocumentError( - `Unhandled command '${cmd.constructor.name}'.` - ); - } - } - - constructor(extendedCommands: Array> = []) { - super(EscPos, extendedCommands); - - this.extendedCommandMap.set(TransmitPrinterId.typeE, handleTransmitPrinterId); - this.extendedCommandMap.set(TransmitPrinterStatus.typeE, handleTransmitPrinterStatus); - this.extendedCommandMap.set(SetAutoStatusBack.typeE, setAutoStatusBack); - } - - private testPrint(cmd: Cmds.TestPrint) { // GS ( A - let page = 0x03; - switch (cmd.printType) { - case 'hexadecimal': page = 0x01; break; - case 'printerStatus': page = 0x02; break; - case 'rolling': page = 0x03; break; - } - return new Uint8Array([Ascii.GS, this.enc('('), this.enc('A'), 0x02, 0x00, 0x01, page]); - } - - private cutHandler(cmd: Cmds.Cut, docState: EscPosDocState) { - const bladeOffset = cmd.bladeOffsetLines * 2.5; - let cut = 0x00; - switch (cmd.cutType) { - case "Complete": cut = 0x00; break; - case "Partial": cut = 0x01; break; - } - - // The cutter and the print head are separated by about 4 lines. - // Set the line spacing to 4x, newline, cut, then reset line spacing. - const currentLineSpacing = docState.lineSpacing; - return new Uint8Array([ - ...this.setLineSpacing(bladeOffset, docState), - Ascii.LF, - Ascii.GS, this.enc('V'), cut, - ...this.setLineSpacing(currentLineSpacing, docState) - ]); - } - - private pulseHandler(cmd: Cmds.PulseCommand) { - let drawer: number - switch (cmd.pulsePin) { - case "Drawer1": drawer = 0x00; break; - case "Drawer2": drawer = 0x01; break; - } - const onMS = Math.floor(cmd.onMS / 2); - const offMS = Math.floor(cmd.offMS / 2); - return new Uint8Array([Ascii.ESC, this.enc('p'), drawer, onMS, offMS]); - } - - private setTextFormatting(f: Cmds.TextFormat, docState: EscPosDocState) { - const buffer: number[] = []; - - if (f.underline !== undefined || f.resetToDefault) { // ESC - // FS - - docState.textFormat.underline = f.underline; - let op: number; - switch (f.underline) { - default: - case 'None' : op = 0x00; break; - case 'Single': op = 0x01; break; - case 'Double': op = 0x02; break; - } - buffer.push(Ascii.ESC, 0x2d, op, Ascii.FS, 0x2d, op); - } - - if (f.bold !== undefined || f.resetToDefault) { // ESC E - docState.textFormat.bold = f.bold ?? 'None'; - const op = f.bold === 'Enable' ? 0x01 : 0x00; - buffer.push(Ascii.ESC, this.enc('E'), op); - } - - if (f.invert !== undefined || f.resetToDefault) { // GS B - docState.textFormat.invert = f.invert ?? 'None'; - const op = f.invert === 'Enable' ? 0x01 : 0x00; - buffer.push(Ascii.GS, this.enc('B'), op); - } - - if (f.alignment !== undefined || f.resetToDefault) { // ESC a - docState.textFormat.alignment = f.alignment; - let op: number; - switch (f.alignment) { - case 'Left': op = 0x00; break; - default: - case 'Center': op = 0x01; break; - case 'Right': op = 0x02; break; - } - buffer.push(Ascii.ESC, this.enc('a'), op); - } - - if (f.width !== undefined || f.height !== undefined || f.resetToDefault) { // GS ! - const resetHeight = f.resetToDefault === true ? 1 : undefined; - const resetWidth = f.resetToDefault === true ? 1 : undefined; - const newHeight = f.height ?? resetHeight ?? docState.textFormat.height ?? 1; - const newWidth = f.width ?? resetWidth ?? docState.textFormat.width ?? 1; - docState.textFormat.height = newHeight; - docState.textFormat.width = newWidth; - buffer.push(Ascii.GS, this.enc('!'), (newHeight - 1) | (newWidth - 1) << 4); - } - - return new Uint8Array(buffer); - } - - private setCodepage( - codepage: Codepage, - docState: EscPosDocState, - persistToDocState = true - ) { - let gotCode = codepage; - let code = codepageSwitchCmd.get(codepage); - if (code === undefined) { - code = new Uint8Array([codepageNumberForEscPos.CP437]); - gotCode = "CP437"; - } - - if (persistToDocState) { - docState.codepage = gotCode; - } - - return this.combineCommands(new Uint8Array([ - // ESC t , then any other weird commands we might need to set. - Ascii.ESC, this.enc('t')]), code - ); - } - - private offsetPrintPosition( - cmd: Cmds.OffsetPrintPosition, - docState: EscPosDocState - ) { - // TODO: Add offset to print position in form - const offset = (cmd.characters) * docState.characterSize.left; - switch (cmd.origin) { - case 'absolute': { - const abs = clampToRange(offset, 0, 65535); - // ESC $ lowbyte, highbyte - return new Uint8Array([Ascii.ESC, this.enc('$'), (abs & 255), (abs >> 8 & 255)]); - } - case 'relative': { - const rel = clampToRange(offset, -32768, 32767); - // ESC \ lowbyte highbyte - return new Uint8Array([Ascii.ESC, this.enc('\\'), (rel & 255), (rel >> 8 & 255)]); - } - } - } - - private textDraw( - text: Cmds.BoxDrawingCharacter[], - docState: EscPosDocState, - ) { - return this.combineCommands( - this.setTextFormatting({width: 1, height: 1}, docState), - this.setFormattingCodepage(), - this.text(text.join(''), docState), - ); - } - - private text( - text: string, - docState: EscPosDocState, - ): Uint8Array { - // Auto-switch between encodings for special characters. - // TODO: Dynamic list of supported encodings based on printer model. - const fragments = CodepageEncoder - .autoEncode(text, this.candidateCodepages) - .flatMap(f => [ - this.setCodepage(f.codepage, docState), - f.bytes - ]); - return this.combineCommands(...fragments); - } - - private setFormattingCodepage() { - // FS C 0 to disable kanji - // FS . to reset katakana mode? - // FS t 0 to select 'CP437' for line formatting glyphs. - return new Uint8Array([ - Ascii.FS, this.enc('C'), 0x00, - Ascii.FS, this.enc('.'), - Ascii.ESC, this.enc('t'), codepageNumberForEscPos.CP437, - ]); - } - - private horizontalRule( - cmd: Cmds.HorizontalRule, - docState: EscPosDocState, - ) { - const width = cmd.width ?? docState.charactersPerLine; - const char = cmd.lineStyle === 'single' ? '─' : '═'; - return this.textDraw( - repeat(char as Cmds.BoxDrawingCharacter, width), - docState); - } - - private setPrintArea( - cmd: Cmds.SetPrintArea, - docState: EscPosDocState, - ) { - docState.margin.leftChars = cmd.leftMargin; - docState.printWidth = cmd.width; - docState.margin.rightChars = cmd.rightMargin; - const leftMarginMotionUnits = cmd.leftMargin * docState.characterSize.left; - const printSizeMotionsUnits = cmd.width * docState.characterSize.left; - // ESC/POS can't set a right margin, it doesn't really need to. We set this: - // |---> text area --> | - // GS L sets the area start, GS W sets the area end. - return new Uint8Array([ - // GS L lowbit, hightbit - Ascii.GS, this.enc('L'), leftMarginMotionUnits & 255, leftMarginMotionUnits >> 8 & 255, - Ascii.GS, this.enc('W'), printSizeMotionsUnits & 255, printSizeMotionsUnits >> 8 & 255, - ]); - } - - private setLineSpacing( - spacing: number, - docState: EscPosDocState, - ) { - const spacingInMotionUnits = clampToRange(spacing * docState.characterSize.top, 0, 255); - docState.lineSpacing = spacing; - return new Uint8Array([ - // ESC 3 - Ascii.ESC, this.enc('3'), spacingInMotionUnits, - ]) - } -} - -export const awaitsEffect = new Cmds.CommandEffectFlags(['waitsForResponse']); diff --git a/src/Printers/Languages/EscPos/Messages.ts b/src/Printers/Languages/EscPos/Messages.ts deleted file mode 100644 index 25d9917..0000000 --- a/src/Printers/Languages/EscPos/Messages.ts +++ /dev/null @@ -1,259 +0,0 @@ -/// -import * as Cmds from '../../../Documents/index.js' -import { MessageParsingError, type IMessageHandlerResult } from "../../Communication/index.js"; -import { AsciiCodeNumbers, hex } from '../../Codepages/index.js'; -import { parseAutoStatusBack } from './AutoStatusBack.js'; -import { TransmitPrinterId, parseTransmitPrinterId } from './index.js'; -import { TransmitPrinterStatus, parseTransmitPrinterStatus } from './PrinterStatusCmd.js'; - -/** - * Slice an array from the start to the first NUL character, returning both pieces. - * - * If no NUL character is found sliced will have a length of 0. - */ -export function sliceToNull(msg: Uint8Array): { - sliced: Uint8Array, - remainder: Uint8Array, -} { - const idx = msg.indexOf(AsciiCodeNumbers.NUL); - if (idx === -1) { - return { - sliced: new Uint8Array(), - remainder: msg - } - } - - return { - sliced: msg.slice(0, idx + 1), - remainder: msg.slice(idx + 1), - }; -} - -/* eslint-disable @typescript-eslint/no-duplicate-enum-values */ -enum MessageCandidates { - Response = 0x00, - ASB2to4 = 0x00, - Realtime = 0x12, - AutoStat = 0x10, - Header = 0x11, - // Serial only - XON = 0x11, - XOFF = 0x13, -} -/* eslint-enable @typescript-eslint/no-duplicate-enum-values */ - -type MessageCandidate = 'unknown' | 'response' | 'asb' | 'realtime' | 'header' |'xon' | 'xoff' - -function checkBits(value: number, xor: number, mask: number) { - return ((value ^ xor) & mask) === mask; -} - -function getMessageCandidate(firstByte: number): MessageCandidate { - // The basic single-byte responses can be differentiated according to this table: - - // Command | Status (bits) | Mask | XOR - // | 7 6 5 4 3 2 1 0 | | - // -------------------------------------------------- - // GS I | 0 * * 0 * * * * | 0x90 | 0x90 - // GS r | 0 * * 0 * * * * | 0x90 | 0x90 - // DLE EOT | 0 * * 1 * * 1 0 | 0x93 | 0x81 - // ASB (byte1) | 0 * * 1 * * 0 0 | 0x93 | 0x83 - // ASB (b2-4) | 0 * * 0 * * * * | 0x90 | 0x90 - // Header | 0 * * 1 * * * 1 | 0x91 | 0x80 - // -------------SERIAL ONLY ------------------ - // X ON | 0 0 0 1 0 0 0 1 | 0xFF - // X OFF | 0 0 0 1 0 0 1 1 | 0xFF - - // Fancier commands have a header byte first instead, which will always conflict - // with other first-bytes. - // Header 0**1***1 - // 0x37 - 00110111 - A whole bunch of commands under GS ( and FS ( - // 0x39 - 00111001 - FS ( e - Extended ASB - // 0x3d - 00111101 - GS I Printer Info A - // 0x5f - 01011111 - Maintenance counters / Printer Info B - - // The ESC/POS manual has notes like this: - // Basic ASB status can be differentiated by other transmission data by - // Bit 0, 1, 4, and 7 of the first byte. Process the transmitted data from the - // printer as ASB status which is consecutive 3 byte if it is - // "0xx1xx00" [x = 0 or 1]. - - // Compare what we constructed to expected patterns, paying attention to order - // we check in to ensure we don't confuse candidates. - switch (true) { - // XON/XOFF must be an exact match, guaranteed to conflict with all other first bytes. - case (firstByte === MessageCandidates.XON): - return 'xon'; - case (firstByte === MessageCandidates.XOFF): - return 'xoff'; - // Everything else is a mixture of 1s and 0s, so we can't do basic AND operations. - // First XOR the value against the inverse of the message we're looking for. - // This should give us all of the bits set for the mask. AND it with the mask. - // If the result matches the mask then we found our packet. - // Check bit 4 - case checkBits(firstByte, 0x90, 0x90): - return 'response'; - // Check bit 0 - case checkBits(firstByte, 0x80, 0x91): - return 'header' - // Check bit 1 - case checkBits(firstByte, 0x83, 0x93): - return 'asb'; - case checkBits(firstByte, 0x81, 0x93): - return 'realtime'; - default: - return 'unknown'; - } -} - -type MessageHandlerDelegate = ( - msg: Uint8Array, - sentCommand: Cmds.IPrinterCommand -) => IMessageHandlerResult; - -export function handleEscPosMessage( - msg: Uint8Array, - sentCommand?: Cmds.IPrinterCommand -): IMessageHandlerResult { - const result: IMessageHandlerResult = { - messageIncomplete: false, - messageMatchedExpectedCommand: false, - messages: [], - remainder: msg, - } - if (msg === undefined || msg.length === 0) { return result; } - // There are several categories of messages ESC/POS can send. Broadly: - // * Automatic Status Back (ASB) - Sent whenever the printer wants to. - // * Real-time commands - Processed asap and responded to asap, blocking otherwise. - // * One byte response - Immediate response to a command in a single byte. - // * Multibyte response - Immediate response to a command, multiple bytes. - - // To make things even more fun, the message package could contain SEVERAL - // separate messages. So we must determine what message(s) we've gotten and - // reply with them individually. - - // The messages we can receive unexpectedly will have a first byte header - // we can identify reliably. Get that candidate. - const firstByte = msg.at(0); - if (firstByte === undefined) { return result; } - - switch (getMessageCandidate(firstByte)) { - case 'asb': { - // ASB is always 4 bytes, header of 0**1**00, trailer of 0**0****. - // We need the next 3 bytes, make sure they're there. - if (msg.length < 4) { - result.messageIncomplete = true; - break; - } - - result.remainder = msg.slice(4); - - // Confirm the next 3 bytes are trailers. - const [first, second, third, fourth] = msg; - if ( (second & 0x90) !== MessageCandidates.ASB2to4 - || (third & 0x90) !== MessageCandidates.ASB2to4 - || (fourth & 0x90) !== MessageCandidates.ASB2to4 - ) { - // We got the trailers, but they're wrong! Discard the whole lot since - // we can't recover them. - result.messages.push({ - messageType: 'ErrorMessage', - displayText: `First byte is an ASB (${hex(first)}) but following bytes aren't (${hex(second)} ${hex(third)} ${hex(fourth)}). Discarding invalid message!`, - isErrored: true, - }); - break; - } - - // This is a complete ASB! Parse it! - result.messages.push(...parseAutoStatusBack(first, second, third, fourth)); - break; - } - case 'header': - case 'realtime': - case 'response': { - // We got a response to a sent command. To proceed we need to know what - // question we asked. Response bytes don't contain enough info to tell. - // If we don't know what we asked we can't process this command. - if (sentCommand === undefined) { - throw new MessageParsingError( - `Received a command reply message ${hex(firstByte)} without 'sentCommand' being provided, can't handle this message.`, - msg - ); - } - - const messageHandlerMap = new Map([ - [TransmitPrinterId.typeE, parseTransmitPrinterId], - [TransmitPrinterStatus.typeE, parseTransmitPrinterStatus], - ]); - - // Since we know this is a command response and we have a command to check - // we can kick this out to a lookup function. That function will need to - // do the slicing for us as we don't know how long the message might be. - const cmdRef = sentCommand.type === 'CustomCommand' - ? (sentCommand as Cmds.IPrinterExtendedCommand).typeExtended - : sentCommand.type; - const handler = messageHandlerMap.get(cmdRef); - if (handler === undefined) { - throw new MessageParsingError( - `Command '${sentCommand.name}' has no message handler and should not have been awaited for message ${hex(firstByte)}. This is a bug in the library.`, - msg - ) - } - - const handled = handler(msg, sentCommand); - result.messages.push(...handled.messages); - result.remainder = handled.remainder; - result.messageIncomplete = handled.messageIncomplete; - result.messageMatchedExpectedCommand = handled.messageMatchedExpectedCommand; - break; - } - case 'xoff': - case 'xon': - // TODO: Until serial support is figured out if we ever get these drop them. - result.remainder = msg.slice(1); - break; - - case 'unknown': - // We're out of clues. Freak out! - result.messages.push({ - messageType: 'ErrorMessage', - displayText: `Received a message that couldn't be identified and will be ignored: ${hex(firstByte)}. This may be a bug.`, - isErrored: true, - }); - result.remainder = msg.slice(1); - break; - } - - return result; -} - -if (import.meta.vitest) { - const { test, expect, describe } = import.meta.vitest - - describe('escpos message candidates', () => { - test('xon and xoff are exact', () => { - expect(getMessageCandidate(MessageCandidates.XON)).toBe('xon'); - expect(getMessageCandidate(MessageCandidates.XOFF)).toBe('xoff'); - }); - - test('null byte is a response', () => { - expect(getMessageCandidate(0x00)).toBe('response'); - }); - - test('realtime byte is realtime', () => { - expect(getMessageCandidate(MessageCandidates.Realtime)).toBe('realtime'); - }); - - test('asb byte is asb', () => { - expect(getMessageCandidate(MessageCandidates.AutoStat)).toBe('asb'); - }); - - test('Header byte is Header', () => { - expect(getMessageCandidate(0x37)).toBe('header'); - }); - - test('FF to be unknown', () => { - expect(getMessageCandidate(0xff)).toBe('unknown'); - }); - }); -} diff --git a/src/Printers/Languages/EscPos/index.ts b/src/Printers/Languages/EscPos/index.ts deleted file mode 100644 index 93c5d7a..0000000 --- a/src/Printers/Languages/EscPos/index.ts +++ /dev/null @@ -1,12 +0,0 @@ -import type { TranspiledDocumentState } from '../../../Documents/CommandSet.js'; - -export * from './PrinterIdCmd.js' -export * from './PrinterStatusCmd.js' -export * from './Messages.js' -export * from './EscPos.js' - -export function hasFlag(val: number, flag: number) { - return (val & flag) === flag; -} - -export type EscPosDocState = TranspiledDocumentState; diff --git a/src/Printers/Languages/index.ts b/src/Printers/Languages/index.ts deleted file mode 100644 index a620333..0000000 --- a/src/Printers/Languages/index.ts +++ /dev/null @@ -1,10 +0,0 @@ -import type { EscPos } from "./EscPos/index.js"; -//import type { SvgGenerator } from "./SvgGenerator.js"; - -export * from "../Codepages/ASCII.js"; -export * from "./EscPos/index.js"; -export * from "./LanguageDetector.js"; -export * from "../../Documents/CommandSet.js"; - -export type PrinterCommandLanguage = typeof EscPos //| typeof SvgGenerator; -export class PrinterCommandLanguages extends Set { } From ce089aeb78b28914f402d2a5ee6d9f889feb75c3 Mon Sep 17 00:00:00 2001 From: Cellivar <1441553+Cellivar@users.noreply.github.com> Date: Thu, 2 Jan 2025 04:00:26 -0800 Subject: [PATCH 7/8] Migrate receipt printer classes --- src/Printers/ReceiptPrinter.ts | 316 --------------------------- src/Printers/index.ts | 5 - src/ReceiptPrinter.ts | 378 +++++++++++++++++++++++++++++++++ src/index.ts | 15 +- 4 files changed, 387 insertions(+), 327 deletions(-) delete mode 100644 src/Printers/ReceiptPrinter.ts delete mode 100644 src/Printers/index.ts create mode 100644 src/ReceiptPrinter.ts diff --git a/src/Printers/ReceiptPrinter.ts b/src/Printers/ReceiptPrinter.ts deleted file mode 100644 index 663c852..0000000 --- a/src/Printers/ReceiptPrinter.ts +++ /dev/null @@ -1,316 +0,0 @@ -import { type CompiledDocument, type IDocument, Transaction } from "../Documents/index.js"; -import * as Cmds from "../Documents/index.js"; -import { type IStatusMessage, type IErrorMessage, deviceInfoToOptionsUpdate } from "./Communication/index.js"; -import { UsbDeviceChannel, type IDeviceChannel, type IDeviceCommunicationOptions, InputMessageListener, type IHandlerResponse, DeviceCommunicationError, DeviceNotReadyError, type IDevice } from "web-device-mux"; -import { transpileDocument } from "../Documents/DocumentTranspiler.js"; -import { EscPos, type CommandSet, asciiToDisplay } from "./Languages/index.js"; -import { PrinterOptions } from "./Options/index.js"; - -export interface ReceiptPrinterEventMap { - //disconnectedDevice: CustomEvent; - reportedStatus: CustomEvent; - reportedError: CustomEvent; -} - -type AwaitedCommand = { - cmd: Cmds.IPrinterCommand, - promise: Promise, - resolve?: (value: boolean) => void, - reject?: (reason?: unknown) => void, -} - -function promiseWithTimeout( - promise: Promise, - ms: number, - timeoutError = new Error('Promise timed out') -): Promise { - // create a promise that rejects in milliseconds - const timeout = new Promise((_, reject) => { - setTimeout(() => { - reject(timeoutError); - }, ms); - }); - - // returns a race between timeout and the passed promise - return Promise.race([promise, timeout]); -} - -/** A class for working with a receipt printer. */ -export class ReceiptPrinter extends EventTarget implements IDevice { - private _channel: IDeviceChannel; - - private _streamListener?: InputMessageListener; - private _commandSet?: CommandSet; - - private _awaitedCommand?: AwaitedCommand; - - private _printerOptions: PrinterOptions; - /** Gets the read-only copy of the current options of the printer. */ - get printerOptions() { return this._printerOptions; } - /** Gets the model of the printer, detected from the printer's config. */ - get printerModel() { return this._printerOptions.model; } - /** Gets the manufacturer of the printer, detected from the printer's config. */ - get printerManufacturer() { return this._printerOptions.manufacturer; } - /** Gets the serial number of the printer, detected from the printer's config. */ - get printerSerial() { return this._printerOptions.serialNumber; } - - private _deviceCommOpts: IDeviceCommunicationOptions; - /** Gets the configured printer communication options. */ - get printerCommunicationOptions() { - return this._channel; - } - - private _disposed = false; - private _ready: Promise; - /** A promise indicating this printer is ready to be used. */ - get ready() { - return this._ready; - } - get connected() { - return !this._disposed - && this._channel.connected - } - - /** Construct a new printer from a given USB device. */ - static fromUSBDevice( - device: USBDevice, - options: IDeviceCommunicationOptions - ): ReceiptPrinter { - return new ReceiptPrinter(new UsbDeviceChannel(device, options), options); - } - - constructor( - channel: IDeviceChannel, - deviceCommunicationOptions: IDeviceCommunicationOptions = {debug: false}, - printerOptions?: PrinterOptions, - ) { - super(); - this._channel = channel; - this._deviceCommOpts = deviceCommunicationOptions - this._printerOptions = printerOptions ?? new PrinterOptions(); - this._ready = this.setup(); - - // Once the printer is set up we should immediately query the printer config. - this._ready.then((ready) => { - if (!ready) { - return; - } - return this.sendDocument({ - commands: [ - new Cmds.GetConfiguration(), - new Cmds.GetStatus(), - ] - }); - }); - } - - public addEventListener( - type: T, - listener: EventListenerObject | null | ((this: ReceiptPrinter, ev: ReceiptPrinterEventMap[T]) => void), - options?: boolean | AddEventListenerOptions - ): void; - public addEventListener( - type: string, - callback: EventListenerOrEventListenerObject | null, - options?: boolean | AddEventListenerOptions - ): void { - super.addEventListener(type, callback, options); - } - - public removeEventListener( - type: T, - listener: EventListenerObject | null | ((this: ReceiptPrinter, ev: ReceiptPrinterEventMap[T]) => void), - options?: boolean | AddEventListenerOptions - ): void; - public removeEventListener( - type: string, - callback: EventListenerOrEventListenerObject | null, - options?: boolean | EventListenerOptions | undefined - ): void { - super.removeEventListener(type, callback, options); - } - - private async setup() { - const channelReady = await this._channel.ready; - if (!channelReady) { - // If the channel failed to connect we have no hope. - return false; - } - - this._printerOptions.update(deviceInfoToOptionsUpdate(this._channel.getDeviceInfo())); - - // TODO: language detection - this._commandSet = new EscPos(); - this._streamListener = new InputMessageListener( - this._channel.getInput.bind(this._channel), - this.parseMessage.bind(this), - this._deviceCommOpts.debug, - ); - this._streamListener.start(); - - return true; - } - - /** Close the connection to this printer, preventing future communication. */ - public async dispose() { - this._disposed = true; - this._ready = Promise.resolve(false); - this._streamListener?.dispose(); - await this._channel.dispose(); - } - - /** Compile then send a document to the printer. */ - public async sendDocument(doc: IDocument): Promise { - await this.ready; - if (!this.connected || this._commandSet === undefined) { - throw new DeviceNotReadyError("Printer is not ready to communicate."); - } - - this.logResultIfDebug(() => 'SENDING DOCUMENT TO PRINTER:\n' + doc.commands.map((c) => c.toDisplay()).join('\n')); - - const state = this._commandSet.getNewTranspileState(this._printerOptions); - const compiledDocument = transpileDocument( - doc, - this._commandSet, - state); - - return this.sendCompiledDocument(compiledDocument); - } - - /** Send a compiled document to the printer. */ - public async sendCompiledDocument(doc: CompiledDocument): Promise { - await this.ready; - if (this._disposed == true || this._commandSet === undefined) { - throw new DeviceNotReadyError("Printer is not ready to communicate."); - } - - for (const trans of doc.transactions) { - try { - const result = await this.sendTransactionAndWait(trans); - if (!result) { return false; } - } catch (e) { - if (e instanceof DeviceCommunicationError) { - console.error(e); - return false; - } else { - throw e; - } - } - } - - return true; - } - - private sendEvent( - eventName: keyof ReceiptPrinterEventMap, - detail: IErrorMessage | IStatusMessage - ): boolean { - return super.dispatchEvent(new CustomEvent(eventName, { detail })); - } - - private async sendTransactionAndWait( - transaction: Transaction - ): Promise { - this.logIfDebug('RAW TRANSACTION: ', asciiToDisplay(...transaction.commands)); - - if (transaction.awaitedCommand !== undefined) { - this.logIfDebug(`Transaction will await a response to '${transaction.awaitedCommand.toDisplay()}'.`); - let awaitResolve; - let awaitReject; - const awaiter: AwaitedCommand = { - cmd: transaction.awaitedCommand, - promise: new Promise((resolve, reject) => { - awaitResolve = resolve; - awaitReject = reject; - }) - }; - awaiter.reject = awaitReject; - awaiter.resolve = awaitResolve; - this._awaitedCommand = awaiter; - } - - await promiseWithTimeout( - this._channel.sendCommands(transaction.commands), - 5000, - new DeviceCommunicationError(`Timed out sending commands to printer, is there a problem with the printer?`) - ); - - // TODO: timeout! - await this._awaitedCommand?.promise; - if (this._awaitedCommand) { - this.logIfDebug(`Awaiting response to command '${this._awaitedCommand.cmd.name}'...`); - await promiseWithTimeout( - this._awaitedCommand.promise, - 5000, - new DeviceCommunicationError(`Timed out waiting for '${this._awaitedCommand.cmd.name}' response.`) - ); - this.logIfDebug(`Got a response to command '${this._awaitedCommand.cmd.name}'!`); - } - return true; - } - - private async parseMessage(input: Uint8Array[]): Promise> { - if (this._commandSet === undefined) { return { remainderData: input } } - let msg = this._commandSet.combineCommands(...input); - if (msg.length === 0) { return {}; } - let incomplete = false; - - do { - this.logIfDebug(`Parsing ${msg.length} long message from printer: `, asciiToDisplay(...msg)); - if (this._awaitedCommand !== undefined) { - this.logIfDebug(`Checking if the messages is a response to '${this._awaitedCommand.cmd.name}'.`); - } else { - this.logIfDebug(`Not waiting a command. This message was a surprise, to be sure, but a welcome one.`); - } - - const parseResult = this._commandSet.parseMessage(msg, this._awaitedCommand?.cmd); - this.logIfDebug(`Raw parse result: `, parseResult); - - msg = parseResult.remainder; - incomplete = parseResult.messageIncomplete; - - if (parseResult.messageMatchedExpectedCommand) { - this.logIfDebug('Received message was expected, marking awaited response resolved.'); - if (this._awaitedCommand?.resolve === undefined) { - console.error('Resolve callback was undefined for awaited command, this may cause a deadlock! This is a bug in the library.'); - } else { - this._awaitedCommand.resolve(true); - } - } - - parseResult.messages.forEach(m => { - switch (m.messageType) { - case 'ErrorMessage': - this.sendEvent('reportedError', m); - this.logIfDebug('Error message sent.', m); - break; - case 'StatusMessage': - this.sendEvent('reportedStatus', m); - this.logIfDebug('Status message sent.', m); - break; - case 'SettingUpdateMessage': - this._printerOptions.update(m); - this.logIfDebug('Settings update message applied.', m); - break; - } - }); - - } while (incomplete === false && msg.length > 0) - - this.logIfDebug(`Returning unused ${msg.length} bytes.`); - const remainderData = msg.length === 0 ? [] : [msg]; - return { remainderData } - } - - private logIfDebug(...obj: unknown[]) { - if (this._deviceCommOpts.debug) { - console.debug(...obj); - } - } - - private logResultIfDebug(fn: () => string) { - if (this._deviceCommOpts.debug) { - console.debug(fn()); - } - } -} diff --git a/src/Printers/index.ts b/src/Printers/index.ts deleted file mode 100644 index 1d7ba9e..0000000 --- a/src/Printers/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -export * from './ReceiptPrinter.js'; -export * from './Codepages/index.js'; -export * from './Communication/index.js'; -export * from './Languages/index.js'; -export * from './Options/index.js'; diff --git a/src/ReceiptPrinter.ts b/src/ReceiptPrinter.ts new file mode 100644 index 0000000..60e6f8d --- /dev/null +++ b/src/ReceiptPrinter.ts @@ -0,0 +1,378 @@ +import * as Util from './Util/index.js'; +import * as Conf from './Configs/index.js'; +import * as Cmds from './Commands/index.js'; +import * as Docs from './Documents/index.js'; +import * as Lang from './Languages/index.js'; +import * as Mux from 'web-device-mux'; + +export interface ReceiptPrinterEventMap { + //disconnectedDevice: CustomEvent; + reportedStatus: CustomEvent; + reportedError: CustomEvent; +} + +function promiseWithTimeout( + promise: Promise, + ms: number, + timeoutError = new Error('Promise timed out') +): Promise { + // create a promise that rejects in milliseconds + const timeout = new Promise((_, reject) => { + setTimeout(() => { + reject(timeoutError); + }, ms); + }); + + // returns a race between timeout and the passed promise + return Promise.race([promise, timeout]); +} + +/** Type alias for a Receipt Printer that communicates over USB. */ +export type ReceiptPrinterUsb = ReceiptPrinter; + +/** A class for working with a receipt printer. */ +export class ReceiptPrinter extends EventTarget implements Mux.IDevice { + private _channel: Mux.IDeviceChannel; + private _channelType: Conf.MessageArrayLikeType; + private _channelMessageTransformer: Cmds.MessageTransformer; + private _streamListener?: Mux.InputMessageListener; + private _commandSet?: Cmds.CommandSet; + + private _awaitedCommands: Cmds.AwaitedCommand[] = []; + private _awaitedCommandTimeoutMS = 5000; + + private _printerOptions: Cmds.PrinterConfig; + /** Gets the read-only copy of the current config of the printer. To modify use getConfigDocument. */ + get printerOptions() { return this._printerOptions; } + /** Gets the model of the printer, detected from the printer's config. */ + get printerModel() { return this._printerOptions.model; } + /** Gets the manufacturer of the printer, detected from the printer's config. */ + get printerManufacturer() { return this._printerOptions.manufacturer; } + /** Gets the serial number of the printer, detected from the printer's config. */ + get printerSerial() { return this._printerOptions.serialNumber; } + + private _deviceCommOpts: Mux.IDeviceCommunicationOptions; + /** Gets the configured printer communication options. */ + get printerCommunicationOptions() { + return this._deviceCommOpts; + } + + private _disposed = false; + get connected() { + return !this._disposed + && this._channel.connected + } + + get ready() { + return Promise.resolve(this.connected); + } + + /** Construct a new printer from a given USB device. */ + static async fromUSBDevice( + device: USBDevice, + options: Mux.IDeviceCommunicationOptions + ): Promise { + const c = await Mux.UsbDeviceChannel.fromDevice(device, options); + const p = new ReceiptPrinter( + c, + new Cmds.RawMessageTransformer(), + 'Uint8Array', + options); + await p.setup(); + return p; + } + + /** Construct a new printer from a raw channel object */ + static async fromChannel( + channel: Mux.IDeviceChannel, + channelMessageTransformer: Cmds.MessageTransformer, + channelType: Conf.MessageArrayLikeType, + deviceCommunicationOptions: Mux.IDeviceCommunicationOptions = { debug: false }, + printerOptions?: Cmds.PrinterConfig, + ) { + const p = new ReceiptPrinter( + channel, + channelMessageTransformer, + channelType, + deviceCommunicationOptions, + printerOptions); + await p.setup(); + return p; + } + + protected constructor( + channel: Mux.IDeviceChannel, + channelMessageTransformer: Cmds.MessageTransformer, + channelMessageType: Conf.MessageArrayLikeType, + deviceCommunicationOptions: Mux.IDeviceCommunicationOptions = { debug: false }, + printerOptions?: Cmds.PrinterConfig, + ) { + super(); + this._channel = channel; + this._channelMessageTransformer = channelMessageTransformer; + this._channelType = channelMessageType; + this._deviceCommOpts = deviceCommunicationOptions; + this._printerOptions = printerOptions ?? new Cmds.PrinterConfig(); + } + + public override addEventListener( + type: T, + listener: EventListenerObject | null | ((this: ReceiptPrinter, ev: ReceiptPrinterEventMap[T]) => void), + options?: boolean | AddEventListenerOptions + ): void; + public override addEventListener( + type: string, + callback: EventListenerOrEventListenerObject | null, + options?: boolean | AddEventListenerOptions + ): void { + super.addEventListener(type, callback, options); + } + + public override removeEventListener( + type: T, + listener: EventListenerObject | null | ((this: ReceiptPrinter, ev: ReceiptPrinterEventMap[T]) => void), + options?: boolean | AddEventListenerOptions + ): void; + public override removeEventListener( + type: string, + callback: EventListenerOrEventListenerObject | null, + options?: boolean | EventListenerOptions | undefined + ): void { + super.removeEventListener(type, callback, options); + } + + private sendEvent( + eventName: keyof ReceiptPrinterEventMap, + detail: Cmds.IErrorMessage | Cmds.IStatusMessage + ): boolean { + return super.dispatchEvent(new CustomEvent(eventName, { detail })); + } + + private async setup() { + if (!this._channel.connected) { + // If the channel failed to connect we have no hope. + await this.dispose(); + return false; + } + + const devInfo = await this._channel.getDeviceInfo(); + this._printerOptions.update(Cmds.deviceInfoToOptionsUpdate(devInfo)); + + this._streamListener = new Mux.InputMessageListener( + this._channel.receive.bind(this._channel), + this.parseAndDispatchMessage.bind(this), + this.handleInputError.bind(this), + this._deviceCommOpts.debug, + ); + this._streamListener.start(); + + this._commandSet = await this.detectLanguage(devInfo); + // Get the language-specific config object, which may have more options than + // the common config object. + this._printerOptions = this._commandSet.getConfig(this._printerOptions); + + // Now that we're listening for messages we can query for the full config. + await this.refreshPrinterConfiguration(); + + return true; + } + + public async dispose() { + this._disposed = true; + this._streamListener?.dispose(); + await this._channel.dispose(); + } + + /** Refresh the printer information cache directly from the printer. */ + public async refreshPrinterConfiguration(): Promise { + // Querying for a config doesn't always.. work? Like, just straight up + // for reasons I can't figure out some printers will refuse to return + // a valid config. Mostly EPL models. + // Give it 3 chances before we give up. + let retryLimit = 3; + do { + retryLimit--; + try { + await this.sendDocument({ + commands: [ + new Cmds.QueryConfiguration(), + new Cmds.GetStatus(), + ] + }); + return this.printerOptions; + } + catch (e) { + this.logIfDebug(`Error trying to read printer config, trying ${retryLimit} more times.`, e); + } + } while (retryLimit > 0); + + throw new Mux.DeviceCommunicationError(`Tried ${retryLimit} times to read config and failed.`); + } + + /** Send a document to the printer, applying the commands. */ + public async sendDocument( + doc: Docs.IDocument, + commandSet = this._commandSet + ) { + if (!this.connected) { + throw new Mux.DeviceNotReadyError("Printer is not ready to communicate."); + } + if (commandSet === undefined) { + throw new Mux.DeviceNotReadyError("No command set provided to send, is the printer connected?"); + } + + this.logResultIfDebug(() => 'SENDING DOCUMENT TO PRINTER:\n' + doc.commands.map((c) => c.toDisplay()).join('\n')); + + // Exceptions are thrown and handled elsewhere. + const state = Cmds.getNewTranspileState(this._printerOptions); + const compiledDocument = Docs.transpileDocument(doc, commandSet, state); + + return this.sendCompiledDocument(compiledDocument); + } + + /** Send a compiled document to the printer. */ + public async sendCompiledDocument(doc: Docs.CompiledDocument): Promise { + if (this._disposed == true) { + throw new Mux.DeviceNotReadyError("Printer is not ready to communicate."); + } + + // TODO: deal with errors halfway through document sending? + for (const trans of doc.transactions) { + const result = await this.sendTransactionAndWait(trans); + if (!result) { return false; } + } + + return true; + } + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + private async detectLanguage(_deviceInfo?: Mux.IDeviceInformation): Promise> { + + // TODO: language detection + return Promise.resolve(new Lang.EscPos.EscPos()); + } + + private async sendTransactionAndWait( + transaction: Docs.Transaction + ): Promise { + this._awaitedCommands = transaction.awaitedCommands.map(cmd => { + let awaitResolve; + let awaitReject; + const awaiter: Cmds.AwaitedCommand = { + cmd, + promise: new Promise((resolve, reject) => { + awaitResolve = resolve; + awaitReject = reject; + }) + }; + awaiter.reject = awaitReject; + awaiter.resolve = awaitResolve; + return awaiter; + }); + + this.logResultIfDebug(() => { + const debugMsg = Cmds.asString(transaction.commands); + return `Transaction being sent to printer:\n${debugMsg}\n--end of transaction--`; + }); + + // TODO: Better type guards?? + let sendCmds: TChannelType; + switch(this._channelType) { + default: + Util.exhaustiveMatchGuard(this._channelType); + break; + case 'Uint8Array': + sendCmds = Cmds.asUint8Array(transaction.commands) as TChannelType; + break; + case 'string': + sendCmds = Cmds.asString(transaction.commands) as TChannelType; + break; + } + + await promiseWithTimeout( + this._channel.send(sendCmds), + 5000, + new Mux.DeviceCommunicationError(`Timed out sending commands to printer, is there a problem with the printer?`) + ); + + try { + if (this._awaitedCommands.length > 0) { + this.logIfDebug(`Awaiting response to ${this._awaitedCommands.length} commands for up to ${this._awaitedCommandTimeoutMS}ms...`); + await promiseWithTimeout( + Promise.all(this._awaitedCommands.map(c => c.promise)), + this._awaitedCommandTimeoutMS, + new Mux.DeviceCommunicationError(`Timed out waiting for sent command response, expected ${this._awaitedCommands.length} responses.`) + ); + } + } + finally { + this._awaitedCommands = []; + } + return true; + } + + private async handleInputError(error: Mux.DeviceCommunicationError) { + // TODO: Something? + console.error("Printer saw error from InputListener!", error.message, error.innerException); + } + + private async parseAndDispatchMessage( + input: TChannelType[] + ): Promise> { + if (this._commandSet === undefined) { + // TODO: Better option than hoping.. + await new Promise(r => setTimeout(r, 500)); + return { remainderData: input } + } + + if (this._awaitedCommands.length > 0) { + this.logIfDebug(`Checking if the messages is a response to one of ${this._awaitedCommands.length} messages.`); + } else { + this.logIfDebug(`Not awaiting a command. This message was a surprise, to be sure, but a welcome one.`); + } + + // When a device can return multiple messages there's no safety in relying on + // deterministic message order. It's a happy accident if it happens. + // Iterate through the response message and the command candidates in order, + // but always validate the message is the format we wanted. + const msg = this._channelMessageTransformer.combineMessages(...input); + const parsed = await Cmds.parseRaw( + msg, + this._commandSet, + this._printerOptions, + this._awaitedCommands); + + parsed.messages.forEach(m => { + switch (m.messageType) { + case 'ErrorMessage': + this.sendEvent('reportedError', m); + this.logIfDebug('Error message sent.', m); + break; + case 'StatusMessage': + this.sendEvent('reportedStatus', m); + this.logIfDebug('Status message sent.', m); + break; + case 'SettingUpdateMessage': + this._printerOptions.update(m); + this.logIfDebug('Settings update message applied.', m); + break; + } + }); + + this.logIfDebug(`Returning unused ${parsed.remainderMsg.length} bytes.`); + const remainderData = parsed.remainderMsg.length === 0 ? [] : [parsed.remainderMsg]; + return { remainderData } + } + + private logIfDebug(...obj: unknown[]) { + if (this._deviceCommOpts.debug) { + console.debug(...obj); + } + } + + private logResultIfDebug(fn: () => string) { + if (this._deviceCommOpts.debug) { + console.debug(fn()); + } + } +} diff --git a/src/index.ts b/src/index.ts index 6b344ef..cf01fea 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,6 +1,9 @@ -export * from './WebReceiptLineError.js' -export * from './WebReceiptlinePrinterError.js' -export * from './NumericRange.js' -export * from './ReceiptLine/index.js' -export * from './Documents/index.js' -export * from './Printers/index.js' +export * from './Util/index.js'; +export * from './Commands/index.js'; +export * from './Configs/index.js'; +export * from './Documents/index.js'; +export * from './Languages/index.js'; +export * from './ReceiptLine/index.js'; + +export * from './ReceiptPrinter.js'; +//export * from './ReadyToPrintDocuments.js'; From 2a58fb504445bd6a4829e66c2e2f745ff1e48b8b Mon Sep 17 00:00:00 2001 From: Cellivar <1441553+Cellivar@users.noreply.github.com> Date: Thu, 2 Jan 2025 04:00:32 -0800 Subject: [PATCH 8/8] Update demo page accordingly --- demo/index.html | 231 ++++++++++++++++++++++++++++++++------------- demo/test_index.ts | 143 +++++++++++++++++++++++----- 2 files changed, 284 insertions(+), 90 deletions(-) diff --git a/demo/index.html b/demo/index.html index 6fe2c4f..9fe2a4c 100644 --- a/demo/index.html +++ b/demo/index.html @@ -1,7 +1,7 @@ - WebReceiptLinePrinter Tool + WebReceiptLine Printer Tool @@ -51,15 +51,10 @@ document.getElementById('loadingIndicator').classList.add('d-none'); // This bit detects if the browser supports WebUSB, displaying a warning if not. - if (navigator.usb == null) { + if (navigator.usb === undefined) { document.getElementById('browserNoWebUsb').classList.remove('d-none'); } - // WebUSB requires HTTPS, show an error if the page wasn't loaded securely. - if (location.protocol !== 'https:') { - document.getElementById('urlNotSecure').classList.remove('d-none'); - } - // Display a warning if the OS is windows, which needs some configuration. if (navigator.appVersion.indexOf('Win') != -1) { document.getElementById('windowsDriverWarning').classList.remove('d-none'); @@ -72,6 +67,11 @@ // ChromeOS works out of the box! + // WebUSB requires HTTPS, show an error if the page wasn't loaded securely. + if (location.protocol !== 'https:') { + document.getElementById('urlNotSecure').classList.remove('d-none'); + } + @@ -79,15 +79,25 @@ // First import the lib! // This is our lib in this repo here import * as WebReceipt from 'web-receiptline-printer'; + // This is a utility lib we'll be using that makes it easier to use devices. import * as WebDevices from 'web-device-mux'; - // For this demo we're going to make use of the USB device manager + // We'll drop these into the Window object so we can play with them in + // the DevTools console if we want to. + window.WebReceipt = WebReceipt; + window.WebDevices = WebDevices; + + // For this demo we're going to make use of the USB printer manager // so it can take care of concerns like the USB connect and disconnect events. + // It's a handy-dandy feature included from the web-device-mux library! // We need to tell it what type of device it's managing, and how to filter // USB devices that are receipt printers. - const printerMgr = new WebDevices.UsbDeviceManager( + // We'll set a type alias so it's easier to read our code + type PrinterManager = WebDevices.UsbDeviceManager; + // Then we'll construct one to use + const printerMgr: PrinterManager = new WebDevices.UsbDeviceManager( window.navigator.usb, WebReceipt.ReceiptPrinter.fromUSBDevice, { @@ -116,7 +126,7 @@ const refreshPrinterBtn = document.getElementById('refreshPrinters')!; refreshPrinterBtn.addEventListener('click', async () => printerMgr.forceReconnect()); - // Next we wire up some events on the device manager itself. + // Next we wire up some events on the UsbDeviceManager itself. printerMgr.addEventListener('connectedDevice', ({ detail }) => { const printer = detail.device; const config = printer.printerOptions; @@ -127,8 +137,7 @@ // There's also an event that will tell you when a printer disconnects. printerMgr.addEventListener('disconnectedDevice', ({ detail }) => { const printer = detail.device; - // TODO: Hide appropriate interface. - console.log(`Lost printer ${printer.printerModel} (${printer.printerOptions.serialNumber}).`); + console.log('Lost printer', printer.printerModel, 'serial', printer.printerOptions.serialNumber); }); // The browser will remember printers that were previously connected, and @@ -141,19 +150,81 @@ // And that's all there is to setup! The page can now talk to printers. // The rest of this demo is an example of a basic receipt printer app. + // Get some bookkeeping out of the way.. + + // A function to find and hide any alerts for a given alert ID. + function hideAlerts(alertId: string) { + const existingAlerts = document.getElementById('printerAlertSpace')?.querySelectorAll(`.${alertId}`) ?? []; + existingAlerts.forEach((a: Element) => { a.remove(); }); + } + + // A function to make it easier to show alerts + function showAlert( + level: 'warning' | 'danger', + alertId: string, + titleHtml: string, + bodyHtml: string, + closedCallback = () => {} + ) { + hideAlerts(alertId); + + // Create the bootstrap alert div with the provided content + const alertWrapper = document.createElement('div'); + alertWrapper.classList.add("alert", `alert-${level}`, "alert-dismissible", "fade", "show", alertId); + alertWrapper.id = alertId; + alertWrapper.role = "alert"; + alertWrapper.innerHTML = ` + +

${titleHtml}

+ ${bodyHtml}`; + + // Add it to the document and activate it + document.getElementById('printerAlertSpace')?.appendChild(alertWrapper); + new bootstrap.Alert(alertWrapper); + + alertWrapper.addEventListener('closed.bs.alert', closedCallback); + } + // The app's logic is wrapped in a class just for ease of reading. class BasicDocumentPrinterApp { constructor( - private manager: WebDevices.UsbDeviceManager, + private manager: PrinterManager, private btnContainer: HTMLElement, private labelForm: HTMLElement, private labelFormInstructions: HTMLElement, ) { // Add a second set of event listeners for printer connect and disconnect to redraw // the printer list when it changes. - this.manager.addEventListener('connectedDevice', () => { + this.manager.addEventListener('connectedDevice', ({ detail }) => { this.activePrinterIndex = -1; this.redrawPrinterButtons(); + + // Printers themselves also have events, let's show an alert on errors. + const printer = detail.device; + printer.addEventListener('reportedError', ({ detail: msg }) => { + // Use the same ID so there's only one error message per printer. + const alertId = `alert-printererror-${printer.printerSerial}`; + hideAlerts(alertId); + + // Error messages are also status messages, such as indicating no problem. + if (msg.errors.size === 0 || msg.errors.has(WebReceipt.ErrorState.NoError)) { return; } + + showAlert( + // Show a warning for this printer + 'warning', + alertId, + `Printer ${printer.printerSerial} has an error`, + // There can be multiple errors, just show their raw values. A better + // application would use these for good messages! + `

    ${Array.from(msg.errors).map(e => `
  • ${e}`)}

+
+

Fix the issue, then dismiss this alert to check the status again.

`, + // And when the alert is dismissed, check the status again! + () => printer.sendDocument({ + commands: [new WebReceipt.GetStatus()] + }) + ); + }); }); this.manager.addEventListener('disconnectedDevice', () => { this.activePrinterIndex = -1; @@ -161,13 +232,13 @@ }); } - get printers(): readonly WebReceipt.ReceiptPrinter[] { + get printers(): readonly WebReceipt.ReceiptPrinterUsb[] { return this.manager.devices; } // Track which printer is currently selected for operations private _activePrinter = 0; - get activePrinter(): WebReceipt.ReceiptPrinter | undefined { + get activePrinter(): WebReceipt.ReceiptPrinterUsb | undefined { return this._activePrinter < 0 || this._activePrinter > this.printers.length ? undefined : this.printers[this._activePrinter]; @@ -192,17 +263,17 @@ /** Highlight only the currently selected printer. */ private redrawPrinterButtonHighlights() { this.printers.forEach((printer, idx) => { - const highlight = this._activePrinter == idx ? "var(--bs-blue)" : "transparent"; + const highlight = this._activePrinter === idx ? "var(--bs-blue)" : "transparent"; const element = document.getElementById(`printer_${idx}`)!; element.style.background = `linear-gradient(to right, ${highlight}, ${highlight}, grey, grey)`; }); } /** Add a printer's button UI to the list of printers. */ - private drawPrinterButton(printer: WebReceipt.ReceiptPrinter, idx: number) { - const highlight = this._activePrinter == idx ? "var(--bs-blue)" : "transparent"; + private drawPrinterButton(printer: WebReceipt.ReceiptPrinterUsb, idx: number) { + const highlight = this._activePrinter === idx ? "var(--bs-blue)" : "transparent"; - // Generate a new label printer button for the given printer. + // Generate a new receipt printer button for the given printer. const element = document.createElement("div"); element.innerHTML = `
  • Settings @@ -255,7 +326,7 @@ .addEventListener('click', async (e) => { e.preventDefault(); const printerIdx = (e.currentTarget as HTMLAnchorElement).dataset.printerIdx as unknown as number; - if (this._activePrinter == printerIdx) { + if (this._activePrinter === printerIdx) { // Don't refresh anything if we already have this printer selected.. return; } @@ -298,7 +369,7 @@ /** Redraw the text canvas size according to the printer. */ private redrawTextCanvas() { const printer = this.activePrinter; - if (printer == null) { + if (printer === undefined) { this.labelForm.classList.add('d-none'); this.labelFormInstructions.classList.remove('d-none'); return; @@ -331,43 +402,32 @@ window.printer_app = app; // Now we'll fire the reconnect since our UI is wired up. - await printerMgr.forceReconnect(); + try { + await printerMgr.forceReconnect(); + } catch (e) { + if (e instanceof WebDevices.DriverAccessDeniedError) { + // This happens when the operating system didn't let Chrome connect. + // Usually either another tab is open talking to the device, or the driver + // is already loaded by another application. + showAlert( + 'danger', + 'alert-printer-comm-error', + `Operating system refused device access`, + `

    This usually happens for one of these reasons: +

      +
    • Another browser tab is already connected. +
    • Another application loaded a driver to talk to the device. +
    • You're on Windows and need to replace the driver. +
    + Fix the issue and re-connect to the device.

    ` + ); + } + } // We're done here. Bring in the dancing lobsters. + - -
    -
    - - - - - -
    -
    -
    - +
    @@ -385,26 +445,65 @@

    Loading....

    -
    -
    Input
    - -
    -
    +
    +
    + + + + + +
    +
    +

    Connect a printer to your computer, then click Add Printer!

    Once a printer is connected select it by clicking on it on the left.

    +
    +
    +
    Input
    + +
    +
    - +
    + diff --git a/demo/test_index.ts b/demo/test_index.ts index c47b31f..3450534 100644 --- a/demo/test_index.ts +++ b/demo/test_index.ts @@ -1,18 +1,32 @@ import * as WebReceipt from '../src/index.js'; import * as WebDevices from 'web-device-mux'; +import bootstrap from 'bootstrap'; // This file exists to test the index.html's typescript. Unfortunately there isn't // a good way to configure Visual Studio Code to, well, treat it as typescript. //////////////////////////////////////////////////////////////////////////////// -// First import the lib! -//import * as WebReceipt from 'web-receiptline-printer'; +// // First import the lib! +// // This is our lib in this repo here +// import * as WebReceipt from 'web-receiptline-printer'; - // For this demo we're going to make use of the USB printer manager - // so it can take care of concerns like the USB connect and disconnect events. - // It's a handy-dandy feature included from the web-device-mux library! - // We need to tell it what type of device it's managing, and how to filter - // USB devices that are receipt printers. - const printerMgr = new WebDevices.UsbDeviceManager( +// // This is a utility lib we'll be using that makes it easier to use devices. +// import * as WebDevices from 'web-device-mux'; + +// // We'll drop these into the Window object so we can play with them in +// // the DevTools console if we want to. +// window.WebReceipt = WebReceipt; +// window.WebDevices = WebDevices; + +// For this demo we're going to make use of the USB printer manager +// so it can take care of concerns like the USB connect and disconnect events. + +// It's a handy-dandy feature included from the web-device-mux library! +// We need to tell it what type of device it's managing, and how to filter +// USB devices that are receipt printers. +// We'll set a type alias so it's easier to read our code +type PrinterManager = WebDevices.UsbDeviceManager; +// Then we'll construct one to use +const printerMgr: PrinterManager = new WebDevices.UsbDeviceManager( window.navigator.usb, WebReceipt.ReceiptPrinter.fromUSBDevice, { @@ -41,7 +55,7 @@ addPrinterBtn.addEventListener('click', async () => printerMgr.promptForNewDevic const refreshPrinterBtn = document.getElementById('refreshPrinters')!; refreshPrinterBtn.addEventListener('click', async () => printerMgr.forceReconnect()); -// Next we wire up some events on the device manager itself. +// Next we wire up some events on the UsbDeviceManager itself. printerMgr.addEventListener('connectedDevice', ({ detail }) => { const printer = detail.device; const config = printer.printerOptions; @@ -52,8 +66,7 @@ printerMgr.addEventListener('connectedDevice', ({ detail }) => { // There's also an event that will tell you when a printer disconnects. printerMgr.addEventListener('disconnectedDevice', ({ detail }) => { const printer = detail.device; - // TODO: Hide appropriate interface. - console.log(`Lost printer ${printer.printerModel} (${printer.printerOptions.serialNumber}).`); + console.log('Lost printer', printer.printerModel, 'serial', printer.printerOptions.serialNumber); }); // The browser will remember printers that were previously connected, and @@ -66,19 +79,81 @@ printerMgr.addEventListener('disconnectedDevice', ({ detail }) => { // And that's all there is to setup! The page can now talk to printers. // The rest of this demo is an example of a basic receipt printer app. +// Get some bookkeeping out of the way.. + +// A function to find and hide any alerts for a given alert ID. +function hideAlerts(alertId: string) { + const existingAlerts = document.getElementById('printerAlertSpace')?.querySelectorAll(`.${alertId}`) ?? []; + existingAlerts.forEach((a: Element) => { a.remove(); }); +} + +// A function to make it easier to show alerts +function showAlert( + level: 'warning' | 'danger', + alertId: string, + titleHtml: string, + bodyHtml: string, + closedCallback = () => {} +) { + hideAlerts(alertId); + + // Create the bootstrap alert div with the provided content + const alertWrapper = document.createElement('div'); + alertWrapper.classList.add("alert", `alert-${level}`, "alert-dismissible", "fade", "show", alertId); + alertWrapper.id = alertId; + alertWrapper.role = "alert"; + alertWrapper.innerHTML = ` + +

    ${titleHtml}

    + ${bodyHtml}`; + + // Add it to the document and activate it + document.getElementById('printerAlertSpace')?.appendChild(alertWrapper); + new bootstrap.Alert(alertWrapper); + + alertWrapper.addEventListener('closed.bs.alert', closedCallback); +} + // The app's logic is wrapped in a class just for ease of reading. class BasicDocumentPrinterApp { constructor( - private manager: WebDevices.UsbDeviceManager, + private manager: PrinterManager, private btnContainer: HTMLElement, private labelForm: HTMLElement, private labelFormInstructions: HTMLElement, ) { // Add a second set of event listeners for printer connect and disconnect to redraw // the printer list when it changes. - this.manager.addEventListener('connectedDevice', () => { + this.manager.addEventListener('connectedDevice', ({ detail }) => { this.activePrinterIndex = -1; this.redrawPrinterButtons(); + + // Printers themselves also have events, let's show an alert on errors. + const printer = detail.device; + printer.addEventListener('reportedError', ({ detail: msg }) => { + // Use the same ID so there's only one error message per printer. + const alertId = `alert-printererror-${printer.printerSerial}`; + hideAlerts(alertId); + + // Error messages are also status messages, such as indicating no problem. + if (msg.errors.size === 0 || msg.errors.has(WebReceipt.ErrorState.NoError)) { return; } + + showAlert( + // Show a warning for this printer + 'warning', + alertId, + `Printer ${printer.printerSerial} has an error`, + // There can be multiple errors, just show their raw values. A better + // application would use these for good messages! + `

      ${Array.from(msg.errors).map(e => `
    • ${e}`)}

    +
    +

    Fix the issue, then dismiss this alert to check the status again.

    `, + // And when the alert is dismissed, check the status again! + () => printer.sendDocument({ + commands: [new WebReceipt.GetStatus()] + }) + ); + }); }); this.manager.addEventListener('disconnectedDevice', () => { this.activePrinterIndex = -1; @@ -86,13 +161,13 @@ class BasicDocumentPrinterApp { }); } - get printers(): readonly WebReceipt.ReceiptPrinter[] { + get printers(): readonly WebReceipt.ReceiptPrinterUsb[] { return this.manager.devices; } // Track which printer is currently selected for operations private _activePrinter = 0; - get activePrinter(): WebReceipt.ReceiptPrinter | undefined { + get activePrinter(): WebReceipt.ReceiptPrinterUsb | undefined { return this._activePrinter < 0 || this._activePrinter > this.printers.length ? undefined : this.printers[this._activePrinter]; @@ -117,17 +192,17 @@ class BasicDocumentPrinterApp { /** Highlight only the currently selected printer. */ private redrawPrinterButtonHighlights() { this.printers.forEach((printer, idx) => { - const highlight = this._activePrinter == idx ? "var(--bs-blue)" : "transparent"; + const highlight = this._activePrinter === idx ? "var(--bs-blue)" : "transparent"; const element = document.getElementById(`printer_${idx}`)!; element.style.background = `linear-gradient(to right, ${highlight}, ${highlight}, grey, grey)`; }); } /** Add a printer's button UI to the list of printers. */ - private drawPrinterButton(printer: WebReceipt.ReceiptPrinter, idx: number) { - const highlight = this._activePrinter == idx ? "var(--bs-blue)" : "transparent"; + private drawPrinterButton(printer: WebReceipt.ReceiptPrinterUsb, idx: number) { + const highlight = this._activePrinter === idx ? "var(--bs-blue)" : "transparent"; - // Generate a new label printer button for the given printer. + // Generate a new receipt printer button for the given printer. const element = document.createElement("div"); element.innerHTML = `
  • Settings @@ -180,7 +255,7 @@ class BasicDocumentPrinterApp { .addEventListener('click', async (e) => { e.preventDefault(); const printerIdx = (e.currentTarget as HTMLAnchorElement).dataset.printerIdx as unknown as number; - if (this._activePrinter == printerIdx) { + if (this._activePrinter === printerIdx) { // Don't refresh anything if we already have this printer selected.. return; } @@ -223,7 +298,7 @@ class BasicDocumentPrinterApp { /** Redraw the text canvas size according to the printer. */ private redrawTextCanvas() { const printer = this.activePrinter; - if (printer == null) { + if (printer === undefined) { this.labelForm.classList.add('d-none'); this.labelFormInstructions.classList.remove('d-none'); return; @@ -256,6 +331,26 @@ declare global { window.printer_app = app; // Now we'll fire the reconnect since our UI is wired up. -await printerMgr.forceReconnect(); +try { + await printerMgr.forceReconnect(); +} catch (e) { + if (e instanceof WebDevices.DriverAccessDeniedError) { + // This happens when the operating system didn't let Chrome connect. + // Usually either another tab is open talking to the device, or the driver + // is already loaded by another application. + showAlert( + 'danger', + 'alert-printer-comm-error', + `Operating system refused device access`, + `

    This usually happens for one of these reasons: +

      +
    • Another browser tab is already connected. +
    • Another application loaded a driver to talk to the device. +
    • You're on Windows and need to replace the driver. +
    + Fix the issue and re-connect to the device.

    ` + ); + } +} // We're done here. Bring in the dancing lobsters.