From e553e15ffacce358578648cc0f8f52c980d29ba2 Mon Sep 17 00:00:00 2001 From: Jakub Andrysek Date: Mon, 30 Jun 2025 00:59:03 +0200 Subject: [PATCH 1/6] feat: add BLE device listing command --- README.md | 9 +- package-lock.json | 1837 ++++++++++++++++++++++++++++++++++-- package.json | 1 + src/commands/index.ts | 3 + src/commands/list-ble.ts | 53 ++ src/commands/tsconfig.json | 1 + 6 files changed, 1830 insertions(+), 74 deletions(-) create mode 100644 src/commands/list-ble.ts diff --git a/README.md b/README.md index 55e738e..2e66250 100644 --- a/README.md +++ b/README.md @@ -46,7 +46,7 @@ Verify that the runtime is installed correctly by running: ### Connecting to the device -All commands interacting with the device require specifying the device connection using either `--port` or `--socket` option. +All commands interacting with the device require specifying the device connection using USB `--port`, `--socket` or `--ble` options. To connect to the device using serial port, use: @@ -56,10 +56,17 @@ To connect to the device using TCP socket, use: jac --socket : +To connect to the device using Bluetooth Low Energy, use: + + jac --ble + To list available serial ports, use: jac list-ports +To list available Bluetooth devices, use: + jac list-ble + To tunnel serial port over TCP, use: jac serial-socket --port --socket diff --git a/package-lock.json b/package-lock.json index 419a13f..de6fbfe 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,7 @@ "license": "GPL-3.0-only", "dependencies": { "@cubicap/esptool-js": "^0.3.2", + "@stoprocent/noble": "^2.3.2", "chalk": "^5.4.1", "cli-progress": "^3.12.0", "crc": "^4.3.2", @@ -1510,6 +1511,366 @@ "url": "https://opencollective.com/serialport/donate" } }, + "node_modules/@stoprocent/bluetooth-hci-socket": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/@stoprocent/bluetooth-hci-socket/-/bluetooth-hci-socket-2.2.3.tgz", + "integrity": "sha512-aQmCkEaybqH7B1K6k2oJoZTf03g3PzmZFfi9+NHDiNVPi2Mbet4Mf86gVNEnctaG5aS9743zEMxSunWOzpt3MQ==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "linux", + "android", + "freebsd", + "win32", + "darwin" + ], + "dependencies": { + "async": "^3.2.6", + "debug": "^4.3.7", + "node-addon-api": "^8.3.1", + "node-gyp-build": "^4.8.4", + "patch-package": "^8.0.0", + "serialport": "^12.0.0" + }, + "optionalDependencies": { + "usb": "^2.14.0" + } + }, + "node_modules/@stoprocent/bluetooth-hci-socket/node_modules/@serialport/bindings-cpp": { + "version": "12.0.1", + "resolved": "https://registry.npmjs.org/@serialport/bindings-cpp/-/bindings-cpp-12.0.1.tgz", + "integrity": "sha512-r2XOwY2dDvbW7dKqSPIk2gzsr6M6Qpe9+/Ngs94fNaNlcTRCV02PfaoDmRgcubpNVVcLATlxSxPTIDw12dbKOg==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@serialport/bindings-interface": "1.2.2", + "@serialport/parser-readline": "11.0.0", + "debug": "4.3.4", + "node-addon-api": "7.0.0", + "node-gyp-build": "4.6.0" + }, + "engines": { + "node": ">=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/serialport/donate" + } + }, + "node_modules/@stoprocent/bluetooth-hci-socket/node_modules/@serialport/bindings-cpp/node_modules/@serialport/parser-delimiter": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/@serialport/parser-delimiter/-/parser-delimiter-11.0.0.tgz", + "integrity": "sha512-aZLJhlRTjSmEwllLG7S4J8s8ctRAS0cbvCpO87smLvl3e4BgzbVgF6Z6zaJd3Aji2uSiYgfedCdNc4L6W+1E2g==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://opencollective.com/serialport/donate" + } + }, + "node_modules/@stoprocent/bluetooth-hci-socket/node_modules/@serialport/bindings-cpp/node_modules/@serialport/parser-readline": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/@serialport/parser-readline/-/parser-readline-11.0.0.tgz", + "integrity": "sha512-rRAivhRkT3YO28WjmmG4FQX6L+KMb5/ikhyylRfzWPw0nSXy97+u07peS9CbHqaNvJkMhH1locp2H36aGMOEIA==", + "license": "MIT", + "optional": true, + "dependencies": { + "@serialport/parser-delimiter": "11.0.0" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://opencollective.com/serialport/donate" + } + }, + "node_modules/@stoprocent/bluetooth-hci-socket/node_modules/@serialport/bindings-cpp/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@stoprocent/bluetooth-hci-socket/node_modules/@serialport/bindings-cpp/node_modules/node-addon-api": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.0.0.tgz", + "integrity": "sha512-vgbBJTS4m5/KkE16t5Ly0WW9hz46swAstv0hYYwMtbG7AznRhNyfLRe8HZAiWIpcHzoO7HxhLuBQj9rJ/Ho0ZA==", + "license": "MIT", + "optional": true + }, + "node_modules/@stoprocent/bluetooth-hci-socket/node_modules/@serialport/bindings-cpp/node_modules/node-gyp-build": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.6.0.tgz", + "integrity": "sha512-NTZVKn9IylLwUzaKjkas1e4u2DLNcV4rdYagA4PWdPwW87Bi7z+BznyKSRwS/761tV/lzCGXplWsiaMjLqP2zQ==", + "license": "MIT", + "optional": true, + "bin": { + "node-gyp-build": "bin.js", + "node-gyp-build-optional": "optional.js", + "node-gyp-build-test": "build-test.js" + } + }, + "node_modules/@stoprocent/bluetooth-hci-socket/node_modules/@serialport/parser-byte-length": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/@serialport/parser-byte-length/-/parser-byte-length-12.0.0.tgz", + "integrity": "sha512-0ei0txFAj+s6FTiCJFBJ1T2hpKkX8Md0Pu6dqMrYoirjPskDLJRgZGLqoy3/lnU1bkvHpnJO+9oJ3PB9v8rNlg==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://opencollective.com/serialport/donate" + } + }, + "node_modules/@stoprocent/bluetooth-hci-socket/node_modules/@serialport/parser-cctalk": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/@serialport/parser-cctalk/-/parser-cctalk-12.0.0.tgz", + "integrity": "sha512-0PfLzO9t2X5ufKuBO34DQKLXrCCqS9xz2D0pfuaLNeTkyGUBv426zxoMf3rsMRodDOZNbFblu3Ae84MOQXjnZw==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://opencollective.com/serialport/donate" + } + }, + "node_modules/@stoprocent/bluetooth-hci-socket/node_modules/@serialport/parser-delimiter": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/@serialport/parser-delimiter/-/parser-delimiter-12.0.0.tgz", + "integrity": "sha512-gu26tVt5lQoybhorLTPsH2j2LnX3AOP2x/34+DUSTNaUTzu2fBXw+isVjQJpUBFWu6aeQRZw5bJol5X9Gxjblw==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://opencollective.com/serialport/donate" + } + }, + "node_modules/@stoprocent/bluetooth-hci-socket/node_modules/@serialport/parser-inter-byte-timeout": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/@serialport/parser-inter-byte-timeout/-/parser-inter-byte-timeout-12.0.0.tgz", + "integrity": "sha512-GnCh8K0NAESfhCuXAt+FfBRz1Cf9CzIgXfp7SdMgXwrtuUnCC/yuRTUFWRvuzhYKoAo1TL0hhUo77SFHUH1T/w==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://opencollective.com/serialport/donate" + } + }, + "node_modules/@stoprocent/bluetooth-hci-socket/node_modules/@serialport/parser-packet-length": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/@serialport/parser-packet-length/-/parser-packet-length-12.0.0.tgz", + "integrity": "sha512-p1hiCRqvGHHLCN/8ZiPUY/G0zrxd7gtZs251n+cfNTn+87rwcdUeu9Dps3Aadx30/sOGGFL6brIRGK4l/t7MuQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/@stoprocent/bluetooth-hci-socket/node_modules/@serialport/parser-readline": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/@serialport/parser-readline/-/parser-readline-12.0.0.tgz", + "integrity": "sha512-O7cywCWC8PiOMvo/gglEBfAkLjp/SENEML46BXDykfKP5mTPM46XMaX1L0waWU6DXJpBgjaL7+yX6VriVPbN4w==", + "license": "MIT", + "optional": true, + "dependencies": { + "@serialport/parser-delimiter": "12.0.0" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://opencollective.com/serialport/donate" + } + }, + "node_modules/@stoprocent/bluetooth-hci-socket/node_modules/@serialport/parser-ready": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/@serialport/parser-ready/-/parser-ready-12.0.0.tgz", + "integrity": "sha512-ygDwj3O4SDpZlbrRUraoXIoIqb8sM7aMKryGjYTIF0JRnKeB1ys8+wIp0RFMdFbO62YriUDextHB5Um5cKFSWg==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://opencollective.com/serialport/donate" + } + }, + "node_modules/@stoprocent/bluetooth-hci-socket/node_modules/@serialport/parser-regex": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/@serialport/parser-regex/-/parser-regex-12.0.0.tgz", + "integrity": "sha512-dCAVh4P/pZrLcPv9NJ2mvPRBg64L5jXuiRxIlyxxdZGH4WubwXVXY/kBTihQmiAMPxbT3yshSX8f2+feqWsxqA==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://opencollective.com/serialport/donate" + } + }, + "node_modules/@stoprocent/bluetooth-hci-socket/node_modules/@serialport/parser-slip-encoder": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/@serialport/parser-slip-encoder/-/parser-slip-encoder-12.0.0.tgz", + "integrity": "sha512-0APxDGR9YvJXTRfY+uRGhzOhTpU5akSH183RUcwzN7QXh8/1jwFsFLCu0grmAUfi+fItCkR+Xr1TcNJLR13VNA==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://opencollective.com/serialport/donate" + } + }, + "node_modules/@stoprocent/bluetooth-hci-socket/node_modules/@serialport/parser-spacepacket": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/@serialport/parser-spacepacket/-/parser-spacepacket-12.0.0.tgz", + "integrity": "sha512-dozONxhPC/78pntuxpz/NOtVps8qIc/UZzdc/LuPvVsqCoJXiRxOg6ZtCP/W58iibJDKPZPAWPGYeZt9DJxI+Q==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://opencollective.com/serialport/donate" + } + }, + "node_modules/@stoprocent/bluetooth-hci-socket/node_modules/@serialport/stream": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/@serialport/stream/-/stream-12.0.0.tgz", + "integrity": "sha512-9On64rhzuqKdOQyiYLYv2lQOh3TZU/D3+IWCR5gk0alPel2nwpp4YwDEGiUBfrQZEdQ6xww0PWkzqth4wqwX3Q==", + "license": "MIT", + "optional": true, + "dependencies": { + "@serialport/bindings-interface": "1.2.2", + "debug": "4.3.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://opencollective.com/serialport/donate" + } + }, + "node_modules/@stoprocent/bluetooth-hci-socket/node_modules/@serialport/stream/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@stoprocent/bluetooth-hci-socket/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==", + "license": "MIT", + "optional": true + }, + "node_modules/@stoprocent/bluetooth-hci-socket/node_modules/node-addon-api": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-8.4.0.tgz", + "integrity": "sha512-D9DI/gXHvVmjHS08SVch0Em8G5S1P+QWtU31appcKT/8wFSPRcdHadIFSAntdMMVM5zz+/DL+bL/gz3UDppqtg==", + "license": "MIT", + "optional": true, + "engines": { + "node": "^18 || ^20 || >= 21" + } + }, + "node_modules/@stoprocent/bluetooth-hci-socket/node_modules/serialport": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/serialport/-/serialport-12.0.0.tgz", + "integrity": "sha512-AmH3D9hHPFmnF/oq/rvigfiAouAKyK/TjnrkwZRYSFZxNggJxwvbAbfYrLeuvq7ktUdhuHdVdSjj852Z55R+uA==", + "license": "MIT", + "optional": true, + "dependencies": { + "@serialport/binding-mock": "10.2.2", + "@serialport/bindings-cpp": "12.0.1", + "@serialport/parser-byte-length": "12.0.0", + "@serialport/parser-cctalk": "12.0.0", + "@serialport/parser-delimiter": "12.0.0", + "@serialport/parser-inter-byte-timeout": "12.0.0", + "@serialport/parser-packet-length": "12.0.0", + "@serialport/parser-readline": "12.0.0", + "@serialport/parser-ready": "12.0.0", + "@serialport/parser-regex": "12.0.0", + "@serialport/parser-slip-encoder": "12.0.0", + "@serialport/parser-spacepacket": "12.0.0", + "@serialport/stream": "12.0.0", + "debug": "4.3.4" + }, + "engines": { + "node": ">=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/serialport/donate" + } + }, + "node_modules/@stoprocent/bluetooth-hci-socket/node_modules/serialport/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@stoprocent/noble": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/@stoprocent/noble/-/noble-2.3.2.tgz", + "integrity": "sha512-q8uBqXXIfq1C8trfk5EgcOzl2W+7wS8X4flPz2iGiTB2M0cLZAIMGDXeX0Rva/OYrXUmFifFjCuW/NlEv3r43g==", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "debug": "^4.3.7", + "node-addon-api": "^8.1.0", + "node-gyp-build": "^4.8.1" + }, + "engines": { + "node": ">=14" + }, + "optionalDependencies": { + "@stoprocent/bluetooth-hci-socket": "^2.2.3" + } + }, "node_modules/@tsconfig/node16": { "version": "16.1.4", "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-16.1.4.tgz", @@ -1584,6 +1945,13 @@ "integrity": "sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==", "license": "MIT" }, + "node_modules/@types/w3c-web-usb": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/@types/w3c-web-usb/-/w3c-web-usb-1.0.10.tgz", + "integrity": "sha512-CHgUI5kTc/QLMP8hODUHhge0D4vx+9UiAwIGiT0sTy/B2XpdX1U5rJt6JSISgr6ikRT7vxV9EVAFeYZqUnl1gQ==", + "license": "MIT", + "optional": true + }, "node_modules/@typescript-eslint/eslint-plugin": { "version": "8.35.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.35.0.tgz", @@ -2056,6 +2424,13 @@ "dev": true, "license": "MIT" }, + "node_modules/@yarnpkg/lockfile": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz", + "integrity": "sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ==", + "license": "BSD-2-Clause", + "optional": true + }, "node_modules/acorn": { "version": "8.15.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", @@ -2108,7 +2483,7 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, + "devOptional": true, "dependencies": { "color-convert": "^2.0.1" }, @@ -2123,7 +2498,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, + "devOptional": true, "dependencies": { "color-name": "~1.1.4" }, @@ -2135,7 +2510,7 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true + "devOptional": true }, "node_modules/argparse": { "version": "2.0.1", @@ -2177,9 +2552,20 @@ } }, "node_modules/async": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.4.tgz", - "integrity": "sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==" + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", + "license": "MIT" + }, + "node_modules/at-least-node": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", + "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==", + "license": "ISC", + "optional": true, + "engines": { + "node": ">= 4.0.0" + } }, "node_modules/b4a": { "version": "1.6.4", @@ -2190,7 +2576,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/basic-ftp": { @@ -2215,7 +2601,7 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "fill-range": "^7.1.1" @@ -2234,6 +2620,56 @@ "node": ">=8" } }, + "node_modules/call-bind": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", + "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", + "license": "MIT", + "optional": true, + "dependencies": { + "call-bind-apply-helpers": "^1.0.0", + "es-define-property": "^1.0.0", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "optional": true, + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -2300,6 +2736,22 @@ "node": "*" } }, + "node_modules/ci-info": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", + "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", + "optional": true, + "engines": { + "node": ">=8" + } + }, "node_modules/cli-progress": { "version": "3.12.0", "resolved": "https://registry.npmjs.org/cli-progress/-/cli-progress-3.12.0.tgz", @@ -2355,7 +2807,7 @@ "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/crc": { @@ -2378,7 +2830,7 @@ "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "path-key": "^3.1.0", @@ -2443,6 +2895,39 @@ "dev": true, "license": "MIT" }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "license": "MIT", + "optional": true, + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "optional": true, + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/eastasianwidth": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", @@ -2460,6 +2945,26 @@ "resolved": "https://registry.npmjs.org/enabled/-/enabled-2.0.0.tgz", "integrity": "sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==" }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/es-module-lexer": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", @@ -2467,6 +2972,19 @@ "dev": true, "license": "MIT" }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "optional": true, + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/esbuild": { "version": "0.25.5", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.5.tgz", @@ -2861,7 +3379,7 @@ "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "to-regex-range": "^5.0.1" @@ -2886,6 +3404,16 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/find-yarn-workspace-root": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/find-yarn-workspace-root/-/find-yarn-workspace-root-2.0.0.tgz", + "integrity": "sha512-1IMnbjt4KzsQfnhnzNd8wUEgXZ44IzZaZmnLYx7D5FZlaHt2gW20Cri8Q+E/t5tIj4+epTBub+2Zxu/vNILzqQ==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "micromatch": "^4.0.2" + } + }, "node_modules/flat-cache": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", @@ -2929,6 +3457,29 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/fs-extra": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "license": "ISC", + "optional": true + }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", @@ -2944,6 +3495,16 @@ "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "optional": true, + "funding": { + "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", @@ -2954,6 +3515,45 @@ "node": "*" } }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "optional": true, + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/get-uri": { "version": "6.0.4", "resolved": "https://registry.npmjs.org/get-uri/-/get-uri-6.0.4.tgz", @@ -3015,6 +3615,26 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "license": "ISC", + "optional": true + }, "node_modules/graphemer": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", @@ -3026,11 +3646,50 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, + "devOptional": true, "engines": { "node": ">=8" } }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "license": "MIT", + "optional": true, + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/html-escaper": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", @@ -3075,6 +3734,18 @@ "node": ">=0.8.19" } }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "license": "ISC", + "optional": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", @@ -3086,6 +3757,22 @@ "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==" }, + "node_modules/is-docker": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", + "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", + "license": "MIT", + "optional": true, + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -3119,7 +3806,7 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=0.12.0" @@ -3136,11 +3823,31 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "license": "MIT", + "optional": true, + "dependencies": { + "is-docker": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "license": "MIT", + "optional": true + }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true, + "devOptional": true, "license": "ISC" }, "node_modules/istanbul-lib-coverage": { @@ -3257,6 +3964,26 @@ "dev": true, "license": "MIT" }, + "node_modules/json-stable-stringify": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.3.0.tgz", + "integrity": "sha512-qtYiSSFlwot9XHtF9bD9c7rwKjr+RecWT//ZnPvSmEjpV5mmPOCN4j8UjY5hbjNkOwZ/jQv3J6R1/pL7RwgMsg==", + "license": "MIT", + "optional": true, + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "isarray": "^2.0.5", + "jsonify": "^0.0.1", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/json-stable-stringify-without-jsonify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", @@ -3264,6 +3991,29 @@ "dev": true, "license": "MIT" }, + "node_modules/jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/jsonify": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.1.tgz", + "integrity": "sha512-2/Ki0GcmuqSrgFyelQq9M05y7PS0mEwuIzrf3f1fPqkVDVRvZrPZtVSMHxdgo8Aq0sxAOb/cr2aqqA3LeWHVPg==", + "license": "Public Domain", + "optional": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/keyv": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", @@ -3271,7 +4021,17 @@ "dev": true, "license": "MIT", "dependencies": { - "json-buffer": "3.0.1" + "json-buffer": "3.0.1" + } + }, + "node_modules/klaw-sync": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/klaw-sync/-/klaw-sync-6.0.0.tgz", + "integrity": "sha512-nIeuVSzdCCs6TDPTqI8w1Yre34sSq7AkZ4B3sfOBbI2CgVSB4Du4aLQijFU2+lhAFCwt9+42Hel6lQNIv6AntQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "graceful-fs": "^4.1.11" } }, "node_modules/kuler": { @@ -3393,6 +4153,16 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", @@ -3407,7 +4177,7 @@ "version": "4.0.8", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "braces": "^3.0.3", @@ -3433,6 +4203,16 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "license": "MIT", + "optional": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/minipass": { "version": "7.1.2", "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", @@ -3522,6 +4302,26 @@ "npm": ">=8.12.1" } }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", + "optional": true, + "dependencies": { + "wrappy": "1" + } + }, "node_modules/one-time": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/one-time/-/one-time-1.0.0.tgz", @@ -3530,6 +4330,23 @@ "fn.name": "1.x.x" } }, + "node_modules/open": { + "version": "7.4.2", + "resolved": "https://registry.npmjs.org/open/-/open-7.4.2.tgz", + "integrity": "sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q==", + "license": "MIT", + "optional": true, + "dependencies": { + "is-docker": "^2.0.0", + "is-wsl": "^2.1.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/optionator": { "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", @@ -3548,6 +4365,16 @@ "node": ">= 0.8.0" } }, + "node_modules/os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/p-limit": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", @@ -3603,6 +4430,54 @@ "node": ">=6" } }, + "node_modules/patch-package": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/patch-package/-/patch-package-8.0.0.tgz", + "integrity": "sha512-da8BVIhzjtgScwDJ2TtKsfT5JFWz1hYoBl9rUQ1f38MC2HwnEIkK8VN3dKMKcP7P7bvvgzNDbfNHtx3MsQb5vA==", + "license": "MIT", + "optional": true, + "dependencies": { + "@yarnpkg/lockfile": "^1.1.0", + "chalk": "^4.1.2", + "ci-info": "^3.7.0", + "cross-spawn": "^7.0.3", + "find-yarn-workspace-root": "^2.0.0", + "fs-extra": "^9.0.0", + "json-stable-stringify": "^1.0.2", + "klaw-sync": "^6.0.0", + "minimist": "^1.2.6", + "open": "^7.4.2", + "rimraf": "^2.6.3", + "semver": "^7.5.3", + "slash": "^2.0.0", + "tmp": "^0.0.33", + "yaml": "^2.2.2" + }, + "bin": { + "patch-package": "index.js" + }, + "engines": { + "node": ">=14", + "npm": ">5" + } + }, + "node_modules/patch-package/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "license": "MIT", + "optional": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -3612,11 +4487,21 @@ "node": ">=8" } }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=8" @@ -3667,7 +4552,7 @@ "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=8.6" @@ -3795,6 +4680,66 @@ "node": ">=0.10.0" } }, + "node_modules/rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "license": "ISC", + "optional": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + } + }, + "node_modules/rimraf/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "license": "MIT", + "optional": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/rimraf/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "license": "ISC", + "optional": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rimraf/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "license": "ISC", + "optional": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/rollup": { "version": "4.44.1", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.44.1.tgz", @@ -3891,7 +4836,7 @@ "version": "7.7.2", "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", - "dev": true, + "devOptional": true, "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -3928,11 +4873,29 @@ "url": "https://opencollective.com/serialport/donate" } }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "license": "MIT", + "optional": true, + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "shebang-regex": "^3.0.0" @@ -3945,7 +4908,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=8" @@ -3979,6 +4942,16 @@ "is-arrayish": "^0.3.1" } }, + "node_modules/slash": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", + "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=6" + } + }, "node_modules/source-map-js": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", @@ -4112,7 +5085,7 @@ "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "has-flag": "^4.0.0" @@ -4240,11 +5213,24 @@ "node": ">=14.0.0" } }, + "node_modules/tmp": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", + "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "license": "MIT", + "optional": true, + "dependencies": { + "os-tmpdir": "~1.0.2" + }, + "engines": { + "node": ">=0.6.0" + } + }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "is-number": "^7.0.0" @@ -4346,6 +5332,16 @@ "dev": true, "license": "MIT" }, + "node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 10.0.0" + } + }, "node_modules/uri-js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", @@ -4356,6 +5352,22 @@ "punycode": "^2.1.0" } }, + "node_modules/usb": { + "version": "2.15.0", + "resolved": "https://registry.npmjs.org/usb/-/usb-2.15.0.tgz", + "integrity": "sha512-BA9r7PFxyYp99wps1N70lIqdPb2Utcl2KkWohDtWUmhDBeM5hDH1Zl/L/CZvWxd5W3RUCNm1g+b+DEKZ6cHzqg==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@types/w3c-web-usb": "^1.0.6", + "node-addon-api": "^8.0.0", + "node-gyp-build": "^4.5.0" + }, + "engines": { + "node": ">=12.22.0 <13.0 || >=14.17.0" + } + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -4678,7 +5690,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, + "devOptional": true, "license": "ISC", "dependencies": { "isexe": "^2.0.0" @@ -4857,6 +5869,26 @@ "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC", + "optional": true + }, + "node_modules/yaml": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.0.tgz", + "integrity": "sha512-4lLa/EcQCB0cJkyts+FpIRx5G/llPxfP6VQU5KByHEhLxY3IJCH0f0Hy1MHI8sClTvsIb8qwRJ6R/ZdlDJ/leQ==", + "license": "ISC", + "optional": true, + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + } + }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", @@ -5646,33 +6678,241 @@ "@serialport/parser-delimiter": "13.0.0" } }, - "@serialport/parser-ready": { - "version": "13.0.0", - "resolved": "https://registry.npmjs.org/@serialport/parser-ready/-/parser-ready-13.0.0.tgz", - "integrity": "sha512-JNUQA+y2Rfs4bU+cGYNqOPnNMAcayhhW+XJZihSLQXOHcZsFnOa2F9YtMg9VXRWIcnHldHYtisp62Etjlw24bw==" - }, - "@serialport/parser-regex": { - "version": "13.0.0", - "resolved": "https://registry.npmjs.org/@serialport/parser-regex/-/parser-regex-13.0.0.tgz", - "integrity": "sha512-m7HpIf56G5XcuDdA3DB34Z0pJiwxNRakThEHjSa4mG05OnWYv0IG8l2oUyYfuGMowQWaVnQ+8r+brlPxGVH+eA==" - }, - "@serialport/parser-slip-encoder": { - "version": "13.0.0", - "resolved": "https://registry.npmjs.org/@serialport/parser-slip-encoder/-/parser-slip-encoder-13.0.0.tgz", - "integrity": "sha512-fUHZEExm6izJ7rg0A1yjXwu4sOzeBkPAjDZPfb+XQoqgtKAk+s+HfICiYn7N2QU9gyaeCO8VKgWwi+b/DowYOg==" - }, - "@serialport/parser-spacepacket": { - "version": "13.0.0", - "resolved": "https://registry.npmjs.org/@serialport/parser-spacepacket/-/parser-spacepacket-13.0.0.tgz", - "integrity": "sha512-DoXJ3mFYmyD8X/8931agJvrBPxqTaYDsPoly9/cwQSeh/q4EjQND9ySXBxpWz5WcpyCU4jOuusqCSAPsbB30Eg==" - }, - "@serialport/stream": { - "version": "13.0.0", - "resolved": "https://registry.npmjs.org/@serialport/stream/-/stream-13.0.0.tgz", - "integrity": "sha512-F7xLJKsjGo2WuEWMSEO1SimRcOA+WtWICsY13r0ahx8s2SecPQH06338g28OT7cW7uRXI7oEQAk62qh5gHJW3g==", + "@serialport/parser-ready": { + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/@serialport/parser-ready/-/parser-ready-13.0.0.tgz", + "integrity": "sha512-JNUQA+y2Rfs4bU+cGYNqOPnNMAcayhhW+XJZihSLQXOHcZsFnOa2F9YtMg9VXRWIcnHldHYtisp62Etjlw24bw==" + }, + "@serialport/parser-regex": { + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/@serialport/parser-regex/-/parser-regex-13.0.0.tgz", + "integrity": "sha512-m7HpIf56G5XcuDdA3DB34Z0pJiwxNRakThEHjSa4mG05OnWYv0IG8l2oUyYfuGMowQWaVnQ+8r+brlPxGVH+eA==" + }, + "@serialport/parser-slip-encoder": { + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/@serialport/parser-slip-encoder/-/parser-slip-encoder-13.0.0.tgz", + "integrity": "sha512-fUHZEExm6izJ7rg0A1yjXwu4sOzeBkPAjDZPfb+XQoqgtKAk+s+HfICiYn7N2QU9gyaeCO8VKgWwi+b/DowYOg==" + }, + "@serialport/parser-spacepacket": { + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/@serialport/parser-spacepacket/-/parser-spacepacket-13.0.0.tgz", + "integrity": "sha512-DoXJ3mFYmyD8X/8931agJvrBPxqTaYDsPoly9/cwQSeh/q4EjQND9ySXBxpWz5WcpyCU4jOuusqCSAPsbB30Eg==" + }, + "@serialport/stream": { + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/@serialport/stream/-/stream-13.0.0.tgz", + "integrity": "sha512-F7xLJKsjGo2WuEWMSEO1SimRcOA+WtWICsY13r0ahx8s2SecPQH06338g28OT7cW7uRXI7oEQAk62qh5gHJW3g==", + "requires": { + "@serialport/bindings-interface": "1.2.2", + "debug": "4.4.0" + } + }, + "@stoprocent/bluetooth-hci-socket": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/@stoprocent/bluetooth-hci-socket/-/bluetooth-hci-socket-2.2.3.tgz", + "integrity": "sha512-aQmCkEaybqH7B1K6k2oJoZTf03g3PzmZFfi9+NHDiNVPi2Mbet4Mf86gVNEnctaG5aS9743zEMxSunWOzpt3MQ==", + "optional": true, + "requires": { + "async": "^3.2.6", + "debug": "^4.3.7", + "node-addon-api": "^8.3.1", + "node-gyp-build": "^4.8.4", + "patch-package": "^8.0.0", + "serialport": "^12.0.0", + "usb": "^2.14.0" + }, + "dependencies": { + "@serialport/bindings-cpp": { + "version": "12.0.1", + "resolved": "https://registry.npmjs.org/@serialport/bindings-cpp/-/bindings-cpp-12.0.1.tgz", + "integrity": "sha512-r2XOwY2dDvbW7dKqSPIk2gzsr6M6Qpe9+/Ngs94fNaNlcTRCV02PfaoDmRgcubpNVVcLATlxSxPTIDw12dbKOg==", + "optional": true, + "requires": { + "@serialport/bindings-interface": "1.2.2", + "@serialport/parser-readline": "11.0.0", + "debug": "4.3.4", + "node-addon-api": "7.0.0", + "node-gyp-build": "4.6.0" + }, + "dependencies": { + "@serialport/parser-delimiter": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/@serialport/parser-delimiter/-/parser-delimiter-11.0.0.tgz", + "integrity": "sha512-aZLJhlRTjSmEwllLG7S4J8s8ctRAS0cbvCpO87smLvl3e4BgzbVgF6Z6zaJd3Aji2uSiYgfedCdNc4L6W+1E2g==", + "optional": true + }, + "@serialport/parser-readline": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/@serialport/parser-readline/-/parser-readline-11.0.0.tgz", + "integrity": "sha512-rRAivhRkT3YO28WjmmG4FQX6L+KMb5/ikhyylRfzWPw0nSXy97+u07peS9CbHqaNvJkMhH1locp2H36aGMOEIA==", + "optional": true, + "requires": { + "@serialport/parser-delimiter": "11.0.0" + } + }, + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "optional": true, + "requires": { + "ms": "2.1.2" + } + }, + "node-addon-api": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.0.0.tgz", + "integrity": "sha512-vgbBJTS4m5/KkE16t5Ly0WW9hz46swAstv0hYYwMtbG7AznRhNyfLRe8HZAiWIpcHzoO7HxhLuBQj9rJ/Ho0ZA==", + "optional": true + }, + "node-gyp-build": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.6.0.tgz", + "integrity": "sha512-NTZVKn9IylLwUzaKjkas1e4u2DLNcV4rdYagA4PWdPwW87Bi7z+BznyKSRwS/761tV/lzCGXplWsiaMjLqP2zQ==", + "optional": true + } + } + }, + "@serialport/parser-byte-length": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/@serialport/parser-byte-length/-/parser-byte-length-12.0.0.tgz", + "integrity": "sha512-0ei0txFAj+s6FTiCJFBJ1T2hpKkX8Md0Pu6dqMrYoirjPskDLJRgZGLqoy3/lnU1bkvHpnJO+9oJ3PB9v8rNlg==", + "optional": true + }, + "@serialport/parser-cctalk": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/@serialport/parser-cctalk/-/parser-cctalk-12.0.0.tgz", + "integrity": "sha512-0PfLzO9t2X5ufKuBO34DQKLXrCCqS9xz2D0pfuaLNeTkyGUBv426zxoMf3rsMRodDOZNbFblu3Ae84MOQXjnZw==", + "optional": true + }, + "@serialport/parser-delimiter": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/@serialport/parser-delimiter/-/parser-delimiter-12.0.0.tgz", + "integrity": "sha512-gu26tVt5lQoybhorLTPsH2j2LnX3AOP2x/34+DUSTNaUTzu2fBXw+isVjQJpUBFWu6aeQRZw5bJol5X9Gxjblw==", + "optional": true + }, + "@serialport/parser-inter-byte-timeout": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/@serialport/parser-inter-byte-timeout/-/parser-inter-byte-timeout-12.0.0.tgz", + "integrity": "sha512-GnCh8K0NAESfhCuXAt+FfBRz1Cf9CzIgXfp7SdMgXwrtuUnCC/yuRTUFWRvuzhYKoAo1TL0hhUo77SFHUH1T/w==", + "optional": true + }, + "@serialport/parser-packet-length": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/@serialport/parser-packet-length/-/parser-packet-length-12.0.0.tgz", + "integrity": "sha512-p1hiCRqvGHHLCN/8ZiPUY/G0zrxd7gtZs251n+cfNTn+87rwcdUeu9Dps3Aadx30/sOGGFL6brIRGK4l/t7MuQ==", + "optional": true + }, + "@serialport/parser-readline": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/@serialport/parser-readline/-/parser-readline-12.0.0.tgz", + "integrity": "sha512-O7cywCWC8PiOMvo/gglEBfAkLjp/SENEML46BXDykfKP5mTPM46XMaX1L0waWU6DXJpBgjaL7+yX6VriVPbN4w==", + "optional": true, + "requires": { + "@serialport/parser-delimiter": "12.0.0" + } + }, + "@serialport/parser-ready": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/@serialport/parser-ready/-/parser-ready-12.0.0.tgz", + "integrity": "sha512-ygDwj3O4SDpZlbrRUraoXIoIqb8sM7aMKryGjYTIF0JRnKeB1ys8+wIp0RFMdFbO62YriUDextHB5Um5cKFSWg==", + "optional": true + }, + "@serialport/parser-regex": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/@serialport/parser-regex/-/parser-regex-12.0.0.tgz", + "integrity": "sha512-dCAVh4P/pZrLcPv9NJ2mvPRBg64L5jXuiRxIlyxxdZGH4WubwXVXY/kBTihQmiAMPxbT3yshSX8f2+feqWsxqA==", + "optional": true + }, + "@serialport/parser-slip-encoder": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/@serialport/parser-slip-encoder/-/parser-slip-encoder-12.0.0.tgz", + "integrity": "sha512-0APxDGR9YvJXTRfY+uRGhzOhTpU5akSH183RUcwzN7QXh8/1jwFsFLCu0grmAUfi+fItCkR+Xr1TcNJLR13VNA==", + "optional": true + }, + "@serialport/parser-spacepacket": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/@serialport/parser-spacepacket/-/parser-spacepacket-12.0.0.tgz", + "integrity": "sha512-dozONxhPC/78pntuxpz/NOtVps8qIc/UZzdc/LuPvVsqCoJXiRxOg6ZtCP/W58iibJDKPZPAWPGYeZt9DJxI+Q==", + "optional": true + }, + "@serialport/stream": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/@serialport/stream/-/stream-12.0.0.tgz", + "integrity": "sha512-9On64rhzuqKdOQyiYLYv2lQOh3TZU/D3+IWCR5gk0alPel2nwpp4YwDEGiUBfrQZEdQ6xww0PWkzqth4wqwX3Q==", + "optional": true, + "requires": { + "@serialport/bindings-interface": "1.2.2", + "debug": "4.3.4" + }, + "dependencies": { + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "optional": true, + "requires": { + "ms": "2.1.2" + } + } + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "optional": true + }, + "node-addon-api": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-8.4.0.tgz", + "integrity": "sha512-D9DI/gXHvVmjHS08SVch0Em8G5S1P+QWtU31appcKT/8wFSPRcdHadIFSAntdMMVM5zz+/DL+bL/gz3UDppqtg==", + "optional": true + }, + "serialport": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/serialport/-/serialport-12.0.0.tgz", + "integrity": "sha512-AmH3D9hHPFmnF/oq/rvigfiAouAKyK/TjnrkwZRYSFZxNggJxwvbAbfYrLeuvq7ktUdhuHdVdSjj852Z55R+uA==", + "optional": true, + "requires": { + "@serialport/binding-mock": "10.2.2", + "@serialport/bindings-cpp": "12.0.1", + "@serialport/parser-byte-length": "12.0.0", + "@serialport/parser-cctalk": "12.0.0", + "@serialport/parser-delimiter": "12.0.0", + "@serialport/parser-inter-byte-timeout": "12.0.0", + "@serialport/parser-packet-length": "12.0.0", + "@serialport/parser-readline": "12.0.0", + "@serialport/parser-ready": "12.0.0", + "@serialport/parser-regex": "12.0.0", + "@serialport/parser-slip-encoder": "12.0.0", + "@serialport/parser-spacepacket": "12.0.0", + "@serialport/stream": "12.0.0", + "debug": "4.3.4" + }, + "dependencies": { + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "optional": true, + "requires": { + "ms": "2.1.2" + } + } + } + } + } + }, + "@stoprocent/noble": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/@stoprocent/noble/-/noble-2.3.2.tgz", + "integrity": "sha512-q8uBqXXIfq1C8trfk5EgcOzl2W+7wS8X4flPz2iGiTB2M0cLZAIMGDXeX0Rva/OYrXUmFifFjCuW/NlEv3r43g==", "requires": { - "@serialport/bindings-interface": "1.2.2", - "debug": "4.4.0" + "@stoprocent/bluetooth-hci-socket": "^2.2.3", + "debug": "^4.3.7", + "node-addon-api": "^8.1.0", + "node-gyp-build": "^4.8.1" } }, "@tsconfig/node16": { @@ -5740,6 +6980,12 @@ "resolved": "https://registry.npmjs.org/@types/triple-beam/-/triple-beam-1.3.5.tgz", "integrity": "sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==" }, + "@types/w3c-web-usb": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/@types/w3c-web-usb/-/w3c-web-usb-1.0.10.tgz", + "integrity": "sha512-CHgUI5kTc/QLMP8hODUHhge0D4vx+9UiAwIGiT0sTy/B2XpdX1U5rJt6JSISgr6ikRT7vxV9EVAFeYZqUnl1gQ==", + "optional": true + }, "@typescript-eslint/eslint-plugin": { "version": "8.35.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.35.0.tgz", @@ -6024,6 +7270,12 @@ } } }, + "@yarnpkg/lockfile": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz", + "integrity": "sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ==", + "optional": true + }, "acorn": { "version": "8.15.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", @@ -6058,7 +7310,7 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, + "devOptional": true, "requires": { "color-convert": "^2.0.1" }, @@ -6067,7 +7319,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, + "devOptional": true, "requires": { "color-name": "~1.1.4" } @@ -6076,7 +7328,7 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true + "devOptional": true } } }, @@ -6116,9 +7368,15 @@ } }, "async": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.4.tgz", - "integrity": "sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==" + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==" + }, + "at-least-node": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", + "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==", + "optional": true }, "b4a": { "version": "1.6.4", @@ -6129,7 +7387,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true + "devOptional": true }, "basic-ftp": { "version": "5.0.3", @@ -6149,7 +7407,7 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", - "dev": true, + "devOptional": true, "requires": { "fill-range": "^7.1.1" } @@ -6160,6 +7418,38 @@ "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", "dev": true }, + "call-bind": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", + "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", + "optional": true, + "requires": { + "call-bind-apply-helpers": "^1.0.0", + "es-define-property": "^1.0.0", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.2" + } + }, + "call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "optional": true, + "requires": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + } + }, + "call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "optional": true, + "requires": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + } + }, "callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -6202,6 +7492,12 @@ "get-func-name": "^2.0.2" } }, + "ci-info": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", + "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", + "optional": true + }, "cli-progress": { "version": "3.12.0", "resolved": "https://registry.npmjs.org/cli-progress/-/cli-progress-3.12.0.tgz", @@ -6254,7 +7550,7 @@ "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true + "devOptional": true }, "crc": { "version": "4.3.2", @@ -6266,7 +7562,7 @@ "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", - "dev": true, + "devOptional": true, "requires": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", @@ -6310,6 +7606,28 @@ "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", "dev": true }, + "define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "optional": true, + "requires": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + } + }, + "dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "optional": true, + "requires": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + } + }, "eastasianwidth": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", @@ -6326,12 +7644,33 @@ "resolved": "https://registry.npmjs.org/enabled/-/enabled-2.0.0.tgz", "integrity": "sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==" }, + "es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "optional": true + }, + "es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "optional": true + }, "es-module-lexer": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", "dev": true }, + "es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "optional": true, + "requires": { + "es-errors": "^1.3.0" + } + }, "esbuild": { "version": "0.25.5", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.5.tgz", @@ -6611,7 +7950,7 @@ "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", - "dev": true, + "devOptional": true, "requires": { "to-regex-range": "^5.0.1" } @@ -6626,6 +7965,15 @@ "path-exists": "^4.0.0" } }, + "find-yarn-workspace-root": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/find-yarn-workspace-root/-/find-yarn-workspace-root-2.0.0.tgz", + "integrity": "sha512-1IMnbjt4KzsQfnhnzNd8wUEgXZ44IzZaZmnLYx7D5FZlaHt2gW20Cri8Q+E/t5tIj4+epTBub+2Zxu/vNILzqQ==", + "optional": true, + "requires": { + "micromatch": "^4.0.2" + } + }, "flat-cache": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", @@ -6657,6 +8005,24 @@ "signal-exit": "^4.0.1" } }, + "fs-extra": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "optional": true, + "requires": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + } + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "optional": true + }, "fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", @@ -6664,12 +8030,46 @@ "dev": true, "optional": true }, + "function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "optional": true + }, "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 }, + "get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "optional": true, + "requires": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + } + }, + "get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "optional": true, + "requires": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + } + }, "get-uri": { "version": "6.0.4", "resolved": "https://registry.npmjs.org/get-uri/-/get-uri-6.0.4.tgz", @@ -6709,6 +8109,18 @@ "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", "dev": true }, + "gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "optional": true + }, + "graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "optional": true + }, "graphemer": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", @@ -6719,7 +8131,31 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true + "devOptional": true + }, + "has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "optional": true, + "requires": { + "es-define-property": "^1.0.0" + } + }, + "has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "optional": true + }, + "hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "optional": true, + "requires": { + "function-bind": "^1.1.2" + } }, "html-escaper": { "version": "2.0.2", @@ -6749,6 +8185,16 @@ "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", "dev": true }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "optional": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, "inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", @@ -6759,6 +8205,12 @@ "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==" }, + "is-docker": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", + "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", + "optional": true + }, "is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -6783,18 +8235,33 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true + "devOptional": true }, "is-stream": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==" }, + "is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "optional": true, + "requires": { + "is-docker": "^2.0.0" + } + }, + "isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "optional": true + }, "isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true + "devOptional": true }, "istanbul-lib-coverage": { "version": "3.2.2", @@ -6883,12 +8350,41 @@ "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", "dev": true }, + "json-stable-stringify": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.3.0.tgz", + "integrity": "sha512-qtYiSSFlwot9XHtF9bD9c7rwKjr+RecWT//ZnPvSmEjpV5mmPOCN4j8UjY5hbjNkOwZ/jQv3J6R1/pL7RwgMsg==", + "optional": true, + "requires": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "isarray": "^2.0.5", + "jsonify": "^0.0.1", + "object-keys": "^1.1.1" + } + }, "json-stable-stringify-without-jsonify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", "dev": true }, + "jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "optional": true, + "requires": { + "graceful-fs": "^4.1.6", + "universalify": "^2.0.0" + } + }, + "jsonify": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.1.tgz", + "integrity": "sha512-2/Ki0GcmuqSrgFyelQq9M05y7PS0mEwuIzrf3f1fPqkVDVRvZrPZtVSMHxdgo8Aq0sxAOb/cr2aqqA3LeWHVPg==", + "optional": true + }, "keyv": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", @@ -6898,6 +8394,15 @@ "json-buffer": "3.0.1" } }, + "klaw-sync": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/klaw-sync/-/klaw-sync-6.0.0.tgz", + "integrity": "sha512-nIeuVSzdCCs6TDPTqI8w1Yre34sSq7AkZ4B3sfOBbI2CgVSB4Du4aLQijFU2+lhAFCwt9+42Hel6lQNIv6AntQ==", + "optional": true, + "requires": { + "graceful-fs": "^4.1.11" + } + }, "kuler": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/kuler/-/kuler-2.0.0.tgz", @@ -6991,6 +8496,12 @@ "semver": "^7.5.3" } }, + "math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "optional": true + }, "merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", @@ -7001,7 +8512,7 @@ "version": "4.0.8", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", - "dev": true, + "devOptional": true, "requires": { "braces": "^3.0.3", "picomatch": "^2.3.1" @@ -7016,6 +8527,12 @@ "brace-expansion": "^2.0.1" } }, + "minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "optional": true + }, "minipass": { "version": "7.1.2", "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", @@ -7060,6 +8577,21 @@ "integrity": "sha512-MO7mLp/8nm6kZNLLyPgz4gHmr9tLoU+pWPLdXuGAx+oZydBHkHWN0ibTonsrfwC2WEQNIQxuZagYwB67JQpAuw==", "dev": true }, + "object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "optional": true + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "optional": true, + "requires": { + "wrappy": "1" + } + }, "one-time": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/one-time/-/one-time-1.0.0.tgz", @@ -7068,6 +8600,16 @@ "fn.name": "1.x.x" } }, + "open": { + "version": "7.4.2", + "resolved": "https://registry.npmjs.org/open/-/open-7.4.2.tgz", + "integrity": "sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q==", + "optional": true, + "requires": { + "is-docker": "^2.0.0", + "is-wsl": "^2.1.1" + } + }, "optionator": { "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", @@ -7082,6 +8624,12 @@ "word-wrap": "^1.2.5" } }, + "os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==", + "optional": true + }, "p-limit": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", @@ -7120,17 +8668,58 @@ "callsites": "^3.0.0" } }, + "patch-package": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/patch-package/-/patch-package-8.0.0.tgz", + "integrity": "sha512-da8BVIhzjtgScwDJ2TtKsfT5JFWz1hYoBl9rUQ1f38MC2HwnEIkK8VN3dKMKcP7P7bvvgzNDbfNHtx3MsQb5vA==", + "optional": true, + "requires": { + "@yarnpkg/lockfile": "^1.1.0", + "chalk": "^4.1.2", + "ci-info": "^3.7.0", + "cross-spawn": "^7.0.3", + "find-yarn-workspace-root": "^2.0.0", + "fs-extra": "^9.0.0", + "json-stable-stringify": "^1.0.2", + "klaw-sync": "^6.0.0", + "minimist": "^1.2.6", + "open": "^7.4.2", + "rimraf": "^2.6.3", + "semver": "^7.5.3", + "slash": "^2.0.0", + "tmp": "^0.0.33", + "yaml": "^2.2.2" + }, + "dependencies": { + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "optional": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + } + } + }, "path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", "dev": true }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "optional": true + }, "path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true + "devOptional": true }, "path-scurry": { "version": "1.11.1", @@ -7164,7 +8753,7 @@ "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true + "devOptional": true }, "postcss": { "version": "8.5.6", @@ -7231,6 +8820,50 @@ "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", "dev": true }, + "rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "optional": true, + "requires": { + "glob": "^7.1.3" + }, + "dependencies": { + "brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "optional": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "optional": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "optional": true, + "requires": { + "brace-expansion": "^1.1.7" + } + } + } + }, "rollup": { "version": "4.44.1", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.44.1.tgz", @@ -7284,7 +8917,7 @@ "version": "7.7.2", "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", - "dev": true + "devOptional": true }, "serialport": { "version": "13.0.0", @@ -7307,11 +8940,25 @@ "debug": "4.4.0" } }, + "set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "optional": true, + "requires": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + } + }, "shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, + "devOptional": true, "requires": { "shebang-regex": "^3.0.0" } @@ -7320,7 +8967,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true + "devOptional": true }, "siginfo": { "version": "2.0.0", @@ -7342,6 +8989,12 @@ "is-arrayish": "^0.3.1" } }, + "slash": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", + "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", + "optional": true + }, "source-map-js": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", @@ -7439,7 +9092,7 @@ "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, + "devOptional": true, "requires": { "has-flag": "^4.0.0" } @@ -7525,11 +9178,20 @@ "integrity": "sha512-t2T/WLB2WRgZ9EpE4jgPJ9w+i66UZfDc8wHh0xrwiRNN+UwH98GIJkTeZqX9rg0i0ptwzqW+uYeIF0T4F8LR7A==", "dev": true }, + "tmp": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", + "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "optional": true, + "requires": { + "os-tmpdir": "~1.0.2" + } + }, "to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, + "devOptional": true, "requires": { "is-number": "^7.0.0" } @@ -7588,6 +9250,12 @@ "integrity": "sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw==", "dev": true }, + "universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "optional": true + }, "uri-js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", @@ -7597,6 +9265,17 @@ "punycode": "^2.1.0" } }, + "usb": { + "version": "2.15.0", + "resolved": "https://registry.npmjs.org/usb/-/usb-2.15.0.tgz", + "integrity": "sha512-BA9r7PFxyYp99wps1N70lIqdPb2Utcl2KkWohDtWUmhDBeM5hDH1Zl/L/CZvWxd5W3RUCNm1g+b+DEKZ6cHzqg==", + "optional": true, + "requires": { + "@types/w3c-web-usb": "^1.0.6", + "node-addon-api": "^8.0.0", + "node-gyp-build": "^4.5.0" + } + }, "util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -7751,7 +9430,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, + "devOptional": true, "requires": { "isexe": "^2.0.0" } @@ -7862,6 +9541,18 @@ "strip-ansi": "^6.0.0" } }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "optional": true + }, + "yaml": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.0.tgz", + "integrity": "sha512-4lLa/EcQCB0cJkyts+FpIRx5G/llPxfP6VQU5KByHEhLxY3IJCH0f0Hy1MHI8sClTvsIb8qwRJ6R/ZdlDJ/leQ==", + "optional": true + }, "yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", diff --git a/package.json b/package.json index 1841fe5..f0b95f1 100644 --- a/package.json +++ b/package.json @@ -32,6 +32,7 @@ ], "dependencies": { "@cubicap/esptool-js": "^0.3.2", + "@stoprocent/noble": "^2.3.2", "chalk": "^5.4.1", "cli-progress": "^3.12.0", "crc": "^4.3.2", diff --git a/src/commands/index.ts b/src/commands/index.ts index 11fd61f..ed6379d 100644 --- a/src/commands/index.ts +++ b/src/commands/index.ts @@ -67,9 +67,11 @@ import fomat from "./format.js"; import resourcesLs from "./resources-ls.js"; import resourcesRead from "./resources-read.js"; import getExamples from "./get-examples.js"; +import listBle from "./list-ble.js"; import { wifiAdd, wifiRemove, wifiGet, wifiSetAp, wifiSetSta, wifiDisable } from "./wifi.js"; jac.addCommand("list-ports", listPorts); +jac.addCommand("list-ble", listBle); jac.addCommand("serial-socket", serialSocket); jac.addCommand("install", install); jac.addCommand("build", build); @@ -104,6 +106,7 @@ jac.addCommand("wifi-sta", wifiSetSta); jac.addCommand("wifi-disable", wifiDisable); + const args = process.argv.slice(2); if (args.length === 0) { diff --git a/src/commands/list-ble.ts b/src/commands/list-ble.ts new file mode 100644 index 0000000..c12039a --- /dev/null +++ b/src/commands/list-ble.ts @@ -0,0 +1,53 @@ +import { Arg, Command, Opt } from "./lib/command.js"; +import { stdout } from "process"; +import noble, { Peripheral } from '@stoprocent/noble'; + +const cmd = new Command("List available BLE devices", { + action: async (options: Record, args: Record) => { + const timeout = options["timeout"] ? Number(options["timeout"]) : 4000; + const table: { name: string, rssi: string }[] = [{ name: "Name", rssi: "RSSI" }]; + const discovered = new Set(); + + function handleDiscovery(peripheral: Peripheral) { + const name = peripheral.advertisement.localName || "(no name)"; + const rssi = peripheral.rssi.toString(); + if (discovered.has(peripheral.id)) return; + if (peripheral.connectable === false) return; + discovered.add(peripheral.id); + table.push({ name, rssi }); + } + + try { + console.log('Waiting for Bluetooth adapter...'); + await noble.waitForPoweredOnAsync(); + console.log('Bluetooth adapter ready'); + console.log(`Scanning BLE devices for ${timeout / 1000} seconds...`); + await noble.startScanningAsync([], false); + noble.on('discover', handleDiscovery); + await new Promise(resolve => setTimeout(resolve, timeout)); + } catch (error) { + console.error('Error discovering BLE devices:', error); + } finally { + await noble.stopScanningAsync(); + noble.stop(); + let maxNameLength = 0; + for (const row of table) { + maxNameLength = Math.max(maxNameLength, row.name.length); + } + let first = true; + for (const row of table) { + stdout.write(row.name.padEnd(maxNameLength) + " " + row.rssi + "\n"); + if (first) { + first = false; + stdout.write("-".repeat(maxNameLength) + " " + "-".repeat(4) + "\n"); + } + } + } + return; + }, + options: { + "timeout": new Opt("Scan timeout in milliseconds (default: 4000)", { defaultValue: "4000" }), + }, +}); + +export default cmd; diff --git a/src/commands/tsconfig.json b/src/commands/tsconfig.json index 75d42cf..1faff37 100644 --- a/src/commands/tsconfig.json +++ b/src/commands/tsconfig.json @@ -4,6 +4,7 @@ "util.ts", "list-ports.ts", "serial-socket.ts", + "list-ble.ts", "monitor.ts", "ls.ts", "read.ts", From 2321993a9f0985c8c57f23b5326a6797a98e1b0e Mon Sep 17 00:00:00 2001 From: Jakub Andrysek Date: Mon, 30 Jun 2025 01:13:49 +0200 Subject: [PATCH 2/6] feat: add Bluetooth LE support to device commands --- src/commands/flash.ts | 3 ++- src/commands/format.ts | 3 ++- src/commands/get-examples.ts | 3 ++- src/commands/index.ts | 1 + src/commands/ls.ts | 3 ++- src/commands/mkdir.ts | 3 ++- src/commands/monitor.ts | 3 ++- src/commands/pull.ts | 3 ++- src/commands/read.ts | 3 ++- src/commands/resources-ls.ts | 3 ++- src/commands/resources-read.ts | 3 ++- src/commands/rm.ts | 3 ++- src/commands/rmdir.ts | 3 ++- src/commands/start.ts | 3 ++- src/commands/status.ts | 3 ++- src/commands/stop.ts | 3 ++- src/commands/upload.ts | 3 ++- src/commands/util.ts | 35 ++++++++++++++++++++++++++-------- src/commands/version.ts | 3 ++- src/commands/wifi.ts | 18 +++++++++++------ src/commands/write.ts | 3 ++- 21 files changed, 76 insertions(+), 32 deletions(-) diff --git a/src/commands/flash.ts b/src/commands/flash.ts index ee2cf1a..be2b0d4 100644 --- a/src/commands/flash.ts +++ b/src/commands/flash.ts @@ -133,9 +133,10 @@ const cmd = new Command("Flash code to device (replace contents of ./code)", { const port = options["port"] as string; const baudrate = options["baudrate"] as string; const socket = options["socket"] as string; + const ble = options["ble"] as string | undefined; const from = options["from"] as string; - const device = await getDevice(port, baudrate, socket, env); + const device = await getDevice(port, baudrate, socket, ble, env); await device.controller.lock().catch((err) => { stderr.write("Error locking device: " + err + "\n"); diff --git a/src/commands/format.ts b/src/commands/format.ts index e024e1a..96f1c18 100644 --- a/src/commands/format.ts +++ b/src/commands/format.ts @@ -8,8 +8,9 @@ const cmd = new Command("Format device storage", { const port = options["port"] as string; const baudrate = options["baudrate"] as string; const socket = options["socket"] as string; + const ble = options["ble"] as string | undefined; - const device = await getDevice(port, baudrate, socket, env); + const device = await getDevice(port, baudrate, socket, ble, env); await device.controller.lock().catch((err) => { stderr.write("Error locking device: " + err + "\n"); diff --git a/src/commands/get-examples.ts b/src/commands/get-examples.ts index 65d7a66..e59e6eb 100644 --- a/src/commands/get-examples.ts +++ b/src/commands/get-examples.ts @@ -12,10 +12,11 @@ const cmd = new Command("Get example project from device", { const port = options["port"] as string; const baudrate = options["baudrate"] as string; const socket = options["socket"] as string; + const ble = options["ble"] as string | undefined; const path_ = args["path"] as string; - const device = await getDevice(port, baudrate, socket, env); + const device = await getDevice(port, baudrate, socket, ble, env); await device.controller.lock().catch((err) => { stderr.write("Error locking device: " + err + "\n"); diff --git a/src/commands/index.ts b/src/commands/index.ts index ed6379d..96a8c4c 100644 --- a/src/commands/index.ts +++ b/src/commands/index.ts @@ -12,6 +12,7 @@ const jac = new Program("jac", "Tools for controlling devices running Jaculus", "port": new Opt("Serial port to use (default: first available)"), "baudrate": new Opt("Baudrate to use", { defaultValue: "921600" }), "socket": new Opt("host:port to use"), + "ble": new Opt("Bluetooth LE address to use", { defaultValue: undefined }), }, action: async (options: Record) => { if (options["help"]) { diff --git a/src/commands/ls.ts b/src/commands/ls.ts index b5c826e..f703b4d 100644 --- a/src/commands/ls.ts +++ b/src/commands/ls.ts @@ -9,13 +9,14 @@ const cmd = new Command("List files in a directory", { const port = options["port"] as string; const baudrate = options["baudrate"] as string; const socket = options["socket"] as string; + const ble = options["ble"] as string | undefined; const path = args["path"] as string; const directoryFlag = options["directory"] as boolean; const sizeFlag = options["size"] as boolean; const flags = (directoryFlag ? "d" : "") + (sizeFlag ? "s" : ""); - const device = await getDevice(port, baudrate, socket, env); + const device = await getDevice(port, baudrate, socket, ble, env); await device.controller.lock().catch((err) => { stderr.write("Error locking device: " + err + "\n"); diff --git a/src/commands/mkdir.ts b/src/commands/mkdir.ts index 9f7bd45..1ec60e4 100644 --- a/src/commands/mkdir.ts +++ b/src/commands/mkdir.ts @@ -8,9 +8,10 @@ const cmd = new Command("Create a directory on device", { const port = options["port"] as string; const baudrate = options["baudrate"] as string; const socket = options["socket"] as string; + const ble = options["ble"] as string | undefined; const path = args["path"] as string; - const device = await getDevice(port, baudrate, socket, env); + const device = await getDevice(port, baudrate, socket, ble, env); await device.controller.lock().catch((err) => { stderr.write("Error locking device: " + err + "\n"); diff --git a/src/commands/monitor.ts b/src/commands/monitor.ts index a5ec7e5..e6b7725 100644 --- a/src/commands/monitor.ts +++ b/src/commands/monitor.ts @@ -16,8 +16,9 @@ const cmd = new Command("Monitor program output", { const port = options["port"] as string; const baudrate = options["baudrate"] as string; const socket = options["socket"] as string; + const ble = options["ble"] as string; - const device = await getDevice(port, baudrate, socket, env); + const device = await getDevice(port, baudrate, socket, ble, env); device.programOutput.onData((data) => { stdout.write(data); diff --git a/src/commands/pull.ts b/src/commands/pull.ts index a07f694..bc39187 100644 --- a/src/commands/pull.ts +++ b/src/commands/pull.ts @@ -8,10 +8,11 @@ const cmd = new Command("Download a file/directory from device", { const port = options["port"] as string; const baudrate = options["baudrate"] as string; const socket = options["socket"] as string; + const ble = options["ble"] as string | undefined; const local = args["local"] as string; const remote = args["remote"] as string; - const device = await getDevice(port, baudrate, socket, env); + const device = await getDevice(port, baudrate, socket, ble, env); await device.controller.lock().catch((err) => { stderr.write("Error locking device: " + err + "\n"); diff --git a/src/commands/read.ts b/src/commands/read.ts index ec0155e..a70ad04 100644 --- a/src/commands/read.ts +++ b/src/commands/read.ts @@ -8,9 +8,10 @@ const cmd = new Command("Read a file from device", { const port = options["port"] as string; const baudrate = options["baudrate"] as string; const socket = options["socket"] as string; + const ble = options["ble"] as string | undefined; const path = args["path"] as string; - const device = await getDevice(port, baudrate, socket, env); + const device = await getDevice(port, baudrate, socket, ble, env); await device.controller.lock().catch((err) => { stderr.write("Error locking device: " + err + "\n"); diff --git a/src/commands/resources-ls.ts b/src/commands/resources-ls.ts index 0c2b197..172c9ef 100644 --- a/src/commands/resources-ls.ts +++ b/src/commands/resources-ls.ts @@ -8,8 +8,9 @@ const cmd = new Command("List available resources", { const port = options["port"] as string; const baudrate = options["baudrate"] as string; const socket = options["socket"] as string; + const ble = options["ble"] as string | undefined; - const device = await getDevice(port, baudrate, socket, env); + const device = await getDevice(port, baudrate, socket, ble, env); await device.controller.lock().catch((err) => { stderr.write("Error locking device: " + err + "\n"); diff --git a/src/commands/resources-read.ts b/src/commands/resources-read.ts index 1108027..fce122f 100644 --- a/src/commands/resources-read.ts +++ b/src/commands/resources-read.ts @@ -8,10 +8,11 @@ const cmd = new Command("Read a resource from device", { const port = options["port"] as string; const baudrate = options["baudrate"] as string; const socket = options["socket"] as string; + const ble = options["ble"] as string | undefined; const name = args["name"] as string; const outfile = options["outfile"] as string | undefined; - const device = await getDevice(port, baudrate, socket, env); + const device = await getDevice(port, baudrate, socket, ble, env); await device.controller.lock().catch((err) => { stderr.write("Error locking device: " + err + "\n"); diff --git a/src/commands/rm.ts b/src/commands/rm.ts index 02ed8cd..b208f94 100644 --- a/src/commands/rm.ts +++ b/src/commands/rm.ts @@ -8,9 +8,10 @@ const cmd = new Command("Delete a file on device", { const port = options["port"] as string; const baudrate = options["baudrate"] as string; const socket = options["socket"] as string; + const ble = options["ble"] as string | undefined; const path = args["path"] as string; - const device = await getDevice(port, baudrate, socket, env); + const device = await getDevice(port, baudrate, socket, ble, env); await device.controller.lock().catch((err) => { stderr.write("Error locking device: " + err + "\n"); diff --git a/src/commands/rmdir.ts b/src/commands/rmdir.ts index 170c8a7..86bc109 100644 --- a/src/commands/rmdir.ts +++ b/src/commands/rmdir.ts @@ -8,9 +8,10 @@ const cmd = new Command("Delete a directory on device", { const port = options["port"] as string; const baudrate = options["baudrate"] as string; const socket = options["socket"] as string; + const ble = options["ble"] as string | undefined; const path = args["path"] as string; - const device = await getDevice(port, baudrate, socket, env); + const device = await getDevice(port, baudrate, socket, ble, env); await device.controller.lock().catch((err) => { stderr.write("Error locking device: " + err + "\n"); diff --git a/src/commands/start.ts b/src/commands/start.ts index e3c8387..00e984e 100644 --- a/src/commands/start.ts +++ b/src/commands/start.ts @@ -8,9 +8,10 @@ const cmd = new Command("Start a program", { const port = options["port"] as string; const baudrate = options["baudrate"] as string; const socket = options["socket"] as string; + const ble = options["ble"] as string | undefined; const entry = options["entry"] as string; - const device = await getDevice(port, baudrate, socket, env); + const device = await getDevice(port, baudrate, socket, ble, env); await device.controller.lock().catch((err) => { stderr.write("Error locking device: " + err + "\n"); diff --git a/src/commands/status.ts b/src/commands/status.ts index 3ed4310..0360573 100644 --- a/src/commands/status.ts +++ b/src/commands/status.ts @@ -8,8 +8,9 @@ const cmd = new Command("Get status of device", { const port = options["port"] as string; const baudrate = options["baudrate"] as string; const socket = options["socket"] as string; + const ble = options["ble"] as string | undefined; - const device = await getDevice(port, baudrate, socket, env); + const device = await getDevice(port, baudrate, socket, ble, env); await device.controller.lock().catch((err) => { stderr.write("Error locking device: " + err + "\n"); diff --git a/src/commands/stop.ts b/src/commands/stop.ts index 0e543dc..50d32bd 100644 --- a/src/commands/stop.ts +++ b/src/commands/stop.ts @@ -8,8 +8,9 @@ const cmd = new Command("Stop a program", { const port = options["port"] as string; const baudrate = options["baudrate"] as string; const socket = options["socket"] as string; + const ble = options["ble"] as string | undefined; - const device = await getDevice(port, baudrate, socket, env); + const device = await getDevice(port, baudrate, socket, ble, env); await device.controller.lock().catch((err) => { stderr.write("Error locking device: " + err + "\n"); diff --git a/src/commands/upload.ts b/src/commands/upload.ts index 7c54eb0..ee4b302 100644 --- a/src/commands/upload.ts +++ b/src/commands/upload.ts @@ -8,10 +8,11 @@ const cmd = new Command("Upload a file/directory to device", { const port = options["port"] as string; const baudrate = options["baudrate"] as string; const socket = options["socket"] as string; + const ble = options["ble"] as string | undefined; const local = args["local"] as string; const remote = args["remote"] as string; - const device = await getDevice(port, baudrate, socket, env); + const device = await getDevice(port, baudrate, socket, ble, env); await device.controller.lock().catch((err) => { stderr.write("Error locking device: " + err + "\n"); diff --git a/src/commands/util.ts b/src/commands/util.ts index e70474f..bc7da6f 100644 --- a/src/commands/util.ts +++ b/src/commands/util.ts @@ -38,9 +38,10 @@ export function defaultSocket(value?: string): string { return value; } -export async function getPortSocket(port?: string | boolean, socket?: string | boolean): Promise<{ type: "port" | "socket", value: string }> { - if (port && socket) { - stderr.write("Must specify either a serial port or a socket, not both\n"); +export async function getPortSocketBle(port?: string | boolean, socket?: string | boolean, ble?: string | boolean): Promise<{ type: "port" | "socket" | "ble", value: string }> { + const count = [!!port, !!socket, !!ble].filter(Boolean).length; + if (count !== 1) { + stderr.write("Must specify exactly one of: serial port, socket, or BLE\n"); throw 1; } @@ -52,7 +53,18 @@ export async function getPortSocket(port?: string | boolean, socket?: string | b return { type: "socket", value: socket === true ? defaultSocket() : socket }; } - return { type: "port", value: await defaultPort() }; + if (ble) { + // BLE does not have a default, must be a string + if (ble === true) { + stderr.write("Must specify BLE device identifier\n"); + throw 1; + } + return { type: "ble", value: ble }; + } + + // Should never reach here + stderr.write("Unknown error in getPortSocketBle\n"); + throw 1; } export function parseSocket(value: string): [string, number] { @@ -68,12 +80,14 @@ export function parseSocket(value: string): [string, number] { return [parts[0], parseInt(parts[1])]; } -export async function getDevice(port: string | undefined, baudrate: string | undefined, socket: string | undefined, env: Env): Promise { + + +export async function getDevice(port: string | undefined, baudrate: string | undefined, socket: string | undefined, ble: string | undefined, env: Env): Promise { if (env.device) { return env.device.value as JacDevice; } - const where = await getPortSocket(port, socket); + const where = await getPortSocketBle(port, socket, ble); let device: JacDevice | undefined = undefined; @@ -109,6 +123,11 @@ export async function getDevice(port: string | undefined, baudrate: string | und })); }); } + // BLE support placeholder: add BLE connection logic here if needed + else if (where.type === "ble") { + stderr.write("BLE support is not implemented yet.\n"); + throw 1; + } if (!device) { stderr.write("Invalid port/socket\n"); @@ -142,9 +161,9 @@ export async function getDevice(port: string | undefined, baudrate: string | und } export async function withDevice(port: string | undefined, baudrate: string | undefined, - socket: string | undefined, env: Env, action: (device: JacDevice) => Promise + socket: string | undefined, ble: string | undefined, env: Env, action: (device: JacDevice) => Promise ): Promise { - const device = await getDevice(port, baudrate, socket, env); + const device = await getDevice(port, baudrate, socket, ble, env); await action(device); await device.destroy(); } diff --git a/src/commands/version.ts b/src/commands/version.ts index b5c1a5d..eded186 100644 --- a/src/commands/version.ts +++ b/src/commands/version.ts @@ -9,10 +9,11 @@ const cmd = new Command("Get version of device firmware", { const port = options["port"] as string; const baudrate = options["baudrate"] as string; const socket = options["socket"] as string; + const ble = options["ble"] as string | undefined; stdout.write("Jaculus-tools version:\n " + version + "\n\n"); - const device = await getDevice(port, baudrate, socket, env); + const device = await getDevice(port, baudrate, socket, ble, env); const status = await device.controller.version().catch((err) => { stderr.write("Error: " + err + "\n"); diff --git a/src/commands/wifi.ts b/src/commands/wifi.ts index 6933e11..75a0637 100644 --- a/src/commands/wifi.ts +++ b/src/commands/wifi.ts @@ -36,11 +36,12 @@ export const wifiAdd = new Command("Add a WiFi network", { const port = options["port"] as string; const baudrate = options["baudrate"] as string; const socket = options["socket"] as string; + const ble = options["ble"] as string | undefined; const ssid = args["ssid"] as string; const password = await readPassword("Password: "); - const device = await getDevice(port, baudrate, socket, env); + const device = await getDevice(port, baudrate, socket, ble, env); await device.controller.lock().catch((err) => { stderr.write("Error locking device: " + err + "\n"); @@ -68,9 +69,10 @@ export const wifiRemove = new Command("Remove a WiFi network", { const port = options["port"] as string; const baudrate = options["baudrate"] as string; const socket = options["socket"] as string; + const ble = options["ble"] as string | undefined; const ssid = args["ssid"] as string; - const device = await getDevice(port, baudrate, socket, env); + const device = await getDevice(port, baudrate, socket, ble, env); await device.controller.lock().catch((err) => { stderr.write("Error locking device: " + err + "\n"); @@ -98,9 +100,10 @@ export const wifiGet = new Command("Display current WiFi config", { const port = options["port"] as string; const baudrate = options["baudrate"] as string; const socket = options["socket"] as string; + const ble = options["ble"] as string | undefined; const watch = options["watch"] as boolean; - const device = await getDevice(port, baudrate, socket, env); + const device = await getDevice(port, baudrate, socket, ble, env); let first = true; @@ -151,7 +154,8 @@ export const wifiDisable = new Command("Disable WiFi", { const port = options["port"] as string; const baudrate = options["baudrate"] as string; const socket = options["socket"] as string; - const device = await getDevice(port, baudrate, socket, env); + const ble = options["ble"] as string | undefined; + const device = await getDevice(port, baudrate, socket, ble, env); await device.controller.lock().catch((err) => { stderr.write("Error locking device: " + err + "\n"); @@ -176,6 +180,7 @@ export const wifiSetAp = new Command("Set WiFi to AP mode (create a hotspot)", { const port = options["port"] as string; const baudrate = options["baudrate"] as string; const socket = options["socket"] as string; + const ble = options["ble"] as string | undefined; const ssid = args["ssid"] as string | undefined; const pass = await readPassword("Password: "); @@ -190,7 +195,7 @@ export const wifiSetAp = new Command("Set WiFi to AP mode (create a hotspot)", { throw 1; } - const device = await getDevice(port, baudrate, socket, env); + const device = await getDevice(port, baudrate, socket, ble, env); await device.controller.lock().catch((err) => { stderr.write("Error locking device: " + err + "\n"); @@ -224,6 +229,7 @@ export const wifiSetSta = new Command("Set WiFi to Station mode (connect to a wi const port = options["port"] as string; const baudrate = options["baudrate"] as string; const socket = options["socket"] as string; + const ble = options["ble"] as string | undefined; const specificSsid = options["specific"] as string | undefined; const noApFallback = options["no-ap-fallback"] as boolean; @@ -233,7 +239,7 @@ export const wifiSetSta = new Command("Set WiFi to Station mode (connect to a wi throw 1; } - const device = await getDevice(port, baudrate, socket, env); + const device = await getDevice(port, baudrate, socket, ble, env); await device.controller.lock().catch((err) => { stderr.write("Error locking device: " + err + "\n"); throw 1; diff --git a/src/commands/write.ts b/src/commands/write.ts index 06a72f1..e37cfb9 100644 --- a/src/commands/write.ts +++ b/src/commands/write.ts @@ -9,6 +9,7 @@ const cmd = new Command("Write a file to device", { const port = options["port"] as string; const baudrate = options["baudrate"] as string; const socket = options["socket"] as string; + const ble = options["ble"] as string | undefined; const path = args["path"] as string; let str = ""; @@ -38,7 +39,7 @@ const cmd = new Command("Write a file to device", { throw 1; }); - const device = await getDevice(port, baudrate, socket, env); + const device = await getDevice(port, baudrate, socket, ble, env); await device.controller.lock().catch((err) => { stderr.write("Error locking device: " + err + "\n"); From be90f55e329dd1eb3d797b3eb04833424b0e2051 Mon Sep 17 00:00:00 2001 From: Jakub Andrysek Date: Mon, 30 Jun 2025 01:36:47 +0200 Subject: [PATCH 3/6] feat: implement BLE connection logic and add BLEStream class --- src/commands/util.ts | 87 +++++++++++++++++++++++++++++++++-- src/link/streams/bleStream.ts | 54 ++++++++++++++++++++++ src/link/tsconfig.json | 1 + tsconfig.json | 3 +- 4 files changed, 141 insertions(+), 4 deletions(-) create mode 100644 src/link/streams/bleStream.ts diff --git a/src/commands/util.ts b/src/commands/util.ts index bc7da6f..738e432 100644 --- a/src/commands/util.ts +++ b/src/commands/util.ts @@ -6,6 +6,8 @@ import { stderr, stdout } from "process"; import { logger } from "../util/logger.js"; import { Env } from "./lib/command.js"; import readline from "readline"; +import noble, { Peripheral } from '@stoprocent/noble'; +import { BLEStream } from "../link/streams/bleStream.js"; export async function defaultPort(value?: string): Promise { @@ -123,10 +125,89 @@ export async function getDevice(port: string | undefined, baudrate: string | und })); }); } - // BLE support placeholder: add BLE connection logic here if needed else if (where.type === "ble") { - stderr.write("BLE support is not implemented yet.\n"); - throw 1; + // BLE connection by localName or UUID + const target = where.value; + stderr.write(`Connecting to BLE device: ${target}\n`); + await noble.waitForPoweredOnAsync(); + let foundPeripheral: Peripheral | undefined; + let cleanupDone = false; + const cleanup = async () => { + if (cleanupDone) return; + cleanupDone = true; + try { await noble.stopScanningAsync(); } catch {} + noble.removeAllListeners('discover'); + }; + // Helper: is UUID (32 hex chars) + const isUuid = /^[0-9a-fA-F]{32}$/.test(target.replace(/-/g, "")); + try { + if (isUuid) { + // Try direct connection by UUID + try { + foundPeripheral = await noble.connectAsync(target); + stderr.write(`Direct connection successful to: ${foundPeripheral.id}\n`); + } catch (err) { + const msg = err instanceof Error ? err.message : String(err); + stderr.write(`Direct connection failed: ${msg}\n`); + } + } + if (!foundPeripheral) { + // Scan for device by name or UUID + await noble.startScanningAsync(); + const timeout = 10000; + await new Promise((resolve, reject) => { + const onDiscover = async (peripheral: Peripheral) => { + if (peripheral.id === target || peripheral.advertisement.localName === target) { + foundPeripheral = peripheral; + await noble.stopScanningAsync(); + noble.removeListener('discover', onDiscover); + resolve(); + } + }; + noble.on('discover', onDiscover); + setTimeout(() => { + noble.removeListener('discover', onDiscover); + noble.stopScanningAsync(); + if (!foundPeripheral) reject(new Error('BLE device not found')); + }, timeout); + }); + } + if (!foundPeripheral) { + stderr.write('BLE device not found\n'); + throw 1; + } + // Connect if not already connected + if (!foundPeripheral.state || foundPeripheral.state !== 'connected') { + await foundPeripheral.connectAsync(); + } + stderr.write('Connected to BLE device.\n'); + // Discover services and characteristics + const { characteristics } = await foundPeripheral.discoverSomeServicesAndCharacteristicsAsync([ + '00ff' + ], [ + 'ff01' + ]); + const char = characteristics.find(c => c.uuid === 'ff01'); + if (!char) { + stderr.write('BLE characteristic ff01 not found\n'); + await foundPeripheral.disconnectAsync(); + throw 1; + } + // Create JacDevice with BLEStream + device = new JacDevice(new BLEStream(char)); + stderr.write('JacDevice BLE connection established.\n'); + // Cleanup on exit + const exitHandler = async () => { + try { await foundPeripheral?.disconnectAsync(); } catch {} + await cleanup(); + try { await device?.destroy(); } catch {} + }; + process.once('SIGINT', exitHandler); + process.once('SIGQUIT', exitHandler); + process.once('SIGTERM', exitHandler); + } finally { + await cleanup(); + } } if (!device) { diff --git a/src/link/streams/bleStream.ts b/src/link/streams/bleStream.ts new file mode 100644 index 0000000..8128ab6 --- /dev/null +++ b/src/link/streams/bleStream.ts @@ -0,0 +1,54 @@ +// BLEStream implements Duplex for BLE characteristic +import { Characteristic } from "@stoprocent/noble"; +import { Duplex } from "../stream.js"; + +export class BLEStream implements Duplex { + private characteristic: Characteristic; + private callbacks: { + "data"?: (data: Buffer) => void, + "error"?: (err: any) => void, + "end"?: () => void + } = {}; + private isOpen = true; + + constructor(characteristic: Characteristic) { + this.characteristic = characteristic; + this.characteristic.on('data', (data: Buffer) => { + if (this.callbacks["data"]) this.callbacks["data"](data); + }); + this.characteristic.on('error', (err: any) => { + if (this.callbacks["error"]) this.callbacks["error"](err); + }); + this.characteristic.subscribe(); + } + + public put(c: number): void { + this.write(Buffer.from([c])); + } + + public write(buf: Buffer): void { + if (!this.isOpen) throw new Error('BLEStream is closed'); + this.characteristic.write(buf, false, (err: Error | undefined) => { + if (err && this.callbacks["error"]) this.callbacks["error"](err); + }); + } + + public onData(callback?: (data: Buffer) => void): void { + this.callbacks["data"] = callback; + } + + public onEnd(callback?: () => void): void { + this.callbacks["end"] = callback; + } + + public onError(callback?: (err: any) => void): void { + this.callbacks["error"] = callback; + } + + public destroy(): Promise { + this.isOpen = false; + this.characteristic.unsubscribe(); + if (this.callbacks["end"]) this.callbacks["end"](); + return Promise.resolve(); + } +} diff --git a/src/link/tsconfig.json b/src/link/tsconfig.json index 59ed5ee..c8df7c9 100644 --- a/src/link/tsconfig.json +++ b/src/link/tsconfig.json @@ -8,6 +8,7 @@ "stream.ts", "streams/serialStream.ts", "streams/socketStream.ts", + "streams/bleStream.ts", "encoders/interface.ts", "encoders/cobs.ts", ], diff --git a/tsconfig.json b/tsconfig.json index 0c2262f..925bee5 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -2,7 +2,8 @@ "extends": "./tsconfig-base.json", "files": [], "include": [ - "unit/**/*.ts" + "unit/**/*.ts", + "src/link/streams/bleStream.ts" ], "references": [ { From 91176c2c4f05c2fc9a8e17c1278b0fd520db8c89 Mon Sep 17 00:00:00 2001 From: Jakub Andrysek Date: Mon, 30 Jun 2025 02:01:17 +0200 Subject: [PATCH 4/6] feat: add BleClient class for improved BLE device connection handling and add uuid flag --- src/commands/list-ble.ts | 17 ++++-- src/commands/util.ts | 88 ++++--------------------------- src/link/ble/bleClient.ts | 108 ++++++++++++++++++++++++++++++++++++++ src/link/tsconfig.json | 1 + 4 files changed, 131 insertions(+), 83 deletions(-) create mode 100644 src/link/ble/bleClient.ts diff --git a/src/commands/list-ble.ts b/src/commands/list-ble.ts index c12039a..a51c087 100644 --- a/src/commands/list-ble.ts +++ b/src/commands/list-ble.ts @@ -5,16 +5,18 @@ import noble, { Peripheral } from '@stoprocent/noble'; const cmd = new Command("List available BLE devices", { action: async (options: Record, args: Record) => { const timeout = options["timeout"] ? Number(options["timeout"]) : 4000; - const table: { name: string, rssi: string }[] = [{ name: "Name", rssi: "RSSI" }]; + const showUuids = !!options["show-uuids"]; + const table: { name: string, rssi: string, uuid?: string }[] = [{ name: "Name", rssi: "RSSI", uuid: showUuids ? "UUID" : undefined }]; const discovered = new Set(); function handleDiscovery(peripheral: Peripheral) { const name = peripheral.advertisement.localName || "(no name)"; const rssi = peripheral.rssi.toString(); + const uuid = peripheral.id; if (discovered.has(peripheral.id)) return; if (peripheral.connectable === false) return; discovered.add(peripheral.id); - table.push({ name, rssi }); + table.push(showUuids ? { name, rssi, uuid } : { name, rssi }); } try { @@ -31,15 +33,21 @@ const cmd = new Command("List available BLE devices", { await noble.stopScanningAsync(); noble.stop(); let maxNameLength = 0; + let maxUuidLength = 0; for (const row of table) { maxNameLength = Math.max(maxNameLength, row.name.length); + if (showUuids && row.uuid) maxUuidLength = Math.max(maxUuidLength, row.uuid.length); } let first = true; for (const row of table) { - stdout.write(row.name.padEnd(maxNameLength) + " " + row.rssi + "\n"); + let line = row.name.padEnd(maxNameLength) + " " + row.rssi.padEnd(4); + if (showUuids && row.uuid) line += " " + row.uuid.padEnd(maxUuidLength); + stdout.write(line + "\n"); if (first) { first = false; - stdout.write("-".repeat(maxNameLength) + " " + "-".repeat(4) + "\n"); + let sep = "-".repeat(maxNameLength) + " " + "-".repeat(4); + if (showUuids) sep += " " + "-".repeat(maxUuidLength); + stdout.write(sep + "\n"); } } } @@ -47,6 +55,7 @@ const cmd = new Command("List available BLE devices", { }, options: { "timeout": new Opt("Scan timeout in milliseconds (default: 4000)", { defaultValue: "4000" }), + "show-uuids": new Opt("Show UUIDs of discovered devices", { isFlag: true }), }, }); diff --git a/src/commands/util.ts b/src/commands/util.ts index 738e432..75a8ddb 100644 --- a/src/commands/util.ts +++ b/src/commands/util.ts @@ -6,8 +6,8 @@ import { stderr, stdout } from "process"; import { logger } from "../util/logger.js"; import { Env } from "./lib/command.js"; import readline from "readline"; -import noble, { Peripheral } from '@stoprocent/noble'; import { BLEStream } from "../link/streams/bleStream.js"; +import { BleClient } from "../link/ble/bleClient.js"; export async function defaultPort(value?: string): Promise { @@ -128,86 +128,16 @@ export async function getDevice(port: string | undefined, baudrate: string | und else if (where.type === "ble") { // BLE connection by localName or UUID const target = where.value; - stderr.write(`Connecting to BLE device: ${target}\n`); - await noble.waitForPoweredOnAsync(); - let foundPeripheral: Peripheral | undefined; - let cleanupDone = false; - const cleanup = async () => { - if (cleanupDone) return; - cleanupDone = true; - try { await noble.stopScanningAsync(); } catch {} - noble.removeAllListeners('discover'); - }; - // Helper: is UUID (32 hex chars) - const isUuid = /^[0-9a-fA-F]{32}$/.test(target.replace(/-/g, "")); + const bleClient = new BleClient(); + let char; try { - if (isUuid) { - // Try direct connection by UUID - try { - foundPeripheral = await noble.connectAsync(target); - stderr.write(`Direct connection successful to: ${foundPeripheral.id}\n`); - } catch (err) { - const msg = err instanceof Error ? err.message : String(err); - stderr.write(`Direct connection failed: ${msg}\n`); - } - } - if (!foundPeripheral) { - // Scan for device by name or UUID - await noble.startScanningAsync(); - const timeout = 10000; - await new Promise((resolve, reject) => { - const onDiscover = async (peripheral: Peripheral) => { - if (peripheral.id === target || peripheral.advertisement.localName === target) { - foundPeripheral = peripheral; - await noble.stopScanningAsync(); - noble.removeListener('discover', onDiscover); - resolve(); - } - }; - noble.on('discover', onDiscover); - setTimeout(() => { - noble.removeListener('discover', onDiscover); - noble.stopScanningAsync(); - if (!foundPeripheral) reject(new Error('BLE device not found')); - }, timeout); - }); - } - if (!foundPeripheral) { - stderr.write('BLE device not found\n'); - throw 1; - } - // Connect if not already connected - if (!foundPeripheral.state || foundPeripheral.state !== 'connected') { - await foundPeripheral.connectAsync(); - } - stderr.write('Connected to BLE device.\n'); - // Discover services and characteristics - const { characteristics } = await foundPeripheral.discoverSomeServicesAndCharacteristicsAsync([ - '00ff' - ], [ - 'ff01' - ]); - const char = characteristics.find(c => c.uuid === 'ff01'); - if (!char) { - stderr.write('BLE characteristic ff01 not found\n'); - await foundPeripheral.disconnectAsync(); - throw 1; - } - // Create JacDevice with BLEStream - device = new JacDevice(new BLEStream(char)); - stderr.write('JacDevice BLE connection established.\n'); - // Cleanup on exit - const exitHandler = async () => { - try { await foundPeripheral?.disconnectAsync(); } catch {} - await cleanup(); - try { await device?.destroy(); } catch {} - }; - process.once('SIGINT', exitHandler); - process.once('SIGQUIT', exitHandler); - process.once('SIGTERM', exitHandler); - } finally { - await cleanup(); + char = await bleClient.connect(target); + } catch (err) { + stderr.write((err instanceof Error ? err.message : String(err)) + "\n"); + throw 1; } + device = new JacDevice(new BLEStream(char)); + stderr.write('JacDevice BLE connection established.\n'); } if (!device) { diff --git a/src/link/ble/bleClient.ts b/src/link/ble/bleClient.ts new file mode 100644 index 0000000..623748f --- /dev/null +++ b/src/link/ble/bleClient.ts @@ -0,0 +1,108 @@ +import noble, { Peripheral, Characteristic } from '@stoprocent/noble'; +import { stderr } from 'process'; + +export class BleClient { + private foundPeripheral: Peripheral | undefined; + private cleanupDone = false; + private exitHandler: (() => Promise) | undefined; + + async connect(target: string, serviceUUID = '00ff', charUUID = 'ff01', scanTimeout = 10000): Promise { + stderr.write(`Connecting to BLE device: ${target}\n`); + await noble.waitForPoweredOnAsync(); + this.foundPeripheral = undefined; + this.cleanupDone = false; + try { + if (this.isUuid(target)) { + await this.tryDirectConnect(target); + } + if (!this.foundPeripheral) { + await this.scanForPeripheral(target, scanTimeout); + } + if (!this.foundPeripheral) { + stderr.write('BLE device not found\n'); + throw new Error('BLE device not found'); + } + await this.connectPeripheral(); + stderr.write('Connected to BLE device.\n'); + const char = await this.getCharacteristic(serviceUUID, charUUID); + this.setupExitCleanup(); + return char; + } finally { + await this.cleanup(); + } + } + + private isUuid(target: string): boolean { + return /^[0-9a-fA-F]{32}$/.test(target.replace(/-/g, "")); + } + + private async tryDirectConnect(target: string) { + try { + this.foundPeripheral = await noble.connectAsync(target); + stderr.write(`Direct connection successful to: ${this.foundPeripheral.id}\n`); + } catch (err) { + const msg = err instanceof Error ? err.message : String(err); + stderr.write(`Direct connection failed: ${msg}\n`); + } + } + + private async scanForPeripheral(target: string, scanTimeout: number) { + await noble.startScanningAsync(); + await new Promise((resolve, reject) => { + const onDiscover = async (peripheral: Peripheral) => { + if (peripheral.id === target || peripheral.advertisement.localName === target) { + this.foundPeripheral = peripheral; + await noble.stopScanningAsync(); + noble.removeListener('discover', onDiscover); + resolve(); + } + }; + noble.on('discover', onDiscover); + setTimeout(() => { + noble.removeListener('discover', onDiscover); + noble.stopScanningAsync(); + if (!this.foundPeripheral) reject(new Error('BLE device not found')); + }, scanTimeout); + }); + } + + private async connectPeripheral() { + if (!this.foundPeripheral) return; + if (!this.foundPeripheral.state || this.foundPeripheral.state !== 'connected') { + await this.foundPeripheral.connectAsync(); + } + } + + private async getCharacteristic(serviceUUID: string, charUUID: string): Promise { + if (!this.foundPeripheral) throw new Error('No peripheral connected'); + const { characteristics } = await this.foundPeripheral.discoverSomeServicesAndCharacteristicsAsync([ + serviceUUID + ], [ + charUUID + ]); + const char = characteristics.find(c => c.uuid === charUUID); + if (!char) { + stderr.write(`BLE characteristic ${charUUID} not found\n`); + await this.foundPeripheral.disconnectAsync(); + throw new Error(`BLE characteristic ${charUUID} not found`); + } + return char; + } + + private setupExitCleanup() { + this.exitHandler = async () => { + try { await this.foundPeripheral?.disconnectAsync(); } catch {} + await this.cleanup(); + }; + process.once('SIGINT', this.exitHandler); + process.once('SIGQUIT', this.exitHandler); + process.once('SIGTERM', this.exitHandler); + } + + async cleanup() { + if (this.cleanupDone) return; + this.cleanupDone = true; + try { await noble.stopScanningAsync(); } catch {} + noble.removeAllListeners('discover'); + } +} diff --git a/src/link/tsconfig.json b/src/link/tsconfig.json index c8df7c9..7cf9a11 100644 --- a/src/link/tsconfig.json +++ b/src/link/tsconfig.json @@ -11,6 +11,7 @@ "streams/bleStream.ts", "encoders/interface.ts", "encoders/cobs.ts", + "ble/bleClient.ts", ], "references": [ { From b61f5f67068bbd6afa84865c1a42ca43f301836e Mon Sep 17 00:00:00 2001 From: Jakub Andrysek Date: Mon, 30 Jun 2025 02:07:41 +0200 Subject: [PATCH 5/6] feat: implement BLE device scanning functionality and add bleScan module --- src/commands/list-ble.ts | 64 +++++++++++++--------------------------- src/link/ble/bleScan.ts | 37 +++++++++++++++++++++++ src/link/tsconfig.json | 1 + 3 files changed, 59 insertions(+), 43 deletions(-) create mode 100644 src/link/ble/bleScan.ts diff --git a/src/commands/list-ble.ts b/src/commands/list-ble.ts index a51c087..0c1d09b 100644 --- a/src/commands/list-ble.ts +++ b/src/commands/list-ble.ts @@ -1,54 +1,32 @@ import { Arg, Command, Opt } from "./lib/command.js"; import { stdout } from "process"; -import noble, { Peripheral } from '@stoprocent/noble'; +import { scanBleDevices } from "../link/ble/bleScan.js"; const cmd = new Command("List available BLE devices", { action: async (options: Record, args: Record) => { const timeout = options["timeout"] ? Number(options["timeout"]) : 4000; const showUuids = !!options["show-uuids"]; - const table: { name: string, rssi: string, uuid?: string }[] = [{ name: "Name", rssi: "RSSI", uuid: showUuids ? "UUID" : undefined }]; - const discovered = new Set(); - - function handleDiscovery(peripheral: Peripheral) { - const name = peripheral.advertisement.localName || "(no name)"; - const rssi = peripheral.rssi.toString(); - const uuid = peripheral.id; - if (discovered.has(peripheral.id)) return; - if (peripheral.connectable === false) return; - discovered.add(peripheral.id); - table.push(showUuids ? { name, rssi, uuid } : { name, rssi }); + // Scan for BLE devices + let table = await scanBleDevices(timeout, showUuids); + // Add header row + table = [{ name: "Name", rssi: "RSSI", uuid: showUuids ? "UUID" : undefined }, ...table]; + // Output formatting + let maxNameLength = 0; + let maxUuidLength = 0; + for (const row of table) { + maxNameLength = Math.max(maxNameLength, row.name.length); + if (showUuids && row.uuid) maxUuidLength = Math.max(maxUuidLength, row.uuid.length); } - - try { - console.log('Waiting for Bluetooth adapter...'); - await noble.waitForPoweredOnAsync(); - console.log('Bluetooth adapter ready'); - console.log(`Scanning BLE devices for ${timeout / 1000} seconds...`); - await noble.startScanningAsync([], false); - noble.on('discover', handleDiscovery); - await new Promise(resolve => setTimeout(resolve, timeout)); - } catch (error) { - console.error('Error discovering BLE devices:', error); - } finally { - await noble.stopScanningAsync(); - noble.stop(); - let maxNameLength = 0; - let maxUuidLength = 0; - for (const row of table) { - maxNameLength = Math.max(maxNameLength, row.name.length); - if (showUuids && row.uuid) maxUuidLength = Math.max(maxUuidLength, row.uuid.length); - } - let first = true; - for (const row of table) { - let line = row.name.padEnd(maxNameLength) + " " + row.rssi.padEnd(4); - if (showUuids && row.uuid) line += " " + row.uuid.padEnd(maxUuidLength); - stdout.write(line + "\n"); - if (first) { - first = false; - let sep = "-".repeat(maxNameLength) + " " + "-".repeat(4); - if (showUuids) sep += " " + "-".repeat(maxUuidLength); - stdout.write(sep + "\n"); - } + let first = true; + for (const row of table) { + let line = row.name.padEnd(maxNameLength) + " " + row.rssi.padEnd(4); + if (showUuids && row.uuid) line += " " + row.uuid.padEnd(maxUuidLength); + stdout.write(line + "\n"); + if (first) { + first = false; + let sep = "-".repeat(maxNameLength) + " " + "-".repeat(4); + if (showUuids) sep += " " + "-".repeat(maxUuidLength); + stdout.write(sep + "\n"); } } return; diff --git a/src/link/ble/bleScan.ts b/src/link/ble/bleScan.ts new file mode 100644 index 0000000..269dbea --- /dev/null +++ b/src/link/ble/bleScan.ts @@ -0,0 +1,37 @@ +import noble, { Peripheral } from '@stoprocent/noble'; + +export interface BleScanResult { + name: string; + rssi: string; + uuid?: string; +} + +export async function scanBleDevices(timeout: number = 4000, showUuids: boolean = false): Promise { + const table: BleScanResult[] = []; + const discovered = new Set(); + + function handleDiscovery(peripheral: Peripheral) { + const name = peripheral.advertisement.localName || "(no name)"; + const rssi = peripheral.rssi.toString(); + const uuid = peripheral.id; + if (discovered.has(peripheral.id)) return; + if (peripheral.connectable === false) return; + discovered.add(peripheral.id); + table.push(showUuids ? { name, rssi, uuid } : { name, rssi }); + } + + try { + console.log('Waiting for Bluetooth adapter...'); + await noble.waitForPoweredOnAsync(); + console.log('Bluetooth adapter ready'); + console.log(`Scanning BLE devices for ${timeout / 1000} seconds...`); + await noble.startScanningAsync([], false); + noble.on('discover', handleDiscovery); + await new Promise(resolve => setTimeout(resolve, timeout)); + } finally { + await noble.stopScanningAsync(); + noble.stop(); + console.log(`Scan finished. Devices found: ${table.length}`); + } + return table; +} diff --git a/src/link/tsconfig.json b/src/link/tsconfig.json index 7cf9a11..f156657 100644 --- a/src/link/tsconfig.json +++ b/src/link/tsconfig.json @@ -12,6 +12,7 @@ "encoders/interface.ts", "encoders/cobs.ts", "ble/bleClient.ts", + "ble/bleScan.ts", ], "references": [ { From 89311aae646b6cc5660ea217729965e2a1aa9555 Mon Sep 17 00:00:00 2001 From: Jakub Andrysek Date: Mon, 30 Jun 2025 22:45:21 +0200 Subject: [PATCH 6/6] feat: add BLE command functionalities including get, disable, enable stream, and set name --- src/commands/ble.ts | 149 +++++++++++++++++++++++++++++++++++++ src/commands/index.ts | 5 ++ src/commands/tsconfig.json | 1 + 3 files changed, 155 insertions(+) create mode 100644 src/commands/ble.ts diff --git a/src/commands/ble.ts b/src/commands/ble.ts new file mode 100644 index 0000000..0bd17c3 --- /dev/null +++ b/src/commands/ble.ts @@ -0,0 +1,149 @@ +import { Arg, Command, Env } from "./lib/command.js"; +import { stdout, stderr } from "process"; +import { getDevice } from "./util.js"; + + +enum BleKvNs { + Main = "ble_cfg", +} + +enum BleKeys { + Mode = "mode", + Name = "name", +} + +enum BleMode { + DISABLED, + ENABLED_STREAM, + + // TODO: Can we add this for the future? (use will handle the BLE yourself) + UNMANAGED, +} + +export const bleGet = new Command("Display current BLE config", { + action: async (options: Record, args: Record, env: Env) => { + const port = options["port"] as string; + const baudrate = options["baudrate"] as string; + const socket = options["socket"] as string; + const ble = options["ble"] as string | undefined; + + const device = await getDevice(port, baudrate, socket, ble, env); + + await new Promise((resolve) => setTimeout(resolve, 1000)); + stdout.write("\n-----\n"); + + await device.controller.lock().catch((err) => { + stderr.write("Error locking device: " + err + "\n"); + throw 1; + }); + + const mode = await device.controller.configGetInt(BleKvNs.Main, BleKeys.Mode); + const name = await device.controller.configGetString(BleKvNs.Main, BleKeys.Name); + + stdout.write(`BLE Mode: ${BleMode[mode]} +Device Name: ${name} +`); + + await device.controller.unlock().catch((err) => { + stderr.write("Error unlocking device: " + err + "\n"); + throw 1; + }); + }, + chainable: true +}); + + +export const bleDisable = new Command("Disable WiFi", { + action: async (options: Record, args: Record, env: Env) => { + const port = options["port"] as string; + const baudrate = options["baudrate"] as string; + const socket = options["socket"] as string; + const ble = options["ble"] as string | undefined; + const device = await getDevice(port, baudrate, socket, ble, env); + + await device.controller.lock().catch((err) => { + stderr.write("Error locking device: " + err + "\n"); + throw 1; + }); + + await device.controller.configSetInt(BleKvNs.Main, BleKeys.Mode, BleMode.DISABLED); + + await device.controller.unlock().catch((err) => { + stderr.write("Error unlocking device: " + err + "\n"); + throw 1; + }); + + stdout.write("Wifi config changed.\n"); + }, + chainable: true +}); + + +export const bleEnableStream = new Command("Enable BLE stream mode", { + action: async (options: Record, args: Record, env: Env) => { + const port = options["port"] as string; + const baudrate = options["baudrate"] as string; + const socket = options["socket"] as string; + const ble = options["ble"] as string | undefined; + const device = await getDevice(port, baudrate, socket, ble, env); + + await device.controller.lock().catch((err) => { + stderr.write("Error locking device: " + err + "\n"); + throw 1; + }); + + await device.controller.configSetInt(BleKvNs.Main, BleKeys.Mode, BleMode.ENABLED_STREAM); + + await device.controller.unlock().catch((err) => { + stderr.write("Error unlocking device: " + err + "\n"); + throw 1; + }); + + stdout.write("Wifi config changed.\n"); + }, + chainable: true +}); + + +export const bleSetName = new Command("Set BLE device name", { + action: async (options: Record, args: Record, env: Env) => { + const port = options["port"] as string; + const baudrate = options["baudrate"] as string; + const socket = options["socket"] as string; + const ble = options["ble"] as string | undefined; + + const name = args["name"] as string | undefined; + + if (!name) { + stderr.write("Name is required\n"); + throw 1; + } + + if (name && name.length >= 20) { + stderr.write("Name is too long\n"); + throw 1; + } + + const device = await getDevice(port, baudrate, socket, ble, env); + + await device.controller.lock().catch((err) => { + stderr.write("Error locking device: " + err + "\n"); + throw 1; + }); + + // TODO: Can we enable stream mode here? + await device.controller.configSetInt(BleKvNs.Main, BleKeys.Mode, BleMode.ENABLED_STREAM); + await device.controller.configSetString(BleKvNs.Main, BleKeys.Name, name); + + await device.controller.unlock().catch((err) => { + stderr.write("Error unlocking device: " + err + "\n"); + throw 1; + }); + + stdout.write("Wifi config changed.\n"); + }, + args: [ + new Arg("name", "Name of the BLE device", { required: true }), + ], + chainable: true +}); \ No newline at end of file diff --git a/src/commands/index.ts b/src/commands/index.ts index 96a8c4c..5211e07 100644 --- a/src/commands/index.ts +++ b/src/commands/index.ts @@ -70,6 +70,7 @@ import resourcesRead from "./resources-read.js"; import getExamples from "./get-examples.js"; import listBle from "./list-ble.js"; import { wifiAdd, wifiRemove, wifiGet, wifiSetAp, wifiSetSta, wifiDisable } from "./wifi.js"; +import { bleGet, bleDisable, bleEnableStream, bleSetName} from "./ble.js"; jac.addCommand("list-ports", listPorts); jac.addCommand("list-ble", listBle); @@ -106,6 +107,10 @@ jac.addCommand("wifi-rm", wifiRemove); jac.addCommand("wifi-sta", wifiSetSta); jac.addCommand("wifi-disable", wifiDisable); +jac.addCommand("ble-get", bleGet); +jac.addCommand("ble-disable", bleDisable); +jac.addCommand("ble-enable-stream", bleEnableStream); +jac.addCommand("ble-set-name", bleSetName); const args = process.argv.slice(2); diff --git a/src/commands/tsconfig.json b/src/commands/tsconfig.json index 1faff37..a162bf6 100644 --- a/src/commands/tsconfig.json +++ b/src/commands/tsconfig.json @@ -26,6 +26,7 @@ "resources-read.ts", "get-examples.ts", "wifi.ts", + "ble.ts", "index.ts", "lib/command.ts", ],