diff --git a/.github/workflows/build.yml b/.github/workflows/ci.yml
similarity index 73%
rename from .github/workflows/build.yml
rename to .github/workflows/ci.yml
index 399b649..b7583b5 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/ci.yml
@@ -1,4 +1,4 @@
-name: Build
+name: CI
permissions:
contents: read
pull-requests: write
@@ -22,15 +22,15 @@ jobs:
runs-on: ubuntu-latest
outputs:
version: ${{ steps.get_version.outputs.version }}
- short_sha: ${{ steps.get_sha.outputs.short_sha }}
steps:
- name: Checkout
- uses: actions/checkout@v4
+ uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
with:
persist-credentials: false
+ fetch-depth: 0
- name: Use Node.js
- uses: actions/setup-node@v4
+ uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
with:
node-version: 22.14.0
@@ -41,16 +41,16 @@ jobs:
id: get_version
run: |
set -euo pipefail
- OUTPUT="$(npx semantic-release --dry-run --no-ci 2>/dev/null || true)"
- VERSION="$(echo "$OUTPUT" | awk '/next release version is/ {print $5}' | tail -n1)"
+ MODE=""
+ if [ "${{ github.ref }}" = "refs/heads/main" ]; then MODE="--release"; fi
+ VERSION=$(npx next-version-helper $MODE)
if [ -z "$VERSION" ]; then
- VERSION="$(node -p "require('./package.json').version")"
+ echo "semantic-release did not return a next version." >&2
+ exit 1
fi
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
-
- - name: Get Short SHA
- id: get_sha
- run: echo "short_sha=${GITHUB_SHA::7}" >> "$GITHUB_OUTPUT"
+ env:
+ DEBUG: semantic-release-next-version,semantic-release:*
test:
name: Test (${{ matrix.os }}, node-${{ matrix.node }})
@@ -62,12 +62,12 @@ jobs:
runs-on: ${{ matrix.os }}
steps:
- name: Checkout
- uses: actions/checkout@v4
+ uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
with:
token: ${{ secrets.GITHUB_TOKEN }}
- name: Use Node.js
- uses: actions/setup-node@v4
+ uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
with:
node-version: ${{ matrix.node }}
@@ -92,12 +92,12 @@ jobs:
needs: [test, determine-version]
steps:
- name: Checkout
- uses: actions/checkout@v4
+ uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
with:
persist-credentials: false
- name: Use Node.js
- uses: actions/setup-node@v4
+ uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
with:
node-version: 22.14.0
@@ -105,7 +105,7 @@ jobs:
run: npm ci
- name: Set Version
- run: npm pkg set version="${{ needs.determine-version.outputs.version }}-preview-${{ needs.determine-version.outputs.short_sha }}"
+ run: npm pkg set version="${{ needs.determine-version.outputs.version }}"
- name: Package
run: npm run package
@@ -114,28 +114,32 @@ jobs:
uses: actions/upload-artifact@v4
with:
if-no-files-found: error
- name: esp-exception-decoder-${{ needs.determine-version.outputs.version }}-preview-${{ needs.determine-version.outputs.short_sha }}
+ name: esp-exception-decoder-${{ needs.determine-version.outputs.version }}
path: '*.vsix'
release:
name: Release
if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }}
runs-on: ubuntu-latest
- needs: [test]
+ needs: [determine-version, test]
steps:
- name: Checkout
- uses: actions/checkout@v4
+ uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
with:
persist-credentials: false
+ fetch-depth: 0
- name: Use Node.js
- uses: actions/setup-node@v4
+ uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
with:
node-version: 22.14.0
- name: Install Dependencies
run: npm ci
+ - name: Set Version
+ run: npm pkg set version="${{ needs.determine-version.outputs.version }}"
+
- name: Package
run: npm run package
diff --git a/.gitignore b/.gitignore
index 932784e..297e4b4 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,5 +3,5 @@ out
dist
node_modules
.vscode-test
+coverage
*.vsix
-.nyc_output
diff --git a/.vscode-test.mjs b/.vscode-test.mjs
new file mode 100644
index 0000000..5f5ee6e
--- /dev/null
+++ b/.vscode-test.mjs
@@ -0,0 +1,36 @@
+const noTestTimeout =
+ typeof process.env.NO_TEST_TIMEOUT === 'string' &&
+ /true/i.test(process.env.NO_TEST_TIMEOUT)
+
+export default {
+ tests: [
+ {
+ label: 'extension-host',
+ files: 'out/test/**/*.test.js',
+ extensionDevelopmentPath: process.cwd(),
+ skipExtensionDependencies: true,
+ env: {
+ TEST_DEBUG: process.env.DEBUG,
+ NO_COLOR: undefined,
+ FORCE_COLOR: '1',
+ MOCHA_COLORS: '1',
+ },
+ mocha: {
+ ui: 'bdd',
+ color: true,
+ timeout: noTestTimeout ? 0 : 2_000,
+ },
+ },
+ ],
+ coverage: {
+ reporter: ['text-summary', 'html', 'lcovonly'],
+ includeAll: true,
+ exclude: [
+ 'src/test/**',
+ 'out/test/**',
+ '**/*.test.*',
+ '**/test/**',
+ '**/__tests__/**',
+ ],
+ },
+}
diff --git a/.vscode/launch.json b/.vscode/launch.json
index f5a66a3..df26216 100644
--- a/.vscode/launch.json
+++ b/.vscode/launch.json
@@ -7,7 +7,15 @@
"request": "launch",
"args": ["--extensionDevelopmentPath=${workspaceFolder}"],
"outFiles": ["${workspaceFolder}/dist/**/*.js"],
- "preLaunchTask": "${defaultBuildTask}"
+ "sourceMaps": true,
+ "sourceMapPathOverrides": {
+ "webpack:/esp-exception-decoder/*": "${workspaceFolder}/*",
+ "webpack://esp-exception-decoder/*": "${workspaceFolder}/*",
+ "webpack:///./*": "${workspaceFolder}/*",
+ "webpack://*/./*": "${workspaceFolder}/*",
+ "webpack://*/*": "${workspaceFolder}/*"
+ },
+ "preLaunchTask": "build:webpack:debug"
},
{
"name": "Extension Tests",
diff --git a/.vscode/tasks.json b/.vscode/tasks.json
index 598f058..dc0889c 100644
--- a/.vscode/tasks.json
+++ b/.vscode/tasks.json
@@ -2,6 +2,19 @@
"version": "2.0.0",
"tasks": [
{
+ "label": "build:webpack",
+ "type": "npm",
+ "script": "build",
+ "problemMatcher": []
+ },
+ {
+ "label": "build:webpack:debug",
+ "type": "npm",
+ "script": "build:debug",
+ "problemMatcher": []
+ },
+ {
+ "label": "watch:webpack",
"type": "npm",
"script": "watch",
"problemMatcher": "$ts-webpack-watch",
@@ -28,7 +41,7 @@
},
{
"label": "tasks: watch-tests",
- "dependsOn": ["npm: watch", "npm: watch-tests"],
+ "dependsOn": ["watch:webpack", "npm: watch-tests"],
"problemMatcher": []
},
{
diff --git a/.vscodeignore b/.vscodeignore
index a51cca4..be842c8 100644
--- a/.vscodeignore
+++ b/.vscodeignore
@@ -17,4 +17,5 @@ eslint.config.*
.github/**
release.config.js
CHANGELOG.md
-.nyc_output
+.vscode-test.mjs
+coverage/**
diff --git a/README.md b/README.md
index ad10a49..51cacc7 100644
--- a/README.md
+++ b/README.md
@@ -1,38 +1,80 @@
# ESP Exception Decoder
-ESP Exception Decoder helps you understand crash stack traces and backtraces from ESP8266 and ESP32 boards.
+ESP Exception Decoder helps you analyze ESP8266 and ESP32 crashes by decoding stack traces, backtraces, and panic output. It supports both automatic crash capture (recommended) and manual decoding workflows.
-
+
- **VS Code (recommended):** Runs as a Visual Studio Code extension and integrates with [BoardLab](https://github.com/dankeboy36/boardlab).
-- **Arduino IDE 2.2.x (deprecated):** Older **1.x** versions still work when installed manually as a VSIX.
+- **Arduino IDE 2.2.x (deprecated):** Only the **1.x** release line is supported via manual VSIX installation.
> [!NOTE]
> **Arduino IDE 2.2.x support is deprecated.**
>
> Starting with version **2.x**, ESP Exception Decoder targets **Visual Studio Code** and requires **BoardLab**.
>
-> Arduino IDE 2.2.x users must stay on the **1.x** release line and install the extension manually as a VSIX.
+> Arduino IDE 2.2.x users must stay on the **1.x** release line and install the extension manually as a VSIX file.
This extension uses the [TraceBreaker CLI](https://github.com/dankeboy36/trbr) internally. This project is inspired by the original Java-based [ESP8266/ESP32 Exception Stack Trace Decoder](https://github.com/me-no-dev/EspExceptionDecoder). The RISC-V decoder implementation originates from the [`esp_idf_monitor`](https://github.com/espressif/esp-idf-monitor/blob/fae383ecf281655abaa5e65433f671e274316d10/esp_idf_monitor/gdb_panic_server.py).
## Usage
+### Recommended: ESP Crash Capturer (VS Code + BoardLab)
+
+1. In BoardLab, select the target **Sketch**, **Board** (`esp32` or `esp8266`), and **Port**.
+2. Compile and upload the sketch.
+ > **ⓘ** Recommended: compile with debug symbols for better stacktrace/source decoding.
+3. Open the **ESP Decoder** panel and the **ESP Crash Capturer** view.
+
+ > **ⓘ** If you cannot see the **ESP Crash Capturer** view, open the _Command Palette_ (Ctrl/⌘+Shift+P) and run `ESP Decoder: Focus on ESP Crash Capturer View`.
+
+4. Click **+** in the view title to create a crash capturer.
+
+ 
+
+5. Click **Start Capturing** on the capturer item (this enables crash detection for the current device session). The serial monitor is handled automatically.
+
+ 
+
+6. Reproduce the crash on your board while the monitor is running.
+7. Open the captured event with **Preview Crash Event**.
+
+ 
+
+8. If the capturer reports problems, open **Quick Fixes...** from the capturer item menu.
+
+ 
+
+> [!TIP]
+> FQBN mismatch warnings compare the capturer setup with the sketch/build configuration.
+> They do not verify the exact firmware currently flashed on the board from monitor output.
+> If things look out of sync, recompile, upload again, and sync the capturer target.
+
+The Capturer analyzes the serial monitor stream and groups repeated crash signatures. Each captured crash event can be previewed and decoded without manually copying stack traces. There is no need to stop capturing before compiling or uploading new firmware. Uploads are handled automatically, and a new crash event group is created for the newly flashed ELF. Previously recorded events can be cleared manually if desired.
+
+### Manual: Decoder Terminal (Legacy Workflow)
+
+This workflow is kept for compatibility and for users who already have copied crash output. It does not require a Capturer and works independently of the monitor integration.
+
+Use this if you already have copied crash text and want to decode it manually.
+
1. Compile the sketch.
2. Upload the sketch to the ESP8266/ESP32 board.
-3. Open the Monitor and wait for an exception.
-4. When an exception occurs, open the **ESP Exception Decoder terminal**:
- - Open the _Command Palette_ using Ctrl/⌘+Shift+P.
+3. Open the serial monitor and wait for a crash (panic output or backtrace).
+4. When a crash occurs, open the **ESP Exception Decoder terminal**:
+ - Open the _Command Palette_ with Ctrl/⌘+Shift+P.
- Run `ESP Exception Decoder: Show Decoder Terminal`.
-
5. Paste the exception stack trace or backtrace into the decoder terminal.
- > **ⓘ** For more details on copying and pasting in the terminal, see the VS Code documentation: https://code.visualstudio.com/docs/terminal/basics#copy-paste
+
+ 
+
+> [!TIP]
+> For terminal copy/paste details, see: https://code.visualstudio.com/docs/terminal/basics#_copy-paste
## Installation
### VS Code
-Install from the Visual Studio Code Marketplace.
+Install from the Visual Studio Code Marketplace (required for the 2.x release line).
- ESP Exception Decoder: https://marketplace.visualstudio.com/items?itemName=dankeboy36.esp-exception-decoder
diff --git a/eslint.config.mjs b/eslint.config.mjs
index 7ee8b21..9920094 100644
--- a/eslint.config.mjs
+++ b/eslint.config.mjs
@@ -8,7 +8,7 @@ export default [
...neostandard({
semi: false,
ts: true,
- ignores: ['out', 'dist', 'node_modules', '.vscode-test', '.nyc_output'],
+ ignores: ['out', 'dist', 'node_modules', '.vscode-test'],
}),
{
plugins: {
@@ -21,6 +21,7 @@ export default [
'@stylistic/comma-dangle': 'off',
'@stylistic/indent': 'off',
'@stylistic/no-tabs': 'off',
+ '@stylistic/brace-style': 'off',
'@stylistic/space-before-function-paren': [
'error',
{
diff --git a/package-lock.json b/package-lock.json
index 284c751..9cc6bd3 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -9,12 +9,14 @@
"version": "1.1.1",
"license": "MIT",
"dependencies": {
+ "@vscode/debugadapter": "^1.68.0",
"debug": "^4.4.3",
- "fqbn": "^1.3.1",
- "trbr": "^0.1.24"
+ "error-replay-debug-adapter": "^0.1.1",
+ "fqbn": "^1.4.0",
+ "markdown-escape": "^2.0.0",
+ "trbr": "^0.3.1"
},
"devDependencies": {
- "@istanbuljs/nyc-config-typescript": "^1.0.2",
"@semantic-release/changelog": "^6.0.3",
"@semantic-release/commit-analyzer": "^13.0.1",
"@semantic-release/exec": "^7.1.0",
@@ -22,34 +24,35 @@
"@semantic-release/github": "^12.0.0",
"@semantic-release/release-notes-generator": "^14.1.0",
"@types/debug": "^4.1.12",
- "@types/glob": "^9.0.0",
+ "@types/markdown-escape": "^1.1.3",
"@types/mocha": "^10.0.10",
"@types/node": "20.x",
"@types/temp": "^0.9.1",
"@types/vscode": "^1.78.0",
"@typescript-eslint/eslint-plugin": "^8.44.1",
"@typescript-eslint/parser": "^8.44.1",
+ "@vscode/test-cli": "^0.0.12",
"@vscode/test-electron": "^2.5.2",
- "@vscode/vsce": "^3.6.2",
+ "@vscode/vsce": "^3.7.1",
+ "boards-list": "^2.0.0",
"eslint": "^9.31.0",
"eslint-config-prettier": "^10.1.5",
"eslint-plugin-import": "^2.32.0",
"eslint-plugin-prettier": "^5.5.1",
- "glob": "^11.0.3",
"mocha": "^11.7.4",
"neostandard": "^0.12.2",
- "nyc": "^17.1.0",
"prettier": "^3.6.2",
"prettier-plugin-jsdoc": "^1.3.3",
"prettier-plugin-organize-imports": "^4.1.0",
"prettier-plugin-packagejson": "^2.5.18",
"rimraf": "^6.0.1",
"semantic-release": "^25.0.0",
+ "semantic-release-next-version": "^0.1.3",
"semantic-release-vsce": "^6.0.12",
"temp": "^0.9.4",
"ts-loader": "^9.5.4",
"typescript": "^5.0.4",
- "vscode-arduino-api": "^0.2.1",
+ "vscode-arduino-api": "^0.2.4",
"webpack": "^5.81.0",
"webpack-cli": "^5.0.2"
},
@@ -58,54 +61,51 @@
}
},
"node_modules/@actions/core": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/@actions/core/-/core-2.0.1.tgz",
- "integrity": "sha512-oBfqT3GwkvLlo1fjvhQLQxuwZCGTarTE5OuZ2Wg10hvhBj7LRIlF611WT4aZS6fDhO5ZKlY7lCAZTlpmyaHaeg==",
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/@actions/core/-/core-3.0.0.tgz",
+ "integrity": "sha512-zYt6cz+ivnTmiT/ksRVriMBOiuoUpDCJJlZ5KPl2/FRdvwU3f7MPh9qftvbkXJThragzUZieit2nyHUyw53Seg==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@actions/exec": "^2.0.0",
- "@actions/http-client": "^3.0.0"
+ "@actions/exec": "^3.0.0",
+ "@actions/http-client": "^4.0.0"
}
},
"node_modules/@actions/exec": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/@actions/exec/-/exec-2.0.0.tgz",
- "integrity": "sha512-k8ngrX2voJ/RIN6r9xB82NVqKpnMRtxDoiO+g3olkIUpQNqjArXrCQceduQZCQj3P3xm32pChRLqRrtXTlqhIw==",
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/@actions/exec/-/exec-3.0.0.tgz",
+ "integrity": "sha512-6xH/puSoNBXb72VPlZVm7vQ+svQpFyA96qdDBvhB8eNZOE8LtPf9L4oAsfzK/crCL8YZ+19fKYVnM63Sl+Xzlw==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@actions/io": "^2.0.0"
+ "@actions/io": "^3.0.2"
}
},
"node_modules/@actions/http-client": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-3.0.0.tgz",
- "integrity": "sha512-1s3tXAfVMSz9a4ZEBkXXRQD4QhY3+GAsWSbaYpeknPOKEeyRiU3lH+bHiLMZdo2x/fIeQ/hscL1wCkDLVM2DZQ==",
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-4.0.0.tgz",
+ "integrity": "sha512-QuwPsgVMsD6qaPD57GLZi9sqzAZCtiJT8kVBCDpLtxhL5MydQ4gS+DrejtZZPdIYyB1e95uCK9Luyds7ybHI3g==",
"dev": true,
"license": "MIT",
"dependencies": {
"tunnel": "^0.0.6",
- "undici": "^5.28.5"
+ "undici": "^6.23.0"
}
},
"node_modules/@actions/http-client/node_modules/undici": {
- "version": "5.29.0",
- "resolved": "https://registry.npmjs.org/undici/-/undici-5.29.0.tgz",
- "integrity": "sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg==",
+ "version": "6.23.0",
+ "resolved": "https://registry.npmjs.org/undici/-/undici-6.23.0.tgz",
+ "integrity": "sha512-VfQPToRA5FZs/qJxLIinmU59u0r7LXqoJkCzinq3ckNJp3vKEh7jTWN589YQ5+aoAC/TGRLyJLCPKcLQbM8r9g==",
"dev": true,
"license": "MIT",
- "dependencies": {
- "@fastify/busboy": "^2.0.0"
- },
"engines": {
- "node": ">=14.0"
+ "node": ">=18.17"
}
},
"node_modules/@actions/io": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/@actions/io/-/io-2.0.0.tgz",
- "integrity": "sha512-Jv33IN09XLO+0HS79aaODsvIRyduiF7NY/F6LYeK5oeUmrsz7aFdRphQjFoESF4jS7lMauDOttKALcpapVDIAg==",
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/@actions/io/-/io-3.0.2.tgz",
+ "integrity": "sha512-nRBchcMM+QK1pdjO7/idu86rbJI5YHUKCvKs0KxnSYbVe3F51UfGxuZX4Qy/fWlp6l7gWFwIkrOzN+oUK03kfw==",
"dev": true,
"license": "MIT"
},
@@ -258,22 +258,22 @@
}
},
"node_modules/@azure/msal-browser": {
- "version": "4.27.0",
- "resolved": "https://registry.npmjs.org/@azure/msal-browser/-/msal-browser-4.27.0.tgz",
- "integrity": "sha512-bZ8Pta6YAbdd0o0PEaL1/geBsPrLEnyY/RDWqvF1PP9RUH8EMLvUMGoZFYS6jSlUan6KZ9IMTLCnwpWWpQRK/w==",
+ "version": "4.28.2",
+ "resolved": "https://registry.npmjs.org/@azure/msal-browser/-/msal-browser-4.28.2.tgz",
+ "integrity": "sha512-6vYUMvs6kJxJgxaCmHn/F8VxjLHNh7i9wzfwPGf8kyBJ8Gg2yvBXx175Uev8LdrD1F5C4o7qHa2CC4IrhGE1XQ==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@azure/msal-common": "15.13.3"
+ "@azure/msal-common": "15.14.2"
},
"engines": {
"node": ">=0.8.0"
}
},
"node_modules/@azure/msal-common": {
- "version": "15.13.3",
- "resolved": "https://registry.npmjs.org/@azure/msal-common/-/msal-common-15.13.3.tgz",
- "integrity": "sha512-shSDU7Ioecya+Aob5xliW9IGq1Ui8y4EVSdWGyI1Gbm4Vg61WpP95LuzcY214/wEjSn6w4PZYD4/iVldErHayQ==",
+ "version": "15.14.2",
+ "resolved": "https://registry.npmjs.org/@azure/msal-common/-/msal-common-15.14.2.tgz",
+ "integrity": "sha512-n8RBJEUmd5QotoqbZfd+eGBkzuFI1KX6jw2b3WcpSyGjwmzoeI/Jb99opIBPHpb8y312NB+B6+FGi2ZVSR8yfA==",
"dev": true,
"license": "MIT",
"engines": {
@@ -281,13 +281,13 @@
}
},
"node_modules/@azure/msal-node": {
- "version": "3.8.4",
- "resolved": "https://registry.npmjs.org/@azure/msal-node/-/msal-node-3.8.4.tgz",
- "integrity": "sha512-lvuAwsDpPDE/jSuVQOBMpLbXuVuLsPNRwWCyK3/6bPlBk0fGWegqoZ0qjZclMWyQ2JNvIY3vHY7hoFmFmFQcOw==",
+ "version": "3.8.7",
+ "resolved": "https://registry.npmjs.org/@azure/msal-node/-/msal-node-3.8.7.tgz",
+ "integrity": "sha512-a+Xnrae+uwLnlw68bplS1X4kuJ9F/7K6afuMFyRkNIskhjgDezl5Fhrx+1pmAlDmC0VaaAxjRQMp1OmcqVwkIg==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@azure/msal-common": "15.13.3",
+ "@azure/msal-common": "15.14.2",
"jsonwebtoken": "^9.0.0",
"uuid": "^8.3.0"
},
@@ -296,13 +296,13 @@
}
},
"node_modules/@babel/code-frame": {
- "version": "7.27.1",
- "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz",
- "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==",
+ "version": "7.29.0",
+ "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz",
+ "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@babel/helper-validator-identifier": "^7.27.1",
+ "@babel/helper-validator-identifier": "^7.28.5",
"js-tokens": "^4.0.0",
"picocolors": "^1.1.1"
},
@@ -310,177 +310,6 @@
"node": ">=6.9.0"
}
},
- "node_modules/@babel/compat-data": {
- "version": "7.28.5",
- "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.5.tgz",
- "integrity": "sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/core": {
- "version": "7.28.5",
- "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.5.tgz",
- "integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/code-frame": "^7.27.1",
- "@babel/generator": "^7.28.5",
- "@babel/helper-compilation-targets": "^7.27.2",
- "@babel/helper-module-transforms": "^7.28.3",
- "@babel/helpers": "^7.28.4",
- "@babel/parser": "^7.28.5",
- "@babel/template": "^7.27.2",
- "@babel/traverse": "^7.28.5",
- "@babel/types": "^7.28.5",
- "@jridgewell/remapping": "^2.3.5",
- "convert-source-map": "^2.0.0",
- "debug": "^4.1.0",
- "gensync": "^1.0.0-beta.2",
- "json5": "^2.2.3",
- "semver": "^6.3.1"
- },
- "engines": {
- "node": ">=6.9.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/babel"
- }
- },
- "node_modules/@babel/core/node_modules/convert-source-map": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
- "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/@babel/core/node_modules/semver": {
- "version": "6.3.1",
- "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
- "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
- "dev": true,
- "license": "ISC",
- "bin": {
- "semver": "bin/semver.js"
- }
- },
- "node_modules/@babel/generator": {
- "version": "7.28.5",
- "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.5.tgz",
- "integrity": "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/parser": "^7.28.5",
- "@babel/types": "^7.28.5",
- "@jridgewell/gen-mapping": "^0.3.12",
- "@jridgewell/trace-mapping": "^0.3.28",
- "jsesc": "^3.0.2"
- },
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/helper-compilation-targets": {
- "version": "7.27.2",
- "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz",
- "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/compat-data": "^7.27.2",
- "@babel/helper-validator-option": "^7.27.1",
- "browserslist": "^4.24.0",
- "lru-cache": "^5.1.1",
- "semver": "^6.3.1"
- },
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/helper-compilation-targets/node_modules/lru-cache": {
- "version": "5.1.1",
- "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
- "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "yallist": "^3.0.2"
- }
- },
- "node_modules/@babel/helper-compilation-targets/node_modules/semver": {
- "version": "6.3.1",
- "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
- "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
- "dev": true,
- "license": "ISC",
- "bin": {
- "semver": "bin/semver.js"
- }
- },
- "node_modules/@babel/helper-compilation-targets/node_modules/yallist": {
- "version": "3.1.1",
- "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
- "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
- "dev": true,
- "license": "ISC"
- },
- "node_modules/@babel/helper-globals": {
- "version": "7.28.0",
- "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz",
- "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/helper-module-imports": {
- "version": "7.27.1",
- "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz",
- "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/traverse": "^7.27.1",
- "@babel/types": "^7.27.1"
- },
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/helper-module-transforms": {
- "version": "7.28.3",
- "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.3.tgz",
- "integrity": "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-module-imports": "^7.27.1",
- "@babel/helper-validator-identifier": "^7.27.1",
- "@babel/traverse": "^7.28.3"
- },
- "engines": {
- "node": ">=6.9.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0"
- }
- },
- "node_modules/@babel/helper-string-parser": {
- "version": "7.27.1",
- "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz",
- "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6.9.0"
- }
- },
"node_modules/@babel/helper-validator-identifier": {
"version": "7.28.5",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz",
@@ -491,92 +320,14 @@
"node": ">=6.9.0"
}
},
- "node_modules/@babel/helper-validator-option": {
- "version": "7.27.1",
- "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz",
- "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/helpers": {
- "version": "7.28.4",
- "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.4.tgz",
- "integrity": "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/template": "^7.27.2",
- "@babel/types": "^7.28.4"
- },
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/parser": {
- "version": "7.28.5",
- "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.5.tgz",
- "integrity": "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/types": "^7.28.5"
- },
- "bin": {
- "parser": "bin/babel-parser.js"
- },
- "engines": {
- "node": ">=6.0.0"
- }
- },
- "node_modules/@babel/template": {
- "version": "7.27.2",
- "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz",
- "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/code-frame": "^7.27.1",
- "@babel/parser": "^7.27.2",
- "@babel/types": "^7.27.1"
- },
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/traverse": {
- "version": "7.28.5",
- "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.5.tgz",
- "integrity": "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/code-frame": "^7.27.1",
- "@babel/generator": "^7.28.5",
- "@babel/helper-globals": "^7.28.0",
- "@babel/parser": "^7.28.5",
- "@babel/template": "^7.27.2",
- "@babel/types": "^7.28.5",
- "debug": "^4.3.1"
- },
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/types": {
- "version": "7.28.5",
- "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.5.tgz",
- "integrity": "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==",
+ "node_modules/@bcoe/v8-coverage": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-1.0.2.tgz",
+ "integrity": "sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==",
"dev": true,
"license": "MIT",
- "dependencies": {
- "@babel/helper-string-parser": "^7.27.1",
- "@babel/helper-validator-identifier": "^7.28.5"
- },
"engines": {
- "node": ">=6.9.0"
+ "node": ">=18"
}
},
"node_modules/@colors/colors": {
@@ -601,9 +352,9 @@
}
},
"node_modules/@emnapi/core": {
- "version": "1.7.1",
- "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.7.1.tgz",
- "integrity": "sha512-o1uhUASyo921r2XtHYOHy7gdkGLge8ghBEQHMWmyJFoXlpU58kIrhhN3w26lpQb6dspetweapMn2CSNwQ8I4wg==",
+ "version": "1.8.1",
+ "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.8.1.tgz",
+ "integrity": "sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg==",
"dev": true,
"license": "MIT",
"optional": true,
@@ -613,9 +364,9 @@
}
},
"node_modules/@emnapi/runtime": {
- "version": "1.7.1",
- "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.7.1.tgz",
- "integrity": "sha512-PVtJr5CmLwYAU9PZDMITZoR5iAOShYREoR45EyyLrbntV50mdePTgUn4AmOw90Ifcj+x2kRjdzr1HP3RrNiHGA==",
+ "version": "1.8.1",
+ "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.8.1.tgz",
+ "integrity": "sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg==",
"dev": true,
"license": "MIT",
"optional": true,
@@ -847,16 +598,6 @@
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
}
},
- "node_modules/@fastify/busboy": {
- "version": "2.1.1",
- "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.1.1.tgz",
- "integrity": "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=14"
- }
- },
"node_modules/@humanfs/core": {
"version": "0.19.1",
"resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz",
@@ -920,193 +661,14 @@
"url": "https://github.com/sponsors/nzakas"
}
},
- "node_modules/@isaacs/balanced-match": {
- "version": "4.0.1",
- "resolved": "https://registry.npmjs.org/@isaacs/balanced-match/-/balanced-match-4.0.1.tgz",
- "integrity": "sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": "20 || >=22"
- }
- },
- "node_modules/@isaacs/brace-expansion": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/@isaacs/brace-expansion/-/brace-expansion-5.0.0.tgz",
- "integrity": "sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@isaacs/balanced-match": "^4.0.1"
- },
- "engines": {
- "node": "20 || >=22"
- }
- },
"node_modules/@isaacs/cliui": {
- "version": "8.0.2",
- "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
- "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "string-width": "^5.1.2",
- "string-width-cjs": "npm:string-width@^4.2.0",
- "strip-ansi": "^7.0.1",
- "strip-ansi-cjs": "npm:strip-ansi@^6.0.1",
- "wrap-ansi": "^8.1.0",
- "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0"
- },
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@isaacs/cliui/node_modules/emoji-regex": {
- "version": "9.2.2",
- "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
- "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/@isaacs/cliui/node_modules/string-width": {
- "version": "5.1.2",
- "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
- "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "eastasianwidth": "^0.2.0",
- "emoji-regex": "^9.2.2",
- "strip-ansi": "^7.0.1"
- },
- "engines": {
- "node": ">=12"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/@istanbuljs/load-nyc-config": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz",
- "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "camelcase": "^5.3.1",
- "find-up": "^4.1.0",
- "get-package-type": "^0.1.0",
- "js-yaml": "^3.13.1",
- "resolve-from": "^5.0.0"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/@istanbuljs/load-nyc-config/node_modules/argparse": {
- "version": "1.0.10",
- "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
- "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "sprintf-js": "~1.0.2"
- }
- },
- "node_modules/@istanbuljs/load-nyc-config/node_modules/find-up": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
- "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "locate-path": "^5.0.0",
- "path-exists": "^4.0.0"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/@istanbuljs/load-nyc-config/node_modules/js-yaml": {
- "version": "3.14.2",
- "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz",
- "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "argparse": "^1.0.7",
- "esprima": "^4.0.0"
- },
- "bin": {
- "js-yaml": "bin/js-yaml.js"
- }
- },
- "node_modules/@istanbuljs/load-nyc-config/node_modules/locate-path": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
- "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "p-locate": "^4.1.0"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/@istanbuljs/load-nyc-config/node_modules/p-limit": {
- "version": "2.3.0",
- "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
- "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "p-try": "^2.0.0"
- },
- "engines": {
- "node": ">=6"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/@istanbuljs/load-nyc-config/node_modules/p-locate": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz",
- "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "p-limit": "^2.2.0"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/@istanbuljs/load-nyc-config/node_modules/resolve-from": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz",
- "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/@istanbuljs/nyc-config-typescript": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/@istanbuljs/nyc-config-typescript/-/nyc-config-typescript-1.0.2.tgz",
- "integrity": "sha512-iKGIyMoyJuFnJRSVTZ78POIRvNnwZaWIf8vG4ZS3rQq58MMDrqEX2nnzx0R28V2X8JvmKYiqY9FP2hlJsm8A0w==",
+ "version": "9.0.0",
+ "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-9.0.0.tgz",
+ "integrity": "sha512-AokJm4tuBHillT+FpMtxQ60n8ObyXBatq7jD2/JA9dxbDDokKQm8KMht5ibGzLVU9IJDIKK4TPKgMHEYMn3lMg==",
"dev": true,
- "license": "ISC",
- "dependencies": {
- "@istanbuljs/schema": "^0.1.2"
- },
+ "license": "BlueOak-1.0.0",
"engines": {
- "node": ">=8"
- },
- "peerDependencies": {
- "nyc": ">=15"
+ "node": ">=18"
}
},
"node_modules/@istanbuljs/schema": {
@@ -1130,17 +692,6 @@
"@jridgewell/trace-mapping": "^0.3.24"
}
},
- "node_modules/@jridgewell/remapping": {
- "version": "2.3.5",
- "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz",
- "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@jridgewell/gen-mapping": "^0.3.5",
- "@jridgewell/trace-mapping": "^0.3.24"
- }
- },
"node_modules/@jridgewell/resolve-uri": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
@@ -1720,9 +1271,9 @@
"license": "ISC"
},
"node_modules/@pnpm/npm-conf": {
- "version": "2.3.1",
- "resolved": "https://registry.npmjs.org/@pnpm/npm-conf/-/npm-conf-2.3.1.tgz",
- "integrity": "sha512-c83qWb22rNRuB0UaVCI0uRPNRr8Z0FWnEIvT47jiHAmOIUHbBOg5XvV7pM5x+rKn9HRpjxquDbXYSXr3fAKFcw==",
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/@pnpm/npm-conf/-/npm-conf-3.0.2.tgz",
+ "integrity": "sha512-h104Kh26rR8tm+a3Qkc5S4VLYint3FE48as7+/5oCEcKR2idC/pF1G6AhIXKI+eHPJa/3J9i5z0Al47IeGHPkA==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -1739,28 +1290,32 @@
"resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz",
"integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==",
"dev": true,
- "license": "BSD-3-Clause"
+ "license": "BSD-3-Clause",
+ "peer": true
},
"node_modules/@protobufjs/base64": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz",
"integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==",
"dev": true,
- "license": "BSD-3-Clause"
+ "license": "BSD-3-Clause",
+ "peer": true
},
"node_modules/@protobufjs/codegen": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz",
"integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==",
"dev": true,
- "license": "BSD-3-Clause"
+ "license": "BSD-3-Clause",
+ "peer": true
},
"node_modules/@protobufjs/eventemitter": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz",
"integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==",
"dev": true,
- "license": "BSD-3-Clause"
+ "license": "BSD-3-Clause",
+ "peer": true
},
"node_modules/@protobufjs/fetch": {
"version": "1.1.0",
@@ -1768,6 +1323,7 @@
"integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==",
"dev": true,
"license": "BSD-3-Clause",
+ "peer": true,
"dependencies": {
"@protobufjs/aspromise": "^1.1.1",
"@protobufjs/inquire": "^1.1.0"
@@ -1778,35 +1334,40 @@
"resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz",
"integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==",
"dev": true,
- "license": "BSD-3-Clause"
+ "license": "BSD-3-Clause",
+ "peer": true
},
"node_modules/@protobufjs/inquire": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz",
"integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==",
"dev": true,
- "license": "BSD-3-Clause"
+ "license": "BSD-3-Clause",
+ "peer": true
},
"node_modules/@protobufjs/path": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz",
"integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==",
"dev": true,
- "license": "BSD-3-Clause"
+ "license": "BSD-3-Clause",
+ "peer": true
},
"node_modules/@protobufjs/pool": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz",
"integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==",
"dev": true,
- "license": "BSD-3-Clause"
+ "license": "BSD-3-Clause",
+ "peer": true
},
"node_modules/@protobufjs/utf8": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz",
"integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==",
"dev": true,
- "license": "BSD-3-Clause"
+ "license": "BSD-3-Clause",
+ "peer": true
},
"node_modules/@rtsao/scc": {
"version": "1.1.0",
@@ -2209,9 +1770,9 @@
}
},
"node_modules/@semantic-release/github": {
- "version": "12.0.2",
- "resolved": "https://registry.npmjs.org/@semantic-release/github/-/github-12.0.2.tgz",
- "integrity": "sha512-qyqLS+aSGH1SfXIooBKjs7mvrv0deg8v+jemegfJg1kq6ji+GJV8CO08VJDEsvjp3O8XJmTTIAjjZbMzagzsdw==",
+ "version": "12.0.6",
+ "resolved": "https://registry.npmjs.org/@semantic-release/github/-/github-12.0.6.tgz",
+ "integrity": "sha512-aYYFkwHW3c6YtHwQF0t0+lAjlU+87NFOZuH2CvWFD0Ylivc7MwhZMiHOJ0FMpIgPpCVib/VUAcOwvrW0KnxQtA==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -2310,13 +1871,13 @@
}
},
"node_modules/@semantic-release/npm": {
- "version": "13.1.3",
- "resolved": "https://registry.npmjs.org/@semantic-release/npm/-/npm-13.1.3.tgz",
- "integrity": "sha512-q7zreY8n9V0FIP1Cbu63D+lXtRAVAIWb30MH5U3TdrfXt6r2MIrWCY0whAImN53qNvSGp0Zt07U95K+Qp9GpEg==",
+ "version": "13.1.4",
+ "resolved": "https://registry.npmjs.org/@semantic-release/npm/-/npm-13.1.4.tgz",
+ "integrity": "sha512-z5Fn9ftK1QQgFxMSuOd3DtYbTl4hWI2trCEvZcEJMQJy1/OBR0WHcxqzfVun455FSkHML8KgvPxJEa9MtZIBsg==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@actions/core": "^2.0.0",
+ "@actions/core": "^3.0.0",
"@semantic-release/error": "^4.0.0",
"aggregate-error": "^5.0.0",
"env-ci": "^11.2.0",
@@ -2354,55 +1915,142 @@
"resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-5.0.0.tgz",
"integrity": "sha512-gOsf2YwSlleG6IjRYG2A7k0HmBMEo6qVNk9Bp/EaLgAJT5ngH6PXbqa4ItvnEwCm/velL5jAnQgsHsWnjhGmvw==",
"dev": true,
- "license": "MIT",
+ "license": "MIT",
+ "dependencies": {
+ "clean-stack": "^5.2.0",
+ "indent-string": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/@semantic-release/npm/node_modules/clean-stack": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-5.3.0.tgz",
+ "integrity": "sha512-9ngPTOhYGQqNVSfeJkYXHmF7AGWp4/nN5D/QqNQs3Dvxd1Kk/WpjHfNujKHYUQ/5CoGyOyFNoWSPk5afzP0QVg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "escape-string-regexp": "5.0.0"
+ },
+ "engines": {
+ "node": ">=14.16"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/@semantic-release/npm/node_modules/escape-string-regexp": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz",
+ "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/@semantic-release/npm/node_modules/hosted-git-info": {
+ "version": "9.0.2",
+ "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-9.0.2.tgz",
+ "integrity": "sha512-M422h7o/BR3rmCQ8UHi7cyyMqKltdP9Uo+J2fXK+RSAY+wTcKOIRyhTuKv4qn+DJf3g+PL890AzId5KZpX+CBg==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "lru-cache": "^11.1.0"
+ },
+ "engines": {
+ "node": "^20.17.0 || >=22.9.0"
+ }
+ },
+ "node_modules/@semantic-release/npm/node_modules/indent-string": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-5.0.0.tgz",
+ "integrity": "sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/@semantic-release/npm/node_modules/lru-cache": {
+ "version": "11.2.6",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.6.tgz",
+ "integrity": "sha512-ESL2CrkS/2wTPfuend7Zhkzo2u0daGJ/A2VucJOgQ/C48S/zB8MMeMHSGKYpXhIjbPxfuezITkaBH1wqv00DDQ==",
+ "dev": true,
+ "license": "BlueOak-1.0.0",
+ "engines": {
+ "node": "20 || >=22"
+ }
+ },
+ "node_modules/@semantic-release/npm/node_modules/normalize-package-data": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-8.0.0.tgz",
+ "integrity": "sha512-RWk+PI433eESQ7ounYxIp67CYuVsS1uYSonX3kA6ps/3LWfjVQa/ptEg6Y3T6uAMq1mWpX9PQ+qx+QaHpsc7gQ==",
+ "dev": true,
+ "license": "BSD-2-Clause",
"dependencies": {
- "clean-stack": "^5.2.0",
- "indent-string": "^5.0.0"
+ "hosted-git-info": "^9.0.0",
+ "semver": "^7.3.5",
+ "validate-npm-package-license": "^3.0.4"
},
"engines": {
- "node": ">=18"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
+ "node": "^20.17.0 || >=22.9.0"
}
},
- "node_modules/@semantic-release/npm/node_modules/clean-stack": {
- "version": "5.3.0",
- "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-5.3.0.tgz",
- "integrity": "sha512-9ngPTOhYGQqNVSfeJkYXHmF7AGWp4/nN5D/QqNQs3Dvxd1Kk/WpjHfNujKHYUQ/5CoGyOyFNoWSPk5afzP0QVg==",
+ "node_modules/@semantic-release/npm/node_modules/read-pkg": {
+ "version": "10.1.0",
+ "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-10.1.0.tgz",
+ "integrity": "sha512-I8g2lArQiP78ll51UeMZojewtYgIRCKCWqZEgOO8c/uefTI+XDXvCSXu3+YNUaTNvZzobrL5+SqHjBrByRRTdg==",
"dev": true,
"license": "MIT",
"dependencies": {
- "escape-string-regexp": "5.0.0"
+ "@types/normalize-package-data": "^2.4.4",
+ "normalize-package-data": "^8.0.0",
+ "parse-json": "^8.3.0",
+ "type-fest": "^5.4.4",
+ "unicorn-magic": "^0.4.0"
},
"engines": {
- "node": ">=14.16"
+ "node": ">=20"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/@semantic-release/npm/node_modules/escape-string-regexp": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz",
- "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==",
+ "node_modules/@semantic-release/npm/node_modules/type-fest": {
+ "version": "5.4.4",
+ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-5.4.4.tgz",
+ "integrity": "sha512-JnTrzGu+zPV3aXIUhnyWJj4z/wigMsdYajGLIYakqyOW1nPllzXEJee0QQbHj+CTIQtXGlAjuK0UY+2xTyjVAw==",
"dev": true,
- "license": "MIT",
+ "license": "(MIT OR CC0-1.0)",
+ "dependencies": {
+ "tagged-tag": "^1.0.0"
+ },
"engines": {
- "node": ">=12"
+ "node": ">=20"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/@semantic-release/npm/node_modules/indent-string": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-5.0.0.tgz",
- "integrity": "sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==",
+ "node_modules/@semantic-release/npm/node_modules/unicorn-magic": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.4.0.tgz",
+ "integrity": "sha512-wH590V9VNgYH9g3lH9wWjTrUoKsjLF6sGLjhR4sH1LWpLmCOH0Zf7PukhDA8BiS7KHe4oPNkcTHqYkj7SOGUOw==",
"dev": true,
"license": "MIT",
"engines": {
- "node": ">=12"
+ "node": ">=20"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
@@ -2506,24 +2154,24 @@
}
},
"node_modules/@textlint/ast-node-types": {
- "version": "15.5.0",
- "resolved": "https://registry.npmjs.org/@textlint/ast-node-types/-/ast-node-types-15.5.0.tgz",
- "integrity": "sha512-K0LEuuTo4rza8yDrlYkRdXLao8Iz/QBMsQdIxRrOOrLYb4HAtZaypZ78c+J6rDA1UlGxadZVLmkkiv4KV5fMKQ==",
+ "version": "15.5.1",
+ "resolved": "https://registry.npmjs.org/@textlint/ast-node-types/-/ast-node-types-15.5.1.tgz",
+ "integrity": "sha512-2ABQSaQoM9u9fycXLJKcCv4XQulJWTUSwjo6F0i/ujjqOH8/AZ2A0RDKKbAddqxDhuabVB20lYoEsZZgzehccg==",
"dev": true,
"license": "MIT"
},
"node_modules/@textlint/linter-formatter": {
- "version": "15.5.0",
- "resolved": "https://registry.npmjs.org/@textlint/linter-formatter/-/linter-formatter-15.5.0.tgz",
- "integrity": "sha512-DPTm2+VXKID41qKQWagg/4JynM6hEEpvbq0PlGsEoC4Xm7IqXIxFym3mSf5+ued0cuiIV1hR9kgXjqGdP035tw==",
+ "version": "15.5.1",
+ "resolved": "https://registry.npmjs.org/@textlint/linter-formatter/-/linter-formatter-15.5.1.tgz",
+ "integrity": "sha512-7wfzpcQtk7TZ3UJO2deTI71mJCm4VvPGUmSwE4iuH6FoaxpdWpwSBiMLcZtjYrt/oIFOtNz0uf5rI+xJiHTFww==",
"dev": true,
"license": "MIT",
"dependencies": {
"@azu/format-text": "^1.0.2",
"@azu/style-format": "^1.0.1",
- "@textlint/module-interop": "15.5.0",
- "@textlint/resolver": "15.5.0",
- "@textlint/types": "15.5.0",
+ "@textlint/module-interop": "15.5.1",
+ "@textlint/resolver": "15.5.1",
+ "@textlint/types": "15.5.1",
"chalk": "^4.1.2",
"debug": "^4.4.3",
"js-yaml": "^4.1.1",
@@ -2566,27 +2214,27 @@
}
},
"node_modules/@textlint/module-interop": {
- "version": "15.5.0",
- "resolved": "https://registry.npmjs.org/@textlint/module-interop/-/module-interop-15.5.0.tgz",
- "integrity": "sha512-rqfouEhBEgZlR9umswWXXRBcmmSM28Trpr9b0duzgehKYVc7wSQCuQMagr6YBJa2NRMfRNinupusbJXMg0ij2A==",
+ "version": "15.5.1",
+ "resolved": "https://registry.npmjs.org/@textlint/module-interop/-/module-interop-15.5.1.tgz",
+ "integrity": "sha512-Y1jcFGCKNSmHxwsLO3mshOfLYX4Wavq2+w5BG6x5lGgZv0XrF1xxURRhbnhns4LzCu0fAcx6W+3V8/1bkyTZCw==",
"dev": true,
"license": "MIT"
},
"node_modules/@textlint/resolver": {
- "version": "15.5.0",
- "resolved": "https://registry.npmjs.org/@textlint/resolver/-/resolver-15.5.0.tgz",
- "integrity": "sha512-kK5nFbg5N3kVoZExQI/dnYjCInmTltvXDnuCRrBxHI01i6kO/o8R7Lc2aFkAZ6/NUZuRPalkyDdwZJke4/R2wg==",
+ "version": "15.5.1",
+ "resolved": "https://registry.npmjs.org/@textlint/resolver/-/resolver-15.5.1.tgz",
+ "integrity": "sha512-CVHxMIm8iNGccqM12CQ/ycvh+HjJId4RyC6as5ynCcp2E1Uy1TCe0jBWOpmLsbT4Nx15Ke29BmspyByawuIRyA==",
"dev": true,
"license": "MIT"
},
"node_modules/@textlint/types": {
- "version": "15.5.0",
- "resolved": "https://registry.npmjs.org/@textlint/types/-/types-15.5.0.tgz",
- "integrity": "sha512-EjAPbuA+3NyQ9WyFP7iUlddi35F3mGrf4tb4cZM0nWywbtEJ3+XAYqL+5RsF0qFeSguxGir09NdZOWrG9wVOUQ==",
+ "version": "15.5.1",
+ "resolved": "https://registry.npmjs.org/@textlint/types/-/types-15.5.1.tgz",
+ "integrity": "sha512-IY1OVZZk8LOOrbapYCsaeH7XSJT89HVukixDT8CoiWMrKGCTCZ3/Kzoa3DtMMbY8jtY777QmPOVCNnR+8fF6YQ==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@textlint/ast-node-types": "15.5.0"
+ "@textlint/ast-node-types": "15.5.1"
}
},
"node_modules/@tybys/wasm-util": {
@@ -2639,16 +2287,12 @@
"dev": true,
"license": "MIT"
},
- "node_modules/@types/glob": {
- "version": "9.0.0",
- "resolved": "https://registry.npmjs.org/@types/glob/-/glob-9.0.0.tgz",
- "integrity": "sha512-00UxlRaIUvYm4R4W9WYkN8/J+kV8fmOQ7okeH6YFtGWFMt3odD45tpG5yA5wnL7HE6lLgjaTW5n14ju2hl2NNA==",
- "deprecated": "This is a stub types definition. glob provides its own type definitions, so you do not need this installed.",
+ "node_modules/@types/istanbul-lib-coverage": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz",
+ "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==",
"dev": true,
- "license": "MIT",
- "dependencies": {
- "glob": "*"
- }
+ "license": "MIT"
},
"node_modules/@types/json-schema": {
"version": "7.0.15",
@@ -2664,6 +2308,13 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/@types/markdown-escape": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/@types/markdown-escape/-/markdown-escape-1.1.3.tgz",
+ "integrity": "sha512-JIc1+s3y5ujKnt/+N+wq6s/QdL2qZ11fP79MijrVXsAAnzSxCbT2j/3prHRouJdZ2yFLN3vkP0HytfnoCczjOw==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/@types/mdast": {
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz",
@@ -2689,9 +2340,9 @@
"license": "MIT"
},
"node_modules/@types/node": {
- "version": "20.19.27",
- "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.27.tgz",
- "integrity": "sha512-N2clP5pJhB2YnZJ3PIHFk5RkygRX5WO/5f0WC08tp0wd+sv0rsJk3MqWn3CbNmT2J505a5336jaQj4ph1AdMug==",
+ "version": "20.19.33",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.33.tgz",
+ "integrity": "sha512-Rs1bVAIdBs5gbTIKza/tgpMuG1k3U/UMJLWecIMxNdJFDMzcM5LOiLVRYh3PilWEYDIeUDv7bpiHPLPsbydGcw==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -2730,27 +2381,27 @@
"license": "MIT"
},
"node_modules/@types/vscode": {
- "version": "1.107.0",
- "resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.107.0.tgz",
- "integrity": "sha512-XS8YE1jlyTIowP64+HoN30OlC1H9xqSlq1eoLZUgFEC8oUTO6euYZxti1xRiLSfZocs4qytTzR6xCBYtioQTCg==",
+ "version": "1.109.0",
+ "resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.109.0.tgz",
+ "integrity": "sha512-0Pf95rnwEIwDbmXGC08r0B4TQhAbsHQ5UyTIgVgoieDe4cOnf92usuR5dEczb6bTKEp7ziZH4TV1TRGPPCExtw==",
"dev": true,
"license": "MIT"
},
"node_modules/@typescript-eslint/eslint-plugin": {
- "version": "8.51.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.51.0.tgz",
- "integrity": "sha512-XtssGWJvypyM2ytBnSnKtHYOGT+4ZwTnBVl36TA4nRO2f4PRNGz5/1OszHzcZCvcBMh+qb7I06uoCmLTRdR9og==",
+ "version": "8.56.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.56.0.tgz",
+ "integrity": "sha512-lRyPDLzNCuae71A3t9NEINBiTn7swyOhvUj3MyUOxb8x6g6vPEFoOU+ZRmGMusNC3X3YMhqMIX7i8ShqhT74Pw==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@eslint-community/regexpp": "^4.10.0",
- "@typescript-eslint/scope-manager": "8.51.0",
- "@typescript-eslint/type-utils": "8.51.0",
- "@typescript-eslint/utils": "8.51.0",
- "@typescript-eslint/visitor-keys": "8.51.0",
- "ignore": "^7.0.0",
+ "@eslint-community/regexpp": "^4.12.2",
+ "@typescript-eslint/scope-manager": "8.56.0",
+ "@typescript-eslint/type-utils": "8.56.0",
+ "@typescript-eslint/utils": "8.56.0",
+ "@typescript-eslint/visitor-keys": "8.56.0",
+ "ignore": "^7.0.5",
"natural-compare": "^1.4.0",
- "ts-api-utils": "^2.2.0"
+ "ts-api-utils": "^2.4.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -2760,23 +2411,23 @@
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
- "@typescript-eslint/parser": "^8.51.0",
- "eslint": "^8.57.0 || ^9.0.0",
+ "@typescript-eslint/parser": "^8.56.0",
+ "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
"typescript": ">=4.8.4 <6.0.0"
}
},
"node_modules/@typescript-eslint/parser": {
- "version": "8.51.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.51.0.tgz",
- "integrity": "sha512-3xP4XzzDNQOIqBMWogftkwxhg5oMKApqY0BAflmLZiFYHqyhSOxv/cd/zPQLTcCXr4AkaKb25joocY0BD1WC6A==",
+ "version": "8.56.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.56.0.tgz",
+ "integrity": "sha512-IgSWvLobTDOjnaxAfDTIHaECbkNlAlKv2j5SjpB2v7QHKv1FIfjwMy8FsDbVfDX/KjmCmYICcw7uGaXLhtsLNg==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@typescript-eslint/scope-manager": "8.51.0",
- "@typescript-eslint/types": "8.51.0",
- "@typescript-eslint/typescript-estree": "8.51.0",
- "@typescript-eslint/visitor-keys": "8.51.0",
- "debug": "^4.3.4"
+ "@typescript-eslint/scope-manager": "8.56.0",
+ "@typescript-eslint/types": "8.56.0",
+ "@typescript-eslint/typescript-estree": "8.56.0",
+ "@typescript-eslint/visitor-keys": "8.56.0",
+ "debug": "^4.4.3"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -2786,20 +2437,20 @@
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
- "eslint": "^8.57.0 || ^9.0.0",
+ "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
"typescript": ">=4.8.4 <6.0.0"
}
},
"node_modules/@typescript-eslint/project-service": {
- "version": "8.51.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.51.0.tgz",
- "integrity": "sha512-Luv/GafO07Z7HpiI7qeEW5NW8HUtZI/fo/kE0YbtQEFpJRUuR0ajcWfCE5bnMvL7QQFrmT/odMe8QZww8X2nfQ==",
+ "version": "8.56.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.56.0.tgz",
+ "integrity": "sha512-M3rnyL1vIQOMeWxTWIW096/TtVP+8W3p/XnaFflhmcFp+U4zlxUxWj4XwNs6HbDeTtN4yun0GNTTDBw/SvufKg==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@typescript-eslint/tsconfig-utils": "^8.51.0",
- "@typescript-eslint/types": "^8.51.0",
- "debug": "^4.3.4"
+ "@typescript-eslint/tsconfig-utils": "^8.56.0",
+ "@typescript-eslint/types": "^8.56.0",
+ "debug": "^4.4.3"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -2813,14 +2464,14 @@
}
},
"node_modules/@typescript-eslint/scope-manager": {
- "version": "8.51.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.51.0.tgz",
- "integrity": "sha512-JhhJDVwsSx4hiOEQPeajGhCWgBMBwVkxC/Pet53EpBVs7zHHtayKefw1jtPaNRXpI9RA2uocdmpdfE7T+NrizA==",
+ "version": "8.56.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.56.0.tgz",
+ "integrity": "sha512-7UiO/XwMHquH+ZzfVCfUNkIXlp/yQjjnlYUyYz7pfvlK3/EyyN6BK+emDmGNyQLBtLGaYrTAI6KOw8tFucWL2w==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@typescript-eslint/types": "8.51.0",
- "@typescript-eslint/visitor-keys": "8.51.0"
+ "@typescript-eslint/types": "8.56.0",
+ "@typescript-eslint/visitor-keys": "8.56.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -2831,9 +2482,9 @@
}
},
"node_modules/@typescript-eslint/tsconfig-utils": {
- "version": "8.51.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.51.0.tgz",
- "integrity": "sha512-Qi5bSy/vuHeWyir2C8u/uqGMIlIDu8fuiYWv48ZGlZ/k+PRPHtaAu7erpc7p5bzw2WNNSniuxoMSO4Ar6V9OXw==",
+ "version": "8.56.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.56.0.tgz",
+ "integrity": "sha512-bSJoIIt4o3lKXD3xmDh9chZcjCz5Lk8xS7Rxn+6l5/pKrDpkCwtQNQQwZ2qRPk7TkUYhrq3WPIHXOXlbXP0itg==",
"dev": true,
"license": "MIT",
"engines": {
@@ -2848,17 +2499,17 @@
}
},
"node_modules/@typescript-eslint/type-utils": {
- "version": "8.51.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.51.0.tgz",
- "integrity": "sha512-0XVtYzxnobc9K0VU7wRWg1yiUrw4oQzexCG2V2IDxxCxhqBMSMbjB+6o91A+Uc0GWtgjCa3Y8bi7hwI0Tu4n5Q==",
+ "version": "8.56.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.56.0.tgz",
+ "integrity": "sha512-qX2L3HWOU2nuDs6GzglBeuFXviDODreS58tLY/BALPC7iu3Fa+J7EOTwnX9PdNBxUI7Uh0ntP0YWGnxCkXzmfA==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@typescript-eslint/types": "8.51.0",
- "@typescript-eslint/typescript-estree": "8.51.0",
- "@typescript-eslint/utils": "8.51.0",
- "debug": "^4.3.4",
- "ts-api-utils": "^2.2.0"
+ "@typescript-eslint/types": "8.56.0",
+ "@typescript-eslint/typescript-estree": "8.56.0",
+ "@typescript-eslint/utils": "8.56.0",
+ "debug": "^4.4.3",
+ "ts-api-utils": "^2.4.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -2868,14 +2519,14 @@
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
- "eslint": "^8.57.0 || ^9.0.0",
+ "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
"typescript": ">=4.8.4 <6.0.0"
}
},
"node_modules/@typescript-eslint/types": {
- "version": "8.51.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.51.0.tgz",
- "integrity": "sha512-TizAvWYFM6sSscmEakjY3sPqGwxZRSywSsPEiuZF6d5GmGD9Gvlsv0f6N8FvAAA0CD06l3rIcWNbsN1e5F/9Ag==",
+ "version": "8.56.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.56.0.tgz",
+ "integrity": "sha512-DBsLPs3GsWhX5HylbP9HNG15U0bnwut55Lx12bHB9MpXxQ+R5GC8MwQe+N1UFXxAeQDvEsEDY6ZYwX03K7Z6HQ==",
"dev": true,
"license": "MIT",
"engines": {
@@ -2887,21 +2538,21 @@
}
},
"node_modules/@typescript-eslint/typescript-estree": {
- "version": "8.51.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.51.0.tgz",
- "integrity": "sha512-1qNjGqFRmlq0VW5iVlcyHBbCjPB7y6SxpBkrbhNWMy/65ZoncXCEPJxkRZL8McrseNH6lFhaxCIaX+vBuFnRng==",
+ "version": "8.56.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.56.0.tgz",
+ "integrity": "sha512-ex1nTUMWrseMltXUHmR2GAQ4d+WjkZCT4f+4bVsps8QEdh0vlBsaCokKTPlnqBFqqGaxilDNJG7b8dolW2m43Q==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@typescript-eslint/project-service": "8.51.0",
- "@typescript-eslint/tsconfig-utils": "8.51.0",
- "@typescript-eslint/types": "8.51.0",
- "@typescript-eslint/visitor-keys": "8.51.0",
- "debug": "^4.3.4",
- "minimatch": "^9.0.4",
- "semver": "^7.6.0",
+ "@typescript-eslint/project-service": "8.56.0",
+ "@typescript-eslint/tsconfig-utils": "8.56.0",
+ "@typescript-eslint/types": "8.56.0",
+ "@typescript-eslint/visitor-keys": "8.56.0",
+ "debug": "^4.4.3",
+ "minimatch": "^9.0.5",
+ "semver": "^7.7.3",
"tinyglobby": "^0.2.15",
- "ts-api-utils": "^2.2.0"
+ "ts-api-utils": "^2.4.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -2915,16 +2566,16 @@
}
},
"node_modules/@typescript-eslint/utils": {
- "version": "8.51.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.51.0.tgz",
- "integrity": "sha512-11rZYxSe0zabiKaCP2QAwRf/dnmgFgvTmeDTtZvUvXG3UuAdg/GU02NExmmIXzz3vLGgMdtrIosI84jITQOxUA==",
+ "version": "8.56.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.56.0.tgz",
+ "integrity": "sha512-RZ3Qsmi2nFGsS+n+kjLAYDPVlrzf7UhTffrDIKr+h2yzAlYP/y5ZulU0yeDEPItos2Ph46JAL5P/On3pe7kDIQ==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@eslint-community/eslint-utils": "^4.7.0",
- "@typescript-eslint/scope-manager": "8.51.0",
- "@typescript-eslint/types": "8.51.0",
- "@typescript-eslint/typescript-estree": "8.51.0"
+ "@eslint-community/eslint-utils": "^4.9.1",
+ "@typescript-eslint/scope-manager": "8.56.0",
+ "@typescript-eslint/types": "8.56.0",
+ "@typescript-eslint/typescript-estree": "8.56.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -2934,19 +2585,19 @@
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
- "eslint": "^8.57.0 || ^9.0.0",
+ "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
"typescript": ">=4.8.4 <6.0.0"
}
},
"node_modules/@typescript-eslint/visitor-keys": {
- "version": "8.51.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.51.0.tgz",
- "integrity": "sha512-mM/JRQOzhVN1ykejrvwnBRV3+7yTKK8tVANVN3o1O0t0v7o+jqdVu9crPy5Y9dov15TJk/FTIgoUGHrTOVL3Zg==",
+ "version": "8.56.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.56.0.tgz",
+ "integrity": "sha512-q+SL+b+05Ud6LbEE35qe4A99P+htKTKVbyiNEe45eCbJFyh/HVK9QXwlrbz+Q4L8SOW4roxSVwXYj4DMBT7Ieg==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@typescript-eslint/types": "8.51.0",
- "eslint-visitor-keys": "^4.2.1"
+ "@typescript-eslint/types": "8.56.0",
+ "eslint-visitor-keys": "^5.0.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -2957,22 +2608,22 @@
}
},
"node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": {
- "version": "4.2.1",
- "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz",
- "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==",
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.0.tgz",
+ "integrity": "sha512-A0XeIi7CXU7nPlfHS9loMYEKxUaONu/hTEzHTGba9Huu94Cq1hPivf+DE5erJozZOky0LfvXAyrV/tcswpLI0Q==",
"dev": true,
"license": "Apache-2.0",
"engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ "node": "^20.19.0 || ^22.13.0 || >=24"
},
"funding": {
"url": "https://opencollective.com/eslint"
}
},
"node_modules/@typespec/ts-http-runtime": {
- "version": "0.3.2",
- "resolved": "https://registry.npmjs.org/@typespec/ts-http-runtime/-/ts-http-runtime-0.3.2.tgz",
- "integrity": "sha512-IlqQ/Gv22xUC1r/WQm4StLkYQmaaTsXAhUVsNE0+xiyf0yRFiH5++q78U3bw6bLKDCTmh0uqKB9eG9+Bt75Dkg==",
+ "version": "0.3.3",
+ "resolved": "https://registry.npmjs.org/@typespec/ts-http-runtime/-/ts-http-runtime-0.3.3.tgz",
+ "integrity": "sha512-91fp6CAAJSRtH5ja95T1FHSKa8aPW9/Zw6cta81jlZTUw/+Vq8jM/AfF/14h2b71wwR84JUTW/3Y8QPhDAawFA==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -3253,6 +2904,248 @@
"win32"
]
},
+ "node_modules/@vscode/debugadapter": {
+ "version": "1.68.0",
+ "resolved": "https://registry.npmjs.org/@vscode/debugadapter/-/debugadapter-1.68.0.tgz",
+ "integrity": "sha512-D6gk5Fw2y4FV8oYmltoXpj+VAZexxJFopN/mcZ6YcgzQE9dgq2L45Aj3GLxScJOD6GeLILcxJIaA8l3v11esGg==",
+ "license": "MIT",
+ "dependencies": {
+ "@vscode/debugprotocol": "1.68.0"
+ },
+ "engines": {
+ "node": ">=14"
+ }
+ },
+ "node_modules/@vscode/debugprotocol": {
+ "version": "1.68.0",
+ "resolved": "https://registry.npmjs.org/@vscode/debugprotocol/-/debugprotocol-1.68.0.tgz",
+ "integrity": "sha512-2J27dysaXmvnfuhFGhfeuxfHRXunqNPxtBoR3koiTOA9rdxWNDTa1zIFLCFMSHJ9MPTPKFcBeblsyaCJCIlQxg==",
+ "license": "MIT"
+ },
+ "node_modules/@vscode/test-cli": {
+ "version": "0.0.12",
+ "resolved": "https://registry.npmjs.org/@vscode/test-cli/-/test-cli-0.0.12.tgz",
+ "integrity": "sha512-iYN0fDg29+a2Xelle/Y56Xvv7Nc8Thzq4VwpzAF/SIE6918rDicqfsQxV6w1ttr2+SOm+10laGuY9FG2ptEKsQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/mocha": "^10.0.10",
+ "c8": "^10.1.3",
+ "chokidar": "^3.6.0",
+ "enhanced-resolve": "^5.18.3",
+ "glob": "^10.3.10",
+ "minimatch": "^9.0.3",
+ "mocha": "^11.7.4",
+ "supports-color": "^10.2.2",
+ "yargs": "^17.7.2"
+ },
+ "bin": {
+ "vscode-test": "out/bin.mjs"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@vscode/test-cli/node_modules/@isaacs/cliui": {
+ "version": "8.0.2",
+ "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
+ "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "string-width": "^5.1.2",
+ "string-width-cjs": "npm:string-width@^4.2.0",
+ "strip-ansi": "^7.0.1",
+ "strip-ansi-cjs": "npm:strip-ansi@^6.0.1",
+ "wrap-ansi": "^8.1.0",
+ "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@vscode/test-cli/node_modules/ansi-styles": {
+ "version": "6.2.3",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz",
+ "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/@vscode/test-cli/node_modules/chokidar": {
+ "version": "3.6.0",
+ "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
+ "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "anymatch": "~3.1.2",
+ "braces": "~3.0.2",
+ "glob-parent": "~5.1.2",
+ "is-binary-path": "~2.1.0",
+ "is-glob": "~4.0.1",
+ "normalize-path": "~3.0.0",
+ "readdirp": "~3.6.0"
+ },
+ "engines": {
+ "node": ">= 8.10.0"
+ },
+ "funding": {
+ "url": "https://paulmillr.com/funding/"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.2"
+ }
+ },
+ "node_modules/@vscode/test-cli/node_modules/emoji-regex": {
+ "version": "9.2.2",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
+ "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@vscode/test-cli/node_modules/glob": {
+ "version": "10.5.0",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz",
+ "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==",
+ "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "foreground-child": "^3.1.0",
+ "jackspeak": "^3.1.2",
+ "minimatch": "^9.0.4",
+ "minipass": "^7.1.2",
+ "package-json-from-dist": "^1.0.0",
+ "path-scurry": "^1.11.1"
+ },
+ "bin": {
+ "glob": "dist/esm/bin.mjs"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/@vscode/test-cli/node_modules/glob-parent": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
+ "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "is-glob": "^4.0.1"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/@vscode/test-cli/node_modules/jackspeak": {
+ "version": "3.4.3",
+ "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz",
+ "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==",
+ "dev": true,
+ "license": "BlueOak-1.0.0",
+ "dependencies": {
+ "@isaacs/cliui": "^8.0.2"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ },
+ "optionalDependencies": {
+ "@pkgjs/parseargs": "^0.11.0"
+ }
+ },
+ "node_modules/@vscode/test-cli/node_modules/lru-cache": {
+ "version": "10.4.3",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz",
+ "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/@vscode/test-cli/node_modules/path-scurry": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz",
+ "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==",
+ "dev": true,
+ "license": "BlueOak-1.0.0",
+ "dependencies": {
+ "lru-cache": "^10.2.0",
+ "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0"
+ },
+ "engines": {
+ "node": ">=16 || 14 >=14.18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/@vscode/test-cli/node_modules/readdirp": {
+ "version": "3.6.0",
+ "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
+ "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "picomatch": "^2.2.1"
+ },
+ "engines": {
+ "node": ">=8.10.0"
+ }
+ },
+ "node_modules/@vscode/test-cli/node_modules/string-width": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
+ "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "eastasianwidth": "^0.2.0",
+ "emoji-regex": "^9.2.2",
+ "strip-ansi": "^7.0.1"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/@vscode/test-cli/node_modules/supports-color": {
+ "version": "10.2.2",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-10.2.2.tgz",
+ "integrity": "sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/supports-color?sponsor=1"
+ }
+ },
+ "node_modules/@vscode/test-cli/node_modules/wrap-ansi": {
+ "version": "8.1.0",
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz",
+ "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-styles": "^6.1.0",
+ "string-width": "^5.0.1",
+ "strip-ansi": "^7.0.1"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
+ }
+ },
"node_modules/@vscode/test-electron": {
"version": "2.5.2",
"resolved": "https://registry.npmjs.org/@vscode/test-electron/-/test-electron-2.5.2.tgz",
@@ -3789,9 +3682,9 @@
}
},
"node_modules/ajv": {
- "version": "8.17.1",
- "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz",
- "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==",
+ "version": "8.18.0",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz",
+ "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -3837,9 +3730,9 @@
}
},
"node_modules/ansi-escapes": {
- "version": "7.2.0",
- "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.2.0.tgz",
- "integrity": "sha512-g6LhBsl+GBPRWGWsBtutpzBYuIIdBkLEvad5C/va/74Db018+5TZiyA26cZJAr3Rft5lprVqOIPxf5Vid6tqAw==",
+ "version": "7.3.0",
+ "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.3.0.tgz",
+ "integrity": "sha512-BvU8nYgGQBxcmMuEeUEmNTvrMVjJNSH7RgW24vXexN4Ven6qCvy4TntnvlnwnMLTVlcRQQdbRY8NKnaIoeWDNg==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -3888,32 +3781,27 @@
"dev": true,
"license": "MIT"
},
- "node_modules/append-transform": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/append-transform/-/append-transform-2.0.0.tgz",
- "integrity": "sha512-7yeyCEurROLQJFv5Xj4lEGTy0borxepjFv1g22oAdqFu//SrAlDl1O1Nxx15SH1RoliUml6p8dwJW9jvZughhg==",
+ "node_modules/anymatch": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
+ "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
"dev": true,
- "license": "MIT",
+ "license": "ISC",
"dependencies": {
- "default-require-extensions": "^3.0.0"
+ "normalize-path": "^3.0.0",
+ "picomatch": "^2.0.4"
},
"engines": {
- "node": ">=8"
+ "node": ">= 8"
}
},
- "node_modules/archy": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz",
- "integrity": "sha512-Xg+9RwCg/0p32teKdGMPTPnVXKD0w3DfHnFTficozsAgsvq2XenPJq/MYpzzQ/v8zrOyJn6Ds39VA4JIDwFfqw==",
- "dev": true,
- "license": "MIT"
- },
"node_modules/ardunno-cli": {
"version": "0.1.12",
"resolved": "https://registry.npmjs.org/ardunno-cli/-/ardunno-cli-0.1.12.tgz",
"integrity": "sha512-QP8A+XO27XcT/irz4II9l9ktoQstaEkJa2K0XCNcCXAsYxuqcl8xetqCO1xueEoX32QwZsCSKX5ekdYqDJQ9LA==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"nice-grpc-common": "^2.0.2",
"protobufjs": "^7.2.3"
@@ -4182,9 +4070,9 @@
"optional": true
},
"node_modules/baseline-browser-mapping": {
- "version": "2.9.11",
- "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.11.tgz",
- "integrity": "sha512-Sg0xJUNDU1sJNGdfGWhVHX0kkZ+HWcvmVymJbj6NSgZZmW/8S9Y2HQ5euytnIgakgxN6papOAWiwDo1ctFDcoQ==",
+ "version": "2.9.19",
+ "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.19.tgz",
+ "integrity": "sha512-ipDqC8FrAl/76p2SSWKSI+H9tFwm7vYqXQrItCuiVPt26Km0jS+NzSsBWAaBusvSbQcfJG+JitdMm+wZAgTYqg==",
"dev": true,
"license": "Apache-2.0",
"bin": {
@@ -4198,6 +4086,19 @@
"dev": true,
"license": "Apache-2.0"
},
+ "node_modules/binary-extensions": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz",
+ "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/binary-searching": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/binary-searching/-/binary-searching-2.0.5.tgz",
@@ -4251,17 +4152,19 @@
}
},
"node_modules/boards-list": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/boards-list/-/boards-list-1.1.0.tgz",
- "integrity": "sha512-KZ+/nDH2MdmZ3lMgQqvBtwfHTPKcuF667UCqO31D0Pk7pqt7hhJzrZrbeOBkSwD10WU2kAByP7f8eSPz6VLVgg==",
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/boards-list/-/boards-list-2.0.0.tgz",
+ "integrity": "sha512-avuOBbVu4ij+xrfaD8rIBYB9cHDPwSOPDqmKCZNK3ejzdRJHGrRKSMqWXsOOSNV9OapBWWNHDZGikdQOtY/Jqg==",
"dev": true,
"license": "MIT",
"dependencies": {
- "ardunno-cli": "^0.1.7",
"clone": "^2.1.2",
"deep-equal": "^2.2.3",
- "fqbn": "^1.0.3",
"natural-compare": "^1.4.0"
+ },
+ "peerDependencies": {
+ "ardunno-cli": "^0.1.7",
+ "fqbn": "^1.4.0"
}
},
"node_modules/boolbase": {
@@ -4415,20 +4318,189 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/caching-transform": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/caching-transform/-/caching-transform-4.0.0.tgz",
- "integrity": "sha512-kpqOvwXnjjN44D89K5ccQC+RUrsy7jB/XLlRrx0D7/2HNcTPqzsb6XgYoErwko6QsV184CA2YgS1fxDiiDZMWA==",
+ "node_modules/c8": {
+ "version": "10.1.3",
+ "resolved": "https://registry.npmjs.org/c8/-/c8-10.1.3.tgz",
+ "integrity": "sha512-LvcyrOAaOnrrlMpW22n690PUvxiq4Uf9WMhQwNJ9vgagkL/ph1+D4uvjvDA5XCbykrc0sx+ay6pVi9YZ1GnhyA==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "@bcoe/v8-coverage": "^1.0.1",
+ "@istanbuljs/schema": "^0.1.3",
+ "find-up": "^5.0.0",
+ "foreground-child": "^3.1.1",
+ "istanbul-lib-coverage": "^3.2.0",
+ "istanbul-lib-report": "^3.0.1",
+ "istanbul-reports": "^3.1.6",
+ "test-exclude": "^7.0.1",
+ "v8-to-istanbul": "^9.0.0",
+ "yargs": "^17.7.2",
+ "yargs-parser": "^21.1.1"
+ },
+ "bin": {
+ "c8": "bin/c8.js"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "peerDependencies": {
+ "monocart-coverage-reports": "^2"
+ },
+ "peerDependenciesMeta": {
+ "monocart-coverage-reports": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/c8/node_modules/@isaacs/cliui": {
+ "version": "8.0.2",
+ "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
+ "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "string-width": "^5.1.2",
+ "string-width-cjs": "npm:string-width@^4.2.0",
+ "strip-ansi": "^7.0.1",
+ "strip-ansi-cjs": "npm:strip-ansi@^6.0.1",
+ "wrap-ansi": "^8.1.0",
+ "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/c8/node_modules/ansi-styles": {
+ "version": "6.2.3",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz",
+ "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/c8/node_modules/emoji-regex": {
+ "version": "9.2.2",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
+ "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/c8/node_modules/glob": {
+ "version": "10.5.0",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz",
+ "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==",
+ "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "foreground-child": "^3.1.0",
+ "jackspeak": "^3.1.2",
+ "minimatch": "^9.0.4",
+ "minipass": "^7.1.2",
+ "package-json-from-dist": "^1.0.0",
+ "path-scurry": "^1.11.1"
+ },
+ "bin": {
+ "glob": "dist/esm/bin.mjs"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/c8/node_modules/jackspeak": {
+ "version": "3.4.3",
+ "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz",
+ "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==",
+ "dev": true,
+ "license": "BlueOak-1.0.0",
+ "dependencies": {
+ "@isaacs/cliui": "^8.0.2"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ },
+ "optionalDependencies": {
+ "@pkgjs/parseargs": "^0.11.0"
+ }
+ },
+ "node_modules/c8/node_modules/lru-cache": {
+ "version": "10.4.3",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz",
+ "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/c8/node_modules/path-scurry": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz",
+ "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==",
+ "dev": true,
+ "license": "BlueOak-1.0.0",
+ "dependencies": {
+ "lru-cache": "^10.2.0",
+ "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0"
+ },
+ "engines": {
+ "node": ">=16 || 14 >=14.18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/c8/node_modules/string-width": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
+ "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==",
"dev": true,
"license": "MIT",
"dependencies": {
- "hasha": "^5.0.0",
- "make-dir": "^3.0.0",
- "package-hash": "^4.0.0",
- "write-file-atomic": "^3.0.0"
+ "eastasianwidth": "^0.2.0",
+ "emoji-regex": "^9.2.2",
+ "strip-ansi": "^7.0.1"
},
"engines": {
- "node": ">=8"
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/c8/node_modules/test-exclude": {
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-7.0.1.tgz",
+ "integrity": "sha512-pFYqmTw68LXVjeWJMST4+borgQP2AyMNbg1BpZh9LbyhUeNkeaPF9gzfPGUAnSMV3qPYdWUwDIjjCLiSDOl7vg==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "@istanbuljs/schema": "^0.1.2",
+ "glob": "^10.4.1",
+ "minimatch": "^9.0.4"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/c8/node_modules/wrap-ansi": {
+ "version": "8.1.0",
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz",
+ "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-styles": "^6.1.0",
+ "string-width": "^5.0.1",
+ "strip-ansi": "^7.0.1"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
}
},
"node_modules/call-bind": {
@@ -4488,20 +4560,10 @@
"node": ">=6"
}
},
- "node_modules/camelcase": {
- "version": "5.3.1",
- "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
- "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6"
- }
- },
"node_modules/caniuse-lite": {
- "version": "1.0.30001762",
- "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001762.tgz",
- "integrity": "sha512-PxZwGNvH7Ak8WX5iXzoK1KPZttBXNPuaOvI2ZYU7NrlM+d9Ov+TUvlLOBNGzVXAntMSMMlJPd+jY6ovrVjSmUw==",
+ "version": "1.0.30001769",
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001769.tgz",
+ "integrity": "sha512-BCfFL1sHijQlBGWBMuJyhZUhzo7wer5sVj9hqekB/7xn0Ypy+pER/edCYQm4exbXj4WiySGp40P8UuTh6w1srg==",
"dev": true,
"funding": [
{
@@ -4558,9 +4620,9 @@
}
},
"node_modules/cheerio": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.1.2.tgz",
- "integrity": "sha512-IkxPpb5rS/d1IiLbHMgfPuS0FgiWTtFIm/Nj+2woXDLTZ7fOT2eqzgYbdMlLweqlHbsZjxEChoVK+7iph7jyQg==",
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.2.0.tgz",
+ "integrity": "sha512-WDrybc/gKFpTYQutKIK6UvfcuxijIZfMfXaYm8NMsPQxSYvf+13fXUJ4rztGGbJcBQ/GF55gvrZ0Bc0bj/mqvg==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -4569,11 +4631,11 @@
"domhandler": "^5.0.3",
"domutils": "^3.2.2",
"encoding-sniffer": "^0.2.1",
- "htmlparser2": "^10.0.0",
+ "htmlparser2": "^10.1.0",
"parse5": "^7.3.0",
"parse5-htmlparser2-tree-adapter": "^7.1.0",
"parse5-parser-stream": "^7.1.2",
- "undici": "^7.12.0",
+ "undici": "^7.19.0",
"whatwg-mimetype": "^4.0.0"
},
"engines": {
@@ -4749,24 +4811,6 @@
"node": ">=8"
}
},
- "node_modules/cli-highlight/node_modules/wrap-ansi": {
- "version": "7.0.0",
- "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
- "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "ansi-styles": "^4.0.0",
- "string-width": "^4.1.0",
- "strip-ansi": "^6.0.0"
- },
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
- }
- },
"node_modules/cli-highlight/node_modules/yargs": {
"version": "16.2.0",
"resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz",
@@ -5002,24 +5046,6 @@
"node": ">=8"
}
},
- "node_modules/cliui/node_modules/wrap-ansi": {
- "version": "7.0.0",
- "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
- "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "ansi-styles": "^4.0.0",
- "string-width": "^4.1.0",
- "strip-ansi": "^6.0.0"
- },
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
- }
- },
"node_modules/clone": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz",
@@ -5104,22 +5130,15 @@
}
},
"node_modules/comment-parser": {
- "version": "1.4.1",
- "resolved": "https://registry.npmjs.org/comment-parser/-/comment-parser-1.4.1.tgz",
- "integrity": "sha512-buhp5kePrmda3vhc5B9t7pUQXAb2Tnd0qgpkIhPhkHXxJpiPJ11H0ZEU0oBpJ2QztSbzG/ZxMj/CHsYJqRHmyg==",
+ "version": "1.4.5",
+ "resolved": "https://registry.npmjs.org/comment-parser/-/comment-parser-1.4.5.tgz",
+ "integrity": "sha512-aRDkn3uyIlCFfk5NUA+VdwMmMsh8JGhc4hapfV4yxymHGQ3BVskMQfoXGpCo5IoBuQ9tS5iiVKhCpTcB4pW4qw==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 12.0.0"
}
},
- "node_modules/commondir": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz",
- "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==",
- "dev": true,
- "license": "MIT"
- },
"node_modules/compare-func": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/compare-func/-/compare-func-2.0.0.tgz",
@@ -5220,13 +5239,6 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/convert-source-map": {
- "version": "1.9.0",
- "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz",
- "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==",
- "dev": true,
- "license": "MIT"
- },
"node_modules/core-util-is": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz",
@@ -5424,20 +5436,10 @@
}
}
},
- "node_modules/decamelize": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz",
- "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/decode-named-character-reference": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.2.0.tgz",
- "integrity": "sha512-c6fcElNV6ShtZXmsgNgFFV5tVX2PaV4g+MOAkb8eXHvn6sryJBrZa9r0zV6+dtTyoCKxtDy5tyQ5ZwQuidtd+Q==",
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.3.0.tgz",
+ "integrity": "sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -5515,9 +5517,9 @@
"license": "MIT"
},
"node_modules/default-browser": {
- "version": "5.4.0",
- "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-5.4.0.tgz",
- "integrity": "sha512-XDuvSq38Hr1MdN47EDvYtx3U0MTqpCEn+F6ft8z2vYDzMrvQhVp0ui9oQdqW3MvK3vqUETglt1tVGgjLuJ5izg==",
+ "version": "5.5.0",
+ "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-5.5.0.tgz",
+ "integrity": "sha512-H9LMLr5zwIbSxrmvikGuI/5KGhZ8E2zH3stkMgM5LpOWDutGM2JZaj460Udnf1a+946zc7YBgrqEWwbk7zHvGw==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -5533,28 +5535,12 @@
},
"node_modules/default-browser-id": {
"version": "5.0.1",
- "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-5.0.1.tgz",
- "integrity": "sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=18"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/default-require-extensions": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/default-require-extensions/-/default-require-extensions-3.0.1.tgz",
- "integrity": "sha512-eXTJmRbm2TIt9MgWTsOH1wEuhew6XGZcMeGKCtLedIg/NCsg1iBePXkceTdK4Fii7pzmN9tGsZhKzZ4h7O/fxw==",
+ "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-5.0.1.tgz",
+ "integrity": "sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q==",
"dev": true,
"license": "MIT",
- "dependencies": {
- "strip-bom": "^4.0.0"
- },
"engines": {
- "node": ">=8"
+ "node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
@@ -5845,9 +5831,9 @@
}
},
"node_modules/electron-to-chromium": {
- "version": "1.5.267",
- "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.267.tgz",
- "integrity": "sha512-0Drusm6MVRXSOJpGbaSVgcQsuB4hEkMpHXaVstcPmhu5LIedxs1xNK/nIxmQIU/RPC0+1/o0AVZfBTkTNJOdUw==",
+ "version": "1.5.286",
+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.286.tgz",
+ "integrity": "sha512-9tfDXhJ4RKFNerfjdCcZfufu49vg620741MNs26a9+bhLThdB+plgMeou98CAaHu/WATj2iHOOHTp1hWtABj2A==",
"dev": true,
"license": "ISC"
},
@@ -5891,14 +5877,14 @@
}
},
"node_modules/enhanced-resolve": {
- "version": "5.18.4",
- "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.4.tgz",
- "integrity": "sha512-LgQMM4WXU3QI+SYgEc2liRgznaD5ojbmY3sb8LxyguVkIg5FxdpTkvk72te2R38/TGKxH634oLxXRGY6d7AP+Q==",
+ "version": "5.19.0",
+ "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.19.0.tgz",
+ "integrity": "sha512-phv3E1Xl4tQOShqSte26C7Fl84EwUdZsyOuSSk9qtAGyyQs2s3jJzComh+Abf4g187lUUAvH+H26omrqia2aGg==",
"dev": true,
"license": "MIT",
"dependencies": {
"graceful-fs": "^4.2.4",
- "tapable": "^2.2.0"
+ "tapable": "^2.3.0"
},
"engines": {
"node": ">=10.13.0"
@@ -6108,6 +6094,20 @@
"is-arrayish": "^0.2.1"
}
},
+ "node_modules/error-replay-debug-adapter": {
+ "version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/error-replay-debug-adapter/-/error-replay-debug-adapter-0.1.1.tgz",
+ "integrity": "sha512-G+fHIyrZm0tAqv7J79807mDYtBguScuJUlW4sOqQUsByPyXLl2GYYbnrth3rNosGOYkwd4Zfa/Ky018YtDkicg==",
+ "license": "MIT",
+ "workspaces": [
+ "packages/adapter",
+ "packages/mock",
+ "packages/extension"
+ ],
+ "engines": {
+ "vscode": "^1.101.0"
+ }
+ },
"node_modules/es-abstract": {
"version": "1.24.1",
"resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.1.tgz",
@@ -6309,13 +6309,6 @@
"url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/es6-error": {
- "version": "4.1.1",
- "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz",
- "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==",
- "dev": true,
- "license": "MIT"
- },
"node_modules/escalade": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
@@ -6679,9 +6672,9 @@
}
},
"node_modules/eslint-plugin-n": {
- "version": "17.23.1",
- "resolved": "https://registry.npmjs.org/eslint-plugin-n/-/eslint-plugin-n-17.23.1.tgz",
- "integrity": "sha512-68PealUpYoHOBh332JLLD9Sj7OQUDkFpmcfqt8R9sySfFSeuGJjMTJQvCRRB96zO3A/PELRLkPrzsHmzEFQQ5A==",
+ "version": "17.24.0",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-n/-/eslint-plugin-n-17.24.0.tgz",
+ "integrity": "sha512-/gC7/KAYmfNnPNOb3eu8vw+TdVnV0zhdQwexsw6FLXbhzroVj20vRn2qL8lDWDGnAQ2J8DhdfvXxX9EoxvERvw==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -6729,14 +6722,14 @@
}
},
"node_modules/eslint-plugin-prettier": {
- "version": "5.5.4",
- "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.5.4.tgz",
- "integrity": "sha512-swNtI95SToIz05YINMA6Ox5R057IMAmWZ26GqPxusAp1TZzj+IdY9tXNWWD3vkF/wEqydCONcwjTFpxybBqZsg==",
+ "version": "5.5.5",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.5.5.tgz",
+ "integrity": "sha512-hscXkbqUZ2sPithAuLm5MXL+Wph+U7wHngPBv9OMWwlP8iaflyxpjTYZkmdgB4/vPIhemRlBEoLrH7UC1n7aUw==",
"dev": true,
"license": "MIT",
"dependencies": {
- "prettier-linter-helpers": "^1.0.0",
- "synckit": "^0.11.7"
+ "prettier-linter-helpers": "^1.0.1",
+ "synckit": "^0.11.12"
},
"engines": {
"node": "^14.18.0 || >=16.0.0"
@@ -6995,20 +6988,6 @@
"url": "https://opencollective.com/eslint"
}
},
- "node_modules/esprima": {
- "version": "4.0.1",
- "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
- "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
- "dev": true,
- "license": "BSD-2-Clause",
- "bin": {
- "esparse": "bin/esparse.js",
- "esvalidate": "bin/esvalidate.js"
- },
- "engines": {
- "node": ">=4"
- }
- },
"node_modules/esquery": {
"version": "1.7.0",
"resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz",
@@ -7284,24 +7263,6 @@
"node": ">=8"
}
},
- "node_modules/find-cache-dir": {
- "version": "3.3.2",
- "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.2.tgz",
- "integrity": "sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "commondir": "^1.0.1",
- "make-dir": "^3.0.2",
- "pkg-dir": "^4.1.0"
- },
- "engines": {
- "node": ">=8"
- },
- "funding": {
- "url": "https://github.com/avajs/find-cache-dir?sponsor=1"
- }
- },
"node_modules/find-up": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
@@ -7471,27 +7432,6 @@
"readable-stream": "^2.0.0"
}
},
- "node_modules/fromentries": {
- "version": "1.3.2",
- "resolved": "https://registry.npmjs.org/fromentries/-/fromentries-1.3.2.tgz",
- "integrity": "sha512-cHEpEQHUg0f8XdtZCc2ZAhrHzKzT0MrFUTcvx+hfxYu7rGMDc5SKoXFh+n4YigxsHXRzc6OrCshdR1bWH6HHyg==",
- "dev": true,
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/feross"
- },
- {
- "type": "patreon",
- "url": "https://www.patreon.com/feross"
- },
- {
- "type": "consulting",
- "url": "https://feross.org/support"
- }
- ],
- "license": "MIT"
- },
"node_modules/fs-constants": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz",
@@ -7522,6 +7462,21 @@
"dev": true,
"license": "ISC"
},
+ "node_modules/fsevents": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
+ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "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",
@@ -7584,16 +7539,6 @@
"node": ">= 0.4"
}
},
- "node_modules/gensync": {
- "version": "1.0.0-beta.2",
- "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
- "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6.9.0"
- }
- },
"node_modules/get-caller-file": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
@@ -7641,16 +7586,6 @@
"url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/get-package-type": {
- "version": "0.1.0",
- "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz",
- "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=8.0.0"
- }
- },
"node_modules/get-proto": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
@@ -7696,9 +7631,9 @@
}
},
"node_modules/get-tsconfig": {
- "version": "4.13.0",
- "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.0.tgz",
- "integrity": "sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ==",
+ "version": "4.13.6",
+ "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.6.tgz",
+ "integrity": "sha512-shZT/QMiSHc/YBLxxOkMtgSid5HFoauqCE3/exfsEcwg1WkeqjG+V40yBbBrsD+jW2HDXcs28xOfcbm2jI8Ddw==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -7709,9 +7644,9 @@
}
},
"node_modules/git-hooks-list": {
- "version": "4.1.1",
- "resolved": "https://registry.npmjs.org/git-hooks-list/-/git-hooks-list-4.1.1.tgz",
- "integrity": "sha512-cmP497iLq54AZnv4YRAEMnEyQ1eIn4tGKbmswqwmFV4GBnAqE8NLtWxxdXa++AalfgL5EBH4IxTPyquEuGY/jA==",
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/git-hooks-list/-/git-hooks-list-4.2.1.tgz",
+ "integrity": "sha512-WNvqJjOxxs/8ZP9+DWdwWJ7cDsd60NHf39XnD82pDVrKO5q7xfPqpkK6hwEAmBa/ZSEE4IOoR75EzbbIuwGlMw==",
"dev": true,
"license": "MIT",
"funding": {
@@ -7745,6 +7680,7 @@
"version": "11.1.0",
"resolved": "https://registry.npmjs.org/glob/-/glob-11.1.0.tgz",
"integrity": "sha512-vuNwKSaKiqm7g0THUBu2x7ckSs3XJLXE+2ssL7/MfTGPLLcrJQ/4Uq1CjPTtO5cCIiRxqvN6Twy1qOwhL0Xjcw==",
+ "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me",
"dev": true,
"license": "BlueOak-1.0.0",
"dependencies": {
@@ -7785,14 +7721,40 @@
"dev": true,
"license": "BSD-2-Clause"
},
+ "node_modules/glob/node_modules/balanced-match": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.2.tgz",
+ "integrity": "sha512-x0K50QvKQ97fdEz2kPehIerj+YTeptKF9hyYkKf6egnwmMWAkADiO0QCzSp0R5xN8FTZgYaBfSaue46Ej62nMg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "jackspeak": "^4.2.3"
+ },
+ "engines": {
+ "node": "20 || >=22"
+ }
+ },
+ "node_modules/glob/node_modules/brace-expansion": {
+ "version": "5.0.2",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.2.tgz",
+ "integrity": "sha512-Pdk8c9poy+YhOgVWw1JNN22/HcivgKWwpxKq04M/jTmHyCZn12WPJebZxdjSa5TmBqISrUSgNYU3eRORljfCCw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^4.0.2"
+ },
+ "engines": {
+ "node": "20 || >=22"
+ }
+ },
"node_modules/glob/node_modules/minimatch": {
- "version": "10.1.1",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.1.1.tgz",
- "integrity": "sha512-enIvLvRAFZYXJzkCYG5RKmPfrFArdLv+R+lbQ53BmIMLIry74bjKzX6iHAm8WYamJkhSSEabrWN5D97XnKObjQ==",
+ "version": "10.2.0",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.0.tgz",
+ "integrity": "sha512-ugkC31VaVg9cF0DFVoADH12k6061zNZkZON+aX8AWsR9GhPcErkcMBceb6znR8wLERM2AkkOxy2nWRLpT9Jq5w==",
"dev": true,
"license": "BlueOak-1.0.0",
"dependencies": {
- "@isaacs/brace-expansion": "^5.0.0"
+ "brace-expansion": "^5.0.2"
},
"engines": {
"node": "20 || >=22"
@@ -8003,36 +7965,6 @@
"url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/hasha": {
- "version": "5.2.2",
- "resolved": "https://registry.npmjs.org/hasha/-/hasha-5.2.2.tgz",
- "integrity": "sha512-Hrp5vIK/xr5SkeN2onO32H0MgNZ0f17HRNH39WfL0SYUNOTZ5Lz1TJ8Pajo/87dYGEFlLMm7mIc/k/s6Bvz9HQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "is-stream": "^2.0.0",
- "type-fest": "^0.8.0"
- },
- "engines": {
- "node": ">=8"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/hasha/node_modules/is-stream": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz",
- "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=8"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
"node_modules/hasown": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
@@ -8099,9 +8031,9 @@
"license": "MIT"
},
"node_modules/htmlparser2": {
- "version": "10.0.0",
- "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-10.0.0.tgz",
- "integrity": "sha512-TwAZM+zE5Tq3lrEHvOlvwgj1XLWQCtaaibSN11Q+gGBAS7Y1uZSWwXXRe4iF6OXnaq1riyQAPFOBtYc77Mxq0g==",
+ "version": "10.1.0",
+ "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-10.1.0.tgz",
+ "integrity": "sha512-VTZkM9GWRAtEpveh7MSF6SjjrpNVNNVJfFup7xTY3UpFtm67foy9HDVXneLtFVt4pMz5kZtgNcvCniNFb1hlEQ==",
"dev": true,
"funding": [
"https://github.com/fb55/htmlparser2?sponsor=1",
@@ -8114,14 +8046,14 @@
"dependencies": {
"domelementtype": "^2.3.0",
"domhandler": "^5.0.3",
- "domutils": "^3.2.1",
- "entities": "^6.0.0"
+ "domutils": "^3.2.2",
+ "entities": "^7.0.1"
}
},
"node_modules/htmlparser2/node_modules/entities": {
- "version": "6.0.1",
- "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz",
- "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==",
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz",
+ "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==",
"dev": true,
"license": "BSD-2-Clause",
"engines": {
@@ -8458,6 +8390,19 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/is-binary-path": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
+ "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "binary-extensions": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/is-boolean-object": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.2.2.tgz",
@@ -8891,13 +8836,6 @@
"url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/is-typedarray": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz",
- "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==",
- "dev": true,
- "license": "MIT"
- },
"node_modules/is-unicode-supported": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-2.1.0.tgz",
@@ -8955,16 +8893,6 @@
"url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/is-windows": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz",
- "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/is-wsl": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.0.tgz",
@@ -9031,141 +8959,17 @@
"lodash.uniqby": "^4.7.0"
},
"engines": {
- "node": "^18.17 || >=20.6.1"
- }
- },
- "node_modules/istanbul-lib-coverage": {
- "version": "3.2.2",
- "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz",
- "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==",
- "dev": true,
- "license": "BSD-3-Clause",
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/istanbul-lib-hook": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/istanbul-lib-hook/-/istanbul-lib-hook-3.0.0.tgz",
- "integrity": "sha512-Pt/uge1Q9s+5VAZ+pCo16TYMWPBIl+oaNIjgLQxcX0itS6ueeaA+pEfThZpH8WxhFgCiEb8sAJY6MdUKgiIWaQ==",
- "dev": true,
- "license": "BSD-3-Clause",
- "dependencies": {
- "append-transform": "^2.0.0"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/istanbul-lib-instrument": {
- "version": "6.0.3",
- "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz",
- "integrity": "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==",
- "dev": true,
- "license": "BSD-3-Clause",
- "dependencies": {
- "@babel/core": "^7.23.9",
- "@babel/parser": "^7.23.9",
- "@istanbuljs/schema": "^0.1.3",
- "istanbul-lib-coverage": "^3.2.0",
- "semver": "^7.5.4"
- },
- "engines": {
- "node": ">=10"
- }
- },
- "node_modules/istanbul-lib-processinfo": {
- "version": "2.0.3",
- "resolved": "https://registry.npmjs.org/istanbul-lib-processinfo/-/istanbul-lib-processinfo-2.0.3.tgz",
- "integrity": "sha512-NkwHbo3E00oybX6NGJi6ar0B29vxyvNwoC7eJ4G4Yq28UfY758Hgn/heV8VRFhevPED4LXfFz0DQ8z/0kw9zMg==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "archy": "^1.0.0",
- "cross-spawn": "^7.0.3",
- "istanbul-lib-coverage": "^3.2.0",
- "p-map": "^3.0.0",
- "rimraf": "^3.0.0",
- "uuid": "^8.3.2"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/istanbul-lib-processinfo/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==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "balanced-match": "^1.0.0",
- "concat-map": "0.0.1"
- }
- },
- "node_modules/istanbul-lib-processinfo/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",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "fs.realpath": "^1.0.0",
- "inflight": "^1.0.4",
- "inherits": "2",
- "minimatch": "^3.1.1",
- "once": "^1.3.0",
- "path-is-absolute": "^1.0.0"
- },
- "engines": {
- "node": "*"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
- }
- },
- "node_modules/istanbul-lib-processinfo/node_modules/minimatch": {
- "version": "3.1.2",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
- "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "brace-expansion": "^1.1.7"
- },
- "engines": {
- "node": "*"
- }
- },
- "node_modules/istanbul-lib-processinfo/node_modules/p-map": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/p-map/-/p-map-3.0.0.tgz",
- "integrity": "sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "aggregate-error": "^3.0.0"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/istanbul-lib-processinfo/node_modules/rimraf": {
- "version": "3.0.2",
- "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
- "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
- "deprecated": "Rimraf versions prior to v4 are no longer supported",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "glob": "^7.1.3"
- },
- "bin": {
- "rimraf": "bin.js"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
+ "node": "^18.17 || >=20.6.1"
+ }
+ },
+ "node_modules/istanbul-lib-coverage": {
+ "version": "3.2.2",
+ "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz",
+ "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=8"
}
},
"node_modules/istanbul-lib-report": {
@@ -9199,21 +9003,6 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/istanbul-lib-source-maps": {
- "version": "4.0.1",
- "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz",
- "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==",
- "dev": true,
- "license": "BSD-3-Clause",
- "dependencies": {
- "debug": "^4.1.1",
- "istanbul-lib-coverage": "^3.0.0",
- "source-map": "^0.6.1"
- },
- "engines": {
- "node": ">=10"
- }
- },
"node_modules/istanbul-reports": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz",
@@ -9265,13 +9054,13 @@
}
},
"node_modules/jackspeak": {
- "version": "4.1.1",
- "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.1.1.tgz",
- "integrity": "sha512-zptv57P3GpL+O0I7VdMJNBZCu+BPHVQUk55Ft8/QCJjTVxrnJHuVuX/0Bl2A6/+2oyR/ZMEuFKwmzqqZ/U5nPQ==",
+ "version": "4.2.3",
+ "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.2.3.tgz",
+ "integrity": "sha512-ykkVRwrYvFm1nb2AJfKKYPr0emF6IiXDYUaFx4Zn9ZuIH7MrzEZ3sD5RlqGXNRpHtvUHJyOnCEFxOlNDtGo7wg==",
"dev": true,
"license": "BlueOak-1.0.0",
"dependencies": {
- "@isaacs/cliui": "^8.0.2"
+ "@isaacs/cliui": "^9.0.0"
},
"engines": {
"node": "20 || >=22"
@@ -9341,19 +9130,6 @@
"js-yaml": "bin/js-yaml.js"
}
},
- "node_modules/jsesc": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz",
- "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==",
- "dev": true,
- "license": "MIT",
- "bin": {
- "jsesc": "bin/jsesc"
- },
- "engines": {
- "node": ">=6"
- }
- },
"node_modules/json-buffer": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz",
@@ -9652,16 +9428,16 @@
}
},
"node_modules/lodash": {
- "version": "4.17.21",
- "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
- "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
+ "version": "4.17.23",
+ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz",
+ "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==",
"dev": true,
"license": "MIT"
},
"node_modules/lodash-es": {
- "version": "4.17.22",
- "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.22.tgz",
- "integrity": "sha512-XEawp1t0gxSi9x01glktRZ5HDy0HXqrM0x5pXQM98EaI0NxO6jVM7omDOxsuEo5UIASAnm2bRp1Jt/e0a2XU8Q==",
+ "version": "4.17.23",
+ "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.23.tgz",
+ "integrity": "sha512-kVI48u3PZr38HdYz98UmfPnXl2DXrpdctLrFLCd3kOx1xUkOmpFPx7gCWWM5MPkL/fD8zb+Ph0QzjGFs4+hHWg==",
"dev": true,
"license": "MIT"
},
@@ -9685,13 +9461,6 @@
"dev": true,
"license": "MIT"
},
- "node_modules/lodash.flattendeep": {
- "version": "4.4.0",
- "resolved": "https://registry.npmjs.org/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz",
- "integrity": "sha512-uHaJFihxmJcEX3kT4I23ABqKKalJ/zDrDg0lsFtc1h+3uw49SIJ5beyhx5ExVRti3AvKoOJngIj7xz3oylPdWQ==",
- "dev": true,
- "license": "MIT"
- },
"node_modules/lodash.includes": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz",
@@ -9797,7 +9566,8 @@
"resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz",
"integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==",
"dev": true,
- "license": "Apache-2.0"
+ "license": "Apache-2.0",
+ "peer": true
},
"node_modules/loose-envify": {
"version": "1.4.0",
@@ -9856,36 +9626,16 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/make-dir": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz",
- "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "semver": "^6.0.0"
- },
- "engines": {
- "node": ">=8"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/make-dir/node_modules/semver": {
- "version": "6.3.1",
- "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
- "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
- "dev": true,
- "license": "ISC",
- "bin": {
- "semver": "bin/semver.js"
- }
+ "node_modules/markdown-escape": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/markdown-escape/-/markdown-escape-2.0.0.tgz",
+ "integrity": "sha512-Trz4v0+XWlwy68LJIyw3bLbsJiC8XAbRCKF9DbEtZjyndKOGVx6n+wNB0VfoRmY2LKboQLeniap3xrb6LGSJ8A==",
+ "license": "MIT"
},
"node_modules/markdown-it": {
- "version": "14.1.0",
- "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.0.tgz",
- "integrity": "sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==",
+ "version": "14.1.1",
+ "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.1.tgz",
+ "integrity": "sha512-BuU2qnTti9YKgK5N+IeMubp14ZUKUUw7yeJbkjtosvHiP0AZ5c8IAgEMk79D0eC8F23r4Ac/q8cAIFdm2FtyoA==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -10679,10 +10429,49 @@
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
}
},
+ "node_modules/mocha/node_modules/@isaacs/cliui": {
+ "version": "8.0.2",
+ "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
+ "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "string-width": "^5.1.2",
+ "string-width-cjs": "npm:string-width@^4.2.0",
+ "strip-ansi": "^7.0.1",
+ "strip-ansi-cjs": "npm:strip-ansi@^6.0.1",
+ "wrap-ansi": "^8.1.0",
+ "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/mocha/node_modules/ansi-styles": {
+ "version": "6.2.3",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz",
+ "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/mocha/node_modules/emoji-regex": {
+ "version": "9.2.2",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
+ "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/mocha/node_modules/glob": {
"version": "10.5.0",
"resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz",
"integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==",
+ "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me",
"dev": true,
"license": "ISC",
"dependencies": {
@@ -10740,6 +10529,24 @@
"url": "https://github.com/sponsors/isaacs"
}
},
+ "node_modules/mocha/node_modules/string-width": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
+ "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "eastasianwidth": "^0.2.0",
+ "emoji-regex": "^9.2.2",
+ "strip-ansi": "^7.0.1"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/mocha/node_modules/supports-color": {
"version": "8.1.1",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz",
@@ -10756,6 +10563,24 @@
"url": "https://github.com/chalk/supports-color?sponsor=1"
}
},
+ "node_modules/mocha/node_modules/wrap-ansi": {
+ "version": "8.1.0",
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz",
+ "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-styles": "^6.1.0",
+ "string-width": "^5.0.1",
+ "strip-ansi": "^7.0.1"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
+ }
+ },
"node_modules/ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
@@ -10874,14 +10699,15 @@
"integrity": "sha512-7RNWbls5kAL1QVUOXvBsv1uO0wPQK3lHv+cY1gwkTzirnG1Nop4cBJZubpgziNbaVc/bl9QJcyvsf/NQxa3rjQ==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"ts-error": "^1.0.6"
}
},
"node_modules/node-abi": {
- "version": "3.85.0",
- "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.85.0.tgz",
- "integrity": "sha512-zsFhmbkAzwhTft6nd3VxcG0cvJsT70rL+BIGHWVq5fi6MwGrHwzqKaxXE+Hl2GmnGItnDKPPkO5/LQqjVkIdFg==",
+ "version": "3.87.0",
+ "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.87.0.tgz",
+ "integrity": "sha512-+CGM1L1CgmtheLcBuleyYOn7NWPVu0s0EJH2C4puxgEZb9h8QpR9G2dBfZJOAUhi7VQxuBPMd0hiISWcTyiYyQ==",
"dev": true,
"license": "MIT",
"optional": true,
@@ -10916,19 +10742,6 @@
"node": ">=18"
}
},
- "node_modules/node-preload": {
- "version": "0.2.1",
- "resolved": "https://registry.npmjs.org/node-preload/-/node-preload-0.2.1.tgz",
- "integrity": "sha512-RM5oyBy45cLEoHqCeh+MNuFAxO0vTFBLskvQbOKnEE7YTTSN4tbN8QWDIPQ6L+WvKsB/qLEGpYe2ZZ9d4W9OIQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "process-on-spawn": "^1.0.0"
- },
- "engines": {
- "node": ">=8"
- }
- },
"node_modules/node-releases": {
"version": "2.0.27",
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz",
@@ -10951,47 +10764,54 @@
}
},
"node_modules/normalize-package-data": {
- "version": "8.0.0",
- "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-8.0.0.tgz",
- "integrity": "sha512-RWk+PI433eESQ7ounYxIp67CYuVsS1uYSonX3kA6ps/3LWfjVQa/ptEg6Y3T6uAMq1mWpX9PQ+qx+QaHpsc7gQ==",
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-6.0.2.tgz",
+ "integrity": "sha512-V6gygoYb/5EmNI+MEGrWkC+e6+Rr7mTmfHrxDbLzxQogBkgzo76rkok0Am6thgSF7Mv2nLOajAJj5vDJZEFn7g==",
"dev": true,
"license": "BSD-2-Clause",
"dependencies": {
- "hosted-git-info": "^9.0.0",
+ "hosted-git-info": "^7.0.0",
"semver": "^7.3.5",
"validate-npm-package-license": "^3.0.4"
},
"engines": {
- "node": "^20.17.0 || >=22.9.0"
+ "node": "^16.14.0 || >=18.0.0"
}
},
"node_modules/normalize-package-data/node_modules/hosted-git-info": {
- "version": "9.0.2",
- "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-9.0.2.tgz",
- "integrity": "sha512-M422h7o/BR3rmCQ8UHi7cyyMqKltdP9Uo+J2fXK+RSAY+wTcKOIRyhTuKv4qn+DJf3g+PL890AzId5KZpX+CBg==",
+ "version": "7.0.2",
+ "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-7.0.2.tgz",
+ "integrity": "sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w==",
"dev": true,
"license": "ISC",
"dependencies": {
- "lru-cache": "^11.1.0"
+ "lru-cache": "^10.0.1"
},
"engines": {
- "node": "^20.17.0 || >=22.9.0"
+ "node": "^16.14.0 || >=18.0.0"
}
},
"node_modules/normalize-package-data/node_modules/lru-cache": {
- "version": "11.2.4",
- "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.4.tgz",
- "integrity": "sha512-B5Y16Jr9LB9dHVkh6ZevG+vAbOsNOYCX+sXvFWFu7B3Iz5mijW3zdbMyhsh8ANd2mSWBYdJgnqi+mL7/LrOPYg==",
+ "version": "10.4.3",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz",
+ "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==",
"dev": true,
- "license": "BlueOak-1.0.0",
+ "license": "ISC"
+ },
+ "node_modules/normalize-path": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
+ "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
+ "dev": true,
+ "license": "MIT",
"engines": {
- "node": "20 || >=22"
+ "node": ">=0.10.0"
}
},
"node_modules/normalize-url": {
- "version": "8.1.0",
- "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-8.1.0.tgz",
- "integrity": "sha512-X06Mfd/5aKsRHc0O0J5CUedwnPmnDtLF2+nq+KN9KSDlJHkPuh0JUviWjEWMe0SW/9TDdSLVPuk7L5gGTIA1/w==",
+ "version": "8.1.1",
+ "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-8.1.1.tgz",
+ "integrity": "sha512-JYc0DPlpGWB40kH5g07gGTrYuMqV653k3uBKY6uITPWds3M0ov3GaWGp9lbE3Bzngx8+XkfzgvASb9vk9JDFXQ==",
"dev": true,
"license": "MIT",
"engines": {
@@ -11002,9 +10822,9 @@
}
},
"node_modules/npm": {
- "version": "11.7.0",
- "resolved": "https://registry.npmjs.org/npm/-/npm-11.7.0.tgz",
- "integrity": "sha512-wiCZpv/41bIobCoJ31NStIWKfAxxYyD1iYnWCtiyns8s5v3+l8y0HCP/sScuH6B5+GhIfda4HQKiqeGZwJWhFw==",
+ "version": "11.10.0",
+ "resolved": "https://registry.npmjs.org/npm/-/npm-11.10.0.tgz",
+ "integrity": "sha512-i8hE43iSIAMFuYVi8TxsEISdELM4fIza600aLjJ0ankGPLqd0oTPKMJqAcO/QWm307MbSlWGzJcNZ0lGMQgHPA==",
"bundleDependencies": [
"@isaacs/string-locale-compare",
"@npmcli/arborist",
@@ -11084,8 +10904,8 @@
],
"dependencies": {
"@isaacs/string-locale-compare": "^1.1.0",
- "@npmcli/arborist": "^9.1.9",
- "@npmcli/config": "^10.4.5",
+ "@npmcli/arborist": "^9.3.0",
+ "@npmcli/config": "^10.7.0",
"@npmcli/fs": "^5.0.0",
"@npmcli/map-workspaces": "^5.0.3",
"@npmcli/metavuln-calculator": "^9.0.3",
@@ -11093,28 +10913,28 @@
"@npmcli/promise-spawn": "^9.0.1",
"@npmcli/redact": "^4.0.0",
"@npmcli/run-script": "^10.0.3",
- "@sigstore/tuf": "^4.0.0",
+ "@sigstore/tuf": "^4.0.1",
"abbrev": "^4.0.0",
"archy": "~1.0.0",
"cacache": "^20.0.3",
"chalk": "^5.6.2",
- "ci-info": "^4.3.1",
+ "ci-info": "^4.4.0",
"cli-columns": "^4.0.0",
"fastest-levenshtein": "^1.0.16",
"fs-minipass": "^3.0.3",
- "glob": "^13.0.0",
+ "glob": "^13.0.2",
"graceful-fs": "^4.2.11",
"hosted-git-info": "^9.0.2",
"ini": "^6.0.0",
"init-package-json": "^8.2.4",
- "is-cidr": "^6.0.1",
+ "is-cidr": "^6.0.3",
"json-parse-even-better-errors": "^5.0.0",
"libnpmaccess": "^10.0.3",
- "libnpmdiff": "^8.0.12",
- "libnpmexec": "^10.1.11",
- "libnpmfund": "^7.0.12",
+ "libnpmdiff": "^8.1.1",
+ "libnpmexec": "^10.2.1",
+ "libnpmfund": "^7.0.15",
"libnpmorg": "^8.0.1",
- "libnpmpack": "^9.0.12",
+ "libnpmpack": "^9.1.1",
"libnpmpublish": "^11.1.3",
"libnpmsearch": "^9.0.1",
"libnpmteam": "^8.0.2",
@@ -11124,7 +10944,7 @@
"minipass": "^7.1.1",
"minipass-pipeline": "^1.2.4",
"ms": "^2.1.2",
- "node-gyp": "^12.1.0",
+ "node-gyp": "^12.2.0",
"nopt": "^9.0.0",
"npm-audit-report": "^7.0.0",
"npm-install-checks": "^8.0.0",
@@ -11134,21 +10954,21 @@
"npm-registry-fetch": "^19.1.1",
"npm-user-validate": "^4.0.0",
"p-map": "^7.0.4",
- "pacote": "^21.0.4",
+ "pacote": "^21.3.1",
"parse-conflict-json": "^5.0.1",
"proc-log": "^6.1.0",
"qrcode-terminal": "^0.12.0",
"read": "^5.0.1",
- "semver": "^7.7.3",
+ "semver": "^7.7.4",
"spdx-expression-parse": "^4.0.0",
- "ssri": "^13.0.0",
+ "ssri": "^13.0.1",
"supports-color": "^10.2.2",
- "tar": "^7.5.2",
+ "tar": "^7.5.7",
"text-table": "~0.2.0",
"tiny-relative-date": "^2.0.2",
"treeverse": "^3.0.0",
- "validate-npm-package-name": "^7.0.0",
- "which": "^6.0.0"
+ "validate-npm-package-name": "^7.0.2",
+ "which": "^6.0.1"
},
"bin": {
"npm": "bin/npm-cli.js",
@@ -11198,7 +11018,7 @@
}
},
"node_modules/npm/node_modules/@isaacs/brace-expansion": {
- "version": "5.0.0",
+ "version": "5.0.1",
"dev": true,
"inBundle": true,
"license": "MIT",
@@ -11244,7 +11064,7 @@
}
},
"node_modules/npm/node_modules/@npmcli/arborist": {
- "version": "9.1.9",
+ "version": "9.3.0",
"dev": true,
"inBundle": true,
"license": "ISC",
@@ -11262,7 +11082,7 @@
"@npmcli/run-script": "^10.0.0",
"bin-links": "^6.0.0",
"cacache": "^20.0.1",
- "common-ancestor-path": "^1.0.1",
+ "common-ancestor-path": "^2.0.0",
"hosted-git-info": "^9.0.0",
"json-stringify-nice": "^1.1.4",
"lru-cache": "^11.2.1",
@@ -11291,7 +11111,7 @@
}
},
"node_modules/npm/node_modules/@npmcli/config": {
- "version": "10.4.5",
+ "version": "10.7.0",
"dev": true,
"inBundle": true,
"license": "ISC",
@@ -11486,7 +11306,7 @@
}
},
"node_modules/npm/node_modules/@sigstore/core": {
- "version": "3.0.0",
+ "version": "3.1.0",
"dev": true,
"inBundle": true,
"license": "Apache-2.0",
@@ -11504,52 +11324,43 @@
}
},
"node_modules/npm/node_modules/@sigstore/sign": {
- "version": "4.0.1",
+ "version": "4.1.0",
"dev": true,
"inBundle": true,
"license": "Apache-2.0",
"dependencies": {
"@sigstore/bundle": "^4.0.0",
- "@sigstore/core": "^3.0.0",
+ "@sigstore/core": "^3.1.0",
"@sigstore/protobuf-specs": "^0.5.0",
- "make-fetch-happen": "^15.0.2",
- "proc-log": "^5.0.0",
+ "make-fetch-happen": "^15.0.3",
+ "proc-log": "^6.1.0",
"promise-retry": "^2.0.1"
},
"engines": {
"node": "^20.17.0 || >=22.9.0"
}
},
- "node_modules/npm/node_modules/@sigstore/sign/node_modules/proc-log": {
- "version": "5.0.0",
- "dev": true,
- "inBundle": true,
- "license": "ISC",
- "engines": {
- "node": "^18.17.0 || >=20.5.0"
- }
- },
"node_modules/npm/node_modules/@sigstore/tuf": {
- "version": "4.0.0",
+ "version": "4.0.1",
"dev": true,
"inBundle": true,
"license": "Apache-2.0",
"dependencies": {
"@sigstore/protobuf-specs": "^0.5.0",
- "tuf-js": "^4.0.0"
+ "tuf-js": "^4.1.0"
},
"engines": {
"node": "^20.17.0 || >=22.9.0"
}
},
"node_modules/npm/node_modules/@sigstore/verify": {
- "version": "3.0.0",
+ "version": "3.1.0",
"dev": true,
"inBundle": true,
"license": "Apache-2.0",
"dependencies": {
"@sigstore/bundle": "^4.0.0",
- "@sigstore/core": "^3.0.0",
+ "@sigstore/core": "^3.1.0",
"@sigstore/protobuf-specs": "^0.5.0"
},
"engines": {
@@ -11566,33 +11377,18 @@
}
},
"node_modules/npm/node_modules/@tufjs/models": {
- "version": "4.0.0",
+ "version": "4.1.0",
"dev": true,
"inBundle": true,
"license": "MIT",
"dependencies": {
"@tufjs/canonical-json": "2.0.0",
- "minimatch": "^9.0.5"
+ "minimatch": "^10.1.1"
},
"engines": {
"node": "^20.17.0 || >=22.9.0"
}
},
- "node_modules/npm/node_modules/@tufjs/models/node_modules/minimatch": {
- "version": "9.0.5",
- "dev": true,
- "inBundle": true,
- "license": "ISC",
- "dependencies": {
- "brace-expansion": "^2.0.1"
- },
- "engines": {
- "node": ">=16 || 14 >=14.17"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
- }
- },
"node_modules/npm/node_modules/abbrev": {
"version": "4.0.0",
"dev": true,
@@ -11632,12 +11428,6 @@
"inBundle": true,
"license": "MIT"
},
- "node_modules/npm/node_modules/balanced-match": {
- "version": "1.0.2",
- "dev": true,
- "inBundle": true,
- "license": "MIT"
- },
"node_modules/npm/node_modules/bin-links": {
"version": "6.0.0",
"dev": true,
@@ -11666,15 +11456,6 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/npm/node_modules/brace-expansion": {
- "version": "2.0.2",
- "dev": true,
- "inBundle": true,
- "license": "MIT",
- "dependencies": {
- "balanced-match": "^1.0.0"
- }
- },
"node_modules/npm/node_modules/cacache": {
"version": "20.0.3",
"dev": true,
@@ -11719,7 +11500,7 @@
}
},
"node_modules/npm/node_modules/ci-info": {
- "version": "4.3.1",
+ "version": "4.4.0",
"dev": true,
"funding": [
{
@@ -11734,7 +11515,7 @@
}
},
"node_modules/npm/node_modules/cidr-regex": {
- "version": "5.0.1",
+ "version": "5.0.2",
"dev": true,
"inBundle": true,
"license": "BSD-2-Clause",
@@ -11768,10 +11549,13 @@
}
},
"node_modules/npm/node_modules/common-ancestor-path": {
- "version": "1.0.1",
+ "version": "2.0.0",
"dev": true,
"inBundle": true,
- "license": "ISC"
+ "license": "BlueOak-1.0.0",
+ "engines": {
+ "node": ">= 18"
+ }
},
"node_modules/npm/node_modules/cssesc": {
"version": "3.0.0",
@@ -11803,7 +11587,7 @@
}
},
"node_modules/npm/node_modules/diff": {
- "version": "8.0.2",
+ "version": "8.0.3",
"dev": true,
"inBundle": true,
"license": "BSD-3-Clause",
@@ -11870,12 +11654,12 @@
}
},
"node_modules/npm/node_modules/glob": {
- "version": "13.0.0",
+ "version": "13.0.2",
"dev": true,
"inBundle": true,
"license": "BlueOak-1.0.0",
"dependencies": {
- "minimatch": "^10.1.1",
+ "minimatch": "^10.1.2",
"minipass": "^7.1.2",
"path-scurry": "^2.0.0"
},
@@ -11998,7 +11782,7 @@
}
},
"node_modules/npm/node_modules/ip-address": {
- "version": "10.0.1",
+ "version": "10.1.0",
"dev": true,
"inBundle": true,
"license": "MIT",
@@ -12019,12 +11803,12 @@
}
},
"node_modules/npm/node_modules/is-cidr": {
- "version": "6.0.1",
+ "version": "6.0.3",
"dev": true,
"inBundle": true,
"license": "BSD-2-Clause",
"dependencies": {
- "cidr-regex": "5.0.1"
+ "cidr-regex": "^5.0.1"
},
"engines": {
"node": ">=20"
@@ -12040,12 +11824,12 @@
}
},
"node_modules/npm/node_modules/isexe": {
- "version": "3.1.1",
+ "version": "4.0.0",
"dev": true,
"inBundle": true,
- "license": "ISC",
+ "license": "BlueOak-1.0.0",
"engines": {
- "node": ">=16"
+ "node": ">=20"
}
},
"node_modules/npm/node_modules/json-parse-even-better-errors": {
@@ -12101,12 +11885,12 @@
}
},
"node_modules/npm/node_modules/libnpmdiff": {
- "version": "8.0.12",
+ "version": "8.1.1",
"dev": true,
"inBundle": true,
"license": "ISC",
"dependencies": {
- "@npmcli/arborist": "^9.1.9",
+ "@npmcli/arborist": "^9.3.0",
"@npmcli/installed-package-contents": "^4.0.0",
"binary-extensions": "^3.0.0",
"diff": "^8.0.2",
@@ -12120,12 +11904,12 @@
}
},
"node_modules/npm/node_modules/libnpmexec": {
- "version": "10.1.11",
+ "version": "10.2.1",
"dev": true,
"inBundle": true,
"license": "ISC",
"dependencies": {
- "@npmcli/arborist": "^9.1.9",
+ "@npmcli/arborist": "^9.3.0",
"@npmcli/package-json": "^7.0.0",
"@npmcli/run-script": "^10.0.0",
"ci-info": "^4.0.0",
@@ -12143,12 +11927,12 @@
}
},
"node_modules/npm/node_modules/libnpmfund": {
- "version": "7.0.12",
+ "version": "7.0.15",
"dev": true,
"inBundle": true,
"license": "ISC",
"dependencies": {
- "@npmcli/arborist": "^9.1.9"
+ "@npmcli/arborist": "^9.3.0"
},
"engines": {
"node": "^20.17.0 || >=22.9.0"
@@ -12168,12 +11952,12 @@
}
},
"node_modules/npm/node_modules/libnpmpack": {
- "version": "9.0.12",
+ "version": "9.1.1",
"dev": true,
"inBundle": true,
"license": "ISC",
"dependencies": {
- "@npmcli/arborist": "^9.1.9",
+ "@npmcli/arborist": "^9.3.0",
"@npmcli/run-script": "^10.0.0",
"npm-package-arg": "^13.0.0",
"pacote": "^21.0.2"
@@ -12243,10 +12027,10 @@
}
},
"node_modules/npm/node_modules/lru-cache": {
- "version": "11.2.2",
+ "version": "11.2.6",
"dev": true,
"inBundle": true,
- "license": "ISC",
+ "license": "BlueOak-1.0.0",
"engines": {
"node": "20 || >=22"
}
@@ -12274,12 +12058,12 @@
}
},
"node_modules/npm/node_modules/minimatch": {
- "version": "10.1.1",
+ "version": "10.1.2",
"dev": true,
"inBundle": true,
"license": "BlueOak-1.0.0",
"dependencies": {
- "@isaacs/brace-expansion": "^5.0.0"
+ "@isaacs/brace-expansion": "^5.0.1"
},
"engines": {
"node": "20 || >=22"
@@ -12310,13 +12094,13 @@
}
},
"node_modules/npm/node_modules/minipass-fetch": {
- "version": "5.0.0",
+ "version": "5.0.1",
"dev": true,
"inBundle": true,
"license": "MIT",
"dependencies": {
"minipass": "^7.0.3",
- "minipass-sized": "^1.0.3",
+ "minipass-sized": "^2.0.0",
"minizlib": "^3.0.1"
},
"engines": {
@@ -12375,24 +12159,12 @@
}
},
"node_modules/npm/node_modules/minipass-sized": {
- "version": "1.0.3",
- "dev": true,
- "inBundle": true,
- "license": "ISC",
- "dependencies": {
- "minipass": "^3.0.0"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/npm/node_modules/minipass-sized/node_modules/minipass": {
- "version": "3.3.6",
+ "version": "2.0.0",
"dev": true,
"inBundle": true,
"license": "ISC",
"dependencies": {
- "yallist": "^4.0.0"
+ "minipass": "^7.1.2"
},
"engines": {
"node": ">=8"
@@ -12435,7 +12207,7 @@
}
},
"node_modules/npm/node_modules/node-gyp": {
- "version": "12.1.0",
+ "version": "12.2.0",
"dev": true,
"inBundle": true,
"license": "MIT",
@@ -12447,7 +12219,7 @@
"nopt": "^9.0.0",
"proc-log": "^6.0.0",
"semver": "^7.3.5",
- "tar": "^7.5.2",
+ "tar": "^7.5.4",
"tinyglobby": "^0.2.12",
"which": "^6.0.0"
},
@@ -12612,7 +12384,7 @@
}
},
"node_modules/npm/node_modules/pacote": {
- "version": "21.0.4",
+ "version": "21.3.1",
"dev": true,
"inBundle": true,
"license": "ISC",
@@ -12657,7 +12429,7 @@
}
},
"node_modules/npm/node_modules/path-scurry": {
- "version": "2.0.0",
+ "version": "2.0.1",
"dev": true,
"inBundle": true,
"license": "BlueOak-1.0.0",
@@ -12673,7 +12445,7 @@
}
},
"node_modules/npm/node_modules/postcss-selector-parser": {
- "version": "7.1.0",
+ "version": "7.1.1",
"dev": true,
"inBundle": true,
"license": "MIT",
@@ -12792,7 +12564,7 @@
"optional": true
},
"node_modules/npm/node_modules/semver": {
- "version": "7.7.3",
+ "version": "7.7.4",
"dev": true,
"inBundle": true,
"license": "ISC",
@@ -12816,17 +12588,17 @@
}
},
"node_modules/npm/node_modules/sigstore": {
- "version": "4.0.0",
+ "version": "4.1.0",
"dev": true,
"inBundle": true,
"license": "Apache-2.0",
"dependencies": {
"@sigstore/bundle": "^4.0.0",
- "@sigstore/core": "^3.0.0",
+ "@sigstore/core": "^3.1.0",
"@sigstore/protobuf-specs": "^0.5.0",
- "@sigstore/sign": "^4.0.0",
- "@sigstore/tuf": "^4.0.0",
- "@sigstore/verify": "^3.0.0"
+ "@sigstore/sign": "^4.1.0",
+ "@sigstore/tuf": "^4.0.1",
+ "@sigstore/verify": "^3.1.0"
},
"engines": {
"node": "^20.17.0 || >=22.9.0"
@@ -12913,7 +12685,7 @@
"license": "CC0-1.0"
},
"node_modules/npm/node_modules/ssri": {
- "version": "13.0.0",
+ "version": "13.0.1",
"dev": true,
"inBundle": true,
"license": "ISC",
@@ -12963,7 +12735,7 @@
}
},
"node_modules/npm/node_modules/tar": {
- "version": "7.5.2",
+ "version": "7.5.7",
"dev": true,
"inBundle": true,
"license": "BlueOak-1.0.0",
@@ -13054,14 +12826,14 @@
}
},
"node_modules/npm/node_modules/tuf-js": {
- "version": "4.0.0",
+ "version": "4.1.0",
"dev": true,
"inBundle": true,
"license": "MIT",
"dependencies": {
- "@tufjs/models": "4.0.0",
- "debug": "^4.4.1",
- "make-fetch-happen": "^15.0.0"
+ "@tufjs/models": "4.1.0",
+ "debug": "^4.4.3",
+ "make-fetch-happen": "^15.0.1"
},
"engines": {
"node": "^20.17.0 || >=22.9.0"
@@ -13118,7 +12890,7 @@
}
},
"node_modules/npm/node_modules/validate-npm-package-name": {
- "version": "7.0.0",
+ "version": "7.0.2",
"dev": true,
"inBundle": true,
"license": "ISC",
@@ -13136,335 +12908,50 @@
}
},
"node_modules/npm/node_modules/which": {
- "version": "6.0.0",
+ "version": "6.0.1",
"dev": true,
"inBundle": true,
"license": "ISC",
"dependencies": {
- "isexe": "^3.1.1"
+ "isexe": "^4.0.0"
},
"bin": {
"node-which": "bin/which.js"
},
"engines": {
"node": "^20.17.0 || >=22.9.0"
- }
- },
- "node_modules/npm/node_modules/write-file-atomic": {
- "version": "7.0.0",
- "dev": true,
- "inBundle": true,
- "license": "ISC",
- "dependencies": {
- "imurmurhash": "^0.1.4",
- "signal-exit": "^4.0.1"
- },
- "engines": {
- "node": "^20.17.0 || >=22.9.0"
- }
- },
- "node_modules/npm/node_modules/yallist": {
- "version": "4.0.0",
- "dev": true,
- "inBundle": true,
- "license": "ISC"
- },
- "node_modules/nth-check": {
- "version": "2.1.1",
- "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz",
- "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==",
- "dev": true,
- "license": "BSD-2-Clause",
- "dependencies": {
- "boolbase": "^1.0.0"
- },
- "funding": {
- "url": "https://github.com/fb55/nth-check?sponsor=1"
- }
- },
- "node_modules/nyc": {
- "version": "17.1.0",
- "resolved": "https://registry.npmjs.org/nyc/-/nyc-17.1.0.tgz",
- "integrity": "sha512-U42vQ4czpKa0QdI1hu950XuNhYqgoM+ZF1HT+VuUHL9hPfDPVvNQyltmMqdE9bUHMVa+8yNbc3QKTj8zQhlVxQ==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "@istanbuljs/load-nyc-config": "^1.0.0",
- "@istanbuljs/schema": "^0.1.2",
- "caching-transform": "^4.0.0",
- "convert-source-map": "^1.7.0",
- "decamelize": "^1.2.0",
- "find-cache-dir": "^3.2.0",
- "find-up": "^4.1.0",
- "foreground-child": "^3.3.0",
- "get-package-type": "^0.1.0",
- "glob": "^7.1.6",
- "istanbul-lib-coverage": "^3.0.0",
- "istanbul-lib-hook": "^3.0.0",
- "istanbul-lib-instrument": "^6.0.2",
- "istanbul-lib-processinfo": "^2.0.2",
- "istanbul-lib-report": "^3.0.0",
- "istanbul-lib-source-maps": "^4.0.0",
- "istanbul-reports": "^3.0.2",
- "make-dir": "^3.0.0",
- "node-preload": "^0.2.1",
- "p-map": "^3.0.0",
- "process-on-spawn": "^1.0.0",
- "resolve-from": "^5.0.0",
- "rimraf": "^3.0.0",
- "signal-exit": "^3.0.2",
- "spawn-wrap": "^2.0.0",
- "test-exclude": "^6.0.0",
- "yargs": "^15.0.2"
- },
- "bin": {
- "nyc": "bin/nyc.js"
- },
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/nyc/node_modules/ansi-regex": {
- "version": "5.0.1",
- "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
- "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/nyc/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==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "balanced-match": "^1.0.0",
- "concat-map": "0.0.1"
- }
- },
- "node_modules/nyc/node_modules/cliui": {
- "version": "6.0.0",
- "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz",
- "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "string-width": "^4.2.0",
- "strip-ansi": "^6.0.0",
- "wrap-ansi": "^6.2.0"
- }
- },
- "node_modules/nyc/node_modules/find-up": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
- "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "locate-path": "^5.0.0",
- "path-exists": "^4.0.0"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/nyc/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",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "fs.realpath": "^1.0.0",
- "inflight": "^1.0.4",
- "inherits": "2",
- "minimatch": "^3.1.1",
- "once": "^1.3.0",
- "path-is-absolute": "^1.0.0"
- },
- "engines": {
- "node": "*"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
- }
- },
- "node_modules/nyc/node_modules/locate-path": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
- "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "p-locate": "^4.1.0"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/nyc/node_modules/minimatch": {
- "version": "3.1.2",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
- "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "brace-expansion": "^1.1.7"
- },
- "engines": {
- "node": "*"
- }
- },
- "node_modules/nyc/node_modules/p-limit": {
- "version": "2.3.0",
- "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
- "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "p-try": "^2.0.0"
- },
- "engines": {
- "node": ">=6"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/nyc/node_modules/p-locate": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz",
- "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "p-limit": "^2.2.0"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/nyc/node_modules/p-map": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/p-map/-/p-map-3.0.0.tgz",
- "integrity": "sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "aggregate-error": "^3.0.0"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/nyc/node_modules/resolve-from": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz",
- "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/nyc/node_modules/rimraf": {
- "version": "3.0.2",
- "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
- "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
- "deprecated": "Rimraf versions prior to v4 are no longer supported",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "glob": "^7.1.3"
- },
- "bin": {
- "rimraf": "bin.js"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
- }
- },
- "node_modules/nyc/node_modules/signal-exit": {
- "version": "3.0.7",
- "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
- "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==",
- "dev": true,
- "license": "ISC"
- },
- "node_modules/nyc/node_modules/strip-ansi": {
- "version": "6.0.1",
- "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
- "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "ansi-regex": "^5.0.1"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/nyc/node_modules/wrap-ansi": {
- "version": "6.2.0",
- "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz",
- "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "ansi-styles": "^4.0.0",
- "string-width": "^4.1.0",
- "strip-ansi": "^6.0.0"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/nyc/node_modules/y18n": {
- "version": "4.0.3",
- "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz",
- "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==",
- "dev": true,
- "license": "ISC"
+ }
},
- "node_modules/nyc/node_modules/yargs": {
- "version": "15.4.1",
- "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz",
- "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==",
+ "node_modules/npm/node_modules/write-file-atomic": {
+ "version": "7.0.0",
"dev": true,
- "license": "MIT",
+ "inBundle": true,
+ "license": "ISC",
"dependencies": {
- "cliui": "^6.0.0",
- "decamelize": "^1.2.0",
- "find-up": "^4.1.0",
- "get-caller-file": "^2.0.1",
- "require-directory": "^2.1.1",
- "require-main-filename": "^2.0.0",
- "set-blocking": "^2.0.0",
- "string-width": "^4.2.0",
- "which-module": "^2.0.0",
- "y18n": "^4.0.0",
- "yargs-parser": "^18.1.2"
+ "imurmurhash": "^0.1.4",
+ "signal-exit": "^4.0.1"
},
"engines": {
- "node": ">=8"
+ "node": "^20.17.0 || >=22.9.0"
}
},
- "node_modules/nyc/node_modules/yargs-parser": {
- "version": "18.1.3",
- "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz",
- "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==",
+ "node_modules/npm/node_modules/yallist": {
+ "version": "4.0.0",
"dev": true,
- "license": "ISC",
+ "inBundle": true,
+ "license": "ISC"
+ },
+ "node_modules/nth-check": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz",
+ "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==",
+ "dev": true,
+ "license": "BSD-2-Clause",
"dependencies": {
- "camelcase": "^5.0.0",
- "decamelize": "^1.2.0"
+ "boolbase": "^1.0.0"
},
- "engines": {
- "node": ">=6"
+ "funding": {
+ "url": "https://github.com/fb55/nth-check?sponsor=1"
}
},
"node_modules/object-assign": {
@@ -13759,13 +13246,13 @@
}
},
"node_modules/ovsx": {
- "version": "0.10.7",
- "resolved": "https://registry.npmjs.org/ovsx/-/ovsx-0.10.7.tgz",
- "integrity": "sha512-UjBQlB5xSDD+biAylCZ8Q/k3An9K3y9FYa+hT/HTbJkzOQP+gaNHX20CaOo4lrYT1iJXdiePH9zS2uvCXdDNDA==",
+ "version": "0.10.9",
+ "resolved": "https://registry.npmjs.org/ovsx/-/ovsx-0.10.9.tgz",
+ "integrity": "sha512-gY6912U50YzzNdAEFr9IxAqu59pKySXZzJUxzHRzi3/h/fWFdDDFCCXyjik6VL4TmiVKeor1Yv/cg7I3KfOUuQ==",
"dev": true,
"license": "EPL-2.0",
"dependencies": {
- "@vscode/vsce": "^3.2.1",
+ "@vscode/vsce": "^3.7.1",
"commander": "^6.2.1",
"follow-redirects": "^1.14.6",
"is-ci": "^2.0.0",
@@ -13775,7 +13262,7 @@
"yauzl-promise": "^4.0.0"
},
"bin": {
- "ovsx": "lib/ovsx"
+ "ovsx": "bin/ovsx"
},
"engines": {
"node": ">= 20"
@@ -13942,22 +13429,6 @@
"node": ">=6"
}
},
- "node_modules/package-hash": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/package-hash/-/package-hash-4.0.0.tgz",
- "integrity": "sha512-whdkPIooSu/bASggZ96BWVvZTRMOFxnyUG5PnTSGKoJE2gd5mbVNmR2Nj20QFzxYYgAXpoqC+AiXzl+UMRh7zQ==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "graceful-fs": "^4.1.15",
- "hasha": "^5.0.0",
- "lodash.flattendeep": "^4.4.0",
- "release-zalgo": "^1.0.0"
- },
- "engines": {
- "node": ">=8"
- }
- },
"node_modules/package-json-from-dist": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz",
@@ -14156,9 +13627,9 @@
}
},
"node_modules/path-scurry/node_modules/lru-cache": {
- "version": "11.2.4",
- "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.4.tgz",
- "integrity": "sha512-B5Y16Jr9LB9dHVkh6ZevG+vAbOsNOYCX+sXvFWFu7B3Iz5mijW3zdbMyhsh8ANd2mSWBYdJgnqi+mL7/LrOPYg==",
+ "version": "11.2.6",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.6.tgz",
+ "integrity": "sha512-ESL2CrkS/2wTPfuend7Zhkzo2u0daGJ/A2VucJOgQ/C48S/zB8MMeMHSGKYpXhIjbPxfuezITkaBH1wqv00DDQ==",
"dev": true,
"license": "BlueOak-1.0.0",
"engines": {
@@ -14183,13 +13654,14 @@
"license": "MIT"
},
"node_modules/peowly": {
- "version": "1.3.2",
- "resolved": "https://registry.npmjs.org/peowly/-/peowly-1.3.2.tgz",
- "integrity": "sha512-BYIrwr8JCXY49jUZscgw311w9oGEKo7ux/s+BxrhKTQbiQ0iYNdZNJ5LgagaeercQdFHwnR7Z5IxxFWVQ+BasQ==",
+ "version": "1.3.3",
+ "resolved": "https://registry.npmjs.org/peowly/-/peowly-1.3.3.tgz",
+ "integrity": "sha512-5UmUtvuCv3KzBX2NuQw2uF28o0t8Eq4KkPRZfUCzJs+DiNVKw7OaYn29vNDgrt/Pggs23CPlSTqgzlhHJfpT0A==",
"dev": true,
"license": "MIT",
"engines": {
- "node": ">=18.6.0"
+ "node": ">=18.6.0",
+ "typescript": ">=5.8"
}
},
"node_modules/picocolors": {
@@ -14436,9 +13908,9 @@
}
},
"node_modules/prettier": {
- "version": "3.7.4",
- "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.7.4.tgz",
- "integrity": "sha512-v6UNi1+3hSlVvv8fSaoUbggEM5VErKmmpGA7Pl3HF8V6uKY7rvClBOJlH6yNwQtfTueNkGVpOv/mtWL9L4bgRA==",
+ "version": "3.8.1",
+ "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.1.tgz",
+ "integrity": "sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==",
"dev": true,
"license": "MIT",
"bin": {
@@ -14500,14 +13972,13 @@
}
},
"node_modules/prettier-plugin-packagejson": {
- "version": "2.5.20",
- "resolved": "https://registry.npmjs.org/prettier-plugin-packagejson/-/prettier-plugin-packagejson-2.5.20.tgz",
- "integrity": "sha512-G8cowPh+QmJJECTZlrPDKWkVVcwrFjF2rGcw546w3N8blLoc4szSs8UUPfFVxHUNLUjiru71Ah83g1lZkeK9Bw==",
+ "version": "2.5.22",
+ "resolved": "https://registry.npmjs.org/prettier-plugin-packagejson/-/prettier-plugin-packagejson-2.5.22.tgz",
+ "integrity": "sha512-G6WalmoUssKF8ZXkni0+n4324K+gG143KPysSQNW+FrR0XyNb3BdRxchGC/Q1FE/F702p7/6KU7r4mv0WSWbzA==",
"dev": true,
"license": "MIT",
"dependencies": {
- "sort-package-json": "3.5.0",
- "synckit": "0.11.11"
+ "sort-package-json": "3.6.0"
},
"peerDependencies": {
"prettier": ">= 1.16.0"
@@ -14541,19 +14012,6 @@
"dev": true,
"license": "MIT"
},
- "node_modules/process-on-spawn": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/process-on-spawn/-/process-on-spawn-1.1.0.tgz",
- "integrity": "sha512-JOnOPQ/8TZgjs1JIH/m9ni7FfimjNa/PRx7y/Wb5qdItsnhO0jE4AT7fC0HjC28DUQWDr50dwSYZLdRMlqDq3Q==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "fromentries": "^1.2.0"
- },
- "engines": {
- "node": ">=8"
- }
- },
"node_modules/prop-types": {
"version": "15.8.1",
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
@@ -14580,6 +14038,7 @@
"dev": true,
"hasInstallScript": true,
"license": "BSD-3-Clause",
+ "peer": true,
"dependencies": {
"@protobufjs/aspromise": "^1.1.2",
"@protobufjs/base64": "^1.1.2",
@@ -14631,9 +14090,9 @@
}
},
"node_modules/qs": {
- "version": "6.14.1",
- "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.1.tgz",
- "integrity": "sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ==",
+ "version": "6.14.2",
+ "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.2.tgz",
+ "integrity": "sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q==",
"dev": true,
"license": "BSD-3-Clause",
"dependencies": {
@@ -14754,42 +14213,20 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/read-package-up/node_modules/hosted-git-info": {
- "version": "7.0.2",
- "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-7.0.2.tgz",
- "integrity": "sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w==",
+ "node_modules/read-package-up/node_modules/type-fest": {
+ "version": "4.41.0",
+ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz",
+ "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==",
"dev": true,
- "license": "ISC",
- "dependencies": {
- "lru-cache": "^10.0.1"
- },
+ "license": "(MIT OR CC0-1.0)",
"engines": {
- "node": "^16.14.0 || >=18.0.0"
- }
- },
- "node_modules/read-package-up/node_modules/lru-cache": {
- "version": "10.4.3",
- "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz",
- "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==",
- "dev": true,
- "license": "ISC"
- },
- "node_modules/read-package-up/node_modules/normalize-package-data": {
- "version": "6.0.2",
- "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-6.0.2.tgz",
- "integrity": "sha512-V6gygoYb/5EmNI+MEGrWkC+e6+Rr7mTmfHrxDbLzxQogBkgzo76rkok0Am6thgSF7Mv2nLOajAJj5vDJZEFn7g==",
- "dev": true,
- "license": "BSD-2-Clause",
- "dependencies": {
- "hosted-git-info": "^7.0.0",
- "semver": "^7.3.5",
- "validate-npm-package-license": "^3.0.4"
+ "node": ">=16"
},
- "engines": {
- "node": "^16.14.0 || >=18.0.0"
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/read-package-up/node_modules/read-pkg": {
+ "node_modules/read-pkg": {
"version": "9.0.1",
"resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-9.0.1.tgz",
"integrity": "sha512-9viLL4/n1BJUCT1NXVTdS1jtm80yDEgR5T4yCelII49Mbj0v1rZdKqj7zCiYdbB0CuCgdrvHcNogAKTFPBocFA==",
@@ -14809,7 +14246,7 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/read-package-up/node_modules/type-fest": {
+ "node_modules/read-pkg/node_modules/type-fest": {
"version": "4.41.0",
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz",
"integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==",
@@ -14822,7 +14259,7 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/read-package-up/node_modules/unicorn-magic": {
+ "node_modules/read-pkg/node_modules/unicorn-magic": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.1.0.tgz",
"integrity": "sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ==",
@@ -14835,42 +14272,6 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/read-pkg": {
- "version": "10.0.0",
- "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-10.0.0.tgz",
- "integrity": "sha512-A70UlgfNdKI5NSvTTfHzLQj7NJRpJ4mT5tGafkllJ4wh71oYuGm/pzphHcmW4s35iox56KSK721AihodoXSc/A==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@types/normalize-package-data": "^2.4.4",
- "normalize-package-data": "^8.0.0",
- "parse-json": "^8.3.0",
- "type-fest": "^5.2.0",
- "unicorn-magic": "^0.3.0"
- },
- "engines": {
- "node": ">=20"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/read-pkg/node_modules/type-fest": {
- "version": "5.3.1",
- "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-5.3.1.tgz",
- "integrity": "sha512-VCn+LMHbd4t6sF3wfU/+HKT63C9OoyrSIf4b+vtWHpt2U7/4InZG467YDNMFMR70DdHjAdpPWmw2lzRdg0Xqqg==",
- "dev": true,
- "license": "(MIT OR CC0-1.0)",
- "dependencies": {
- "tagged-tag": "^1.0.0"
- },
- "engines": {
- "node": ">=20"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
"node_modules/readable-stream": {
"version": "2.3.8",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz",
@@ -14972,31 +14373,18 @@
}
},
"node_modules/registry-auth-token": {
- "version": "5.1.0",
- "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-5.1.0.tgz",
- "integrity": "sha512-GdekYuwLXLxMuFTwAPg5UKGLW/UXzQrZvH/Zj791BQif5T05T0RsaLfHc9q3ZOKi7n+BoprPD9mJ0O0k4xzUlw==",
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-5.1.1.tgz",
+ "integrity": "sha512-P7B4+jq8DeD2nMsAcdfaqHbssgHtZ7Z5+++a5ask90fvmJ8p5je4mOa+wzu+DB4vQ5tdJV/xywY+UnVFeQLV5Q==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@pnpm/npm-conf": "^2.1.0"
+ "@pnpm/npm-conf": "^3.0.2"
},
"engines": {
"node": ">=14"
}
},
- "node_modules/release-zalgo": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/release-zalgo/-/release-zalgo-1.0.0.tgz",
- "integrity": "sha512-gUAyHVHPPC5wdqX/LG4LWtRYtgjxyX78oanFNTMMyFEfOqdC54s3eE82imuWKbOeqYht2CrNf64Qb8vgmmtZGA==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "es6-error": "^4.0.1"
- },
- "engines": {
- "node": ">=4"
- }
- },
"node_modules/require-directory": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
@@ -15017,13 +14405,6 @@
"node": ">=0.10.0"
}
},
- "node_modules/require-main-filename": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz",
- "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==",
- "dev": true,
- "license": "ISC"
- },
"node_modules/resolve": {
"version": "1.22.11",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz",
@@ -15136,14 +14517,40 @@
"url": "https://github.com/sponsors/isaacs"
}
},
+ "node_modules/rimraf/node_modules/balanced-match": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.2.tgz",
+ "integrity": "sha512-x0K50QvKQ97fdEz2kPehIerj+YTeptKF9hyYkKf6egnwmMWAkADiO0QCzSp0R5xN8FTZgYaBfSaue46Ej62nMg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "jackspeak": "^4.2.3"
+ },
+ "engines": {
+ "node": "20 || >=22"
+ }
+ },
+ "node_modules/rimraf/node_modules/brace-expansion": {
+ "version": "5.0.2",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.2.tgz",
+ "integrity": "sha512-Pdk8c9poy+YhOgVWw1JNN22/HcivgKWwpxKq04M/jTmHyCZn12WPJebZxdjSa5TmBqISrUSgNYU3eRORljfCCw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^4.0.2"
+ },
+ "engines": {
+ "node": "20 || >=22"
+ }
+ },
"node_modules/rimraf/node_modules/glob": {
- "version": "13.0.0",
- "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.0.tgz",
- "integrity": "sha512-tvZgpqk6fz4BaNZ66ZsRaZnbHvP/jG3uKJvAZOwEVUL4RTA5nJeeLYfyN9/VA8NX/V3IBG+hkeuGpKjvELkVhA==",
+ "version": "13.0.3",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.3.tgz",
+ "integrity": "sha512-/g3B0mC+4x724v1TgtBlBtt2hPi/EWptsIAmXUx9Z2rvBYleQcsrmaOzd5LyL50jf/Soi83ZDJmw2+XqvH/EeA==",
"dev": true,
"license": "BlueOak-1.0.0",
"dependencies": {
- "minimatch": "^10.1.1",
+ "minimatch": "^10.2.0",
"minipass": "^7.1.2",
"path-scurry": "^2.0.0"
},
@@ -15155,13 +14562,13 @@
}
},
"node_modules/rimraf/node_modules/minimatch": {
- "version": "10.1.1",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.1.1.tgz",
- "integrity": "sha512-enIvLvRAFZYXJzkCYG5RKmPfrFArdLv+R+lbQ53BmIMLIry74bjKzX6iHAm8WYamJkhSSEabrWN5D97XnKObjQ==",
+ "version": "10.2.0",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.0.tgz",
+ "integrity": "sha512-ugkC31VaVg9cF0DFVoADH12k6061zNZkZON+aX8AWsR9GhPcErkcMBceb6znR8wLERM2AkkOxy2nWRLpT9Jq5w==",
"dev": true,
"license": "BlueOak-1.0.0",
"dependencies": {
- "@isaacs/brace-expansion": "^5.0.0"
+ "brace-expansion": "^5.0.2"
},
"engines": {
"node": "20 || >=22"
@@ -15272,157 +14679,79 @@
"license": "MIT",
"dependencies": {
"call-bound": "^1.0.2",
- "es-errors": "^1.3.0",
- "is-regex": "^1.2.1"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/safer-buffer": {
- "version": "2.1.2",
- "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
- "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/sax": {
- "version": "1.4.3",
- "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.3.tgz",
- "integrity": "sha512-yqYn1JhPczigF94DMS+shiDMjDowYO6y9+wB/4WgO0Y19jWYk0lQ4tuG5KI7kj4FTp1wxPj5IFfcrz/s1c3jjQ==",
- "dev": true,
- "license": "BlueOak-1.0.0"
- },
- "node_modules/schema-utils": {
- "version": "4.3.3",
- "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.3.tgz",
- "integrity": "sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@types/json-schema": "^7.0.9",
- "ajv": "^8.9.0",
- "ajv-formats": "^2.1.1",
- "ajv-keywords": "^5.1.0"
- },
- "engines": {
- "node": ">= 10.13.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/webpack"
- }
- },
- "node_modules/secretlint": {
- "version": "10.2.2",
- "resolved": "https://registry.npmjs.org/secretlint/-/secretlint-10.2.2.tgz",
- "integrity": "sha512-xVpkeHV/aoWe4vP4TansF622nBEImzCY73y/0042DuJ29iKIaqgoJ8fGxre3rVSHHbxar4FdJobmTnLp9AU0eg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@secretlint/config-creator": "^10.2.2",
- "@secretlint/formatter": "^10.2.2",
- "@secretlint/node": "^10.2.2",
- "@secretlint/profiler": "^10.2.2",
- "debug": "^4.4.1",
- "globby": "^14.1.0",
- "read-pkg": "^9.0.1"
- },
- "bin": {
- "secretlint": "bin/secretlint.js"
- },
- "engines": {
- "node": ">=20.0.0"
- }
- },
- "node_modules/secretlint/node_modules/hosted-git-info": {
- "version": "7.0.2",
- "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-7.0.2.tgz",
- "integrity": "sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "lru-cache": "^10.0.1"
- },
- "engines": {
- "node": "^16.14.0 || >=18.0.0"
- }
- },
- "node_modules/secretlint/node_modules/lru-cache": {
- "version": "10.4.3",
- "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz",
- "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==",
- "dev": true,
- "license": "ISC"
- },
- "node_modules/secretlint/node_modules/normalize-package-data": {
- "version": "6.0.2",
- "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-6.0.2.tgz",
- "integrity": "sha512-V6gygoYb/5EmNI+MEGrWkC+e6+Rr7mTmfHrxDbLzxQogBkgzo76rkok0Am6thgSF7Mv2nLOajAJj5vDJZEFn7g==",
- "dev": true,
- "license": "BSD-2-Clause",
- "dependencies": {
- "hosted-git-info": "^7.0.0",
- "semver": "^7.3.5",
- "validate-npm-package-license": "^3.0.4"
- },
- "engines": {
- "node": "^16.14.0 || >=18.0.0"
- }
- },
- "node_modules/secretlint/node_modules/read-pkg": {
- "version": "9.0.1",
- "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-9.0.1.tgz",
- "integrity": "sha512-9viLL4/n1BJUCT1NXVTdS1jtm80yDEgR5T4yCelII49Mbj0v1rZdKqj7zCiYdbB0CuCgdrvHcNogAKTFPBocFA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@types/normalize-package-data": "^2.4.3",
- "normalize-package-data": "^6.0.0",
- "parse-json": "^8.0.0",
- "type-fest": "^4.6.0",
- "unicorn-magic": "^0.1.0"
+ "es-errors": "^1.3.0",
+ "is-regex": "^1.2.1"
},
"engines": {
- "node": ">=18"
+ "node": ">= 0.4"
},
"funding": {
- "url": "https://github.com/sponsors/sindresorhus"
+ "url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/secretlint/node_modules/type-fest": {
- "version": "4.41.0",
- "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz",
- "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==",
+ "node_modules/safer-buffer": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
+ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
"dev": true,
- "license": "(MIT OR CC0-1.0)",
+ "license": "MIT"
+ },
+ "node_modules/sax": {
+ "version": "1.4.4",
+ "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.4.tgz",
+ "integrity": "sha512-1n3r/tGXO6b6VXMdFT54SHzT9ytu9yr7TaELowdYpMqY/Ao7EnlQGmAQ1+RatX7Tkkdm6hONI2owqNx2aZj5Sw==",
+ "dev": true,
+ "license": "BlueOak-1.0.0",
"engines": {
- "node": ">=16"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
+ "node": ">=11.0.0"
}
},
- "node_modules/secretlint/node_modules/unicorn-magic": {
- "version": "0.1.0",
- "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.1.0.tgz",
- "integrity": "sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ==",
+ "node_modules/schema-utils": {
+ "version": "4.3.3",
+ "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.3.tgz",
+ "integrity": "sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==",
"dev": true,
"license": "MIT",
+ "dependencies": {
+ "@types/json-schema": "^7.0.9",
+ "ajv": "^8.9.0",
+ "ajv-formats": "^2.1.1",
+ "ajv-keywords": "^5.1.0"
+ },
"engines": {
- "node": ">=18"
+ "node": ">= 10.13.0"
},
"funding": {
- "url": "https://github.com/sponsors/sindresorhus"
+ "type": "opencollective",
+ "url": "https://opencollective.com/webpack"
+ }
+ },
+ "node_modules/secretlint": {
+ "version": "10.2.2",
+ "resolved": "https://registry.npmjs.org/secretlint/-/secretlint-10.2.2.tgz",
+ "integrity": "sha512-xVpkeHV/aoWe4vP4TansF622nBEImzCY73y/0042DuJ29iKIaqgoJ8fGxre3rVSHHbxar4FdJobmTnLp9AU0eg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@secretlint/config-creator": "^10.2.2",
+ "@secretlint/formatter": "^10.2.2",
+ "@secretlint/node": "^10.2.2",
+ "@secretlint/profiler": "^10.2.2",
+ "debug": "^4.4.1",
+ "globby": "^14.1.0",
+ "read-pkg": "^9.0.1"
+ },
+ "bin": {
+ "secretlint": "bin/secretlint.js"
+ },
+ "engines": {
+ "node": ">=20.0.0"
}
},
"node_modules/semantic-release": {
- "version": "25.0.2",
- "resolved": "https://registry.npmjs.org/semantic-release/-/semantic-release-25.0.2.tgz",
- "integrity": "sha512-6qGjWccl5yoyugHt3jTgztJ9Y0JVzyH8/Voc/D8PlLat9pwxQYXz7W1Dpnq5h0/G5GCYGUaDSlYcyk3AMh5A6g==",
+ "version": "25.0.3",
+ "resolved": "https://registry.npmjs.org/semantic-release/-/semantic-release-25.0.3.tgz",
+ "integrity": "sha512-WRgl5GcypwramYX4HV+eQGzUbD7UUbljVmS+5G1uMwX/wLgYuJAxGeerXJDMO2xshng4+FXqCgyB5QfClV6WjA==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -15452,7 +14781,6 @@
"read-package-up": "^12.0.0",
"resolve-from": "^5.0.0",
"semver": "^7.3.2",
- "semver-diff": "^5.0.0",
"signale": "^1.2.1",
"yargs": "^18.0.0"
},
@@ -15463,10 +14791,43 @@
"node": "^22.14.0 || >= 24.10.0"
}
},
+ "node_modules/semantic-release-next-version": {
+ "version": "0.1.3",
+ "resolved": "https://registry.npmjs.org/semantic-release-next-version/-/semantic-release-next-version-0.1.3.tgz",
+ "integrity": "sha512-tsOggf4scIrPZtVb1XNBpxMc1b6MCLgmAQcrQVbDsnkN1PsNqXpsYiBdC+eGvBayHpBdvt9W+NBQQVhZQTIuyg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "debug": "^4.4.3",
+ "meow": "^14.0.0",
+ "semver": "^7.7.3",
+ "tinyexec": "^1.0.2"
+ },
+ "bin": {
+ "next-version-helper": "bin/cli.cjs"
+ },
+ "peerDependencies": {
+ "@semantic-release/commit-analyzer": ">=13 <14",
+ "semantic-release": ">=25 <26"
+ }
+ },
+ "node_modules/semantic-release-next-version/node_modules/meow": {
+ "version": "14.0.0",
+ "resolved": "https://registry.npmjs.org/meow/-/meow-14.0.0.tgz",
+ "integrity": "sha512-JhC3R1f6dbspVtmF3vKjAWz1EVIvwFrGGPLSdU6rK79xBwHWTuHoLnRX/t1/zHS1Ch1Y2UtIrih7DAHuH9JFJA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=20"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/semantic-release-vsce": {
- "version": "6.0.20",
- "resolved": "https://registry.npmjs.org/semantic-release-vsce/-/semantic-release-vsce-6.0.20.tgz",
- "integrity": "sha512-F8WNK/l9TmB21qYQYn7hWQ40aae5jyhuck4Q96kxzlpC1AiIb7piM2OH/I9nBP3ByiEcVJ9fK+Z3A1f//3iBQg==",
+ "version": "6.0.24",
+ "resolved": "https://registry.npmjs.org/semantic-release-vsce/-/semantic-release-vsce-6.0.24.tgz",
+ "integrity": "sha512-zKCx4LgOeZV+ff4I2FwDPhsOlSPd6ZKElt+82j2biwjAlDx7zT9QFm5lSHDEl6bb6WYfCxhtEmDfo23u/3JNQA==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -15494,14 +14855,40 @@
"node": ">=18"
}
},
+ "node_modules/semantic-release-vsce/node_modules/balanced-match": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.2.tgz",
+ "integrity": "sha512-x0K50QvKQ97fdEz2kPehIerj+YTeptKF9hyYkKf6egnwmMWAkADiO0QCzSp0R5xN8FTZgYaBfSaue46Ej62nMg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "jackspeak": "^4.2.3"
+ },
+ "engines": {
+ "node": "20 || >=22"
+ }
+ },
+ "node_modules/semantic-release-vsce/node_modules/brace-expansion": {
+ "version": "5.0.2",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.2.tgz",
+ "integrity": "sha512-Pdk8c9poy+YhOgVWw1JNN22/HcivgKWwpxKq04M/jTmHyCZn12WPJebZxdjSa5TmBqISrUSgNYU3eRORljfCCw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^4.0.2"
+ },
+ "engines": {
+ "node": "20 || >=22"
+ }
+ },
"node_modules/semantic-release-vsce/node_modules/glob": {
- "version": "13.0.0",
- "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.0.tgz",
- "integrity": "sha512-tvZgpqk6fz4BaNZ66ZsRaZnbHvP/jG3uKJvAZOwEVUL4RTA5nJeeLYfyN9/VA8NX/V3IBG+hkeuGpKjvELkVhA==",
+ "version": "13.0.3",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.3.tgz",
+ "integrity": "sha512-/g3B0mC+4x724v1TgtBlBtt2hPi/EWptsIAmXUx9Z2rvBYleQcsrmaOzd5LyL50jf/Soi83ZDJmw2+XqvH/EeA==",
"dev": true,
"license": "BlueOak-1.0.0",
"dependencies": {
- "minimatch": "^10.1.1",
+ "minimatch": "^10.2.0",
"minipass": "^7.1.2",
"path-scurry": "^2.0.0"
},
@@ -15513,13 +14900,13 @@
}
},
"node_modules/semantic-release-vsce/node_modules/minimatch": {
- "version": "10.1.1",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.1.1.tgz",
- "integrity": "sha512-enIvLvRAFZYXJzkCYG5RKmPfrFArdLv+R+lbQ53BmIMLIry74bjKzX6iHAm8WYamJkhSSEabrWN5D97XnKObjQ==",
+ "version": "10.2.0",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.0.tgz",
+ "integrity": "sha512-ugkC31VaVg9cF0DFVoADH12k6061zNZkZON+aX8AWsR9GhPcErkcMBceb6znR8wLERM2AkkOxy2nWRLpT9Jq5w==",
"dev": true,
"license": "BlueOak-1.0.0",
"dependencies": {
- "@isaacs/brace-expansion": "^5.0.0"
+ "brace-expansion": "^5.0.2"
},
"engines": {
"node": "20 || >=22"
@@ -15659,15 +15046,30 @@
}
},
"node_modules/semantic-release/node_modules/lru-cache": {
- "version": "11.2.4",
- "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.4.tgz",
- "integrity": "sha512-B5Y16Jr9LB9dHVkh6ZevG+vAbOsNOYCX+sXvFWFu7B3Iz5mijW3zdbMyhsh8ANd2mSWBYdJgnqi+mL7/LrOPYg==",
+ "version": "11.2.6",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.6.tgz",
+ "integrity": "sha512-ESL2CrkS/2wTPfuend7Zhkzo2u0daGJ/A2VucJOgQ/C48S/zB8MMeMHSGKYpXhIjbPxfuezITkaBH1wqv00DDQ==",
"dev": true,
"license": "BlueOak-1.0.0",
"engines": {
"node": "20 || >=22"
}
},
+ "node_modules/semantic-release/node_modules/normalize-package-data": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-8.0.0.tgz",
+ "integrity": "sha512-RWk+PI433eESQ7ounYxIp67CYuVsS1uYSonX3kA6ps/3LWfjVQa/ptEg6Y3T6uAMq1mWpX9PQ+qx+QaHpsc7gQ==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "hosted-git-info": "^9.0.0",
+ "semver": "^7.3.5",
+ "validate-npm-package-license": "^3.0.4"
+ },
+ "engines": {
+ "node": "^20.17.0 || >=22.9.0"
+ }
+ },
"node_modules/semantic-release/node_modules/p-reduce": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/p-reduce/-/p-reduce-3.0.0.tgz",
@@ -15699,6 +15101,26 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/semantic-release/node_modules/read-pkg": {
+ "version": "10.1.0",
+ "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-10.1.0.tgz",
+ "integrity": "sha512-I8g2lArQiP78ll51UeMZojewtYgIRCKCWqZEgOO8c/uefTI+XDXvCSXu3+YNUaTNvZzobrL5+SqHjBrByRRTdg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/normalize-package-data": "^2.4.4",
+ "normalize-package-data": "^8.0.0",
+ "parse-json": "^8.3.0",
+ "type-fest": "^5.4.4",
+ "unicorn-magic": "^0.4.0"
+ },
+ "engines": {
+ "node": ">=20"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/semantic-release/node_modules/resolve-from": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz",
@@ -15728,9 +15150,9 @@
}
},
"node_modules/semantic-release/node_modules/type-fest": {
- "version": "5.3.1",
- "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-5.3.1.tgz",
- "integrity": "sha512-VCn+LMHbd4t6sF3wfU/+HKT63C9OoyrSIf4b+vtWHpt2U7/4InZG467YDNMFMR70DdHjAdpPWmw2lzRdg0Xqqg==",
+ "version": "5.4.4",
+ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-5.4.4.tgz",
+ "integrity": "sha512-JnTrzGu+zPV3aXIUhnyWJj4z/wigMsdYajGLIYakqyOW1nPllzXEJee0QQbHj+CTIQtXGlAjuK0UY+2xTyjVAw==",
"dev": true,
"license": "(MIT OR CC0-1.0)",
"dependencies": {
@@ -15743,6 +15165,19 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/semantic-release/node_modules/unicorn-magic": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.4.0.tgz",
+ "integrity": "sha512-wH590V9VNgYH9g3lH9wWjTrUoKsjLF6sGLjhR4sH1LWpLmCOH0Zf7PukhDA8BiS7KHe4oPNkcTHqYkj7SOGUOw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=20"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/semantic-release/node_modules/wrap-ansi": {
"version": "9.0.2",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz",
@@ -15790,9 +15225,9 @@
}
},
"node_modules/semver": {
- "version": "7.7.3",
- "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz",
- "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==",
+ "version": "7.7.4",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz",
+ "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==",
"dev": true,
"license": "ISC",
"bin": {
@@ -15802,23 +15237,6 @@
"node": ">=10"
}
},
- "node_modules/semver-diff": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/semver-diff/-/semver-diff-5.0.0.tgz",
- "integrity": "sha512-0HbGtOm+S7T6NGQ/pxJSJipJvc4DK3FcRVMRkhsIwJDJ4Jcz5DQC1cPPzB5GhzyHjwttW878HaWQq46CkL3cqg==",
- "deprecated": "Deprecated as the semver package now supports this built-in.",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "semver": "^7.3.5"
- },
- "engines": {
- "node": ">=12"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
"node_modules/semver-regex": {
"version": "4.0.5",
"resolved": "https://registry.npmjs.org/semver-regex/-/semver-regex-4.0.5.tgz",
@@ -15842,13 +15260,6 @@
"randombytes": "^2.1.0"
}
},
- "node_modules/set-blocking": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
- "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==",
- "dev": true,
- "license": "ISC"
- },
"node_modules/set-function-length": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz",
@@ -16208,186 +15619,84 @@
"engines": {
"node": ">=14.16"
},
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/slice-ansi": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz",
- "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "ansi-styles": "^4.0.0",
- "astral-regex": "^2.0.0",
- "is-fullwidth-code-point": "^3.0.0"
- },
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/chalk/slice-ansi?sponsor=1"
- }
- },
- "node_modules/sort-object-keys": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/sort-object-keys/-/sort-object-keys-2.0.1.tgz",
- "integrity": "sha512-R89fO+z3x7hiKPXX5P0qim+ge6Y60AjtlW+QQpRozrrNcR1lw9Pkpm5MLB56HoNvdcLHL4wbpq16OcvGpEDJIg==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/sort-package-json": {
- "version": "3.5.0",
- "resolved": "https://registry.npmjs.org/sort-package-json/-/sort-package-json-3.5.0.tgz",
- "integrity": "sha512-moY4UtptUuP5sPuu9H9dp8xHNel7eP5/Kz/7+90jTvC0IOiPH2LigtRM/aSFSxreaWoToHUVUpEV4a2tAs2oKQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "detect-indent": "^7.0.1",
- "detect-newline": "^4.0.1",
- "git-hooks-list": "^4.0.0",
- "is-plain-obj": "^4.1.0",
- "semver": "^7.7.1",
- "sort-object-keys": "^2.0.0",
- "tinyglobby": "^0.2.12"
- },
- "bin": {
- "sort-package-json": "cli.js"
- },
- "engines": {
- "node": ">=20"
- }
- },
- "node_modules/source-map": {
- "version": "0.6.1",
- "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
- "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
- "dev": true,
- "license": "BSD-3-Clause",
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/source-map-support": {
- "version": "0.5.21",
- "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz",
- "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "buffer-from": "^1.0.0",
- "source-map": "^0.6.0"
- }
- },
- "node_modules/spawn-error-forwarder": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/spawn-error-forwarder/-/spawn-error-forwarder-1.0.0.tgz",
- "integrity": "sha512-gRjMgK5uFjbCvdibeGJuy3I5OYz6VLoVdsOJdA6wV0WlfQVLFueoqMxwwYD9RODdgb6oUIvlRlsyFSiQkMKu0g==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/spawn-wrap": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/spawn-wrap/-/spawn-wrap-2.0.0.tgz",
- "integrity": "sha512-EeajNjfN9zMnULLwhZZQU3GWBoFNkbngTUPfaawT4RkMiviTxcX0qfhVbGey39mfctfDHkWtuecgQ8NJcyQWHg==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "foreground-child": "^2.0.0",
- "is-windows": "^1.0.2",
- "make-dir": "^3.0.0",
- "rimraf": "^3.0.0",
- "signal-exit": "^3.0.2",
- "which": "^2.0.1"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/spawn-wrap/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==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "balanced-match": "^1.0.0",
- "concat-map": "0.0.1"
- }
- },
- "node_modules/spawn-wrap/node_modules/foreground-child": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-2.0.0.tgz",
- "integrity": "sha512-dCIq9FpEcyQyXKCkyzmlPTFNgrCzPudOe+mhvJU5zAtlBnGVy2yKxtfsxK2tQBThwq225jcvBjpw1Gr40uzZCA==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "cross-spawn": "^7.0.0",
- "signal-exit": "^3.0.2"
- },
- "engines": {
- "node": ">=8.0.0"
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/spawn-wrap/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",
+ "node_modules/slice-ansi": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz",
+ "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==",
"dev": true,
- "license": "ISC",
+ "license": "MIT",
"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"
+ "ansi-styles": "^4.0.0",
+ "astral-regex": "^2.0.0",
+ "is-fullwidth-code-point": "^3.0.0"
},
"engines": {
- "node": "*"
+ "node": ">=10"
},
"funding": {
- "url": "https://github.com/sponsors/isaacs"
+ "url": "https://github.com/chalk/slice-ansi?sponsor=1"
}
},
- "node_modules/spawn-wrap/node_modules/minimatch": {
- "version": "3.1.2",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
- "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+ "node_modules/sort-object-keys": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/sort-object-keys/-/sort-object-keys-2.1.0.tgz",
+ "integrity": "sha512-SOiEnthkJKPv2L6ec6HMwhUcN0/lppkeYuN1x63PbyPRrgSPIuBJCiYxYyvWRTtjMlOi14vQUCGUJqS6PLVm8g==",
"dev": true,
- "license": "ISC",
+ "license": "MIT"
+ },
+ "node_modules/sort-package-json": {
+ "version": "3.6.0",
+ "resolved": "https://registry.npmjs.org/sort-package-json/-/sort-package-json-3.6.0.tgz",
+ "integrity": "sha512-fyJsPLhWvY7u2KsKPZn1PixbXp+1m7V8NWqU8CvgFRbMEX41Ffw1kD8n0CfJiGoaSfoAvbrqRRl/DcHO8omQOQ==",
+ "dev": true,
+ "license": "MIT",
"dependencies": {
- "brace-expansion": "^1.1.7"
+ "detect-indent": "^7.0.2",
+ "detect-newline": "^4.0.1",
+ "git-hooks-list": "^4.1.1",
+ "is-plain-obj": "^4.1.0",
+ "semver": "^7.7.3",
+ "sort-object-keys": "^2.0.1",
+ "tinyglobby": "^0.2.15"
+ },
+ "bin": {
+ "sort-package-json": "cli.js"
},
"engines": {
- "node": "*"
+ "node": ">=20"
}
},
- "node_modules/spawn-wrap/node_modules/rimraf": {
- "version": "3.0.2",
- "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
- "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
- "deprecated": "Rimraf versions prior to v4 are no longer supported",
+ "node_modules/source-map": {
+ "version": "0.6.1",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+ "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
"dev": true,
- "license": "ISC",
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/source-map-support": {
+ "version": "0.5.21",
+ "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz",
+ "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==",
+ "dev": true,
+ "license": "MIT",
"dependencies": {
- "glob": "^7.1.3"
- },
- "bin": {
- "rimraf": "bin.js"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
+ "buffer-from": "^1.0.0",
+ "source-map": "^0.6.0"
}
},
- "node_modules/spawn-wrap/node_modules/signal-exit": {
- "version": "3.0.7",
- "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
- "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==",
+ "node_modules/spawn-error-forwarder": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/spawn-error-forwarder/-/spawn-error-forwarder-1.0.0.tgz",
+ "integrity": "sha512-gRjMgK5uFjbCvdibeGJuy3I5OYz6VLoVdsOJdA6wV0WlfQVLFueoqMxwwYD9RODdgb6oUIvlRlsyFSiQkMKu0g==",
"dev": true,
- "license": "ISC"
+ "license": "MIT"
},
"node_modules/spdx-correct": {
"version": "3.2.0",
@@ -16435,13 +15744,6 @@
"through2": "~2.0.0"
}
},
- "node_modules/sprintf-js": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
- "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==",
- "dev": true,
- "license": "BSD-3-Clause"
- },
"node_modules/stable-hash": {
"version": "0.0.5",
"resolved": "https://registry.npmjs.org/stable-hash/-/stable-hash-0.0.5.tgz",
@@ -16728,16 +16030,6 @@
"node": ">=8"
}
},
- "node_modules/strip-bom": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz",
- "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=8"
- }
- },
"node_modules/strip-final-newline": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-4.0.0.tgz",
@@ -16836,9 +16128,9 @@
}
},
"node_modules/synckit": {
- "version": "0.11.11",
- "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.11.tgz",
- "integrity": "sha512-MeQTA1r0litLUf0Rp/iisCaL8761lKAZHaimlbGK4j0HysC4PLfqygQj9srcs0m2RdtDYnF8UuYyKpbjHYp7Jw==",
+ "version": "0.11.12",
+ "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.12.tgz",
+ "integrity": "sha512-Bh7QjT8/SuKUIfObSXNHNSK6WHo6J1tHCqJsuaFDP7gP0fkzSfTxI8y85JrppZ0h8l0maIgc2tfuZQ6/t3GtnQ==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -17017,7 +16309,7 @@
"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",
+ "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me",
"dev": true,
"license": "ISC",
"dependencies": {
@@ -17063,9 +16355,9 @@
}
},
"node_modules/tempy": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/tempy/-/tempy-3.1.0.tgz",
- "integrity": "sha512-7jDLIdD2Zp0bDe5r3D2qtkd1QOCacylBuL7oa4udvN6v2pqr4+LcCr67C8DR1zkpaZ8XosF5m1yQSabKAW6f2g==",
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/tempy/-/tempy-3.2.0.tgz",
+ "integrity": "sha512-d79HhZya5Djd7am0q+W4RTsSU+D/aJzM+4Y4AGJGuGlgM2L6sx5ZvOYTmZjqPhrDrV6xJTtRSm1JCLj6V6LHLQ==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -17125,9 +16417,9 @@
}
},
"node_modules/terser": {
- "version": "5.44.1",
- "resolved": "https://registry.npmjs.org/terser/-/terser-5.44.1.tgz",
- "integrity": "sha512-t/R3R/n0MSwnnazuPpPNVO60LX0SKL45pyl9YlvxIdkH0Of7D5qM2EVe+yASRIlY5pZ73nclYJfNANGWPwFDZw==",
+ "version": "5.46.0",
+ "resolved": "https://registry.npmjs.org/terser/-/terser-5.46.0.tgz",
+ "integrity": "sha512-jTwoImyr/QbOWFFso3YoU3ik0jBBDJ6JTOQiy/J2YxVJdZCc+5u7skhNwiOR3FQIygFqVUPHl7qbbxtjW2K3Qg==",
"dev": true,
"license": "BSD-2-Clause",
"dependencies": {
@@ -17185,67 +16477,6 @@
"dev": true,
"license": "MIT"
},
- "node_modules/test-exclude": {
- "version": "6.0.0",
- "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz",
- "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "@istanbuljs/schema": "^0.1.2",
- "glob": "^7.1.4",
- "minimatch": "^3.0.4"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/test-exclude/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==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "balanced-match": "^1.0.0",
- "concat-map": "0.0.1"
- }
- },
- "node_modules/test-exclude/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",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "fs.realpath": "^1.0.0",
- "inflight": "^1.0.4",
- "inherits": "2",
- "minimatch": "^3.1.1",
- "once": "^1.3.0",
- "path-is-absolute": "^1.0.0"
- },
- "engines": {
- "node": "*"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
- }
- },
- "node_modules/test-exclude/node_modules/minimatch": {
- "version": "3.1.2",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
- "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "brace-expansion": "^1.1.7"
- },
- "engines": {
- "node": "*"
- }
- },
"node_modules/text-table": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
@@ -17319,6 +16550,16 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/tinyexec": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.2.tgz",
+ "integrity": "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ }
+ },
"node_modules/tinyglobby": {
"version": "0.2.15",
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz",
@@ -17413,23 +16654,23 @@
}
},
"node_modules/trbr": {
- "version": "0.1.24",
- "resolved": "https://registry.npmjs.org/trbr/-/trbr-0.1.24.tgz",
- "integrity": "sha512-XRp3dweW4N04TvnQmdiAYIynZA60esM81nTPUp5YLRoYb+jTiFMcqDuuksbxF9w8wuamf3uL2V3Rj7cQ7IEdlw==",
+ "version": "0.3.1",
+ "resolved": "https://registry.npmjs.org/trbr/-/trbr-0.3.1.tgz",
+ "integrity": "sha512-dmNQWWgDN/eK1Veb4AJ8kZtQYJG61MDp6JF8crdVnnuBvJH5lGqDlJdRrFhCLmxmqVGERWKcZ6utUdJpokRJ1g==",
"license": "GPL-3.0",
"dependencies": {
"clipboardy": "^4.0.0",
"commander": "^12.1.0",
- "debug": "^4.4.0",
- "fqbn": "^1.3.0",
+ "debug": "^4.4.3",
+ "fqbn": "^1.4.0",
"lodash.debounce": "^4.0.8",
"tinyrainbow": "^2.0.0"
}
},
"node_modules/ts-api-utils": {
- "version": "2.3.0",
- "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.3.0.tgz",
- "integrity": "sha512-6eg3Y9SF7SsAvGzRHQvvc1skDAhwI4YQ32ui1scxD1Ccr0G5qIIbUBT3pFTKX8kmWIQClHobtUdNuaBgwdfdWg==",
+ "version": "2.4.0",
+ "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.4.0.tgz",
+ "integrity": "sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA==",
"dev": true,
"license": "MIT",
"engines": {
@@ -17480,7 +16721,8 @@
"resolved": "https://registry.npmjs.org/ts-error/-/ts-error-1.0.6.tgz",
"integrity": "sha512-tLJxacIQUM82IR7JO1UUkKlYuUTmoY9HBJAmNWFzheSlDS5SPMcNIepejHJa4BpPQLAcbRhRf3GDJzyj6rbKvA==",
"dev": true,
- "license": "MIT"
+ "license": "MIT",
+ "peer": true
},
"node_modules/ts-loader": {
"version": "9.5.4",
@@ -17593,16 +16835,6 @@
"node": ">= 0.8.0"
}
},
- "node_modules/type-fest": {
- "version": "0.8.1",
- "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz",
- "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==",
- "dev": true,
- "license": "(MIT OR CC0-1.0)",
- "engines": {
- "node": ">=8"
- }
- },
"node_modules/typed-array-buffer": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz",
@@ -17693,16 +16925,6 @@
"underscore": "^1.12.1"
}
},
- "node_modules/typedarray-to-buffer": {
- "version": "3.1.5",
- "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz",
- "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "is-typedarray": "^1.0.0"
- }
- },
"node_modules/typescript": {
"version": "5.9.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
@@ -17718,16 +16940,16 @@
}
},
"node_modules/typescript-eslint": {
- "version": "8.51.0",
- "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.51.0.tgz",
- "integrity": "sha512-jh8ZuM5oEh2PSdyQG9YAEM1TCGuWenLSuSUhf/irbVUNW9O5FhbFVONviN2TgMTBnUmyHv7E56rYnfLZK6TkiA==",
+ "version": "8.56.0",
+ "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.56.0.tgz",
+ "integrity": "sha512-c7toRLrotJ9oixgdW7liukZpsnq5CZ7PuKztubGYlNppuTqhIoWfhgHo/7EU0v06gS2l/x0i2NEFK1qMIf0rIg==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@typescript-eslint/eslint-plugin": "8.51.0",
- "@typescript-eslint/parser": "8.51.0",
- "@typescript-eslint/typescript-estree": "8.51.0",
- "@typescript-eslint/utils": "8.51.0"
+ "@typescript-eslint/eslint-plugin": "8.56.0",
+ "@typescript-eslint/parser": "8.56.0",
+ "@typescript-eslint/typescript-estree": "8.56.0",
+ "@typescript-eslint/utils": "8.56.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -17737,7 +16959,7 @@
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
- "eslint": "^8.57.0 || ^9.0.0",
+ "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
"typescript": ">=4.8.4 <6.0.0"
}
},
@@ -17789,9 +17011,9 @@
"license": "MIT"
},
"node_modules/undici": {
- "version": "7.16.0",
- "resolved": "https://registry.npmjs.org/undici/-/undici-7.16.0.tgz",
- "integrity": "sha512-QEg3HPMll0o3t2ourKwOeUAZ159Kn9mx5pnzHRQO8+Wixmh88YdZRiIwat0iNzNNXn0yoEtXJqFpyW7eM8BV7g==",
+ "version": "7.22.0",
+ "resolved": "https://registry.npmjs.org/undici/-/undici-7.22.0.tgz",
+ "integrity": "sha512-RqslV2Us5BrllB+JeiZnK4peryVTndy9Dnqq62S3yYRRTj0tFQCwEniUy2167skdGOy3vqRzEvl1Dm4sV2ReDg==",
"dev": true,
"license": "MIT",
"engines": {
@@ -17978,6 +17200,28 @@
"uuid": "dist/bin/uuid"
}
},
+ "node_modules/v8-to-istanbul": {
+ "version": "9.3.0",
+ "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz",
+ "integrity": "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "@jridgewell/trace-mapping": "^0.3.12",
+ "@types/istanbul-lib-coverage": "^2.0.1",
+ "convert-source-map": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10.12.0"
+ }
+ },
+ "node_modules/v8-to-istanbul/node_modules/convert-source-map": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
+ "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/validate-npm-package-license": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz",
@@ -18003,24 +17247,24 @@
}
},
"node_modules/vscode-arduino-api": {
- "version": "0.2.1",
- "resolved": "https://registry.npmjs.org/vscode-arduino-api/-/vscode-arduino-api-0.2.1.tgz",
- "integrity": "sha512-s0IPhIvpuPXM+aW483QmAewB7T70KklBL8OtmQqWDhPnIB+Kx+AqXCHjyM/dXwZonQMoqH3+JX3BpGaWoRT5Cw==",
+ "version": "0.2.4",
+ "resolved": "https://registry.npmjs.org/vscode-arduino-api/-/vscode-arduino-api-0.2.4.tgz",
+ "integrity": "sha512-1ti0Yu0EcBthM1ZX0gQpXZTPyXFIVvqlr6u7MYn9gvxg4mhAQaB6VDanxO3S25RnBM21ROeNNCSt7Mp2cNl5ww==",
"dev": true,
"license": "MIT",
- "dependencies": {
- "@types/vscode": "^1.78.0",
- "ardunno-cli": "^0.1.8",
- "boards-list": "^1.1.0"
- },
"engines": {
"vscode": "^1.78.0"
+ },
+ "peerDependencies": {
+ "@types/vscode": "^1.78.0",
+ "ardunno-cli": "^0.1.12",
+ "boards-list": "^2.0.0"
}
},
"node_modules/watchpack": {
- "version": "2.5.0",
- "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.5.0.tgz",
- "integrity": "sha512-e6vZvY6xboSwLz2GD36c16+O/2Z6fKvIf4pOXptw2rY9MVwE/TXc6RGqxD3I3x0a28lwBY7DE+76uTPSsBrrCA==",
+ "version": "2.5.1",
+ "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.5.1.tgz",
+ "integrity": "sha512-Zn5uXdcFNIA1+1Ei5McRd+iRzfhENPCe7LeABkJtNulSxjma+l7ltNx55BWZkRlwRnpOgHqxnjyaDgJnNXnqzg==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -18039,9 +17283,9 @@
"license": "Apache-2.0"
},
"node_modules/webpack": {
- "version": "5.104.1",
- "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.104.1.tgz",
- "integrity": "sha512-Qphch25abbMNtekmEGJmeRUhLDbe+QfiWTiqpKYkpCOWY64v9eyl+KRRLmqOFA2AvKPpc9DC6+u2n76tQLBoaA==",
+ "version": "5.105.2",
+ "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.105.2.tgz",
+ "integrity": "sha512-dRXm0a2qcHPUBEzVk8uph0xWSjV/xZxenQQbLwnwP7caQCYpqG1qddwlyEkIDkYn0K8tvmcrZ+bOrzoQ3HxCDw==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -18055,7 +17299,7 @@
"acorn-import-phases": "^1.0.3",
"browserslist": "^4.28.1",
"chrome-trace-event": "^1.0.2",
- "enhanced-resolve": "^5.17.4",
+ "enhanced-resolve": "^5.19.0",
"es-module-lexer": "^2.0.0",
"eslint-scope": "5.1.1",
"events": "^3.2.0",
@@ -18068,7 +17312,7 @@
"schema-utils": "^4.3.3",
"tapable": "^2.3.0",
"terser-webpack-plugin": "^5.3.16",
- "watchpack": "^2.4.4",
+ "watchpack": "^2.5.1",
"webpack-sources": "^3.3.3"
},
"bin": {
@@ -18296,17 +17540,10 @@
"url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/which-module": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.1.tgz",
- "integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==",
- "dev": true,
- "license": "ISC"
- },
"node_modules/which-typed-array": {
- "version": "1.1.19",
- "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.19.tgz",
- "integrity": "sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==",
+ "version": "1.1.20",
+ "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.20.tgz",
+ "integrity": "sha512-LYfpUkmqwl0h9A2HL09Mms427Q1RZWuOHsukfVcKRq9q95iQxdw0ix1JQrqbcDR9PH1QDwf5Qo8OZb5lksZ8Xg==",
"license": "MIT",
"dependencies": {
"available-typed-arrays": "^1.0.7",
@@ -18356,18 +17593,18 @@
"license": "Apache-2.0"
},
"node_modules/wrap-ansi": {
- "version": "8.1.0",
- "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz",
- "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==",
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
+ "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
"dev": true,
"license": "MIT",
"dependencies": {
- "ansi-styles": "^6.1.0",
- "string-width": "^5.0.1",
- "strip-ansi": "^7.0.1"
+ "ansi-styles": "^4.0.0",
+ "string-width": "^4.1.0",
+ "strip-ansi": "^6.0.0"
},
"engines": {
- "node": ">=12"
+ "node": ">=10"
},
"funding": {
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
@@ -18415,42 +17652,27 @@
"node": ">=8"
}
},
- "node_modules/wrap-ansi/node_modules/ansi-styles": {
- "version": "6.2.3",
- "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz",
- "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==",
+ "node_modules/wrap-ansi/node_modules/ansi-regex": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
"dev": true,
"license": "MIT",
"engines": {
- "node": ">=12"
- },
- "funding": {
- "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ "node": ">=8"
}
},
- "node_modules/wrap-ansi/node_modules/emoji-regex": {
- "version": "9.2.2",
- "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
- "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/wrap-ansi/node_modules/string-width": {
- "version": "5.1.2",
- "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
- "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==",
+ "node_modules/wrap-ansi/node_modules/strip-ansi": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
"dev": true,
"license": "MIT",
"dependencies": {
- "eastasianwidth": "^0.2.0",
- "emoji-regex": "^9.2.2",
- "strip-ansi": "^7.0.1"
+ "ansi-regex": "^5.0.1"
},
"engines": {
- "node": ">=12"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
+ "node": ">=8"
}
},
"node_modules/wrappy": {
@@ -18460,26 +17682,6 @@
"dev": true,
"license": "ISC"
},
- "node_modules/write-file-atomic": {
- "version": "3.0.3",
- "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz",
- "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "imurmurhash": "^0.1.4",
- "is-typedarray": "^1.0.0",
- "signal-exit": "^3.0.2",
- "typedarray-to-buffer": "^3.1.5"
- }
- },
- "node_modules/write-file-atomic/node_modules/signal-exit": {
- "version": "3.0.7",
- "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
- "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==",
- "dev": true,
- "license": "ISC"
- },
"node_modules/wsl-utils": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/wsl-utils/-/wsl-utils-0.1.0.tgz",
diff --git a/package.json b/package.json
index c2a47f7..b47501a 100644
--- a/package.json
+++ b/package.json
@@ -3,7 +3,7 @@
"displayName": "ESP Exception Decoder",
"version": "1.1.1",
"private": true,
- "description": "ESP Exception Decoder helps you understand crash stack traces and backtraces from ESP8266 and ESP32 boards",
+ "description": "ESP Exception Decoder helps you analyze ESP8266 and ESP32 crashes by decoding stack traces, backtraces, and panic output. It supports both automatic crash capture (recommended) and manual decoding workflows",
"categories": [
"Other"
],
@@ -34,7 +34,7 @@
"scripts": {
"prebuild": "rimraf dist",
"build": "webpack --mode production --devtool hidden-source-map",
- "clean": "rimraf dist out *.vsix",
+ "build:debug": "webpack --mode development --devtool source-map",
"compile": "webpack && vsce package",
"compile-tests": "tsc -p . --outDir out",
"format": "prettier --write .",
@@ -43,8 +43,8 @@
"package": "vsce package",
"release": "semantic-release",
"pretest": "npm run compile-tests && npm run compile",
- "test": "node ./out/test/runTest.js",
- "watch": "webpack --watch",
+ "test": "vscode-test --config .vscode-test.mjs --coverage --label extension-host",
+ "watch": "webpack --mode development --devtool source-map --watch",
"watch-tests": "tsc -p . -w --outDir out"
},
"contributes": {
@@ -53,19 +53,287 @@
"command": "espExceptionDecoder.showTerminal",
"title": "Show Decoder Terminal",
"category": "ESP Exception Decoder"
+ },
+ {
+ "command": "espExceptionDecoder.capturerCreate",
+ "title": "Create Crash Capturer",
+ "icon": "$(add)",
+ "category": "ESP Exception Decoder"
+ },
+ {
+ "command": "espExceptionDecoder.capturerRemove",
+ "title": "Delete Crash Capturer",
+ "icon": "$(trash)",
+ "category": "ESP Exception Decoder"
+ },
+ {
+ "command": "espExceptionDecoder.capturerStart",
+ "title": "Start Capturing",
+ "icon": "$(record)",
+ "category": "ESP Exception Decoder"
+ },
+ {
+ "command": "espExceptionDecoder.capturerStop",
+ "title": "Stop Capturing",
+ "icon": "$(debug-stop)",
+ "category": "ESP Exception Decoder"
+ },
+ {
+ "command": "espExceptionDecoder.capturerRefresh",
+ "title": "Refresh",
+ "icon": "$(refresh)",
+ "category": "ESP Exception Decoder"
+ },
+ {
+ "command": "espExceptionDecoder.capturerQuickFix",
+ "title": "Quick Fix",
+ "icon": "$(light-bulb)",
+ "category": "ESP Exception Decoder"
+ },
+ {
+ "command": "espExceptionDecoder.capturerQuickFixSyncSketchBoard",
+ "title": "Sync Sketch Board to Capturer FQBN",
+ "category": "ESP Exception Decoder"
+ },
+ {
+ "command": "espExceptionDecoder.capturerCompileSketchDebug",
+ "title": "Compile Sketch with Debug Symbols",
+ "category": "ESP Exception Decoder"
+ },
+ {
+ "command": "espExceptionDecoder.capturerCompileSketch",
+ "title": "Compile Sketch",
+ "category": "ESP Exception Decoder"
+ },
+ {
+ "command": "espExceptionDecoder.capturerReplayCrash",
+ "title": "Replay Crash",
+ "icon": "$(debug-rerun)",
+ "category": "ESP Exception Decoder"
+ },
+ {
+ "command": "espExceptionDecoder.capturerShowEvent",
+ "title": "Preview Crash Event",
+ "icon": "$(open-preview)",
+ "category": "ESP Exception Decoder"
+ },
+ {
+ "command": "espExceptionDecoder.capturerClearEvent",
+ "title": "Clear Crash Event",
+ "icon": "$(close)",
+ "category": "ESP Exception Decoder"
+ },
+ {
+ "command": "espExceptionDecoder.capturerClearAllEvents",
+ "title": "Clear All Crash Events",
+ "icon": "$(clear-all)",
+ "category": "ESP Exception Decoder"
+ },
+ {
+ "command": "espExceptionDecoder.dumpCrashCapturerState",
+ "title": "Dump ESP Crash Capturer State",
+ "category": "ESP Exception Decoder"
+ },
+ {
+ "command": "espExceptionDecoder.capturerCopyToClipboard",
+ "title": "Copy to Clipboard",
+ "category": "ESP Exception Decoder"
+ }
+ ],
+ "debuggers": [
+ {
+ "type": "esp-crash-replay",
+ "label": "ESP Crash Replay"
+ }
+ ],
+ "menus": {
+ "view/title": [
+ {
+ "command": "espExceptionDecoder.capturerCreate",
+ "when": "view == espExceptionDecoder.capturerView",
+ "group": "navigation@1"
+ },
+ {
+ "command": "espExceptionDecoder.capturerRefresh",
+ "when": "view == espExceptionDecoder.capturerView",
+ "group": "navigation@2"
+ }
+ ],
+ "view/item/context": [
+ {
+ "command": "espExceptionDecoder.capturerStart",
+ "when": "view == espExceptionDecoder.capturerView && (viewItem == espCapturerRootReady || viewItem == espCapturerRootReadyHasEvents)",
+ "group": "inline@1"
+ },
+ {
+ "command": "espExceptionDecoder.capturerStop",
+ "when": "view == espExceptionDecoder.capturerView && (viewItem == espCapturerRootCapturing || viewItem == espCapturerRootCapturingHasEvents || viewItem == espCapturerRootCapturingFixableSync || viewItem == espCapturerRootCapturingFixableSyncHasEvents || viewItem == espCapturerRootCapturingFixableCompile || viewItem == espCapturerRootCapturingFixableCompileHasEvents)",
+ "group": "inline@1"
+ },
+ {
+ "command": "espExceptionDecoder.capturerRemove",
+ "when": "view == espExceptionDecoder.capturerView && (viewItem == espCapturerRootCapturing || viewItem == espCapturerRootCapturingHasEvents || viewItem == espCapturerRootCapturingFixableSync || viewItem == espCapturerRootCapturingFixableSyncHasEvents || viewItem == espCapturerRootCapturingFixableCompile || viewItem == espCapturerRootCapturingFixableCompileHasEvents)",
+ "group": "inline@3"
+ },
+ {
+ "command": "espExceptionDecoder.capturerRemove",
+ "when": "view == espExceptionDecoder.capturerView && (viewItem == espCapturerRootStopped || viewItem == espCapturerRootStoppedHasEvents || viewItem == espCapturerRootStoppedFixableSync || viewItem == espCapturerRootStoppedFixableSyncHasEvents || viewItem == espCapturerRootStoppedFixableCompile || viewItem == espCapturerRootStoppedFixableCompileHasEvents)",
+ "group": "inline@3"
+ },
+ {
+ "command": "espExceptionDecoder.capturerRemove",
+ "when": "view == espExceptionDecoder.capturerView && (viewItem == espCapturerRootReady || viewItem == espCapturerRootReadyHasEvents)",
+ "group": "inline@2"
+ },
+ {
+ "command": "espExceptionDecoder.capturerClearAllEvents",
+ "when": "view == espExceptionDecoder.capturerView && (viewItem == espCapturerRootCapturingHasEvents || viewItem == espCapturerRootReadyHasEvents || viewItem == espCapturerRootStoppedHasEvents || viewItem == espCapturerRootCapturingFixableSyncHasEvents || viewItem == espCapturerRootCapturingFixableCompileHasEvents || viewItem == espCapturerRootStoppedFixableSyncHasEvents || viewItem == espCapturerRootStoppedFixableCompileHasEvents)",
+ "group": "inline@2"
+ },
+ {
+ "submenu": "capturerQuickFixId",
+ "when": "view == espExceptionDecoder.capturerView && (viewItem == espCapturerRootCapturingFixableSync || viewItem == espCapturerRootCapturingFixableSyncHasEvents || viewItem == espCapturerRootStoppedFixableSync || viewItem == espCapturerRootStoppedFixableSyncHasEvents || viewItem == espCapturerRootCapturingFixableCompile || viewItem == espCapturerRootCapturingFixableCompileHasEvents || viewItem == espCapturerRootStoppedFixableCompile || viewItem == espCapturerRootStoppedFixableCompileHasEvents)",
+ "group": "inline@2"
+ },
+ {
+ "command": "espExceptionDecoder.capturerShowEvent",
+ "when": "view == espExceptionDecoder.capturerView && (viewItem == espCapturerEventDetected || viewItem == espCapturerEventDecoding || viewItem == espCapturerEventDecoded || viewItem == espCapturerEventEvaluating || viewItem == espCapturerEventEvaluated)",
+ "group": "inline@1"
+ },
+ {
+ "command": "espExceptionDecoder.capturerReplayCrash",
+ "when": "view == espExceptionDecoder.capturerView && (viewItem == espCapturerEventDecoded || viewItem == espCapturerEventEvaluated)",
+ "group": "inline@2"
+ },
+ {
+ "command": "espExceptionDecoder.capturerClearEvent",
+ "when": "view == espExceptionDecoder.capturerView && (viewItem == espCapturerEventDetected || viewItem == espCapturerEventDecoding || viewItem == espCapturerEventDecoded || viewItem == espCapturerEventEvaluating || viewItem == espCapturerEventEvaluated)",
+ "group": "inline@3"
+ }
+ ],
+ "capturerQuickFixId": [
+ {
+ "command": "espExceptionDecoder.capturerQuickFixSyncSketchBoard",
+ "when": "view == espExceptionDecoder.capturerView && (viewItem == espCapturerRootCapturingFixableSync || viewItem == espCapturerRootCapturingFixableSyncHasEvents || viewItem == espCapturerRootStoppedFixableSync || viewItem == espCapturerRootStoppedFixableSyncHasEvents)",
+ "group": "1_sync@1"
+ },
+ {
+ "command": "espExceptionDecoder.capturerCompileSketchDebug",
+ "when": "view == espExceptionDecoder.capturerView && (viewItem == espCapturerRootCapturingFixableCompile || viewItem == espCapturerRootCapturingFixableCompileHasEvents || viewItem == espCapturerRootStoppedFixableCompile || viewItem == espCapturerRootStoppedFixableCompileHasEvents)",
+ "group": "2_compile@1"
+ },
+ {
+ "command": "espExceptionDecoder.capturerCompileSketch",
+ "when": "view == espExceptionDecoder.capturerView && (viewItem == espCapturerRootCapturingFixableCompile || viewItem == espCapturerRootCapturingFixableCompileHasEvents || viewItem == espCapturerRootStoppedFixableCompile || viewItem == espCapturerRootStoppedFixableCompileHasEvents)",
+ "group": "2_compile@2"
+ }
+ ],
+ "commandPalette": [
+ {
+ "command": "espExceptionDecoder.capturerCreate",
+ "when": "false"
+ },
+ {
+ "command": "espExceptionDecoder.capturerRemove",
+ "when": "false"
+ },
+ {
+ "command": "espExceptionDecoder.capturerStart",
+ "when": "false"
+ },
+ {
+ "command": "espExceptionDecoder.capturerStop",
+ "when": "false"
+ },
+ {
+ "command": "espExceptionDecoder.capturerRefresh",
+ "when": "false"
+ },
+ {
+ "command": "espExceptionDecoder.capturerQuickFix",
+ "when": "false"
+ },
+ {
+ "command": "espExceptionDecoder.capturerQuickFixSyncSketchBoard",
+ "when": "false"
+ },
+ {
+ "command": "espExceptionDecoder.capturerCompileSketchDebug",
+ "when": "false"
+ },
+ {
+ "command": "espExceptionDecoder.capturerCompileSketch",
+ "when": "false"
+ },
+ {
+ "command": "espExceptionDecoder.capturerReplayCrash",
+ "when": "false"
+ },
+ {
+ "command": "espExceptionDecoder.capturerClearEvent",
+ "when": "false"
+ },
+ {
+ "command": "espExceptionDecoder.capturerClearAllEvents",
+ "when": "false"
+ },
+ {
+ "command": "espExceptionDecoder.capturerShowEvent",
+ "when": "false"
+ },
+ {
+ "command": "espExceptionDecoder.capturerCopyToClipboard",
+ "when": "false"
+ }
+ ]
+ },
+ "submenus": [
+ {
+ "id": "capturerQuickFixId",
+ "icon": "$(light-bulb)",
+ "label": "Quick Fixes..."
+ }
+ ],
+ "views": {
+ "espExceptionDecoderPanel": [
+ {
+ "id": "espExceptionDecoder.capturerView",
+ "name": "ESP Crash Capturer",
+ "icon": "$(debug-console)"
+ }
+ ]
+ },
+ "viewsContainers": {
+ "panel": [
+ {
+ "id": "espExceptionDecoderPanel",
+ "title": "ESP Decoder",
+ "icon": "$(debug-console)"
+ }
+ ]
+ },
+ "viewsWelcome": [
+ {
+ "view": "espExceptionDecoder.capturerView",
+ "when": "espExceptionDecoder.capturerAvailable",
+ "contents": "Automatically detect ESP crashes and group repeated crash signatures.\n\nTo get started, open a sketch, select an ESP32 or ESP8266 board, compile and upload your sketch, then start a Capturer instead of a regular serial monitor. [Create your first ESP Crash Capturer](command:espExceptionDecoder.capturerCreate \"Create a new ESP Crash Capturer\"). After creating one, use the $(add) action in the view title to add additional capturers."
}
]
},
"activationEvents": [
- "onStartupFinished"
+ "onStartupFinished",
+ "onFileSystem:espExceptionDecoderReadonlyFs",
+ "onFileSystem:espExceptionDecoderCrashReport"
],
"dependencies": {
+ "@vscode/debugadapter": "^1.68.0",
"debug": "^4.4.3",
- "fqbn": "^1.3.1",
- "trbr": "^0.1.24"
+ "error-replay-debug-adapter": "^0.1.1",
+ "fqbn": "^1.4.0",
+ "markdown-escape": "^2.0.0",
+ "trbr": "^0.3.1"
},
"devDependencies": {
- "@istanbuljs/nyc-config-typescript": "^1.0.2",
"@semantic-release/changelog": "^6.0.3",
"@semantic-release/commit-analyzer": "^13.0.1",
"@semantic-release/exec": "^7.1.0",
@@ -73,34 +341,35 @@
"@semantic-release/github": "^12.0.0",
"@semantic-release/release-notes-generator": "^14.1.0",
"@types/debug": "^4.1.12",
- "@types/glob": "^9.0.0",
+ "@types/markdown-escape": "^1.1.3",
"@types/mocha": "^10.0.10",
"@types/node": "20.x",
"@types/temp": "^0.9.1",
"@types/vscode": "^1.78.0",
"@typescript-eslint/eslint-plugin": "^8.44.1",
"@typescript-eslint/parser": "^8.44.1",
+ "@vscode/test-cli": "^0.0.12",
"@vscode/test-electron": "^2.5.2",
- "@vscode/vsce": "^3.6.2",
+ "@vscode/vsce": "^3.7.1",
+ "boards-list": "^2.0.0",
"eslint": "^9.31.0",
"eslint-config-prettier": "^10.1.5",
"eslint-plugin-import": "^2.32.0",
"eslint-plugin-prettier": "^5.5.1",
- "glob": "^11.0.3",
"mocha": "^11.7.4",
"neostandard": "^0.12.2",
- "nyc": "^17.1.0",
"prettier": "^3.6.2",
"prettier-plugin-jsdoc": "^1.3.3",
"prettier-plugin-organize-imports": "^4.1.0",
"prettier-plugin-packagejson": "^2.5.18",
"rimraf": "^6.0.1",
"semantic-release": "^25.0.0",
+ "semantic-release-next-version": "^0.1.3",
"semantic-release-vsce": "^6.0.12",
"temp": "^0.9.4",
"ts-loader": "^9.5.4",
"typescript": "^5.0.4",
- "vscode-arduino-api": "^0.2.1",
+ "vscode-arduino-api": "^0.2.4",
"webpack": "^5.81.0",
"webpack-cli": "^5.0.2"
},
diff --git a/release.config.js b/release.config.js
index 83a4359..64d678e 100644
--- a/release.config.js
+++ b/release.config.js
@@ -1,16 +1,22 @@
// @ts-check
'use strict'
-/* eslint-disable no-template-curly-in-string */
-
/** @type {import('semantic-release').Options} */
module.exports = {
+ // eslint-disable-next-line no-template-curly-in-string
tagFormat: '${version}',
branches: ['main'],
plugins: [
'@semantic-release/commit-analyzer',
'@semantic-release/release-notes-generator',
'@semantic-release/changelog',
+ [
+ '@semantic-release/npm',
+ {
+ npmPublish: false,
+ tarballDir: 'dist/npm',
+ },
+ ],
[
'@semantic-release/github',
{
@@ -33,6 +39,7 @@ module.exports = {
'@semantic-release/exec',
{
publishCmd:
+ // eslint-disable-next-line no-template-curly-in-string
'echo "release_version=${nextRelease.version}" >> $GITHUB_OUTPUT',
},
],
diff --git a/resources/static/esp-exception-decoder-main.png b/resources/static/esp-exception-decoder-main.png
deleted file mode 100644
index 13b5cd2..0000000
Binary files a/resources/static/esp-exception-decoder-main.png and /dev/null differ
diff --git a/resources/static/esp-exception-decoder.gif b/resources/static/esp-exception-decoder.gif
new file mode 100644
index 0000000..3182256
Binary files /dev/null and b/resources/static/esp-exception-decoder.gif differ
diff --git a/resources/static/usage-capturer-quick-fixes.png b/resources/static/usage-capturer-quick-fixes.png
new file mode 100644
index 0000000..3df4698
Binary files /dev/null and b/resources/static/usage-capturer-quick-fixes.png differ
diff --git a/resources/static/usage-capturer-running-events.png b/resources/static/usage-capturer-running-events.png
new file mode 100644
index 0000000..f5b1948
Binary files /dev/null and b/resources/static/usage-capturer-running-events.png differ
diff --git a/resources/static/usage-capturer-start-capturing.png b/resources/static/usage-capturer-start-capturing.png
new file mode 100644
index 0000000..0e4de40
Binary files /dev/null and b/resources/static/usage-capturer-start-capturing.png differ
diff --git a/resources/static/usage-capturer-view-empty.png b/resources/static/usage-capturer-view-empty.png
new file mode 100644
index 0000000..f99cf75
Binary files /dev/null and b/resources/static/usage-capturer-view-empty.png differ
diff --git a/resources/static/usage-terminal-manual.png b/resources/static/usage-terminal-manual.png
new file mode 100644
index 0000000..490d2bb
Binary files /dev/null and b/resources/static/usage-terminal-manual.png differ
diff --git a/src/boardLabExt.ts b/src/boardLabExt.ts
new file mode 100644
index 0000000..eb4a7c5
--- /dev/null
+++ b/src/boardLabExt.ts
@@ -0,0 +1,137 @@
+import type {
+ BoardIdentifier,
+ BoardsListItemWithBoard,
+ DetectedPorts,
+ PortIdentifier,
+} from 'boards-list'
+import type { FQBN } from 'fqbn'
+import type { Event } from 'vscode'
+import type { BoardDetails, Port, SketchFolder } from 'vscode-arduino-api'
+
+export type MonitorRuntimeState =
+ | 'disconnected'
+ | 'connected'
+ | 'running'
+ | 'suspended'
+
+export interface MonitorClient {
+ readonly port: Port
+ readonly state: MonitorRuntimeState
+ readonly onDidReceiveData: Event
+ readonly onDidChangeState: Event
+ send(message: string | Uint8Array): Promise
+ dispose(): void
+}
+
+export interface MonitorSuspensionOptions {
+ retry?: number
+}
+
+export interface MonitorSuspensionResult {
+ result: Promise
+}
+
+export interface BoardsListWatcher {
+ readonly detectedPorts: DetectedPorts
+ readonly onDidChangeDetectedPorts: Event
+}
+
+export type ApiBoardDetails = BoardDetails
+
+export type PickBoardResult = BoardIdentifier | BoardsListItemWithBoard
+
+export interface BoardPickCandidate {
+ readonly board: BoardIdentifier
+ readonly port?: PortIdentifier
+ readonly selection: PickBoardResult
+}
+
+export type QuickPickFilter = (candidate: T) => boolean | Promise
+
+export interface QuickPickConstraints {
+ readonly filters?: ReadonlyArray>
+}
+
+export interface SketchPickOptions extends QuickPickConstraints {}
+
+export interface BoardPickOptions extends QuickPickConstraints {}
+
+export type SketchPort =
+ | Readonly
+ | PortIdentifier
+ | { readonly port: PortIdentifier }
+ | undefined
+
+export interface PortPickCandidate {
+ readonly port: PortIdentifier
+ readonly selection: SketchPort
+}
+
+export interface PortPickOptions extends QuickPickConstraints {}
+
+type SketchOrPromise = SketchFolder | Promise
+
+// https://github.com/dankeboy36/vscode-arduino-api/issues/19
+export interface BoardLabContextExt {
+ pickSketch(options?: SketchPickOptions): Promise
+ selectSketch(options?: SketchPickOptions): Promise
+ pickBoard(
+ currentSketchOrOptions?: SketchFolder | BoardPickOptions | undefined,
+ options?: BoardPickOptions
+ ): Promise
+ selectBoard(
+ currentSketchOrOptions?: SketchOrPromise | BoardPickOptions | undefined,
+ options?: BoardPickOptions
+ ): Promise
+ setBoardByFqbn(
+ fqbn: string | FQBN,
+ currentSketch?: SketchOrPromise | undefined
+ ): Promise
+ pickPort(
+ currentSketchOrOptions?: SketchFolder | PortPickOptions | undefined,
+ options?: PortPickOptions
+ ): Promise
+ selectPort(
+ currentSketchOrOptions?: SketchOrPromise | PortPickOptions | undefined,
+ options?: PortPickOptions
+ ): Promise
+ createMonitorClient(
+ port: Port,
+ options?: { autoStart?: boolean; baudrate?: string }
+ ): Promise
+ withMonitorSuspended(
+ port: Port,
+ run: (options?: MonitorSuspensionOptions) => Promise,
+ options?: MonitorSuspensionOptions
+ ): Promise
+ readonly boardsListWatcher: BoardsListWatcher
+}
+
+export function isBoardLabContextExt(arg: unknown): arg is BoardLabContextExt {
+ if (typeof arg !== 'object' || arg === null) {
+ return false
+ }
+ const context = arg as Partial
+ if (
+ typeof context.pickSketch !== 'function' ||
+ typeof context.selectSketch !== 'function' ||
+ typeof context.pickBoard !== 'function' ||
+ typeof context.selectBoard !== 'function' ||
+ typeof context.setBoardByFqbn !== 'function' ||
+ typeof context.pickPort !== 'function' ||
+ typeof context.selectPort !== 'function' ||
+ typeof context.createMonitorClient !== 'function' ||
+ typeof context.withMonitorSuspended !== 'function'
+ ) {
+ return false
+ }
+ const watcher = context.boardsListWatcher as
+ | Partial
+ | undefined
+ return (
+ typeof watcher === 'object' &&
+ watcher !== null &&
+ 'detectedPorts' in watcher &&
+ typeof watcher.onDidChangeDetectedPorts === 'function'
+ )
+}
diff --git a/src/boardLabTarget.ts b/src/boardLabTarget.ts
new file mode 100644
index 0000000..cfef929
--- /dev/null
+++ b/src/boardLabTarget.ts
@@ -0,0 +1,540 @@
+import path from 'node:path'
+
+import { FQBN } from 'fqbn'
+
+import type {
+ BoardLabContextExt,
+ BoardPickCandidate,
+ BoardPickOptions,
+} from './boardLabExt'
+
+export interface PortIdentifier {
+ protocol: string
+ address: string
+}
+
+export interface SketchTargetDraft {
+ sketchPath: string
+ fqbn: string
+ port: PortIdentifier
+}
+
+export type SketchTargetSelectionMode =
+ | 'use-sketch-target'
+ | 'select-board'
+ | 'select-port'
+ | 'select-both'
+ | 'use-board-select-port'
+ | 'use-port-select-board'
+ | 'select-board-select-port'
+
+export interface SketchTargetSelectionModeItem {
+ mode: SketchTargetSelectionMode
+ label: string
+ description: string
+}
+
+export interface ResolveSketchTargetWithBoardLabOptions {
+ boardLab: Pick<
+ BoardLabContextExt,
+ 'pickSketch' | 'selectBoard' | 'pickBoard' | 'selectPort' | 'pickPort'
+ >
+ openedSketches: readonly unknown[]
+ pickMode: (
+ items: readonly SketchTargetSelectionModeItem[]
+ ) => Promise
+ onError?: (message: string) => void
+}
+
+const supportedCapturerArchitectures = new Set(['esp32', 'esp8266'])
+type SelectBoardInput = Parameters[0]
+type PickBoardInput = Parameters[0]
+type SelectPortInput = Parameters[0]
+type PickPortInput = Parameters[0]
+
+interface FqbnContainerLike {
+ fqbn?: unknown
+}
+
+interface SketchLike {
+ sketchPath?: unknown
+ uri?: {
+ fsPath?: unknown
+ }
+ configOptions?: unknown
+ board?: FqbnContainerLike
+ fqbn?: unknown
+ port?: unknown
+}
+
+function toNonEmptyString(value: unknown): string | undefined {
+ if (typeof value !== 'string') {
+ return undefined
+ }
+ const trimmed = value.trim()
+ return trimmed.length > 0 ? trimmed : undefined
+}
+
+function tryCreateFqbn(value: string): FQBN | undefined {
+ try {
+ return new FQBN(value)
+ } catch {
+ return undefined
+ }
+}
+
+function resolveBoardFqbn(sketch: unknown): FQBN | undefined {
+ const maybeSketch = sketch as SketchLike | undefined
+ const boardFqbn =
+ toNonEmptyString(maybeSketch?.board?.fqbn) ??
+ toNonEmptyString(maybeSketch?.fqbn)
+ if (!boardFqbn) {
+ return undefined
+ }
+ return tryCreateFqbn(boardFqbn)
+}
+
+export function pathEquals(left: string, right: string): boolean {
+ const normalize = (value: string) =>
+ process.platform === 'win32'
+ ? path.resolve(value).toLowerCase()
+ : path.resolve(value)
+ return normalize(left) === normalize(right)
+}
+
+export function resolveSketchPath(sketch: unknown): string | undefined {
+ const maybeSketch = sketch as SketchLike | undefined
+ const bySketchPath = toNonEmptyString(maybeSketch?.sketchPath)
+ if (bySketchPath) {
+ return path.resolve(bySketchPath)
+ }
+ const byUriPath = toNonEmptyString(maybeSketch?.uri?.fsPath)
+ if (byUriPath) {
+ return path.resolve(byUriPath)
+ }
+ return undefined
+}
+
+export function resolveConfiguredFqbn(sketch: unknown): string | undefined {
+ const maybeSketch = sketch as SketchLike | undefined
+ const configOptions = toNonEmptyString(maybeSketch?.configOptions)
+ const boardFqbn = resolveBoardFqbn(sketch)
+
+ if (boardFqbn && configOptions) {
+ const mergedFromOptions = tryCreateFqbn(
+ `${boardFqbn.sanitize().toString()}:${configOptions}`
+ )
+ if (mergedFromOptions) {
+ return mergedFromOptions.toString()
+ }
+ const mergedFromFqbn = tryCreateFqbn(configOptions)
+ if (mergedFromFqbn) {
+ try {
+ return boardFqbn.withFQBN(mergedFromFqbn.toString()).toString()
+ } catch {
+ // ignore incompatible FQBN and continue with fallback
+ }
+ }
+ return boardFqbn.toString()
+ }
+
+ if (boardFqbn) {
+ return boardFqbn.toString()
+ }
+ if (configOptions) {
+ return tryCreateFqbn(configOptions)?.toString()
+ }
+ return undefined
+}
+
+function resolveSupportedConfiguredFqbn(sketch: unknown): string | undefined {
+ const fqbn = resolveConfiguredFqbn(sketch)
+ if (!fqbn) {
+ return undefined
+ }
+ return isSupportedCapturerFqbn(fqbn) ? fqbn : undefined
+}
+
+export function isPortIdentifierLike(arg: unknown): arg is PortIdentifier {
+ return (
+ typeof arg === 'object' &&
+ arg !== null &&
+ typeof (arg as { protocol?: unknown }).protocol === 'string' &&
+ typeof (arg as { address?: unknown }).address === 'string'
+ )
+}
+
+export function toPortIdentifier(
+ portLike: unknown
+): PortIdentifier | undefined {
+ const directPort = isPortIdentifierLike(portLike) ? portLike : undefined
+ const nestedPort =
+ !directPort &&
+ typeof portLike === 'object' &&
+ portLike !== null &&
+ isPortIdentifierLike((portLike as { port?: unknown }).port)
+ ? (portLike as { port: PortIdentifier }).port
+ : undefined
+ const candidate = directPort ?? nestedPort
+ if (!candidate) {
+ return undefined
+ }
+ return {
+ protocol: candidate.protocol,
+ address: candidate.address,
+ }
+}
+
+export function toDraftFromSketch(
+ sketch: unknown
+): SketchTargetDraft | undefined {
+ const sketchPath = resolveSketchPath(sketch)
+ if (!sketchPath) {
+ return undefined
+ }
+ const fqbn = resolveConfiguredFqbn(sketch)
+ const port = toPortIdentifier((sketch as SketchLike | undefined)?.port)
+ if (!fqbn || !port) {
+ return undefined
+ }
+ return {
+ sketchPath,
+ fqbn,
+ port,
+ }
+}
+
+export function toSketchTargetSelectionModeItems(
+ sketch: unknown
+): SketchTargetSelectionModeItem[] {
+ const hasBoard = Boolean(resolveSupportedConfiguredFqbn(sketch))
+ const hasPort = Boolean(toPortIdentifier((sketch as SketchLike)?.port))
+
+ if (hasBoard && hasPort) {
+ return [
+ {
+ mode: 'use-sketch-target',
+ label: 'Use selected board + port',
+ description: '(Recommended) Use current sketch target',
+ },
+ {
+ mode: 'select-board',
+ label: 'Select other board',
+ description: 'Keep the current port and choose another board',
+ },
+ {
+ mode: 'select-port',
+ label: 'Select other port',
+ description: 'Keep the current board and choose another port',
+ },
+ {
+ mode: 'select-both',
+ label: 'Select both manually',
+ description: 'Choose both board and port',
+ },
+ ]
+ }
+ if (hasBoard && !hasPort) {
+ return [
+ {
+ mode: 'use-board-select-port',
+ label: 'Use selected board + choose port',
+ description: '(Recommended) Keep sketch board and pick a port',
+ },
+ {
+ mode: 'select-board-select-port',
+ label: 'Select board + port',
+ description: 'Choose both board and port',
+ },
+ ]
+ }
+ if (!hasBoard && hasPort) {
+ return [
+ {
+ mode: 'use-port-select-board',
+ label: 'Use selected port + choose board',
+ description: '(Recommended) Keep sketch port and pick a board',
+ },
+ {
+ mode: 'select-board-select-port',
+ label: 'Select board + port',
+ description: 'Choose both board and port',
+ },
+ ]
+ }
+ return [
+ {
+ mode: 'select-both',
+ label: 'Select board + port',
+ description: '(Recommended) Choose both board and port',
+ },
+ ]
+}
+
+export function modeNeedsBoardSelection(
+ mode: SketchTargetSelectionMode
+): boolean {
+ return (
+ mode === 'select-board' ||
+ mode === 'use-port-select-board' ||
+ mode === 'select-both' ||
+ mode === 'select-board-select-port'
+ )
+}
+
+export function modeNeedsPortSelection(
+ mode: SketchTargetSelectionMode
+): boolean {
+ return (
+ mode === 'select-port' ||
+ mode === 'use-board-select-port' ||
+ mode === 'select-both' ||
+ mode === 'select-board-select-port'
+ )
+}
+
+export function resolveSketchByPath(
+ sketchPath: string,
+ openedSketches: readonly unknown[],
+ fallbackSketch: unknown
+): unknown {
+ const normalizedSketchPath = path.resolve(sketchPath)
+ const match = openedSketches.find((candidate) => {
+ const candidatePath = toNonEmptyString(
+ (candidate as SketchLike | undefined)?.sketchPath
+ )
+ return Boolean(
+ candidatePath && pathEquals(candidatePath, normalizedSketchPath)
+ )
+ })
+ return match ?? fallbackSketch
+}
+
+export async function tryExecuteBoardLabCall(
+ run: () => Promise
+): Promise {
+ try {
+ return await run()
+ } catch {
+ return undefined
+ }
+}
+
+export function isSupportedCapturerArch(arch: string): boolean {
+ return supportedCapturerArchitectures.has(arch.trim().toLowerCase())
+}
+
+export function isSupportedCapturerFqbn(fqbn: string): boolean {
+ const parsed = tryCreateFqbn(fqbn)
+ if (!parsed) {
+ return false
+ }
+ return isSupportedCapturerArch(parsed.arch)
+}
+
+export function isSupportedCapturerBoardCandidate(
+ candidate: BoardPickCandidate
+): boolean {
+ const fqbn =
+ resolveConfiguredFqbn(candidate.selection) ??
+ resolveConfiguredFqbn(candidate.board)
+ if (fqbn) {
+ return isSupportedCapturerFqbn(fqbn)
+ }
+
+ // Boards without the installed core does not have the FQBN set
+ // But boards from an index package carries the platform meta object on the board.
+ if (
+ 'platform' in candidate &&
+ typeof candidate.platform === 'object' &&
+ candidate.platform !== null
+ ) {
+ const { platform } = candidate
+ if (
+ 'metadata' in platform &&
+ typeof platform.metadata === 'object' &&
+ platform.metadata !== null
+ ) {
+ const { metadata } = platform
+ if ('id' in metadata && typeof metadata.id === 'string') {
+ let fqbn: FQBN | undefined
+ try {
+ fqbn = new FQBN(`${metadata.id}:placeholder_id`)
+ } catch {}
+ if (fqbn) {
+ return isSupportedCapturerArch(fqbn.arch)
+ }
+ }
+ }
+ }
+ return false
+}
+
+function toCapturerBoardPickOptions(): BoardPickOptions {
+ return {
+ filters: [isSupportedCapturerBoardCandidate],
+ }
+}
+
+export async function selectBoardForSketchTarget(options: {
+ boardLab: Pick
+ openedSketches: readonly unknown[]
+ sketch: unknown
+ onError?: (message: string) => void
+}): Promise<{ sketch: unknown; fqbn: string } | undefined> {
+ const boardPickOptions = toCapturerBoardPickOptions()
+ const selectedBoard =
+ (await tryExecuteBoardLabCall(() =>
+ options.boardLab.selectBoard(
+ options.sketch as SelectBoardInput,
+ boardPickOptions
+ )
+ )) ??
+ (await tryExecuteBoardLabCall(() =>
+ options.boardLab.pickBoard(
+ options.sketch as PickBoardInput,
+ boardPickOptions
+ )
+ ))
+ if (!selectedBoard) {
+ return undefined
+ }
+ const sketchPath = resolveSketchPath(options.sketch)
+ const refreshedSketch = sketchPath
+ ? resolveSketchByPath(sketchPath, options.openedSketches, options.sketch)
+ : options.sketch
+ const refreshedSupportedFqbn = resolveSupportedConfiguredFqbn(refreshedSketch)
+ const selectedSupportedFqbn = resolveSupportedConfiguredFqbn(selectedBoard)
+ const fqbn = refreshedSupportedFqbn ?? selectedSupportedFqbn
+ if (!fqbn) {
+ const resolvedButUnsupported =
+ resolveConfiguredFqbn(refreshedSketch) ??
+ resolveConfiguredFqbn(selectedBoard)
+ options.onError?.(
+ resolvedButUnsupported
+ ? 'Only ESP32 and ESP8266 boards are supported for crash capture.'
+ : 'Could not resolve a valid board selection for the sketch.'
+ )
+ return undefined
+ }
+ return {
+ sketch: refreshedSketch,
+ fqbn,
+ }
+}
+
+export async function selectPortForSketchTarget(options: {
+ boardLab: Pick
+ openedSketches: readonly unknown[]
+ sketch: unknown
+ onError?: (message: string) => void
+}): Promise<{ sketch: unknown; port: PortIdentifier } | undefined> {
+ const selectedPort =
+ (await tryExecuteBoardLabCall(() =>
+ options.boardLab.selectPort(options.sketch as SelectPortInput)
+ )) ??
+ (await tryExecuteBoardLabCall(() =>
+ options.boardLab.pickPort(options.sketch as PickPortInput)
+ ))
+ if (!selectedPort) {
+ return undefined
+ }
+ const sketchPath = resolveSketchPath(options.sketch)
+ const refreshedSketch = sketchPath
+ ? resolveSketchByPath(sketchPath, options.openedSketches, options.sketch)
+ : options.sketch
+ const port =
+ toPortIdentifier((refreshedSketch as SketchLike | undefined)?.port) ??
+ toPortIdentifier(selectedPort)
+ if (!port) {
+ options.onError?.(
+ 'Could not resolve a valid port selection for the sketch.'
+ )
+ return undefined
+ }
+ return {
+ sketch: refreshedSketch,
+ port,
+ }
+}
+
+export async function resolveSketchTargetWithBoardLab(
+ options: ResolveSketchTargetWithBoardLabOptions
+): Promise {
+ const sketchSelection = await tryExecuteBoardLabCall(() =>
+ options.boardLab.pickSketch()
+ )
+ if (!sketchSelection) {
+ return undefined
+ }
+
+ const sketchPath = resolveSketchPath(sketchSelection)
+ if (!sketchPath) {
+ options.onError?.('Could not resolve the sketch path for target selection.')
+ return undefined
+ }
+
+ let sketch = resolveSketchByPath(
+ sketchPath,
+ options.openedSketches,
+ sketchSelection
+ )
+ const modeItems = toSketchTargetSelectionModeItems(sketch)
+ const pickedMode =
+ modeItems.length === 1 ? modeItems[0] : await options.pickMode(modeItems)
+ if (!pickedMode) {
+ return undefined
+ }
+
+ let fqbn = resolveSupportedConfiguredFqbn(sketch)
+ let port = toPortIdentifier((sketch as SketchLike | undefined)?.port)
+
+ if (modeNeedsBoardSelection(pickedMode.mode)) {
+ const boardSelection = await selectBoardForSketchTarget({
+ boardLab: options.boardLab,
+ openedSketches: options.openedSketches,
+ sketch,
+ onError: options.onError,
+ })
+ if (!boardSelection) {
+ return undefined
+ }
+ sketch = resolveSketchByPath(
+ sketchPath,
+ options.openedSketches,
+ boardSelection.sketch
+ )
+ fqbn = boardSelection.fqbn ?? fqbn
+ }
+
+ if (modeNeedsPortSelection(pickedMode.mode)) {
+ const portSelection = await selectPortForSketchTarget({
+ boardLab: options.boardLab,
+ openedSketches: options.openedSketches,
+ sketch,
+ onError: options.onError,
+ })
+ if (!portSelection) {
+ return undefined
+ }
+ sketch = resolveSketchByPath(
+ sketchPath,
+ options.openedSketches,
+ portSelection.sketch
+ )
+ port = portSelection.port ?? port
+ }
+
+ if (!fqbn || !port) {
+ options.onError?.(
+ 'Could not resolve both board and port for the sketch target.'
+ )
+ return undefined
+ }
+
+ return {
+ sketchPath,
+ fqbn,
+ port,
+ }
+}
diff --git a/src/capturer.ts b/src/capturer.ts
new file mode 100644
index 0000000..e615d26
--- /dev/null
+++ b/src/capturer.ts
@@ -0,0 +1,232 @@
+import type { CapturerEvent, DecodeResult } from 'trbr'
+import type vscode from 'vscode'
+import type { ArduinoContext } from 'vscode-arduino-api'
+
+import type { BoardLabContextExt } from './boardLabExt'
+import { capturerConfigStateKey } from './capturer/constants'
+import {
+ CapturerManager as CapturerManagerImpl,
+ __tests as __testsImpl,
+ registerCapturer as registerCapturerImpl,
+} from './capturer/crashCaptureManager'
+import type {
+ CapturerConfig,
+ CapturerConfigDraft,
+ CapturerConfigValidationResult,
+ CapturerDecodedEventPayload,
+ CapturerEventSummary,
+ CapturerNode,
+ CapturerRemovedCapturerMeta,
+ CapturerRemovedEventMeta,
+ CapturerRuntime,
+ CompileBuildOptionsInfo,
+} from './capturer/model'
+
+export type {
+ CapturerDecodedEventMeta,
+ CapturerDecodedEventPayload,
+ CapturerEventNode,
+ CapturerFrameNode,
+ CapturerNode,
+ CapturerRemovedCapturerMeta,
+ CapturerRemovedEventMeta,
+ CapturerRootNode,
+} from './capturer/model'
+
+type EvaluateDecodedEvent = (
+ payload: CapturerDecodedEventPayload
+) => Promise
+
+type OnDecodedEvent = (
+ payload: CapturerDecodedEventPayload
+) => Promise | void
+
+type OnRemovedDecodedEvent = (
+ payload: CapturerRemovedEventMeta
+) => Promise | void
+
+type OnRemovedCapturer = (
+ payload: CapturerRemovedCapturerMeta
+) => Promise | void
+
+export interface CapturerManagerApi
+ extends vscode.Disposable, vscode.TreeDataProvider {
+ readonly onDidChangeTreeData: vscode.Event
+ bindTreeView(treeView: vscode.TreeView): void
+ addUsingBoardLabPickers(): Promise
+ addConfig(draft: CapturerConfigDraft): Promise
+ removeConfig(
+ target?: CapturerNode,
+ options?: { force?: boolean }
+ ): Promise
+ startCapturer(target?: CapturerNode): Promise
+ stopCapturer(target?: CapturerNode): Promise
+ refresh(target?: CapturerNode): Promise
+ quickFix(target?: CapturerNode, preferredAction?: string): Promise
+ compileSketch(target?: CapturerNode): Promise
+ compileSketchWithDebugSymbols(target?: CapturerNode): Promise
+ copyToClipboard(arg?: unknown): Promise
+ replayRecording(
+ target?: CapturerNode,
+ recordingUri?: vscode.Uri
+ ): Promise
+ replayEvent(target?: CapturerNode): Promise
+ deleteEvent(target?: CapturerNode): Promise
+ deleteAllEvents(target?: CapturerNode): Promise
+ dumpCapturerState(target?: CapturerNode): Promise
+ showEvent(target?: CapturerNode): Promise
+ getTreeItem(element: CapturerNode): vscode.TreeItem
+ getChildren(element?: CapturerNode): CapturerNode[]
+ getParent(element: CapturerNode): CapturerNode | undefined
+}
+
+type CapturerManagerCtor = new (
+ context: vscode.ExtensionContext,
+ arduinoContext: ArduinoContext & BoardLabContextExt,
+ crashReportProvider?: unknown,
+ evaluateDecodedEvent?: EvaluateDecodedEvent,
+ onDecodedEvent?: OnDecodedEvent,
+ onRemovedDecodedEvent?: OnRemovedDecodedEvent,
+ onRemovedCapturer?: OnRemovedCapturer
+) => CapturerManagerApi
+
+type RegisterCapturer = (
+ context: vscode.ExtensionContext,
+ arduinoContext: ArduinoContext,
+ options?: {
+ evaluateDecodedEvent?: EvaluateDecodedEvent
+ onDecodedEvent?: OnDecodedEvent
+ onRemovedDecodedEvent?: OnRemovedDecodedEvent
+ onRemovedCapturer?: OnRemovedCapturer
+ }
+) => vscode.Disposable
+
+interface CapturerTestExports {
+ capturerConfigStateKey: typeof capturerConfigStateKey
+ validateCapturerConfig: (
+ value: Partial
+ ) => CapturerConfigValidationResult
+ buildCapturerId: (config: CapturerConfigDraft) => string
+ loadPersistedConfigs: (values: unknown[]) => CapturerConfig[]
+ pathEquals: (left: string, right: string) => boolean
+ isPortIdentifierLike: (arg: unknown) => boolean
+ isPortDetected: (
+ detectedPorts: unknown,
+ port: { protocol: string; address: string } | undefined
+ ) => boolean | undefined
+ extractDetectedPorts: (
+ detectedPorts: unknown
+ ) => Array<{ protocol: string; address: string }> | undefined
+ arePortsEqual: (
+ left: { protocol: string; address: string },
+ right: { protocol: string; address: string }
+ ) => boolean
+ pickNonEmptyString: (
+ record: Record,
+ key: string
+ ) => string | undefined
+ isRecord: (value: unknown) => value is Record
+ resolveSketchBoardName: (
+ sketch: { board?: { name?: string; fqbn?: string } } | undefined,
+ engineFqbn: string
+ ) => string | undefined
+ toEventSummary: (
+ event: CapturerEvent,
+ runtime?: CapturerRuntime
+ ) => CapturerEventSummary
+ toDraftFromSketch: (sketch: unknown) => CapturerConfigDraft | undefined
+ resolveConfiguredFqbn: (sketch: unknown) => string | undefined
+ shouldIgnoreCapturerEvent: (event: CapturerEvent) => boolean
+ resolveElfIdentity: (
+ elfPath: string,
+ previous?:
+ | {
+ path: string
+ size: number
+ mtimeMs: number
+ sha256: string
+ sha256Short: string
+ sessionId: string
+ }
+ | undefined
+ ) => Promise<
+ | {
+ path: string
+ size: number
+ mtimeMs: number
+ sha256: string
+ sha256Short: string
+ sessionId: string
+ }
+ | undefined
+ >
+ upsertEventSummary: (
+ cache: Map,
+ event: CapturerEvent,
+ runtime?: CapturerRuntime
+ ) => CapturerEventSummary
+ splitReplaySegments: (rawText: string) => string[]
+ loadCompileBuildOptions: (
+ buildPath: string
+ ) => Promise
+ collectBuildFlagEntries: (record: Record) => string[]
+ isReadinessEqual: (
+ left: CapturerRuntime['readiness'],
+ right: CapturerRuntime['readiness']
+ ) => boolean
+ isBuildOptionsEqual: (
+ left: CompileBuildOptionsInfo | undefined,
+ right: CompileBuildOptionsInfo | undefined
+ ) => boolean
+ isElfIdentityEqual: (
+ left:
+ | {
+ path: string
+ size: number
+ mtimeMs: number
+ sha256: string
+ sessionId: string
+ }
+ | undefined,
+ right:
+ | {
+ path: string
+ size: number
+ mtimeMs: number
+ sha256: string
+ sessionId: string
+ }
+ | undefined
+ ) => boolean
+ areStringArraysEqual: (left: string[], right: string[]) => boolean
+ chunkEndsWithLineBreak: (chunk: Uint8Array) => boolean
+ resetTransferMetrics: (runtime: {
+ processedBytes: number
+ processedFirstAt?: number
+ processedLastAt?: number
+ }) => void
+ recordTransfer: (
+ runtime: {
+ processedBytes: number
+ processedFirstAt?: number
+ processedLastAt?: number
+ },
+ byteLength: number,
+ atMs: number
+ ) => void
+ classifyDecodedSource: (
+ filePath: string | undefined,
+ sketchPath: string | undefined
+ ) => 'sketch' | 'library' | 'missing'
+ isPathInside: (parentPath: string, candidatePath: string) => boolean
+ workspaceRelativeSketchPath: (sketchPath: string) => string | undefined
+ eventDisplayLabel: (summary: CapturerEventSummary) => string
+ collectDecodedStackFrames: (
+ decoded?: DecodeResult
+ ) => DecodeResult['stacktraceLines']
+}
+
+export const CapturerManager =
+ CapturerManagerImpl as unknown as CapturerManagerCtor
+export const registerCapturer = registerCapturerImpl as RegisterCapturer
+export const __tests = __testsImpl as unknown as CapturerTestExports
diff --git a/src/capturer/constants.ts b/src/capturer/constants.ts
new file mode 100644
index 0000000..7cbb665
--- /dev/null
+++ b/src/capturer/constants.ts
@@ -0,0 +1,44 @@
+export const capturerViewId = 'espExceptionDecoder.capturerView'
+export const capturerConfigStateKey = 'espExceptionDecoder.capturer.configs.v1'
+
+export const statusNoElf = 'No ELF'
+export const statusReady = 'Ready'
+export const statusCapturing = 'Capturing crashes'
+export const statusSuspended = 'Suspended'
+export const statusNotCompiled = 'Not compiled'
+export const statusDisconnected = 'Disconnected'
+export const statusInvalidFqbn = 'Invalid FQBN'
+export const statusNotEspBoard = 'Not ESP board'
+export const statusError = 'Error'
+export const statusWarning = 'Warning'
+export const statusNoSketch = 'Missing Sketch'
+
+export const splitMarker = /^---\s*SNAP\s*---$/im
+export const idleFlushDelayMs = 1200
+
+export const capturerCreateCommandId = 'espExceptionDecoder.capturerCreate'
+export const capturerRemoveCommandId = 'espExceptionDecoder.capturerRemove'
+export const capturerStartCommandId = 'espExceptionDecoder.capturerStart'
+export const capturerStopCommandId = 'espExceptionDecoder.capturerStop'
+export const capturerRefreshCommandId = 'espExceptionDecoder.capturerRefresh'
+export const capturerReplayCrashCommandId =
+ 'espExceptionDecoder.capturerReplayCrash'
+export const capturerClearEventCommandId =
+ 'espExceptionDecoder.capturerClearEvent'
+export const capturerClearAllEventsCommandId =
+ 'espExceptionDecoder.capturerClearAllEvents'
+export const capturerShowEventCommandId =
+ 'espExceptionDecoder.capturerShowEvent'
+export const capturerQuickFixCommandId = 'espExceptionDecoder.capturerQuickFix'
+export const capturerQuickFixSyncSketchBoardCommandId =
+ 'espExceptionDecoder.capturerQuickFixSyncSketchBoard'
+export const capturerCompileSketchCommandId =
+ 'espExceptionDecoder.capturerCompileSketch'
+export const capturerCompileSketchDebugCommandId =
+ 'espExceptionDecoder.capturerCompileSketchDebug'
+export const dumpCapturerStateCommandId =
+ 'espExceptionDecoder.dumpCrashCapturerState'
+export const capturerCopyToClipboardCommandId =
+ 'espExceptionDecoder.capturerCopyToClipboard'
+
+export const supportedCapturerArchitectures = new Set(['esp32', 'esp8266'])
diff --git a/src/capturer/crashCaptureManager.ts b/src/capturer/crashCaptureManager.ts
new file mode 100644
index 0000000..84d27cf
--- /dev/null
+++ b/src/capturer/crashCaptureManager.ts
@@ -0,0 +1,2212 @@
+import { createHash } from 'node:crypto'
+import * as fs from 'node:fs/promises'
+import path from 'node:path'
+
+import { FQBN } from 'fqbn'
+import {
+ createCapturer,
+ decode,
+ type CapturerEvent,
+ type CapturerOptions,
+} from 'trbr'
+import vscode from 'vscode'
+import type { ArduinoContext, SketchFolder } from 'vscode-arduino-api'
+
+import type { BoardLabContextExt } from '../boardLabExt'
+import { isBoardLabContextExt } from '../boardLabExt'
+import {
+ isPortIdentifierLike,
+ pathEquals,
+ resolveConfiguredFqbn,
+ resolveSketchTargetWithBoardLab,
+ toDraftFromSketch,
+ type SketchTargetSelectionModeItem,
+} from '../boardLabTarget'
+import { createDecodeParams, type DecodeParams } from '../decodeParams'
+import { findElfPath, resolveBuildPathFromSketchPath } from '../findElfPath'
+import {
+ capturerClearAllEventsCommandId,
+ capturerClearEventCommandId,
+ capturerCompileSketchCommandId,
+ capturerCompileSketchDebugCommandId,
+ capturerConfigStateKey,
+ capturerCopyToClipboardCommandId,
+ capturerCreateCommandId,
+ capturerQuickFixCommandId,
+ capturerQuickFixSyncSketchBoardCommandId,
+ capturerRefreshCommandId,
+ capturerRemoveCommandId,
+ capturerReplayCrashCommandId,
+ capturerShowEventCommandId,
+ capturerStartCommandId,
+ capturerStopCommandId,
+ capturerViewId,
+ dumpCapturerStateCommandId,
+ idleFlushDelayMs,
+ splitMarker,
+ supportedCapturerArchitectures,
+} from './constants'
+import {
+ CrashReportContentProvider,
+ ReadonlyFsProvider,
+ crashReportScheme,
+ readonlyLibraryScheme,
+ toReadonlyLibraryUri,
+} from './fsProviders'
+import {
+ collectDecodedStackFrames,
+ countHeavyVars,
+ eventDisplayLabel,
+ formatLocalTimestamp,
+ frameToDescription,
+ frameToLabel,
+ toCapturerStateDumpMarkdown,
+ toEventReportDocumentMarkdown,
+ toEventTreeItemMarkdown,
+ toParsedFrameLocation,
+ toRootTreeItemMarkdown,
+} from './markdown'
+import type {
+ CapturerConfig,
+ CapturerConfigDraft,
+ CapturerConfigValidationResult,
+ CapturerDecodedEventPayload,
+ CapturerEventNode,
+ CapturerEventSummary,
+ CapturerNode,
+ CapturerReadiness,
+ CapturerRemovedCapturerMeta,
+ CapturerRemovedEventMeta,
+ CapturerRootNode,
+ CapturerRuntime,
+ CompileBuildOptionsInfo,
+ ElfIdentity,
+ MonitorClientWithClose,
+} from './model'
+import {
+ collectRuntimeProblems,
+ eventContextValue,
+ isReadyToRecord,
+ isRuntimeConnected,
+ resolveRootDescription,
+ rootContextValue,
+ sanitizeFqbn,
+ toRootLabel,
+} from './runtimeModel'
+import { CapturerTreeModel } from './treeModel'
+
+export type {
+ CapturerConfig,
+ CapturerConfigDraft,
+ CapturerDecodedEventMeta,
+ CapturerDecodedEventPayload,
+ CapturerEventNode,
+ CapturerEventSummary,
+ CapturerFrameNode,
+ CapturerNode,
+ CapturerReadiness,
+ CapturerRemovedCapturerMeta,
+ CapturerRemovedEventMeta,
+ CapturerRootNode,
+ CapturerRuntime,
+ CompileBuildOptionsInfo,
+} from './model'
+
+type ArduinoContextWithBoardLab = ArduinoContext & BoardLabContextExt
+
+type EvaluateDecodedEvent = (
+ payload: CapturerDecodedEventPayload
+) => Promise
+
+type OnDecodedEvent = (
+ payload: CapturerDecodedEventPayload
+) => Promise | void
+
+type OnRemovedDecodedEvent = (
+ payload: CapturerRemovedEventMeta
+) => Promise | void
+
+type OnRemovedCapturer = (
+ payload: CapturerRemovedCapturerMeta
+) => Promise | void
+
+type CapturerCommandTarget = CapturerNode | undefined
+type CapturerCompileCommand =
+ | 'boardlab.compile'
+ | 'boardlab.compileWithDebugSymbols'
+type TransferRuntime = Pick<
+ CapturerRuntime,
+ 'processedBytes' | 'processedFirstAt' | 'processedLastAt'
+>
+type DecodeSketchLike = Pick<
+ SketchFolder,
+ 'compileSummary' | 'sketchPath' | 'board'
+>
+
+interface DecodeEventSummaryOptions {
+ includeFrameVars: boolean
+ trackDurationAs?: 'decode' | 'evaluate'
+}
+
+interface QuickFixItem extends vscode.QuickPickItem {
+ action: 'sync-sketch-fqbn' | 'compile-debug' | 'compile'
+ run: () => Promise
+}
+
+export class CapturerManager
+ implements vscode.Disposable, vscode.TreeDataProvider
+{
+ private readonly context: vscode.ExtensionContext
+ private readonly arduinoContext: ArduinoContextWithBoardLab
+ private readonly evaluateDecodedEvent?: EvaluateDecodedEvent
+ private readonly onDecodedEvent?: OnDecodedEvent
+ private readonly onRemovedDecodedEvent?: OnRemovedDecodedEvent
+ private readonly onRemovedCapturer?: OnRemovedCapturer
+ private readonly onDidChangeTreeDataEmitter = new vscode.EventEmitter<
+ CapturerNode | undefined
+ >()
+
+ readonly onDidChangeTreeData: vscode.Event
+ private readonly runtimes = new Map()
+ private readonly treeModel = new CapturerTreeModel()
+ private readonly toDispose: vscode.Disposable[] = []
+ private configs: CapturerConfig[] = []
+ private readonly ownsCrashReportProvider: boolean
+ private readonly crashReportProvider: CrashReportContentProvider
+ private treeView?: vscode.TreeView
+
+ constructor(
+ context: vscode.ExtensionContext,
+ arduinoContext: ArduinoContextWithBoardLab,
+ crashReportProvider?: CrashReportContentProvider,
+ evaluateDecodedEvent?: EvaluateDecodedEvent,
+ onDecodedEvent?: OnDecodedEvent,
+ onRemovedDecodedEvent?: OnRemovedDecodedEvent,
+ onRemovedCapturer?: OnRemovedCapturer
+ ) {
+ this.context = context
+ this.arduinoContext = arduinoContext
+ this.evaluateDecodedEvent = evaluateDecodedEvent
+ this.onDecodedEvent = onDecodedEvent
+ this.onRemovedDecodedEvent = onRemovedDecodedEvent
+ this.onRemovedCapturer = onRemovedCapturer
+ this.onDidChangeTreeData = this.onDidChangeTreeDataEmitter.event
+ this.ownsCrashReportProvider = !crashReportProvider
+ this.crashReportProvider =
+ crashReportProvider ?? new CrashReportContentProvider()
+ const boardsListWatcher = this.arduinoContext.boardsListWatcher
+ this.configs = loadPersistedConfigs(
+ this.context.workspaceState.get(capturerConfigStateKey, [])
+ )
+ for (const config of this.configs) {
+ this.ensureRuntime(config)
+ this.refreshReadiness(config.id)
+ }
+ this.toDispose.push(
+ this.onDidChangeTreeDataEmitter,
+ this.arduinoContext.onDidChangeSketch((event) => {
+ const sketchPath = event.object?.sketchPath
+ if (!sketchPath) {
+ return
+ }
+ const targets = this.configs.filter((config) =>
+ pathEquals(config.sketchPath, sketchPath)
+ )
+ for (const target of targets) {
+ this.refreshReadiness(target.id)
+ }
+ }),
+ this.arduinoContext.onDidChangeCurrentSketch((sketch) => {
+ if (!sketch?.sketchPath) {
+ return
+ }
+ const targets = this.configs.filter((config) =>
+ pathEquals(config.sketchPath, sketch.sketchPath)
+ )
+ for (const target of targets) {
+ this.refreshReadiness(target.id)
+ }
+ })
+ )
+ this.toDispose.push(
+ boardsListWatcher.onDidChangeDetectedPorts(() => {
+ for (const config of this.configs) {
+ this.refreshReadiness(config.id)
+ }
+ })
+ )
+ }
+
+ dispose() {
+ for (const runtime of this.runtimes.values()) {
+ this.disposeRuntime(runtime)
+ }
+ this.runtimes.clear()
+ if (this.ownsCrashReportProvider) {
+ this.crashReportProvider.dispose()
+ }
+ vscode.Disposable.from(...this.toDispose).dispose()
+ }
+
+ bindTreeView(treeView: vscode.TreeView) {
+ this.treeView = treeView
+ this.updateViewBadge()
+ }
+
+ async addUsingBoardLabPickers() {
+ const draft = await resolveSketchTargetWithBoardLab({
+ boardLab: this.arduinoContext,
+ openedSketches: this.arduinoContext.openedSketches,
+ pickMode: async (items: readonly SketchTargetSelectionModeItem[]) =>
+ vscode.window.showQuickPick(items, {
+ placeHolder:
+ 'Create capturer from sketch target or customize board/port',
+ }),
+ onError: (message: string) => vscode.window.showErrorMessage(message),
+ })
+ if (!draft) {
+ return
+ }
+
+ const existing = this.findExistingConfig(draft)
+ if (existing) {
+ const duplicateAction = await vscode.window.showQuickPick(
+ [
+ {
+ label: 'Use existing capturer',
+ description: `${existing.port.address} | ${existing.fqbn}`,
+ action: 'use',
+ },
+ {
+ label: 'Start existing capturer',
+ description: `${existing.port.address} | ${existing.fqbn}`,
+ action: 'start',
+ },
+ {
+ label: 'Cancel',
+ description: 'Keep current setup unchanged',
+ action: 'cancel',
+ },
+ ],
+ {
+ placeHolder:
+ 'A capturer already exists for this sketch, board, and port',
+ }
+ )
+ if (!duplicateAction || duplicateAction.action === 'cancel') {
+ return
+ }
+ if (duplicateAction.action === 'start') {
+ await this.startCapturer({ type: 'root', configId: existing.id })
+ return
+ }
+ this.refreshRoot(existing.id)
+ return
+ }
+
+ await this.addConfig(draft)
+ }
+
+ findExistingConfig(draft: CapturerConfigDraft) {
+ return this.configs.find(
+ (config) =>
+ pathEquals(config.sketchPath, draft.sketchPath) &&
+ config.fqbn === draft.fqbn &&
+ arePortsEqual(config.port, draft.port)
+ )
+ }
+
+ async addConfig(draft: CapturerConfigDraft) {
+ const validated = validateCapturerConfig(draft)
+ if (!validated.ok) {
+ vscode.window.showErrorMessage(validated.message)
+ return undefined
+ }
+ const validatedValue = validated.value
+ const exists = await pathExists(validatedValue.sketchPath)
+ if (!exists) {
+ vscode.window.showErrorMessage(
+ `Sketch path does not exist: ${validatedValue.sketchPath}`
+ )
+ return undefined
+ }
+ const id = buildCapturerId(validatedValue)
+ const config: CapturerConfig = { id, ...validatedValue }
+ const previousIndex = this.configs.findIndex((entry) => entry.id === id)
+ if (previousIndex >= 0) {
+ this.configs[previousIndex] = config
+ } else {
+ this.configs = [...this.configs, config]
+ }
+ await this.context.workspaceState.update(
+ capturerConfigStateKey,
+ this.configs
+ )
+ this.ensureRuntime(config)
+ await this.refreshReadiness(config.id)
+ this.refreshTree()
+ return config
+ }
+
+ async removeConfig(
+ target?: CapturerNode,
+ options: { force?: boolean } = {}
+ ): Promise {
+ const configId = await this.resolveConfigId(
+ target,
+ 'Select capturer to remove'
+ )
+ if (!configId) return
+ const configToRemove = this.configs.find((config) => config.id === configId)
+ if (!configToRemove) return
+ if (!options.force) {
+ const answer = await vscode.window.showInformationMessage(
+ 'Remove Capturer',
+ {
+ modal: true,
+ detail: `Do you want to remove the capturer created for ${path.basename(configToRemove.sketchPath)} on ${configToRemove.port.address} with ${configToRemove.fqbn}?`,
+ },
+ 'Remove'
+ )
+ if (answer !== 'Remove') return
+ }
+ const runtime = this.runtimes.get(configId)
+ if (runtime) {
+ await this.disposeRuntime(runtime)
+ this.runtimes.delete(configId)
+ }
+ this.treeModel.clearConfig(configId)
+ this.configs = this.configs.filter((config) => config.id !== configId)
+ await this.context.workspaceState.update(
+ capturerConfigStateKey,
+ this.configs
+ )
+ if (this.onRemovedCapturer) {
+ try {
+ await this.onRemovedCapturer({ configId })
+ } catch {
+ // ignore replay/codelens cleanup failures
+ }
+ }
+ this.refreshTree()
+ }
+
+ async startCapturer(target?: CapturerNode): Promise {
+ const configId = await this.resolveConfigId(
+ target,
+ 'Select capturer to start'
+ )
+ if (!configId) {
+ return
+ }
+ const runtime = this.runtimes.get(configId)
+ if (!runtime) {
+ return
+ }
+ if (runtime.monitor) {
+ return
+ }
+ const problems = collectRuntimeProblems(runtime)
+ if (!isReadyToRecord(runtime, problems)) {
+ return
+ }
+ try {
+ resetTransferMetrics(runtime)
+ runtime.monitor = await this.arduinoContext.createMonitorClient(
+ runtime.config.port,
+ { autoStart: true }
+ )
+ runtime.monitorState = runtime.monitor.state
+ runtime.lastError = undefined
+ runtime.monitorSubscriptions.push(
+ runtime.monitor.onDidReceiveData((chunk) => {
+ try {
+ runtime.capturer.push(chunk)
+ const now = Date.now()
+ recordTransfer(runtime, chunk.byteLength, now)
+ runtime.lastChunkEndedWithLineBreak = chunkEndsWithLineBreak(chunk)
+ this.scheduleIdleFlush(configId, runtime)
+ } catch (err) {
+ runtime.lastError = err instanceof Error ? err.message : String(err)
+ this.refreshRoot(configId)
+ }
+ }),
+ runtime.monitor.onDidChangeState((state) => {
+ runtime.monitorState = state
+ if (state === 'disconnected') {
+ this.flushRuntime(configId, runtime)
+ }
+ this.refreshRoot(configId)
+ })
+ )
+ await this.refreshReadiness(configId)
+ this.refreshRoot(configId)
+ } catch (err) {
+ runtime.lastError = err instanceof Error ? err.message : String(err)
+ this.refreshRoot(configId)
+ vscode.window.showErrorMessage(
+ `Failed to start capturer ${runtime.config.port.address}: ${runtime.lastError}`
+ )
+ }
+ }
+
+ async stopCapturer(target?: CapturerNode): Promise {
+ const configId = await this.resolveConfigId(
+ target,
+ 'Select capturer to stop'
+ )
+ if (!configId) {
+ return
+ }
+ const runtime = this.runtimes.get(configId)
+ if (!runtime) {
+ return
+ }
+ await this.detachMonitor(runtime)
+ this.refreshRoot(configId)
+ }
+
+ async refresh(target?: CapturerNode): Promise {
+ if (target) {
+ const configId = await this.resolveConfigId(target, 'Select capturer')
+ if (!configId) {
+ return
+ }
+ const runtime = this.runtimes.get(configId)
+ if (runtime) {
+ try {
+ runtime.capturer.flush()
+ } catch (err) {
+ runtime.lastError = err instanceof Error ? err.message : String(err)
+ }
+ }
+ await this.refreshReadiness(configId)
+ this.refreshRoot(configId)
+ return
+ }
+ for (const config of this.configs) {
+ const runtime = this.runtimes.get(config.id)
+ if (runtime) {
+ try {
+ runtime.capturer.flush()
+ } catch (err) {
+ runtime.lastError = err instanceof Error ? err.message : String(err)
+ }
+ }
+ await this.refreshReadiness(config.id)
+ }
+ this.refreshTree()
+ }
+
+ async compileSketch(target?: CapturerNode): Promise {
+ await this.runCompileForRuntime(target, 'boardlab.compile')
+ }
+
+ async compileSketchWithDebugSymbols(target?: CapturerNode): Promise {
+ await this.runCompileForRuntime(target, 'boardlab.compileWithDebugSymbols')
+ }
+
+ async quickFix(
+ target?: CapturerNode,
+ preferredAction?: string
+ ): Promise {
+ const configId = await this.resolveConfigId(
+ target,
+ 'Select capturer to apply quick fix'
+ )
+ if (!configId) {
+ return
+ }
+ const runtime = this.runtimes.get(configId)
+ if (!runtime) {
+ return
+ }
+ const quickFixes = this.collectQuickFixes(runtime)
+ if (quickFixes.length === 0) {
+ vscode.window.showInformationMessage(
+ 'No quick fixes are available for this capturer.'
+ )
+ return
+ }
+ if (typeof preferredAction === 'string' && preferredAction.length > 0) {
+ const preferred = quickFixes.find(
+ (quickFix) => quickFix.action === preferredAction
+ )
+ if (preferred) {
+ await preferred.run()
+ return
+ }
+ }
+ await quickFixes[0].run()
+ }
+
+ collectQuickFixes(runtime: CapturerRuntime): QuickFixItem[] {
+ const rootTarget: CapturerRootNode = {
+ type: 'root',
+ configId: runtime.config.id,
+ }
+ const problems = collectRuntimeProblems(runtime)
+ const hasSketchFqbnMismatch = problems.some(
+ (problem) => problem.code === 'fqbn-mismatch-selected-engine'
+ )
+ const hasBuildFqbnMismatch = problems.some(
+ (problem) => problem.code === 'fqbn-mismatch-selected-build'
+ )
+ const needsCompileQuickFixes =
+ !runtime.readiness.hasCompileSummary ||
+ !runtime.readiness.elfPath ||
+ hasBuildFqbnMismatch
+
+ if (hasSketchFqbnMismatch) {
+ const syncQuickFix: QuickFixItem = {
+ label: 'Sync Sketch Board to Capturer FQBN',
+ description: runtime.config.fqbn,
+ action: 'sync-sketch-fqbn',
+ run: () => this.syncSketchBoardWithCapturer(runtime.config.id),
+ }
+ return [syncQuickFix]
+ }
+ const quickFixes: QuickFixItem[] = []
+ if (needsCompileQuickFixes) {
+ const compileWithDebugQuickFix: QuickFixItem = {
+ label: 'Compile Sketch with Debug Symbols',
+ description: '(Recommended)',
+ action: 'compile-debug',
+ run: () =>
+ this.runCompileForRuntime(
+ rootTarget,
+ 'boardlab.compileWithDebugSymbols'
+ ),
+ }
+ const compileQuickFix: QuickFixItem = {
+ label: 'Compile Sketch',
+ action: 'compile',
+ run: () => this.runCompileForRuntime(rootTarget, 'boardlab.compile'),
+ }
+ quickFixes.push(compileWithDebugQuickFix, compileQuickFix)
+ }
+ return quickFixes
+ }
+
+ async syncSketchBoardWithCapturer(configId: string): Promise {
+ const runtime = this.runtimes.get(configId)
+ if (!runtime) {
+ return
+ }
+ const sketchFolder = this.arduinoContext.openedSketches.find(
+ ({ sketchPath }) => sketchPath === runtime.config.sketchPath
+ )
+ if (!sketchFolder) {
+ vscode.window.showInformationMessage(
+ `Could not find sketch at ${runtime.config.sketchPath}`
+ )
+ return
+ }
+ const targetFqbn = sanitizeFqbn(runtime.config.fqbn)
+ if (!targetFqbn) {
+ vscode.window.showErrorMessage('Capturer FQBN is invalid.')
+ return
+ }
+ const selected = await this.arduinoContext.setBoardByFqbn(
+ targetFqbn,
+ sketchFolder
+ )
+ if (!selected) {
+ vscode.window.showWarningMessage(
+ `Could not set sketch board to capturer FQBN ${runtime.config.fqbn}.`
+ )
+ return
+ }
+ await this.refreshReadiness(configId)
+ this.refreshRoot(configId)
+ }
+
+ async runCompileForRuntime(
+ target: CapturerCommandTarget,
+ command: CapturerCompileCommand
+ ): Promise {
+ const configId = await this.resolveConfigId(
+ target,
+ 'Select capturer to compile sketch'
+ )
+ if (!configId) {
+ return
+ }
+ const runtime = this.runtimes.get(configId)
+ if (!runtime) {
+ return
+ }
+ const sketchFolder = this.arduinoContext.openedSketches.find(
+ ({ sketchPath }) => sketchPath === runtime.config.sketchPath
+ )
+ if (!sketchFolder) {
+ vscode.window.showInformationMessage(
+ `Could not find sketch at ${runtime.config.sketchPath}`
+ )
+ return
+ }
+ await vscode.commands.executeCommand(command, {
+ sketchPath: runtime.config.sketchPath,
+ })
+ await this.refreshReadiness(configId)
+ this.refreshRoot(configId)
+ }
+
+ async copyToClipboard(arg?: unknown): Promise {
+ if (typeof arg !== 'string' || arg.length === 0) {
+ return
+ }
+ await vscode.env.clipboard.writeText(arg)
+ }
+
+ async replayRecording(
+ target?: CapturerNode,
+ recordingUri?: vscode.Uri
+ ): Promise {
+ const configId = await this.resolveConfigId(
+ target,
+ 'Select capturer for replay'
+ )
+ if (!configId) {
+ return
+ }
+ const runtime = this.runtimes.get(configId)
+ if (!runtime) {
+ return
+ }
+ const selectedUri = recordingUri
+ if (!selectedUri) {
+ return
+ }
+ const rawText = await fs.readFile(selectedUri.fsPath, 'utf8')
+ const encoder = new TextEncoder()
+ const segments = splitReplaySegments(rawText)
+ resetTransferMetrics(runtime)
+ for (const segment of segments) {
+ const payload = segment.trim().length > 0 ? segment : ''
+ if (!payload) {
+ continue
+ }
+ const chunk = encoder.encode(`${payload}\n`)
+ runtime.capturer.push(chunk)
+ recordTransfer(runtime, chunk.byteLength, Date.now())
+ runtime.capturer.flush()
+ }
+ this.refreshRoot(configId)
+ }
+
+ async replayEvent(target?: CapturerNode): Promise {
+ if (!target || target.type !== 'event') {
+ return
+ }
+ const runtime = this.runtimes.get(target.configId)
+ const summary = runtime?.eventsBySignature.get(target.signature)
+ if (!runtime || !summary) {
+ return
+ }
+ if (!this.evaluateDecodedEvent) {
+ return
+ }
+ if (
+ summary.decodeState === 'decoding' ||
+ summary.decodeState === 'evaluating'
+ ) {
+ return
+ }
+ if (summary.decodeState === 'evaluated' && summary.decodedResult) {
+ try {
+ const params = await this.resolveDecodeParams(runtime)
+ await this.evaluateDecodedEvent({
+ meta: toDecodedEventMeta(target.configId, summary),
+ params,
+ result: summary.decodedResult,
+ })
+ summary.decodeError = undefined
+ } catch (err) {
+ summary.decodeError = err instanceof Error ? err.message : String(err)
+ } finally {
+ this.refreshEvent(target.configId, target.signature)
+ }
+ return
+ }
+ if (summary.decodeState !== 'decoded' || !summary.decodedResult) {
+ return
+ }
+ summary.decodeState = 'evaluating'
+ this.refreshEvent(target.configId, target.signature)
+ const evaluateStartAt = Date.now()
+ try {
+ const params = await this.resolveDecodeParams(runtime)
+ await this.evaluateDecodedEvent({
+ meta: toDecodedEventMeta(target.configId, summary),
+ params,
+ result: summary.decodedResult,
+ })
+ summary.decodeState = 'evaluated'
+ summary.decodeError = undefined
+ } catch (err) {
+ summary.decodeState = 'decoded'
+ summary.decodeError = err instanceof Error ? err.message : String(err)
+ } finally {
+ summary.lastEvaluateDurationMs = Math.max(0, Date.now() - evaluateStartAt)
+ this.refreshEvent(target.configId, target.signature)
+ }
+ }
+
+ async clearEvent(target?: CapturerNode): Promise {
+ if (!target || (target.type !== 'event' && target.type !== 'frame')) {
+ return
+ }
+ const runtime = this.runtimes.get(target.configId)
+ if (!runtime) {
+ return
+ }
+ const signature = target.signature
+ if (!runtime.eventsBySignature.has(signature)) {
+ return
+ }
+ this.removeSummaryBySignature(runtime, signature)
+ this.refreshRoot(target.configId)
+ }
+
+ async clearAllEvents(target?: CapturerNode): Promise {
+ const configId = await this.resolveConfigId(
+ target,
+ 'Select capturer to clear crash events'
+ )
+ if (!configId) {
+ return
+ }
+ const runtime = this.runtimes.get(configId)
+ if (!runtime || runtime.eventsBySignature.size === 0) {
+ return
+ }
+ for (const signature of [...runtime.eventsBySignature.keys()]) {
+ this.removeSummaryBySignature(runtime, signature)
+ }
+ this.refreshRoot(configId)
+ }
+
+ async dumpCapturerState(target?: CapturerNode): Promise {
+ const runtime = await this.resolveRuntimeForStateDump(target)
+ if (!runtime) {
+ return
+ }
+ const rawState = runtime.capturer.getRawState()
+ const distinctEvents = Array.from(runtime.eventsBySignature.values()).sort(
+ (left, right) => left.firstSeenAt - right.firstSeenAt
+ )
+ await this.showMarkdownReportPreview(
+ `capturer-state-${path.basename(runtime.config.sketchPath)}`,
+ toCapturerStateDumpMarkdown(runtime, rawState, distinctEvents)
+ )
+ }
+
+ async resolveRuntimeForStateDump(
+ target?: CapturerNode
+ ): Promise {
+ const configIdFromTarget = target?.configId
+ if (configIdFromTarget) {
+ return this.runtimes.get(configIdFromTarget)
+ }
+ if (this.configs.length === 0) {
+ vscode.window.showInformationMessage('No ESP Crash Capturer created.')
+ return undefined
+ }
+ if (this.configs.length === 1) {
+ return this.runtimes.get(this.configs[0].id)
+ }
+ const pick = await vscode.window.showQuickPick(
+ this.configs.map((config) => ({
+ label: path.basename(config.sketchPath),
+ description: `${config.port.address} | ${config.fqbn}`,
+ detail: config.sketchPath,
+ configId: config.id,
+ })),
+ {
+ placeHolder: 'Select ESP Crash Capturer state to dump',
+ }
+ )
+ return pick ? this.runtimes.get(pick.configId) : undefined
+ }
+
+ async showEvent(target?: CapturerNode): Promise {
+ if (!target || target.type !== 'event') {
+ return
+ }
+ const runtime = this.runtimes.get(target.configId)
+ const summary = runtime?.eventsBySignature.get(target.signature)
+ if (!runtime || !summary) {
+ return
+ }
+ await this.showMarkdownReportPreview(
+ `crash-event-${path.basename(runtime.config.sketchPath)}`,
+ toEventReportDocumentMarkdown(runtime, summary)
+ )
+ }
+
+ async showMarkdownReportPreview(
+ name: string,
+ content: string
+ ): Promise {
+ const reportUri = this.crashReportProvider.createReportUri(name, content)
+ await vscode.commands.executeCommand('markdown.showPreview', reportUri)
+ }
+
+ getTreeItem(element: CapturerNode): vscode.TreeItem {
+ if (element.type === 'root') {
+ const runtime = this.runtimes.get(element.configId)
+ if (!runtime) {
+ return new vscode.TreeItem(element.configId)
+ }
+ const item = new vscode.TreeItem(
+ toRootLabel(runtime),
+ vscode.TreeItemCollapsibleState.Collapsed
+ )
+ item.id = runtime.config.id
+ item.description = resolveRootDescription(runtime)
+ item.tooltip = toRootTreeItemMarkdown(runtime)
+ item.contextValue = rootContextValue(runtime)
+ item.iconPath = toRuntimeIcon(runtime)
+ return item
+ }
+ if (element.type === 'frame') {
+ const runtime = this.runtimes.get(element.configId)
+ const summary = runtime?.eventsBySignature.get(element.signature)
+ const frame = summary
+ ? collectDecodedStackFrames(summary.decodedResult)[element.frameIndex]
+ : undefined
+ const frameLocation = toParsedFrameLocation(frame)
+ const sourceKind = classifyDecodedSource(
+ frameLocation?.file,
+ runtime?.config.sketchPath
+ )
+ const sourceAvailability = runtime
+ ? this.resolveSourceAvailability(
+ element.configId,
+ runtime,
+ frameLocation?.file,
+ sourceKind
+ )
+ : undefined
+ const frameUri =
+ frameLocation && runtime
+ ? toFrameUri(frameLocation.file, runtime.config.sketchPath)
+ : undefined
+ const item = new vscode.TreeItem(
+ frameToLabel(frame),
+ vscode.TreeItemCollapsibleState.None
+ )
+ item.id = `frame:${element.configId}:${element.signature}:${element.frameIndex}`
+ if (frameUri && frameLocation) {
+ item.resourceUri = frameUri
+ item.description = true
+ item.iconPath = vscode.ThemeIcon.File
+ const shouldAttachOpenCommand = !(
+ sourceKind === 'library' && sourceAvailability === 'missing'
+ )
+ if (shouldAttachOpenCommand) {
+ const line = Math.max(0, frameLocation.line - 1)
+ item.command = {
+ command: 'vscode.open',
+ title: 'Open Frame',
+ arguments: [
+ frameUri,
+ {
+ preview: true,
+ preserveFocus: true,
+ selection: new vscode.Range(line, 0, line, 0),
+ },
+ ],
+ }
+ }
+ } else {
+ item.description = frameToDescription(frame)
+ }
+ item.contextValue = 'espCapturerFrame'
+ return item
+ }
+ const runtime = this.runtimes.get(element.configId)
+ const summary = runtime?.eventsBySignature.get(element.signature)
+ const label = summary
+ ? eventDisplayLabel(summary)
+ : element.signature.trim() || 'Unknown event'
+ const decodedFrames = summary
+ ? collectDecodedStackFrames(summary.decodedResult)
+ : []
+ const collapsibleState =
+ decodedFrames.length === 0
+ ? vscode.TreeItemCollapsibleState.None
+ : summary?.decodeState === 'decoded' ||
+ summary?.decodeState === 'evaluated'
+ ? vscode.TreeItemCollapsibleState.Expanded
+ : vscode.TreeItemCollapsibleState.Collapsed
+ const item = new vscode.TreeItem(label, collapsibleState)
+ item.id = `event:${element.configId}:${element.signature}`
+ item.description = summary ? toEventDescription(summary) : undefined
+ item.tooltip =
+ summary && runtime ? toEventTreeItemMarkdown(runtime, summary) : undefined
+ item.contextValue = eventContextValue(summary)
+ item.iconPath =
+ summary?.decodeState === 'decoding' ||
+ summary?.decodeState === 'evaluating'
+ ? new vscode.ThemeIcon('sync~spin')
+ : new vscode.ThemeIcon('bug')
+ return item
+ }
+
+ getChildren(element?: CapturerNode): CapturerNode[] {
+ if (!element) {
+ return this.treeModel.getRootNodes(this.configs)
+ }
+ if (element.type === 'frame') {
+ return []
+ }
+ if (element.type === 'event') {
+ const runtime = this.runtimes.get(element.configId)
+ const summary = runtime?.eventsBySignature.get(element.signature)
+ const frames = collectDecodedStackFrames(summary?.decodedResult)
+ return this.treeModel.getFrameNodes(
+ element.configId,
+ element.signature,
+ frames.length
+ )
+ }
+ const runtime = this.runtimes.get(element.configId)
+ return this.treeModel.getEventNodes(element.configId, runtime)
+ }
+
+ getParent(element: CapturerNode): CapturerNode | undefined {
+ if (element.type === 'root') {
+ return undefined
+ }
+ if (element.type === 'event') {
+ return this.treeModel.getRootNode(element.configId)
+ }
+ return this.treeModel.getEventNode(element.configId, element.signature)
+ }
+
+ refreshTree() {
+ this.updateViewBadge()
+ this.onDidChangeTreeDataEmitter.fire(undefined)
+ }
+
+ refreshRoot(configId: string): void {
+ this.updateViewBadge()
+ this.onDidChangeTreeDataEmitter.fire(this.treeModel.getRootNode(configId))
+ }
+
+ refreshEvent(configId: string, signature: string): void {
+ this.onDidChangeTreeDataEmitter.fire(
+ this.treeModel.getEventNode(configId, signature)
+ )
+ }
+
+ updateViewBadge() {
+ if (!this.treeView) {
+ return
+ }
+ const activeSessionCount = countActiveCaptureSessions(
+ this.runtimes.values()
+ )
+ this.treeView.badge = toCapturerViewBadge(activeSessionCount)
+ }
+
+ ensureRuntime(config: CapturerConfig): CapturerRuntime {
+ const existing = this.runtimes.get(config.id)
+ if (existing) {
+ return existing
+ }
+ const capturerOptions: CapturerOptions = {}
+ const capturer = createCapturer(capturerOptions)
+ const runtime: CapturerRuntime = {
+ config,
+ capturer,
+ eventsBySignature: new Map(),
+ eventsById: new Map(),
+ unsubscribeDetected: capturer.on('eventDetected', (event) =>
+ this.handleCapturerEvent(config.id, event)
+ ),
+ unsubscribeUpdated: capturer.on('eventUpdated', (event) =>
+ this.handleCapturerEvent(config.id, event)
+ ),
+ monitorSubscriptions: [],
+ sourceAvailabilityByPath: new Map<
+ string,
+ 'checking' | 'present' | 'missing'
+ >(),
+ lastChunkEndedWithLineBreak: true,
+ processedBytes: 0,
+ readiness: { sketchExists: false, hasCompileSummary: false },
+ }
+ this.runtimes.set(config.id, runtime)
+ return runtime
+ }
+
+ handleCapturerEvent(configId: string, event: CapturerEvent): void {
+ const runtime = this.runtimes.get(configId)
+ if (!runtime) {
+ return
+ }
+ if (shouldIgnoreCapturerEvent(event)) {
+ return
+ }
+ const hadSummary = runtime.eventsBySignature.has(event.signature)
+ const summary = upsertEventSummary(
+ runtime.eventsBySignature,
+ event,
+ runtime
+ )
+ runtime.eventsById.set(summary.eventId, summary)
+ this.startAutoDecode(configId, runtime, summary)
+ if (!hadSummary) {
+ this.refreshRoot(configId)
+ this.revealEventNode({
+ type: 'event',
+ configId,
+ signature: summary.signature,
+ })
+ }
+ }
+
+ async revealEventNode(node: CapturerEventNode): Promise {
+ if (!this.treeView) {
+ return
+ }
+ const rootNode = this.treeModel.getRootNode(node.configId)
+ const eventNode = this.getChildren(rootNode).find(
+ (child) => child.type === 'event' && child.signature === node.signature
+ )
+ if (!eventNode) {
+ return
+ }
+ try {
+ await this.treeView.reveal(rootNode, {
+ focus: false,
+ select: false,
+ expand: true,
+ })
+ await this.treeView.reveal(eventNode, {
+ focus: false,
+ select: false,
+ expand: true,
+ })
+ } catch {
+ // ignore reveal failures when the view is not visible or the node is stale
+ }
+ }
+
+ removeSummaryBySignature(runtime: CapturerRuntime, signature: string): void {
+ const removedSummary = runtime.eventsBySignature.get(signature)
+ this.treeModel.removeEventNode(runtime.config.id, signature)
+ runtime.eventsBySignature.delete(signature)
+ for (const [eventId, summary] of runtime.eventsById) {
+ if (summary.signature === signature) {
+ runtime.eventsById.delete(eventId)
+ }
+ }
+ if (removedSummary && this.onRemovedDecodedEvent) {
+ Promise.resolve(
+ this.onRemovedDecodedEvent({
+ key: `${runtime.config.id}:${signature}`,
+ configId: runtime.config.id,
+ signature,
+ eventId: removedSummary.eventId,
+ })
+ ).catch(() => {
+ // ignore replay/codelens cleanup failures
+ })
+ }
+ }
+
+ resolveSourceAvailability(
+ configId: string,
+ runtime: CapturerRuntime,
+ filePath: string | undefined,
+ sourceKind: 'sketch' | 'library' | 'missing'
+ ): 'checking' | 'present' | 'missing' | undefined {
+ if (!filePath || sourceKind !== 'library') {
+ return undefined
+ }
+ const cached = runtime.sourceAvailabilityByPath.get(filePath)
+ if (cached) {
+ return cached
+ }
+ runtime.sourceAvailabilityByPath.set(filePath, 'checking')
+ this.probeSourceAvailability(configId, runtime, filePath)
+ return 'checking'
+ }
+
+ async probeSourceAvailability(
+ configId: string,
+ runtime: CapturerRuntime,
+ filePath: string
+ ): Promise {
+ let next: 'present' | 'missing' = 'present'
+ try {
+ await fs.access(filePath)
+ } catch {
+ next = 'missing'
+ }
+ runtime.sourceAvailabilityByPath.set(filePath, next)
+ this.refreshRoot(configId)
+ }
+
+ async refreshReadiness(configId: string): Promise {
+ const runtime = this.runtimes.get(configId)
+ if (!runtime) {
+ return
+ }
+ const sketchExists = await pathExists(runtime.config.sketchPath)
+ const sketchRuntimeInfo = sketchExists
+ ? await findSketchRuntimeInfo(runtime.config, this.arduinoContext)
+ : undefined
+ const nextReadiness = {
+ sketchExists,
+ hasCompileSummary: sketchRuntimeInfo?.hasCompileSummary ?? false,
+ elfPath: sketchRuntimeInfo?.elfPath,
+ buildPath: sketchRuntimeInfo?.buildPath,
+ buildOptions: sketchRuntimeInfo?.buildOptions,
+ boardName: sketchRuntimeInfo?.boardName,
+ selectedBoardName: sketchRuntimeInfo?.selectedBoardName,
+ selectedBoardFqbn: sketchRuntimeInfo?.selectedBoardFqbn,
+ selectedPort: sketchRuntimeInfo?.selectedPort,
+ configuredPortDetected: isPortDetected(
+ this.arduinoContext.boardsListWatcher.detectedPorts,
+ runtime.config.port
+ ),
+ workspaceRelativeSketchPath: workspaceRelativeSketchPath(
+ runtime.config.sketchPath
+ ),
+ }
+ const prevReadiness = runtime.readiness
+ const prevElfIdentity = runtime.elfIdentity
+ const nextElfIdentity = nextReadiness.elfPath
+ ? await resolveElfIdentity(nextReadiness.elfPath, prevElfIdentity)
+ : undefined
+ runtime.readiness = nextReadiness
+ runtime.elfIdentity = nextElfIdentity
+ if (
+ !isReadinessEqual(prevReadiness, nextReadiness) ||
+ !isElfIdentityEqual(prevElfIdentity, nextElfIdentity)
+ ) {
+ runtime.decodeParams = undefined
+ this.refreshRoot(configId)
+ }
+ }
+
+ async detachMonitor(runtime: CapturerRuntime): Promise {
+ this.flushRuntime(runtime.config.id, runtime)
+ for (const disposable of runtime.monitorSubscriptions) {
+ disposable.dispose()
+ }
+ runtime.monitorSubscriptions.length = 0
+ this.clearIdleFlushTimer(runtime)
+ const monitor = runtime.monitor
+ runtime.monitor = undefined
+ runtime.monitorState = undefined
+ if (monitor) {
+ await this.invokeMonitorMethod(monitor, 'stop')
+ await this.invokeMonitorMethod(monitor, 'close')
+ try {
+ monitor.dispose()
+ } catch {
+ // ignore monitor disposal failures during teardown
+ }
+ }
+ }
+
+ async disposeRuntime(runtime: CapturerRuntime): Promise {
+ await this.detachMonitor(runtime)
+ runtime.unsubscribeDetected()
+ runtime.unsubscribeUpdated()
+ runtime.decodeParams = undefined
+ runtime.sourceAvailabilityByPath.clear()
+ runtime.eventsById.clear()
+ runtime.eventsBySignature.clear()
+ this.treeModel.clearConfig(runtime.config.id)
+ }
+
+ async invokeMonitorMethod(
+ monitor: MonitorClientWithClose,
+ methodName: 'stop' | 'close'
+ ): Promise {
+ const method = methodName === 'stop' ? monitor.stop : monitor.close
+ if (typeof method !== 'function') {
+ return
+ }
+ try {
+ await method.call(monitor)
+ } catch {
+ // ignore monitor method failures during teardown
+ }
+ }
+
+ async resolveConfigId(
+ target: CapturerCommandTarget,
+ placeholder: string
+ ): Promise {
+ if (target?.type === 'root') {
+ return target.configId
+ }
+ if (target?.type === 'event' || target?.type === 'frame') {
+ return target.configId
+ }
+ if (this.configs.length === 1) {
+ return this.configs[0].id
+ }
+ const quickPickItem = await vscode.window.showQuickPick(
+ this.configs.map((config) => ({
+ label: `${config.port.address} | ${config.fqbn}`,
+ detail: config.sketchPath,
+ configId: config.id,
+ })),
+ { placeHolder: placeholder }
+ )
+ return quickPickItem?.configId
+ }
+
+ flushRuntime(configId: string, runtime: CapturerRuntime): void {
+ try {
+ runtime.capturer.flush()
+ } catch (err) {
+ runtime.lastError = err instanceof Error ? err.message : String(err)
+ this.refreshRoot(configId)
+ }
+ }
+
+ scheduleIdleFlush(configId: string, runtime: CapturerRuntime): void {
+ this.clearIdleFlushTimer(runtime)
+ runtime.idleFlushTimer = setTimeout(() => {
+ runtime.idleFlushTimer = undefined
+ if (!runtime.lastChunkEndedWithLineBreak) {
+ return
+ }
+ this.flushRuntime(configId, runtime)
+ }, idleFlushDelayMs)
+ }
+
+ clearIdleFlushTimer(runtime: CapturerRuntime): void {
+ if (runtime.idleFlushTimer) {
+ clearTimeout(runtime.idleFlushTimer)
+ runtime.idleFlushTimer = undefined
+ }
+ }
+
+ startAutoDecode(
+ configId: string,
+ runtime: CapturerRuntime,
+ summary: CapturerEventSummary
+ ): void {
+ if (
+ summary.decodeState === 'decoding' ||
+ summary.decodeState === 'decoded' ||
+ summary.decodeState === 'evaluating' ||
+ summary.decodeState === 'evaluated'
+ ) {
+ return
+ }
+ if (summary.decodeTask) {
+ return
+ }
+ summary.decodeState = 'decoding'
+ this.refreshEvent(configId, summary.signature)
+ summary.decodeTask = (async () => {
+ try {
+ const { params, result } = await this.decodeEventSummary(
+ runtime,
+ summary,
+ {
+ includeFrameVars: true,
+ trackDurationAs: 'decode',
+ }
+ )
+ summary.lastDecodeHeavyVarsCount = countHeavyVars(result)
+ summary.decodedResult = result
+ summary.decodeState = 'decoded'
+ summary.decodeError = undefined
+ if (this.onDecodedEvent) {
+ try {
+ await this.onDecodedEvent({
+ meta: toDecodedEventMeta(configId, summary),
+ params,
+ result,
+ })
+ } catch {
+ // ignore replay/codelens publication failures
+ }
+ }
+ this.refreshEvent(configId, summary.signature)
+ await this.revealEventNode({
+ type: 'event',
+ configId,
+ signature: summary.signature,
+ })
+ } catch (err) {
+ summary.decodeError = err instanceof Error ? err.message : String(err)
+ } finally {
+ if (
+ summary.decodeState !== 'decoded' &&
+ summary.decodeState !== 'evaluated'
+ ) {
+ summary.decodeState = 'detected'
+ }
+ summary.decodeTask = undefined
+ this.refreshEvent(configId, summary.signature)
+ }
+ })()
+ }
+
+ async decodeEventSummary(
+ runtime: CapturerRuntime,
+ summary: CapturerEventSummary,
+ options: DecodeEventSummaryOptions
+ ): Promise<{
+ params: DecodeParams
+ result: CapturerDecodedEventPayload['result']
+ }> {
+ const decodeStartAt = Date.now()
+ try {
+ const params = await this.resolveDecodeParams(runtime)
+ const decoded = await decode(params, summary.rawText, {
+ includeFrameVars: options.includeFrameVars,
+ })
+ if (Array.isArray(decoded)) {
+ throw new Error(`Decode returned ${decoded.length} results`)
+ }
+ return { params, result: decoded }
+ } finally {
+ if ((options.trackDurationAs ?? 'decode') === 'decode') {
+ summary.lastDecodeDurationMs = Math.max(0, Date.now() - decodeStartAt)
+ }
+ }
+ }
+
+ async resolveDecodeParams(runtime: CapturerRuntime): Promise {
+ if (runtime.decodeParams) {
+ return runtime.decodeParams
+ }
+ const sketch = this.arduinoContext.openedSketches.find(
+ (candidate) =>
+ typeof candidate.sketchPath === 'string' &&
+ pathEquals(candidate.sketchPath, runtime.config.sketchPath)
+ )
+ if (!sketch) {
+ throw new Error(
+ `Could not find opened sketch for ${runtime.config.sketchPath}`
+ )
+ }
+ const configuredSketch = withConfiguredFqbn(sketch, runtime.config.fqbn)
+ const fallbackCompileSummary = runtime.readiness.buildPath
+ ? ({
+ buildPath: runtime.readiness.buildPath,
+ } as SketchFolder['compileSummary'])
+ : undefined
+ const compileSummary =
+ configuredSketch.compileSummary ?? fallbackCompileSummary
+ const params = await createDecodeParams({
+ compileSummary,
+ board: configuredSketch.board,
+ sketchPath: runtime.config.sketchPath,
+ })
+ runtime.decodeParams = params
+ return params
+ }
+}
+export function registerCapturer(
+ context: vscode.ExtensionContext,
+ arduinoContext: ArduinoContext,
+ options: {
+ evaluateDecodedEvent?: EvaluateDecodedEvent
+ onDecodedEvent?: OnDecodedEvent
+ onRemovedDecodedEvent?: OnRemovedDecodedEvent
+ onRemovedCapturer?: OnRemovedCapturer
+ } = {}
+): vscode.Disposable {
+ const toDispose: vscode.Disposable[] = []
+ if (!isBoardLabContextExt(arduinoContext)) {
+ vscode.window.showInformationMessage(
+ 'The current BoardLab context does not expose the required monitor and picker APIs. Update BoardLab to the latest version and reload Visual Studio Code.'
+ )
+ return new vscode.Disposable(() => {
+ // noop
+ })
+ }
+ vscode.commands.executeCommand(
+ 'setContext',
+ 'espExceptionDecoder.capturerAvailable',
+ true
+ )
+ const crashReportProvider = new CrashReportContentProvider()
+ const manager = new CapturerManager(
+ context,
+ arduinoContext,
+ crashReportProvider,
+ options.evaluateDecodedEvent,
+ options.onDecodedEvent,
+ options.onRemovedDecodedEvent,
+ options.onRemovedCapturer
+ )
+ const readonlyLibraryProvider = new ReadonlyFsProvider()
+ const treeView = vscode.window.createTreeView(capturerViewId, {
+ treeDataProvider: manager,
+ })
+ manager.bindTreeView(treeView)
+ toDispose.push(
+ manager,
+ treeView,
+ readonlyLibraryProvider,
+ crashReportProvider,
+ vscode.workspace.registerFileSystemProvider(
+ readonlyLibraryScheme,
+ readonlyLibraryProvider,
+ { isReadonly: true }
+ ),
+ vscode.workspace.registerTextDocumentContentProvider(
+ crashReportScheme,
+ crashReportProvider
+ ),
+ vscode.commands.registerCommand(capturerCreateCommandId, () =>
+ manager.addUsingBoardLabPickers()
+ ),
+ vscode.commands.registerCommand(
+ capturerRemoveCommandId,
+ (target: CapturerNode | undefined) => manager.removeConfig(target)
+ ),
+ vscode.commands.registerCommand(
+ capturerStartCommandId,
+ (target: CapturerNode | undefined) => manager.startCapturer(target)
+ ),
+ vscode.commands.registerCommand(
+ capturerStopCommandId,
+ (target: CapturerNode | undefined) => manager.stopCapturer(target)
+ ),
+ vscode.commands.registerCommand(
+ capturerRefreshCommandId,
+ (target: CapturerNode | undefined) => manager.refresh(target)
+ ),
+ vscode.commands.registerCommand(
+ capturerQuickFixCommandId,
+ (target: CapturerNode | undefined, action: string | undefined) =>
+ manager.quickFix(target, action)
+ ),
+ vscode.commands.registerCommand(
+ capturerQuickFixSyncSketchBoardCommandId,
+ (target: CapturerNode | undefined) =>
+ manager.quickFix(target, 'sync-sketch-fqbn')
+ ),
+ vscode.commands.registerCommand(
+ capturerCompileSketchCommandId,
+ (target: CapturerNode | undefined) => manager.compileSketch(target)
+ ),
+ vscode.commands.registerCommand(
+ capturerCompileSketchDebugCommandId,
+ (target: CapturerNode | undefined) =>
+ manager.compileSketchWithDebugSymbols(target)
+ ),
+ vscode.commands.registerCommand(
+ capturerCopyToClipboardCommandId,
+ (arg: unknown) => manager.copyToClipboard(arg)
+ ),
+ vscode.commands.registerCommand(
+ dumpCapturerStateCommandId,
+ (target: CapturerNode | undefined) => manager.dumpCapturerState(target)
+ ),
+ vscode.commands.registerCommand(
+ capturerReplayCrashCommandId,
+ (target: CapturerNode | undefined) => manager.replayEvent(target)
+ ),
+ vscode.commands.registerCommand(
+ capturerClearEventCommandId,
+ (target: CapturerNode | undefined) => manager.clearEvent(target)
+ ),
+ vscode.commands.registerCommand(
+ capturerClearAllEventsCommandId,
+ (target: CapturerNode | undefined) => manager.clearAllEvents(target)
+ ),
+ vscode.commands.registerCommand(
+ capturerShowEventCommandId,
+ (target: CapturerNode | undefined) => manager.showEvent(target)
+ )
+ )
+ return vscode.Disposable.from(...toDispose)
+}
+function toRuntimeIcon(runtime: CapturerRuntime): vscode.ThemeIcon {
+ const problems = collectRuntimeProblems(runtime)
+ if (problems.some((problem) => problem.severity === 'error')) {
+ return new vscode.ThemeIcon('error')
+ }
+ if (problems.some((problem) => problem.severity === 'warning')) {
+ return new vscode.ThemeIcon('warning')
+ }
+ if (runtime.monitorState === 'running') {
+ return new vscode.ThemeIcon('pulse')
+ }
+ if (runtime.monitorState === 'suspended' && isRuntimeConnected(runtime)) {
+ return new vscode.ThemeIcon('sync~spin')
+ }
+ if (isReadyToRecord(runtime, problems)) {
+ return new vscode.ThemeIcon('pass')
+ }
+ return new vscode.ThemeIcon('error')
+}
+function toEventDescription(summary: CapturerEventSummary): string {
+ const createdLabel = `Created ${formatLocalTimestamp(summary.createdAt)}`
+ if (!summary.captureSessionLabel) {
+ return createdLabel
+ }
+ return `${createdLabel} | ${summary.captureSessionLabel}`
+}
+function buildCapturerId(config: CapturerConfigDraft): string {
+ return `${config.port.protocol}://${config.port.address}|${config.fqbn}|${path.normalize(config.sketchPath)}`
+}
+function validateCapturerConfig(
+ value: Partial
+): CapturerConfigValidationResult {
+ if (!isPortIdentifierLike(value.port)) {
+ return {
+ ok: false,
+ message: 'Capturer port is missing or invalid',
+ }
+ }
+ if (typeof value.fqbn !== 'string' || value.fqbn.trim().length === 0) {
+ return {
+ ok: false,
+ message: 'Capturer FQBN is required',
+ }
+ }
+ if (
+ typeof value.sketchPath !== 'string' ||
+ value.sketchPath.trim().length === 0
+ ) {
+ return {
+ ok: false,
+ message: 'Capturer sketch path is required',
+ }
+ }
+ try {
+ const fqbn = new FQBN(value.fqbn)
+ if (!supportedCapturerArchitectures.has(fqbn.arch)) {
+ return {
+ ok: false,
+ message: `Unsupported board architecture: '${fqbn.arch}'. Supported architectures: esp32, esp8266`,
+ }
+ }
+ return {
+ ok: true,
+ value: {
+ port: {
+ protocol: value.port.protocol.trim(),
+ address: value.port.address.trim(),
+ },
+ fqbn: fqbn.toString(),
+ sketchPath: path.resolve(value.sketchPath),
+ },
+ }
+ } catch (err) {
+ return {
+ ok: false,
+ message: `Invalid FQBN: ${err instanceof Error ? err.message : String(err)}`,
+ }
+ }
+}
+async function pathExists(targetPath: string): Promise {
+ try {
+ await fs.access(targetPath)
+ return true
+ } catch {
+ return false
+ }
+}
+async function resolveElfIdentity(
+ elfPath: string,
+ previous?: ElfIdentity
+): Promise {
+ let stats: Awaited>
+ try {
+ stats = await fs.stat(elfPath)
+ } catch {
+ return undefined
+ }
+ if (!stats.isFile()) {
+ return undefined
+ }
+ const size = stats.size
+ const mtimeMs = Math.floor(stats.mtimeMs)
+ if (
+ previous &&
+ previous.path === elfPath &&
+ previous.size === size &&
+ previous.mtimeMs === mtimeMs
+ ) {
+ return previous
+ }
+ let sha256: string
+ try {
+ const content = await fs.readFile(elfPath)
+ sha256 = createHash('sha256').update(content).digest('hex')
+ } catch {
+ return undefined
+ }
+ const sha256Short = sha256.slice(0, 8)
+ return {
+ path: elfPath,
+ size,
+ mtimeMs,
+ sha256,
+ sha256Short,
+ sessionId: `${sha256Short}-${mtimeMs}`,
+ }
+}
+function isPortDetected(
+ detectedPorts: unknown,
+ port: CapturerConfig['port'] | undefined
+): boolean | undefined {
+ if (!port) {
+ return undefined
+ }
+ const detected = extractDetectedPorts(detectedPorts)
+ if (!detected) {
+ return undefined
+ }
+ return detected.some((candidate) => arePortsEqual(candidate, port))
+}
+function extractDetectedPorts(
+ detectedPorts: unknown
+): CapturerConfig['port'][] | undefined {
+ if (!detectedPorts || typeof detectedPorts !== 'object') {
+ return undefined
+ }
+ const entries = Array.isArray(detectedPorts)
+ ? detectedPorts
+ : Object.values(detectedPorts)
+ const ports: CapturerConfig['port'][] = []
+ for (const entry of entries) {
+ if (isPortIdentifierLike(entry)) {
+ ports.push({ protocol: entry.protocol, address: entry.address })
+ continue
+ }
+ const nested = entry?.port
+ if (isPortIdentifierLike(nested)) {
+ ports.push({ protocol: nested.protocol, address: nested.address })
+ }
+ }
+ return ports
+}
+function arePortsEqual(
+ left: CapturerConfig['port'],
+ right: CapturerConfig['port']
+): boolean {
+ return left.protocol === right.protocol && left.address === right.address
+}
+async function loadCompileBuildOptions(
+ buildPath: string
+): Promise {
+ const optionsPath = path.join(buildPath, 'build.options.json')
+ let rawJson: string
+ try {
+ rawJson = await fs.readFile(optionsPath, 'utf8')
+ } catch {
+ return undefined
+ }
+ let parsed: unknown
+ try {
+ parsed = JSON.parse(rawJson)
+ } catch {
+ return {
+ buildPath,
+ optionsPath,
+ flags: [],
+ parseError: 'Invalid JSON',
+ }
+ }
+ if (!isRecord(parsed)) {
+ return {
+ buildPath,
+ optionsPath,
+ flags: [],
+ parseError: 'Unexpected JSON shape',
+ }
+ }
+ return {
+ buildPath,
+ optionsPath,
+ fqbn: pickNonEmptyString(parsed, 'fqbn'),
+ sketchLocation: pickNonEmptyString(parsed, 'sketchLocation'),
+ optimizationFlags: pickNonEmptyString(
+ parsed,
+ 'compiler.optimization_flags'
+ ),
+ customBuildProperties: pickNonEmptyString(parsed, 'customBuildProperties'),
+ flags: collectBuildFlagEntries(parsed),
+ }
+}
+function pickNonEmptyString(
+ record: Record,
+ key: string
+): string | undefined {
+ const value = record[key]
+ if (typeof value !== 'string') {
+ return undefined
+ }
+ const trimmed = value.trim()
+ return trimmed.length > 0 ? trimmed : undefined
+}
+function collectBuildFlagEntries(record: Record): string[] {
+ return Object.entries(record)
+ .flatMap(([key, value]) => {
+ if (typeof value !== 'string') {
+ return []
+ }
+ const trimmed = value.trim()
+ if (trimmed.length === 0 || !key.toLowerCase().includes('flags')) {
+ return []
+ }
+ return [`${key}=${trimmed}`]
+ })
+ .sort((left, right) => left.localeCompare(right))
+}
+function isRecord(value: unknown): value is Record {
+ return typeof value === 'object' && value !== null
+}
+async function findSketchRuntimeInfo(
+ config: CapturerConfig,
+ arduinoContext: ArduinoContextWithBoardLab
+): Promise<
+ Pick<
+ CapturerReadiness,
+ | 'hasCompileSummary'
+ | 'elfPath'
+ | 'buildPath'
+ | 'buildOptions'
+ | 'boardName'
+ | 'selectedBoardName'
+ | 'selectedBoardFqbn'
+ | 'selectedPort'
+ >
+> {
+ const sketchName = path.basename(config.sketchPath)
+ const matchingSketch = arduinoContext.openedSketches.find(
+ (sketch: SketchFolder) => pathEquals(sketch.sketchPath, config.sketchPath)
+ )
+ const compileSummaryBuildPath =
+ typeof matchingSketch?.compileSummary?.buildPath === 'string'
+ ? matchingSketch.compileSummary.buildPath
+ : undefined
+ const compileSummaryElfPath = compileSummaryBuildPath
+ ? await findElfPath(sketchName, compileSummaryBuildPath)
+ : undefined
+ const fallbackBuild = !compileSummaryBuildPath
+ ? await resolveBuildPathFromSketchPath(config.sketchPath, sketchName, {
+ sketchRealPath: await resolveRealPath(config.sketchPath),
+ })
+ : undefined
+ const buildPath = compileSummaryBuildPath ?? fallbackBuild?.buildPath
+ const elfPath = compileSummaryElfPath ?? fallbackBuild?.elfPath
+ const buildOptions = buildPath
+ ? await loadCompileBuildOptions(buildPath)
+ : undefined
+ const hasCompileSummary =
+ typeof compileSummaryBuildPath === 'string' ||
+ typeof fallbackBuild?.buildPath === 'string'
+ return {
+ hasCompileSummary,
+ elfPath,
+ buildPath,
+ buildOptions,
+ boardName: resolveSketchBoardName(matchingSketch, config.fqbn),
+ selectedBoardName:
+ typeof matchingSketch?.board?.name === 'string'
+ ? matchingSketch.board.name
+ : undefined,
+ selectedBoardFqbn:
+ typeof matchingSketch?.board?.fqbn === 'string'
+ ? sanitizeFqbn(matchingSketch.board.fqbn)
+ : undefined,
+ selectedPort: isPortIdentifierLike(matchingSketch?.port)
+ ? {
+ protocol: matchingSketch.port.protocol,
+ address: matchingSketch.port.address,
+ }
+ : undefined,
+ }
+}
+async function resolveRealPath(
+ targetPath: string
+): Promise {
+ try {
+ return await fs.realpath(targetPath)
+ } catch {
+ return undefined
+ }
+}
+function resolveSketchBoardName(
+ sketch: Pick | undefined,
+ engineFqbn: string
+): string | undefined {
+ const name = sketch?.board?.name
+ const sketchFqbn = sketch?.board?.fqbn
+ if (typeof name !== 'string' || typeof sketchFqbn !== 'string') {
+ return undefined
+ }
+ const sanitizedSketchFqbn = sanitizeFqbn(sketchFqbn)
+ const sanitizedEngineFqbn = sanitizeFqbn(engineFqbn)
+ if (!sanitizedSketchFqbn || !sanitizedEngineFqbn) {
+ return undefined
+ }
+ if (sanitizedSketchFqbn !== sanitizedEngineFqbn) {
+ return undefined
+ }
+ return name
+}
+function toEventSummary(
+ event: CapturerEvent,
+ runtime?: CapturerRuntime
+): CapturerEventSummary {
+ const captureSession = runtime?.elfIdentity
+ return {
+ eventId: event.id,
+ signature: event.signature,
+ reason:
+ event.lightweight.reasonLine ??
+ event.lines.find((line) => line.trim().length > 0) ??
+ event.kind,
+ kind: event.kind,
+ createdAt: event.firstSeenAt,
+ captureSessionId: captureSession?.sessionId,
+ captureSessionLabel: captureSession
+ ? `${captureSession.sha256Short} @ ${formatLocalTimestamp(captureSession.mtimeMs)}`
+ : undefined,
+ count: event.count,
+ firstSeenAt: event.firstSeenAt,
+ lastSeenAt: event.lastSeenAt,
+ rawText: event.rawText,
+ programCounter: event.lightweight.programCounter,
+ faultCode: event.lightweight.faultCode,
+ decodeState: 'detected',
+ }
+}
+function toDecodedEventMeta(
+ configId: string,
+ summary: CapturerEventSummary
+): CapturerDecodedEventPayload['meta'] {
+ return {
+ key: `${configId}:${summary.signature}`,
+ configId,
+ signature: summary.signature,
+ eventId: summary.eventId,
+ label: eventDisplayLabel(summary),
+ reason: summary.reason,
+ createdAt: summary.createdAt,
+ captureSessionId: summary.captureSessionId,
+ captureSessionLabel: summary.captureSessionLabel,
+ }
+}
+
+function shouldIgnoreCapturerEvent(event: CapturerEvent): boolean {
+ if (hasDecodeAnchors(event)) {
+ return false
+ }
+ const nonEmptyLines = collectNonEmptyEventLines(event)
+ return nonEmptyLines.length <= 1
+}
+
+function hasDecodeAnchors(event: CapturerEvent): boolean {
+ const programCounter = event?.lightweight?.programCounter
+ if (typeof programCounter === 'number' && Number.isFinite(programCounter)) {
+ return true
+ }
+ const backtraceAddrs = event?.lightweight?.backtraceAddrs
+ if (Array.isArray(backtraceAddrs) && backtraceAddrs.length > 0) {
+ return true
+ }
+ return Array.isArray(event.fastFrames) && event.fastFrames.length > 0
+}
+
+function collectNonEmptyEventLines(event: CapturerEvent): string[] {
+ if (!Array.isArray(event?.lines)) {
+ return []
+ }
+ return event.lines.filter(
+ (line) => typeof line === 'string' && line.trim().length > 0
+ )
+}
+
+function upsertEventSummary(
+ cache: Map,
+ event: CapturerEvent,
+ runtime?: CapturerRuntime
+): CapturerEventSummary {
+ const next = toEventSummary(event, runtime)
+ const previous = cache.get(event.signature)
+ if (!previous) {
+ cache.set(event.signature, next)
+ return next
+ }
+ previous.eventId = next.eventId
+ previous.reason = next.reason
+ previous.kind = next.kind
+ previous.captureSessionId = next.captureSessionId
+ previous.captureSessionLabel = next.captureSessionLabel
+ previous.count = event.count
+ previous.firstSeenAt = event.firstSeenAt
+ previous.lastSeenAt = event.lastSeenAt
+ previous.rawText = event.rawText
+ previous.programCounter = next.programCounter
+ previous.faultCode = next.faultCode
+ cache.set(event.signature, previous)
+ return previous
+}
+function loadPersistedConfigs(values: unknown[]): CapturerConfig[] {
+ return values
+ .map((raw) =>
+ validateCapturerConfig(typeof raw === 'object' && raw !== null ? raw : {})
+ )
+ .filter(
+ (
+ result
+ ): result is Extract =>
+ result.ok
+ )
+ .map(({ value }) => ({
+ id: buildCapturerId(value),
+ ...value,
+ }))
+}
+function splitReplaySegments(rawText: string): string[] {
+ const normalized = rawText.replace(/\r\n/g, '\n')
+ if (splitMarker.test(normalized)) {
+ return normalized
+ .split(/^\s*---\s*SNAP\s*---\s*$/gim)
+ .map((segment) => segment.trim())
+ .filter((segment) => segment.length > 0)
+ }
+ return [normalized]
+}
+function isReadinessEqual(
+ left: CapturerReadiness,
+ right: CapturerReadiness
+): boolean {
+ return (
+ left.sketchExists === right.sketchExists &&
+ left.hasCompileSummary === right.hasCompileSummary &&
+ left.elfPath === right.elfPath &&
+ left.buildPath === right.buildPath &&
+ isBuildOptionsEqual(left.buildOptions, right.buildOptions) &&
+ left.boardName === right.boardName &&
+ left.selectedBoardName === right.selectedBoardName &&
+ left.selectedBoardFqbn === right.selectedBoardFqbn &&
+ arePortsEqualOrUndefined(left.selectedPort, right.selectedPort) &&
+ left.configuredPortDetected === right.configuredPortDetected &&
+ left.workspaceRelativeSketchPath === right.workspaceRelativeSketchPath
+ )
+}
+function arePortsEqualOrUndefined(
+ left: CapturerReadiness['selectedPort'],
+ right: CapturerReadiness['selectedPort']
+): boolean {
+ if (!left && !right) {
+ return true
+ }
+ if (!left || !right) {
+ return false
+ }
+ return arePortsEqual(left, right)
+}
+function isBuildOptionsEqual(
+ left: CompileBuildOptionsInfo | undefined,
+ right: CompileBuildOptionsInfo | undefined
+): boolean {
+ if (!left && !right) {
+ return true
+ }
+ if (!left || !right) {
+ return false
+ }
+ return (
+ left.buildPath === right.buildPath &&
+ left.optionsPath === right.optionsPath &&
+ left.fqbn === right.fqbn &&
+ left.sketchLocation === right.sketchLocation &&
+ left.optimizationFlags === right.optimizationFlags &&
+ left.customBuildProperties === right.customBuildProperties &&
+ left.parseError === right.parseError &&
+ areStringArraysEqual(left.flags, right.flags)
+ )
+}
+function isElfIdentityEqual(
+ left: ElfIdentity | undefined,
+ right: ElfIdentity | undefined
+): boolean {
+ if (!left && !right) {
+ return true
+ }
+ if (!left || !right) {
+ return false
+ }
+ return (
+ left.path === right.path &&
+ left.size === right.size &&
+ left.mtimeMs === right.mtimeMs &&
+ left.sha256 === right.sha256 &&
+ left.sessionId === right.sessionId
+ )
+}
+function areStringArraysEqual(left: string[], right: string[]): boolean {
+ if (left.length !== right.length) {
+ return false
+ }
+ for (let index = 0; index < left.length; index += 1) {
+ if (left[index] !== right[index]) {
+ return false
+ }
+ }
+ return true
+}
+function chunkEndsWithLineBreak(chunk: Uint8Array): boolean {
+ if (chunk.length === 0) {
+ return false
+ }
+ const last = chunk[chunk.length - 1]
+ return last === 0x0a || last === 0x0d
+}
+function resetTransferMetrics(runtime: TransferRuntime): void {
+ runtime.processedBytes = 0
+ runtime.processedFirstAt = undefined
+ runtime.processedLastAt = undefined
+}
+function recordTransfer(
+ runtime: TransferRuntime,
+ byteLength: number,
+ atMs: number
+): void {
+ if (byteLength <= 0) {
+ return
+ }
+ runtime.processedBytes += byteLength
+ if (runtime.processedFirstAt === undefined) {
+ runtime.processedFirstAt = atMs
+ }
+ runtime.processedLastAt = atMs
+}
+function withConfiguredFqbn(
+ sketch: DecodeSketchLike,
+ fqbn: string
+): DecodeSketchLike {
+ if (!sketch.board || typeof sketch.board !== 'object') {
+ return sketch
+ }
+ return {
+ ...sketch,
+ board: {
+ ...sketch.board,
+ fqbn,
+ },
+ }
+}
+function toFrameUri(filePath: string, sketchPath: string): vscode.Uri {
+ if (classifyDecodedSource(filePath, sketchPath) === 'library') {
+ return toReadonlyLibraryUri(filePath)
+ }
+ return vscode.Uri.file(filePath)
+}
+function classifyDecodedSource(
+ filePath: string | undefined,
+ sketchPath: string | undefined
+): 'sketch' | 'library' | 'missing' {
+ if (!filePath || !filePath.trim()) {
+ return 'missing'
+ }
+ if (!sketchPath || !sketchPath.trim()) {
+ return 'library'
+ }
+ return isPathInside(sketchPath, filePath) ? 'sketch' : 'library'
+}
+function isPathInside(parentPath: string, candidatePath: string): boolean {
+ const normalize = (value: string): string =>
+ process.platform === 'win32'
+ ? path.resolve(value).toLowerCase()
+ : path.resolve(value)
+ const parent = normalize(parentPath)
+ const candidate = normalize(candidatePath)
+ const relative = path.relative(parent, candidate)
+ return (
+ relative.length === 0 ||
+ (!relative.startsWith('..') && !path.isAbsolute(relative))
+ )
+}
+function workspaceRelativeSketchPath(sketchPath: string): string | undefined {
+ const workspaceFolder = vscode.workspace.workspaceFolders?.find((folder) => {
+ const relative = path.relative(folder.uri.fsPath, sketchPath)
+ return !relative.startsWith('..') && !path.isAbsolute(relative)
+ })
+ if (!workspaceFolder) {
+ return undefined
+ }
+ return path.relative(workspaceFolder.uri.fsPath, sketchPath)
+}
+
+function countActiveCaptureSessions(
+ runtimes: Iterable
+): number {
+ let count = 0
+ for (const runtime of runtimes) {
+ if (runtime.monitor && runtime.monitorState !== 'disconnected') {
+ count += 1
+ }
+ }
+ return count
+}
+
+function toCapturerViewBadge(
+ activeSessionCount: number
+): vscode.ViewBadge | undefined {
+ if (activeSessionCount <= 0) {
+ return undefined
+ }
+ return {
+ value: activeSessionCount,
+ tooltip: toCapturerViewBadgeTooltip(activeSessionCount),
+ }
+}
+
+function toCapturerViewBadgeTooltip(activeSessionCount: number): string {
+ const noun = activeSessionCount === 1 ? 'session' : 'sessions'
+ return `${activeSessionCount} active crash capture ${noun}`
+}
+/** (non-API) */
+export const __tests = {
+ capturerConfigStateKey,
+ validateCapturerConfig,
+ buildCapturerId,
+ loadPersistedConfigs,
+ pathEquals,
+ isPortIdentifierLike,
+ isPortDetected,
+ extractDetectedPorts,
+ arePortsEqual,
+ pickNonEmptyString,
+ isRecord,
+ resolveSketchBoardName,
+ toEventSummary,
+ toDraftFromSketch,
+ resolveConfiguredFqbn,
+ shouldIgnoreCapturerEvent,
+ resolveElfIdentity,
+ upsertEventSummary,
+ splitReplaySegments,
+ loadCompileBuildOptions,
+ collectBuildFlagEntries,
+ isReadinessEqual,
+ isBuildOptionsEqual,
+ isElfIdentityEqual,
+ areStringArraysEqual,
+ chunkEndsWithLineBreak,
+ resetTransferMetrics,
+ recordTransfer,
+ classifyDecodedSource,
+ isPathInside,
+ workspaceRelativeSketchPath,
+ eventDisplayLabel,
+ collectDecodedStackFrames,
+}
diff --git a/src/capturer/fsProviders.ts b/src/capturer/fsProviders.ts
new file mode 100644
index 0000000..242ed64
--- /dev/null
+++ b/src/capturer/fsProviders.ts
@@ -0,0 +1,150 @@
+import { Dirent } from 'node:fs'
+import * as fs from 'node:fs/promises'
+
+import vscode from 'vscode'
+
+export const readonlyLibraryScheme = 'espExceptionDecoderReadonlyFs'
+export const crashReportScheme = 'espExceptionDecoderCrashReport'
+
+export class CrashReportContentProvider
+ implements vscode.TextDocumentContentProvider, vscode.Disposable
+{
+ private readonly onDidChangeEmitter = new vscode.EventEmitter()
+ private readonly contentByUri = new Map()
+ private readonly uriOrder: string[] = []
+ private readonly maxReports = 128
+
+ readonly onDidChange = this.onDidChangeEmitter.event
+
+ dispose(): void {
+ this.onDidChangeEmitter.dispose()
+ this.contentByUri.clear()
+ this.uriOrder.length = 0
+ }
+
+ provideTextDocumentContent(uri: vscode.Uri): string {
+ const key = uri.toString()
+ return this.contentByUri.get(key) ?? '# Report is no longer available.'
+ }
+
+ createReportUri(name: string, content: string): vscode.Uri {
+ const safeName = toSafeReportName(name)
+ const uniqueId = `${Date.now().toString(36)}-${Math.random()
+ .toString(36)
+ .slice(2, 8)}`
+ const uri = vscode.Uri.from({
+ scheme: crashReportScheme,
+ path: `/${safeName}-${uniqueId}.md`,
+ })
+ const key = uri.toString()
+ this.contentByUri.set(key, content)
+ this.uriOrder.push(key)
+ this.trimCache()
+ this.onDidChangeEmitter.fire(uri)
+ return uri
+ }
+
+ private trimCache(): void {
+ while (this.uriOrder.length > this.maxReports) {
+ const oldest = this.uriOrder.shift()
+ if (oldest) {
+ this.contentByUri.delete(oldest)
+ }
+ }
+ }
+}
+
+export function toReadonlyLibraryUri(filePath: string): vscode.Uri {
+ return vscode.Uri.from({
+ scheme: readonlyLibraryScheme,
+ path: filePath.replace(/\\/g, '/'),
+ query: `source=${encodeURIComponent(filePath)}`,
+ })
+}
+
+export function toSourcePathFromReadonlyUri(uri: vscode.Uri): string {
+ const source = new URLSearchParams(uri.query).get('source')
+ return source && source.length > 0 ? source : uri.path
+}
+
+export class ReadonlyFsProvider
+ implements vscode.FileSystemProvider, vscode.Disposable
+{
+ private readonly onDidChangeFileEmitter = new vscode.EventEmitter<
+ vscode.FileChangeEvent[]
+ >()
+
+ readonly onDidChangeFile = this.onDidChangeFileEmitter.event
+
+ dispose(): void {
+ this.onDidChangeFileEmitter.dispose()
+ }
+
+ watch(): vscode.Disposable {
+ return new vscode.Disposable(() => {
+ // noop
+ })
+ }
+
+ async stat(uri: vscode.Uri): Promise {
+ const targetPath = toSourcePathFromReadonlyUri(uri)
+ const stats = await fs.stat(targetPath)
+ return {
+ type: toFileType(stats),
+ ctime: stats.ctimeMs,
+ mtime: stats.mtimeMs,
+ size: stats.size,
+ }
+ }
+
+ async readDirectory(uri: vscode.Uri): Promise<[string, vscode.FileType][]> {
+ const targetPath = toSourcePathFromReadonlyUri(uri)
+ const entries = await fs.readdir(targetPath, { withFileTypes: true })
+ return entries.map((entry) => [entry.name, toFileType(entry)])
+ }
+
+ async readFile(uri: vscode.Uri): Promise {
+ const targetPath = toSourcePathFromReadonlyUri(uri)
+ return fs.readFile(targetPath)
+ }
+
+ createDirectory(_uri: vscode.Uri): void {
+ throw vscode.FileSystemError.NoPermissions('Read-only filesystem provider')
+ }
+
+ writeFile(_uri: vscode.Uri, _content: Uint8Array): void {
+ throw vscode.FileSystemError.NoPermissions('Read-only filesystem provider')
+ }
+
+ delete(_uri: vscode.Uri): void {
+ throw vscode.FileSystemError.NoPermissions('Read-only filesystem provider')
+ }
+
+ rename(_oldUri: vscode.Uri, _newUri: vscode.Uri): void {
+ throw vscode.FileSystemError.NoPermissions('Read-only filesystem provider')
+ }
+}
+
+function toSafeReportName(value: string): string {
+ const sanitized = value
+ .trim()
+ .toLowerCase()
+ .replace(/[^a-z0-9_-]+/g, '-')
+ .replace(/^-+|-+$/g, '')
+ return sanitized.length > 0 ? sanitized.slice(0, 64) : 'report'
+}
+
+function toFileType(
+ entry: Dirent | Awaited>
+): vscode.FileType {
+ if (entry.isDirectory()) {
+ return vscode.FileType.Directory
+ }
+ if (entry.isFile()) {
+ return vscode.FileType.File
+ }
+ if (entry.isSymbolicLink()) {
+ return vscode.FileType.SymbolicLink
+ }
+ return vscode.FileType.Unknown
+}
diff --git a/src/capturer/markdown.ts b/src/capturer/markdown.ts
new file mode 100644
index 0000000..66477c4
--- /dev/null
+++ b/src/capturer/markdown.ts
@@ -0,0 +1,710 @@
+import path from 'node:path'
+
+import markdownEscape from 'markdown-escape'
+import {
+ type Capturer,
+ type DecodeResult,
+ isGDBLine,
+ isParsedGDBLine,
+ stringifyDecodeResult,
+} from 'trbr'
+import vscode from 'vscode'
+
+import {
+ statusCapturing,
+ statusDisconnected,
+ statusError,
+ statusInvalidFqbn,
+ statusNoElf,
+ statusNoSketch,
+ statusNotCompiled,
+ statusNotEspBoard,
+ statusReady,
+ statusSuspended,
+ statusWarning,
+} from './constants'
+import type { CapturerEventSummary, CapturerRuntime } from './model'
+import {
+ collectRuntimeProblems,
+ getRuntimeStatus,
+ hasRuntimeQuickFix,
+} from './runtimeModel'
+
+interface EventReportMarkdownOptions {
+ headingLevel?: number
+ headingText?: string
+ includeRuntimeSection?: boolean
+ includePayloadSections?: boolean
+ compact?: boolean
+}
+
+interface DecodedLocationSummary {
+ fileAndLine: string
+ method: string
+}
+
+export type DecodedStackFrame = DecodeResult['stacktraceLines'][number]
+
+export function toCapturerStateDumpMarkdown(
+ runtime: CapturerRuntime,
+ rawState: ReturnType,
+ distinctEvents: CapturerEventSummary[]
+): string {
+ const compileOptions = runtime.readiness.buildOptions
+ const lines: string[] = []
+ lines.push('# ESP Crash Capturer State Dump')
+ lines.push('')
+ lines.push('## Runtime')
+ lines.push(`- Generated At: ${toInlineCode(new Date().toISOString())}`)
+ lines.push(
+ `- Runtime: ${toInlineCode(`${runtime.config.port.protocol}://${runtime.config.port.address}`)} | ${toInlineCode(runtime.config.fqbn)}`
+ )
+ lines.push(`- Sketch: ${toInlineCode(runtime.config.sketchPath)}`)
+ lines.push(
+ `- Distinct Events: ${toInlineCode(String(distinctEvents.length))}`
+ )
+ lines.push(`- Raw Line Count: ${toInlineCode(String(rawState.lines.length))}`)
+ lines.push(
+ `- Raw Byte Count: ${toInlineCode(formatBytes(rawState.byteLength))}`
+ )
+ lines.push(
+ `- Processed Data: ${toInlineCode(formatBytes(runtime.processedBytes))}`
+ )
+ lines.push(
+ `- Bandwidth: ${toInlineCode(
+ formatBandwidth(transferBandwidthBytesPerSecond(runtime))
+ )}`
+ )
+ lines.push(
+ `- ELF Path: ${
+ runtime.readiness.elfPath
+ ? toInlineCode(runtime.readiness.elfPath)
+ : '_not available_'
+ }`
+ )
+ if (runtime.elfIdentity) {
+ lines.push(`- ELF SHA256: ${toInlineCode(runtime.elfIdentity.sha256Short)}`)
+ lines.push(
+ `- ELF Modified: ${toInlineCode(
+ formatLocalTimestamp(runtime.elfIdentity.mtimeMs)
+ )}`
+ )
+ lines.push(
+ `- Capture Session: ${toInlineCode(runtime.elfIdentity.sessionId)}`
+ )
+ }
+ lines.push(
+ `- Optimization Flags: ${
+ compileOptions?.optimizationFlags
+ ? toInlineCode(compileOptions.optimizationFlags)
+ : '_not available_'
+ }`
+ )
+ if (compileOptions?.fqbn) {
+ lines.push(`- Build FQBN: ${toInlineCode(compileOptions.fqbn)}`)
+ }
+ lines.push('')
+ lines.push('## Distinct Events')
+ if (distinctEvents.length === 0) {
+ lines.push('_No crash events were detected._')
+ return lines.join('\n')
+ }
+ lines.push('')
+
+ for (const [index, event] of distinctEvents.entries()) {
+ appendEventReportMarkdown(lines, runtime, event, {
+ headingLevel: 3,
+ headingText: `${index + 1}. ${markdownEscape(eventDisplayLabel(event))}`,
+ includeRuntimeSection: true,
+ })
+ }
+ return lines.join('\n')
+}
+
+export function toEventTreeItemMarkdown(
+ runtime: CapturerRuntime,
+ event: CapturerEventSummary
+): vscode.MarkdownString {
+ const md = new vscode.MarkdownString(undefined, true)
+ const lines: string[] = []
+ appendCompactEventTooltipMarkdown(lines, runtime, event)
+ md.appendMarkdown(lines.join('\n'))
+ md.appendMarkdown('\n----\n')
+ md.appendMarkdown(`_Hold ${mouseOverModifierLabel()} key to mouse over_\n`)
+ return md
+}
+
+export function toRootTreeItemMarkdown(
+ runtime: CapturerRuntime
+): vscode.MarkdownString {
+ const md = new vscode.MarkdownString(undefined, true)
+
+ const status = getRuntimeStatus(runtime)
+ const statusIcon = toStatusCodicon(status)
+ const problems = collectRuntimeProblems(runtime)
+
+ md.appendMarkdown(
+ `${statusIcon} ${toInlineCode(runtime.config.port.address)} (${markdownEscape(status)}):\n`
+ )
+ md.appendMarkdown(
+ `- Capturer Board: ${toBoardLabel(
+ runtime.readiness.boardName,
+ runtime.config.fqbn
+ )}\n`
+ )
+ md.appendMarkdown(
+ `- Sketch Board: ${toBoardLabel(
+ runtime.readiness.selectedBoardName,
+ runtime.readiness.selectedBoardFqbn
+ )}\n`
+ )
+
+ if (problems.length > 0) {
+ md.appendMarkdown('\nProblems:\n')
+ const visibleProblems = problems.slice(0, 3)
+ for (const problem of visibleProblems) {
+ const icon = problem.severity === 'error' ? '$(error)' : '$(warning)'
+ md.appendMarkdown(`- ${icon} ${markdownEscape(problem.message)}\n`)
+ }
+ if (problems.length > visibleProblems.length) {
+ md.appendMarkdown(
+ `- $(info) ${problems.length - visibleProblems.length} more issue(s)\n`
+ )
+ }
+ if (hasRuntimeQuickFix(runtime, problems)) {
+ md.appendMarkdown(
+ '\n_Use $(light-bulb) `Quick Fixes...` from the view item menu to resolve issues._\n'
+ )
+ }
+ }
+
+ md.appendMarkdown('\n----\n')
+ md.appendMarkdown(`_Hold ${mouseOverModifierLabel()} key to mouse over_\n`)
+
+ return md
+}
+
+export function toEventReportDocumentMarkdown(
+ runtime: CapturerRuntime,
+ summary: CapturerEventSummary
+): string {
+ const lines: string[] = []
+ lines.push('# ESP Crash Event Report')
+ lines.push('')
+ appendEventReportMarkdown(lines, runtime, summary, {
+ headingLevel: 2,
+ headingText: markdownEscape(eventDisplayLabel(summary)),
+ includeRuntimeSection: true,
+ })
+ return lines.join('\n')
+}
+
+export function countHeavyVars(decoded: DecodeResult): number {
+ let total = Array.isArray(decoded.globals) ? decoded.globals.length : 0
+ for (const line of decoded.stacktraceLines) {
+ if (!isParsedGDBLine(line)) {
+ continue
+ }
+ total += Array.isArray(line.locals) ? line.locals.length : 0
+ total += Array.isArray(line.globals) ? line.globals.length : 0
+ }
+ return total
+}
+
+export function collectDecodedStackFrames(
+ decoded?: DecodeResult
+): DecodedStackFrame[] {
+ if (!decoded) {
+ return []
+ }
+ return decoded.stacktraceLines.filter(isGDBLine)
+}
+
+export function eventDisplayLabel(summary: CapturerEventSummary): string {
+ const decoded = summary.decodedResult
+ if (!decoded) {
+ return summary.reason
+ }
+ const fault =
+ summarizeFaultMessage(decoded.faultInfo?.faultMessage) ??
+ summarizeFaultMessage(summary.reason) ??
+ summary.reason
+ const sourceFile = resolvePrimaryDecodedFile(decoded)
+ if (fault && sourceFile) {
+ return `${fault} · ${sourceFile}`
+ }
+ if (fault) {
+ return fault
+ }
+ return summary.reason
+}
+
+export function frameToLabel(frame: DecodedStackFrame | undefined): string {
+ if (!frame) {
+ return 'Unknown frame'
+ }
+ const address = frame.regAddr
+ if (isParsedGDBLine(frame)) {
+ const fileName = path.basename(frame.file)
+ return `${address}: ${formatFrameMethod(frame)} at ${fileName}:${frame.lineNumber}`
+ }
+ return `${address}: ?? () at ??:${frame.lineNumber}`
+}
+
+export function frameToDescription(
+ frame: DecodedStackFrame | undefined
+): string | undefined {
+ if (!frame) {
+ return undefined
+ }
+ if (!isParsedGDBLine(frame)) {
+ return undefined
+ }
+ return `${path.basename(frame.file)}:${frame.lineNumber}`
+}
+
+export function toParsedFrameLocation(
+ frame: DecodedStackFrame | undefined
+): { file: string; line: number } | undefined {
+ if (!frame || !isParsedGDBLine(frame)) {
+ return undefined
+ }
+ const line = Number(frame.lineNumber)
+ if (!Number.isFinite(line) || line <= 0) {
+ return undefined
+ }
+ return {
+ file: frame.file,
+ line,
+ }
+}
+
+export function formatLocalTimestamp(atMs: number): string {
+ return new Date(atMs).toLocaleString()
+}
+
+function appendEventReportMarkdown(
+ lines: string[],
+ runtime: CapturerRuntime,
+ event: CapturerEventSummary,
+ options: EventReportMarkdownOptions = {}
+): void {
+ const headingLevel = Math.min(6, Math.max(1, options.headingLevel ?? 3))
+ const headingText =
+ options.headingText ?? markdownEscape(eventDisplayLabel(event))
+ const includeRuntimeSection = options.includeRuntimeSection ?? true
+ const includePayloadSections = options.includePayloadSections ?? true
+ const compact = options.compact ?? false
+ const errorSummary = toSingleLine(event.reason) ?? event.reason.trim()
+ const compileOptions = runtime.readiness.buildOptions
+ const capturerBoardLabel = toBoardLabel(
+ runtime.readiness.boardName,
+ runtime.config.fqbn
+ )
+ const sketchBoardLabel = toBoardLabel(
+ runtime.readiness.selectedBoardName,
+ runtime.readiness.selectedBoardFqbn
+ )
+ const decodedException = resolveDecodedExceptionSummary(event)
+ const decodedLocation = resolveDecodedLocationSummary(event.decodedResult)
+
+ lines.push(`${'#'.repeat(headingLevel)} ${headingText}`)
+ lines.push('#### Crash Summary')
+ lines.push(
+ `- Error: ${toInlineCode(errorSummary.length > 0 ? errorSummary : 'Unknown error')}`
+ )
+ lines.push(`- Count: ${toInlineCode(String(event.count))}`)
+ if (decodedException) {
+ lines.push(`- Decoded Exception: ${toInlineCode(decodedException)}`)
+ }
+ if (decodedLocation) {
+ lines.push(`- Decoded Location: ${toInlineCode(decodedLocation)}`)
+ }
+ if (!compact) {
+ lines.push(
+ `- Decode Task: ${toInlineCode(formatTaskDuration(event.lastDecodeDurationMs))}`
+ )
+ lines.push(
+ `- Decode Vars (locals/globals): ${toInlineCode(formatTaskCount(event.lastDecodeHeavyVarsCount))}`
+ )
+ lines.push(
+ `- Evaluate Task (replay): ${toInlineCode(formatTaskDuration(event.lastEvaluateDurationMs))}`
+ )
+ }
+ if (event.decodeError) {
+ lines.push(`- Decode Error: ${toInlineCode(event.decodeError)}`)
+ }
+ lines.push('')
+
+ lines.push('#### Build Context')
+ lines.push(`- Capturer Board: ${capturerBoardLabel}`)
+ lines.push(`- Sketch Board: ${sketchBoardLabel}`)
+ lines.push(
+ `- Sketch: ${toInlineCode(path.basename(runtime.config.sketchPath))}`
+ )
+ lines.push(
+ `- ELF Path: ${
+ runtime.readiness.elfPath
+ ? toInlineCode(runtime.readiness.elfPath)
+ : '_not available_'
+ }`
+ )
+ if (runtime.elfIdentity) {
+ lines.push(`- ELF SHA256: ${toInlineCode(runtime.elfIdentity.sha256Short)}`)
+ lines.push(
+ `- ELF Modified: ${toInlineCode(formatEventTimestamp(runtime.elfIdentity.mtimeMs))}`
+ )
+ }
+ if (!compact) {
+ lines.push(`- Sketch Folder: ${toInlineCode(runtime.config.sketchPath)}`)
+ lines.push(
+ `- Build FQBN: ${
+ compileOptions?.fqbn
+ ? toInlineCode(compileOptions.fqbn)
+ : '_not available_'
+ }`
+ )
+ lines.push(
+ `- Optimization Flags: ${
+ compileOptions?.optimizationFlags
+ ? toInlineCode(compileOptions.optimizationFlags)
+ : '_not available_'
+ }`
+ )
+ }
+ lines.push('')
+
+ if (includePayloadSections) {
+ lines.push('#### Captured Crash')
+ if (event.rawText.trim().length > 0) {
+ lines.push(toCodeBlock(event.rawText))
+ } else {
+ lines.push('_No captured crash payload available._')
+ lines.push('')
+ }
+
+ const decodedStacktrace = toDecodedStacktraceText(event)
+ lines.push('#### Decoded Stacktrace')
+ if (decodedStacktrace) {
+ lines.push(toCodeBlock(decodedStacktrace))
+ } else {
+ lines.push('_Not decoded yet._')
+ lines.push('')
+ }
+ }
+
+ if (!compact) {
+ lines.push('#### Session Details')
+ lines.push(`- State: ${toInlineCode(event.decodeState)}`)
+ lines.push(`- Kind: ${toInlineCode(event.kind)}`)
+ lines.push(
+ `- Created At: ${toInlineCode(formatEventTimestamp(event.createdAt))}`
+ )
+ lines.push(
+ `- First Seen: ${toInlineCode(formatEventTimestamp(event.firstSeenAt))}`
+ )
+ lines.push(
+ `- Last Seen: ${toInlineCode(formatEventTimestamp(event.lastSeenAt))}`
+ )
+ if (includeRuntimeSection) {
+ lines.push(`- Runtime ID: ${toInlineCode(runtime.config.id)}`)
+ lines.push(
+ `- Port: ${toInlineCode(`${runtime.config.port.protocol}://${runtime.config.port.address}`)}`
+ )
+ lines.push(`- Status: ${toInlineCode(getRuntimeStatus(runtime))}`)
+ }
+ if (event.captureSessionLabel) {
+ lines.push(
+ `- Capture Session: ${toInlineCode(event.captureSessionLabel)}`
+ )
+ }
+ if (event.captureSessionId) {
+ lines.push(
+ `- Capture Session ID: ${toInlineCode(event.captureSessionId)}`
+ )
+ }
+ if (typeof event.programCounter === 'number') {
+ lines.push(
+ `- Program Counter: ${toInlineCode(`0x${event.programCounter.toString(16)}`)}`
+ )
+ }
+ if (typeof event.faultCode === 'number') {
+ lines.push(`- Fault Code: ${toInlineCode(String(event.faultCode))}`)
+ }
+ lines.push(`- Signature: ${toInlineCode(event.signature)}`)
+ lines.push(`- Event ID: ${toInlineCode(event.eventId)}`)
+ lines.push('')
+ }
+}
+
+function appendCompactEventTooltipMarkdown(
+ lines: string[],
+ runtime: CapturerRuntime,
+ event: CapturerEventSummary
+): void {
+ const headingText = markdownEscape(eventDisplayLabel(event))
+ const crashSummary =
+ toCompactCrashSummary(event) ??
+ toSingleLine(event.reason) ??
+ 'Unknown crash'
+ const location = resolveDecodedLocationSummaryParts(event.decodedResult)
+ const sketchName = path.basename(runtime.config.sketchPath) || 'unknown'
+ const buildSummary = runtime.elfIdentity?.sha256Short
+ ? `${toInlineCode(runtime.elfIdentity.sha256Short)} · ${toInlineCode(sketchName)}`
+ : toInlineCode(sketchName)
+
+ lines.push(`### $(bug) ${headingText}`)
+ lines.push(`- Crash: ${toInlineCode(crashSummary)}`)
+ lines.push(
+ location
+ ? `- Location: ${toInlineCode(location.fileAndLine)} in ${toInlineCode(location.method)}`
+ : '- Location: unknown (decode needed)'
+ )
+ lines.push(`- Build: ${buildSummary}`)
+}
+
+function toDecodedStacktraceText(
+ event: CapturerEventSummary
+): string | undefined {
+ if (!event.decodedResult) {
+ return undefined
+ }
+ try {
+ return stringifyDecodeResult(event.decodedResult, {
+ color: 'disable',
+ lineSeparator: '\n',
+ })
+ } catch (err) {
+ return `Failed to stringify decoded stacktrace: ${
+ err instanceof Error ? err.message : String(err)
+ }`
+ }
+}
+
+function toCodeBlock(value: string): string {
+ const normalized = value.trimEnd()
+ const escaped = normalized.replace(/```/g, '``\\`')
+ return `\`\`\`text\n${escaped}\n\`\`\`\n`
+}
+
+function formatEventTimestamp(atMs: number): string {
+ return `${formatLocalTimestamp(atMs)} (${new Date(atMs).toISOString()})`
+}
+
+function formatDecodeDuration(durationMs: number): string {
+ if (durationMs < 1000) {
+ return `${Math.round(durationMs)} ms`
+ }
+ return `${(durationMs / 1000).toFixed(2)} s`
+}
+
+function formatTaskDuration(durationMs: number | undefined): string {
+ if (typeof durationMs !== 'number') {
+ return 'not run'
+ }
+ return formatDecodeDuration(durationMs)
+}
+
+function formatTaskCount(value: number | undefined): string {
+ if (typeof value !== 'number') {
+ return 'not run'
+ }
+ return String(value)
+}
+
+function resolveDecodedExceptionSummary(
+ event: CapturerEventSummary
+): string | undefined {
+ return (
+ toSingleLine(event.decodedResult?.faultInfo?.faultMessage) ??
+ toSingleLine(event.reason)
+ )
+}
+
+function toCompactCrashSummary(
+ event: CapturerEventSummary
+): string | undefined {
+ const source =
+ toSingleLine(event.decodedResult?.faultInfo?.faultMessage) ??
+ toSingleLine(event.reason)
+ if (!source) {
+ return undefined
+ }
+ const firstSentenceEnd = source.indexOf('.')
+ const firstSentence =
+ firstSentenceEnd > 0 ? source.slice(0, firstSentenceEnd + 1) : source
+ return clampSingleLine(firstSentence.trim(), 96)
+}
+
+function resolveDecodedLocationSummary(
+ decoded?: DecodeResult
+): string | undefined {
+ const location = resolveDecodedLocationSummaryParts(decoded)
+ return location ? `${location.fileAndLine} in ${location.method}` : undefined
+}
+
+function resolveDecodedLocationSummaryParts(
+ decoded?: DecodeResult
+): DecodedLocationSummary | undefined {
+ const firstParsed = collectDecodedStackFrames(decoded).find(isParsedGDBLine)
+ if (firstParsed) {
+ return {
+ fileAndLine: `${path.basename(firstParsed.file)}:${firstParsed.lineNumber}`,
+ method: formatFrameMethod(firstParsed),
+ }
+ }
+ const pcLocation = decoded?.faultInfo?.programCounter?.location
+ if (pcLocation && isParsedGDBLine(pcLocation)) {
+ return {
+ fileAndLine: `${path.basename(pcLocation.file)}:${pcLocation.lineNumber}`,
+ method: formatFrameMethod(pcLocation),
+ }
+ }
+ return undefined
+}
+
+function toSingleLine(value: string | undefined): string | undefined {
+ if (typeof value !== 'string') {
+ return undefined
+ }
+ const normalized = value.replace(/\s+/g, ' ').trim()
+ return normalized.length > 0 ? normalized : undefined
+}
+
+function clampSingleLine(value: string, maxLength: number): string {
+ if (value.length <= maxLength) {
+ return value
+ }
+ return `${value.slice(0, Math.max(1, maxLength - 1)).trimEnd()}…`
+}
+
+function toStatusCodicon(status: string): string {
+ switch (status) {
+ case statusCapturing:
+ return '$(pulse)'
+ case statusSuspended:
+ return '$(sync~spin)'
+ case statusReady:
+ return '$(pass)'
+ case statusDisconnected:
+ return '$(circle-slash)'
+ case statusWarning:
+ return '$(warning)'
+ case statusNoSketch:
+ case statusInvalidFqbn:
+ case statusNotEspBoard:
+ case statusNotCompiled:
+ case statusNoElf:
+ case statusError:
+ return '$(error)'
+ default:
+ return '$(info)'
+ }
+}
+
+function toInlineCode(value: unknown): string {
+ const normalized = normalizeInlineCodeValue(value)
+ const escaped = normalized.replace(/\\/g, '\\\\').replace(/`/g, '\\`')
+ return `\`${escaped}\``
+}
+
+function toBoardLabel(
+ name: string | undefined,
+ fqbn: string | undefined
+): string {
+ if (!fqbn) {
+ return '_not available_'
+ }
+ const fqbnInline = toInlineCode(fqbn)
+ return name ? `${markdownEscape(name)} (${fqbnInline})` : fqbnInline
+}
+
+function normalizeInlineCodeValue(value: unknown): string {
+ if (typeof value === 'string') {
+ return value
+ }
+ if (value === undefined || value === null) {
+ return ''
+ }
+ if (typeof value === 'object') {
+ try {
+ return JSON.stringify(value)
+ } catch {
+ return String(value)
+ }
+ }
+ return String(value)
+}
+
+function mouseOverModifierLabel(): string {
+ return process.platform === 'darwin' ? 'Option' : 'Alt'
+}
+
+function resolvePrimaryDecodedFile(decoded?: DecodeResult): string | undefined {
+ const firstParsed = collectDecodedStackFrames(decoded).find(isParsedGDBLine)
+ if (!firstParsed) {
+ const pcLocation = decoded?.faultInfo?.programCounter?.location
+ return pcLocation && isParsedGDBLine(pcLocation)
+ ? path.basename(pcLocation.file)
+ : undefined
+ }
+ return path.basename(firstParsed.file)
+}
+
+function formatFrameMethod(frame: DecodedStackFrame): string {
+ if (!isParsedGDBLine(frame)) {
+ return '?? ()'
+ }
+ const args = Array.isArray(frame.args)
+ ? frame.args.map((arg) => arg.name).filter((name) => name.length > 0)
+ : []
+ return `${frame.method} (${args.join(', ')})`
+}
+
+function summarizeFaultMessage(
+ faultMessage: string | undefined
+): string | undefined {
+ if (typeof faultMessage !== 'string') {
+ return undefined
+ }
+ const trimmed = faultMessage.trim()
+ if (!trimmed) {
+ return undefined
+ }
+ const colonIndex = trimmed.indexOf(':')
+ if (colonIndex < 0) {
+ return trimmed
+ }
+ return trimmed.slice(0, colonIndex).trim()
+}
+
+function transferBandwidthBytesPerSecond(
+ runtime: CapturerRuntime,
+ nowMs: number = Date.now()
+): number {
+ if (
+ runtime.processedBytes <= 0 ||
+ runtime.processedFirstAt === undefined ||
+ runtime.processedLastAt === undefined
+ ) {
+ return 0
+ }
+ const referenceNow =
+ runtime.monitorState === 'running' ? nowMs : runtime.processedLastAt
+ const elapsedMs = Math.max(1, referenceNow - runtime.processedFirstAt)
+ return (runtime.processedBytes * 1000) / elapsedMs
+}
+
+function formatBytes(value: number): string {
+ if (value < 1024) {
+ return `${value} B`
+ }
+ if (value < 1024 * 1024) {
+ return `${(value / 1024).toFixed(1)} KiB`
+ }
+ return `${(value / (1024 * 1024)).toFixed(1)} MiB`
+}
+
+function formatBandwidth(value: number): string {
+ return `${formatBytes(Math.max(0, Math.floor(value)))}/s`
+}
diff --git a/src/capturer/model.ts b/src/capturer/model.ts
new file mode 100644
index 0000000..729b9d0
--- /dev/null
+++ b/src/capturer/model.ts
@@ -0,0 +1,169 @@
+import type { Capturer, DecodeResult } from 'trbr'
+import type vscode from 'vscode'
+import type { Port } from 'vscode-arduino-api'
+
+import type { MonitorClient, MonitorRuntimeState } from '../boardLabExt'
+import type { DecodeParams } from '../decodeParams'
+
+export interface CapturerConfigDraft {
+ port: Port
+ fqbn: string
+ sketchPath: string
+}
+
+export interface CapturerConfig extends CapturerConfigDraft {
+ id: string
+}
+
+export interface CompileBuildOptionsInfo {
+ buildPath: string
+ optionsPath: string
+ fqbn?: string
+ sketchLocation?: string
+ optimizationFlags?: string
+ customBuildProperties?: string
+ flags: string[]
+ parseError?: string
+}
+
+export interface CapturerReadiness {
+ sketchExists: boolean
+ hasCompileSummary: boolean
+ elfPath?: string
+ buildPath?: string
+ buildOptions?: CompileBuildOptionsInfo
+ boardName?: string
+ workspaceRelativeSketchPath?: string
+ selectedBoardName?: string
+ selectedBoardFqbn?: string
+ selectedPort?: Port
+ configuredPortDetected?: boolean
+}
+
+export interface ElfIdentity {
+ path: string
+ size: number
+ mtimeMs: number
+ sha256: string
+ sha256Short: string
+ sessionId: string
+}
+
+export interface CapturerEventSummary {
+ eventId: string
+ signature: string
+ reason: string
+ kind: string
+ createdAt: number
+ captureSessionId?: string
+ captureSessionLabel?: string
+ count: number
+ firstSeenAt: number
+ lastSeenAt: number
+ rawText: string
+ programCounter?: number
+ faultCode?: number
+ decodeState: 'detected' | 'decoding' | 'decoded' | 'evaluating' | 'evaluated'
+ decodedResult?: DecodeResult
+ decodeError?: string
+ decodeTask?: Promise
+ lastDecodeDurationMs?: number
+ lastEvaluateDurationMs?: number
+ lastDecodeHeavyVarsCount?: number
+}
+
+export interface CapturerRuntime {
+ readonly config: CapturerConfig
+ readonly capturer: Capturer
+ readonly eventsBySignature: Map
+ readonly eventsById: Map
+ readonly unsubscribeDetected: () => void
+ readonly unsubscribeUpdated: () => void
+ readonly monitorSubscriptions: vscode.Disposable[]
+ readonly sourceAvailabilityByPath: Map<
+ string,
+ 'checking' | 'present' | 'missing'
+ >
+ monitor?: MonitorClient
+ monitorState?: MonitorRuntimeState
+ idleFlushTimer?: NodeJS.Timeout
+ lastChunkEndedWithLineBreak: boolean
+ processedBytes: number
+ processedFirstAt?: number
+ processedLastAt?: number
+ decodeParams?: DecodeParams
+ elfIdentity?: ElfIdentity
+ readiness: CapturerReadiness
+ lastError?: string
+}
+
+export type MonitorClientWithClose = MonitorClient & {
+ close?: () => Promise | void
+ stop?: () => Promise | void
+}
+
+export interface CapturerRootNode {
+ readonly type: 'root'
+ readonly configId: string
+}
+
+export interface CapturerEventNode {
+ readonly type: 'event'
+ readonly configId: string
+ readonly signature: string
+}
+
+export interface CapturerFrameNode {
+ readonly type: 'frame'
+ readonly configId: string
+ readonly signature: string
+ readonly frameIndex: number
+}
+
+export type CapturerNode =
+ | CapturerRootNode
+ | CapturerEventNode
+ | CapturerFrameNode
+
+export interface CapturerConfigValidationSuccess {
+ ok: true
+ value: CapturerConfigDraft
+}
+
+export interface CapturerConfigValidationError {
+ ok: false
+ message: string
+}
+
+export type CapturerConfigValidationResult =
+ | CapturerConfigValidationSuccess
+ | CapturerConfigValidationError
+
+export interface CapturerDecodedEventMeta {
+ key: string
+ configId: string
+ signature: string
+ eventId: string
+ label: string
+ reason: string
+ createdAt: number
+ captureSessionId?: string
+ captureSessionLabel?: string
+}
+
+export interface CapturerDecodedEventPayload {
+ meta: CapturerDecodedEventMeta
+ params: DecodeParams
+ result: DecodeResult
+}
+
+export interface CapturerRemovedEventMeta {
+ key: string
+ configId: string
+ signature: string
+ eventId: string
+}
+
+export interface CapturerRemovedCapturerMeta {
+ configId: string
+}
diff --git a/src/capturer/runtimeModel.ts b/src/capturer/runtimeModel.ts
new file mode 100644
index 0000000..4c7be3b
--- /dev/null
+++ b/src/capturer/runtimeModel.ts
@@ -0,0 +1,386 @@
+import path from 'node:path'
+
+import { FQBN } from 'fqbn'
+
+const statusNoElf = 'No ELF'
+const statusReady = 'Ready'
+const statusCapturing = 'Capturing crashes'
+const statusSuspended = 'Suspended'
+const statusNotCompiled = 'Not compiled'
+const statusDisconnected = 'Disconnected'
+const statusInvalidFqbn = 'Invalid FQBN'
+const statusNotEspBoard = 'Not ESP board'
+const statusError = 'Error'
+const statusWarning = 'Warning'
+const statusNoSketch = 'Missing Sketch'
+const supportedCapturerArchitectures = new Set(['esp32', 'esp8266'])
+
+export type RuntimeProblemSeverity = 'error' | 'warning'
+
+export interface RuntimeProblem {
+ severity: RuntimeProblemSeverity
+ code: string
+ message: string
+}
+
+export interface RuntimePortLike {
+ protocol: string
+ address: string
+}
+
+export interface RuntimeBuildOptionsLike {
+ fqbn?: string
+}
+
+export interface RuntimeReadinessLike {
+ sketchExists: boolean
+ hasCompileSummary: boolean
+ elfPath?: string
+ buildOptions?: RuntimeBuildOptionsLike
+ boardName?: string
+ selectedBoardName?: string
+ selectedBoardFqbn?: string
+ configuredPortDetected?: boolean
+}
+
+export interface RuntimeConfigLike {
+ id: string
+ fqbn: string
+ sketchPath: string
+ port: RuntimePortLike
+}
+
+export interface RuntimeLike {
+ config: RuntimeConfigLike
+ readiness: RuntimeReadinessLike
+ monitor?: unknown
+ monitorState?: 'running' | 'connected' | 'suspended' | 'disconnected'
+ lastError?: string
+ eventsBySignature: Map
+}
+
+export interface EventSummaryLike {
+ decodeState: 'detected' | 'decoding' | 'decoded' | 'evaluating' | 'evaluated'
+}
+
+export function getRuntimeStatus(runtime: RuntimeLike): string {
+ const problems = collectRuntimeProblems(runtime)
+ const firstError = problems.find((problem) => problem.severity === 'error')
+ if (firstError) {
+ switch (firstError.code) {
+ case 'missing-sketch':
+ return statusNoSketch
+ case 'invalid-fqbn':
+ return statusInvalidFqbn
+ case 'unsupported-board':
+ return statusNotEspBoard
+ case 'not-connected':
+ return statusDisconnected
+ case 'not-compiled':
+ return statusNotCompiled
+ case 'no-elf':
+ return statusNoElf
+ default:
+ return statusError
+ }
+ }
+ if (problems.some((problem) => problem.severity === 'warning')) {
+ return statusWarning
+ }
+ if (runtime.monitorState === 'running') {
+ return statusCapturing
+ }
+ if (runtime.monitorState === 'suspended' && isRuntimeConnected(runtime)) {
+ return statusSuspended
+ }
+ if (isReadyToRecord(runtime, problems)) {
+ return statusReady
+ }
+ return statusError
+}
+
+export function resolveRootDescription(runtime: RuntimeLike): string {
+ if (runtime.monitorState === 'running') {
+ return 'Capturing crashes'
+ }
+ const problems = collectRuntimeProblems(runtime)
+ if (!isReadyToRecord(runtime, problems) && problems.length > 0) {
+ if (problems.some((problem) => problem.code === 'invalid-fqbn')) {
+ return statusInvalidFqbn
+ }
+ if (problems.some((problem) => problem.code === 'unsupported-board')) {
+ return statusNotEspBoard
+ }
+ if (problems.some((problem) => problem.code === 'not-connected')) {
+ return 'Not connected'
+ }
+ if (problems.some((problem) => problem.code === 'not-compiled')) {
+ return 'Not compiled'
+ }
+ if (
+ problems.some(
+ (problem) => problem.code === 'fqbn-mismatch-selected-engine'
+ )
+ ) {
+ return 'Sketch FQBN mismatch'
+ }
+ if (
+ problems.some(
+ (problem) => problem.code === 'fqbn-mismatch-selected-build'
+ )
+ ) {
+ return 'Build FQBN mismatch'
+ }
+ return problems[0].message
+ }
+ if (runtime.monitorState === 'suspended' && isRuntimeConnected(runtime)) {
+ return statusSuspended
+ }
+ if (isReadyToRecord(runtime, problems)) {
+ return statusReady
+ }
+ return getRuntimeStatus(runtime)
+}
+
+export function toRootLabel(runtime: RuntimeLike): string {
+ const sketchName = path.basename(runtime.config.sketchPath)
+ const boardLabel =
+ runtime.readiness.boardName ??
+ // TODO: add new extension API to lookup the board via FQBN
+ resolveCapturerBoardId(runtime.config.fqbn)
+ if (!boardLabel) {
+ return `${runtime.config.port.address} · ${sketchName}`
+ }
+ return `${runtime.config.port.address} · ${sketchName} · ${boardLabel}`
+}
+
+export function rootContextValue(runtime: RuntimeLike): string {
+ const hasEvents = runtime.eventsBySignature.size > 0
+ const problems = collectRuntimeProblems(runtime)
+ const quickFixKind = resolveRuntimeQuickFixKind(runtime, problems)
+ if (runtime.monitor && runtime.monitorState !== 'disconnected') {
+ if (quickFixKind === 'sync-sketch-fqbn') {
+ return hasEvents
+ ? 'espCapturerRootCapturingFixableSyncHasEvents'
+ : 'espCapturerRootCapturingFixableSync'
+ }
+ if (quickFixKind === 'compile') {
+ return hasEvents
+ ? 'espCapturerRootCapturingFixableCompileHasEvents'
+ : 'espCapturerRootCapturingFixableCompile'
+ }
+ return hasEvents
+ ? 'espCapturerRootCapturingHasEvents'
+ : 'espCapturerRootCapturing'
+ }
+ if (isReadyToRecord(runtime, problems)) {
+ return hasEvents ? 'espCapturerRootReadyHasEvents' : 'espCapturerRootReady'
+ }
+ if (quickFixKind === 'sync-sketch-fqbn') {
+ return hasEvents
+ ? 'espCapturerRootStoppedFixableSyncHasEvents'
+ : 'espCapturerRootStoppedFixableSync'
+ }
+ if (quickFixKind === 'compile') {
+ return hasEvents
+ ? 'espCapturerRootStoppedFixableCompileHasEvents'
+ : 'espCapturerRootStoppedFixableCompile'
+ }
+ return hasEvents
+ ? 'espCapturerRootStoppedHasEvents'
+ : 'espCapturerRootStopped'
+}
+
+export function hasRuntimeQuickFix(
+ runtime: RuntimeLike,
+ problems: RuntimeProblem[] = collectRuntimeProblems(runtime)
+): boolean {
+ return Boolean(resolveRuntimeQuickFixKind(runtime, problems))
+}
+
+function resolveRuntimeQuickFixKind(
+ runtime: RuntimeLike,
+ problems: RuntimeProblem[] = collectRuntimeProblems(runtime)
+): 'sync-sketch-fqbn' | 'compile' | undefined {
+ const hasSketchFqbnMismatch = problems.some(
+ (problem) => problem.code === 'fqbn-mismatch-selected-engine'
+ )
+ if (hasSketchFqbnMismatch) {
+ return 'sync-sketch-fqbn'
+ }
+ const hasBuildFqbnMismatch = problems.some(
+ (problem) => problem.code === 'fqbn-mismatch-selected-build'
+ )
+ return !runtime.readiness.hasCompileSummary ||
+ !runtime.readiness.elfPath ||
+ hasBuildFqbnMismatch
+ ? 'compile'
+ : undefined
+}
+
+function resolveCapturerBoardId(fqbnValue: string): string | undefined {
+ const sanitized = sanitizeFqbn(fqbnValue)
+ if (!sanitized) {
+ return undefined
+ }
+ try {
+ const fqbn = new FQBN(sanitized)
+ return typeof fqbn.boardId === 'string' && fqbn.boardId.length > 0
+ ? fqbn.boardId
+ : undefined
+ } catch {
+ return undefined
+ }
+}
+
+export function eventContextValue(
+ summary: EventSummaryLike | undefined
+): string {
+ if (!summary) {
+ return 'espCapturerEvent'
+ }
+ switch (summary.decodeState) {
+ case 'decoded':
+ return 'espCapturerEventDecoded'
+ case 'evaluated':
+ return 'espCapturerEventEvaluated'
+ case 'decoding':
+ return 'espCapturerEventDecoding'
+ case 'evaluating':
+ return 'espCapturerEventEvaluating'
+ case 'detected':
+ default:
+ return 'espCapturerEventDetected'
+ }
+}
+
+export function collectRuntimeProblems(runtime: RuntimeLike): RuntimeProblem[] {
+ const problems: RuntimeProblem[] = []
+ const sanitizedConfiguredFqbn = sanitizeFqbn(runtime.config.fqbn)
+ if (!sanitizedConfiguredFqbn) {
+ problems.push({
+ severity: 'error',
+ code: 'invalid-fqbn',
+ message: 'Configured FQBN is invalid',
+ })
+ } else {
+ const configuredArch = new FQBN(sanitizedConfiguredFqbn).arch
+ if (!supportedCapturerArchitectures.has(configuredArch)) {
+ problems.push({
+ severity: 'error',
+ code: 'unsupported-board',
+ message: `Unsupported board architecture '${configuredArch}'. Supported architectures: esp32, esp8266`,
+ })
+ }
+ }
+
+ if (runtime.lastError) {
+ problems.push({
+ severity: 'error',
+ code: 'runtime-error',
+ message: runtime.lastError,
+ })
+ }
+ if (!runtime.readiness.sketchExists) {
+ problems.push({
+ severity: 'error',
+ code: 'missing-sketch',
+ message: 'Sketch folder does not exist',
+ })
+ }
+ if (!isRuntimeConnected(runtime)) {
+ problems.push({
+ severity: 'error',
+ code: 'not-connected',
+ message: 'No device found. This port is not detected by Arduino CLI',
+ })
+ }
+ if (!runtime.readiness.hasCompileSummary) {
+ problems.push({
+ severity: 'error',
+ code: 'not-compiled',
+ message: 'Compile summary is not available for the sketch',
+ })
+ } else if (!runtime.readiness.elfPath) {
+ problems.push({
+ severity: 'error',
+ code: 'no-elf',
+ message: 'ELF not found',
+ })
+ }
+
+ if (isSelectedFqbnDifferentFromEngine(runtime)) {
+ problems.push({
+ severity: 'warning',
+ code: 'fqbn-mismatch-selected-engine',
+ message: 'Selected sketch FQBN differs from capturer FQBN',
+ })
+ }
+ if (isSelectedFqbnDifferentFromBuild(runtime)) {
+ problems.push({
+ severity: 'warning',
+ code: 'fqbn-mismatch-selected-build',
+ message: 'Build FQBN differs from capturer FQBN',
+ })
+ }
+
+ return problems
+}
+
+export function isRuntimeConnected(runtime: RuntimeLike): boolean {
+ if (runtime.readiness.configuredPortDetected === true) {
+ return true
+ }
+ if (runtime.readiness.configuredPortDetected === false) {
+ return false
+ }
+ return (
+ runtime.monitorState === 'running' ||
+ runtime.monitorState === 'connected' ||
+ runtime.monitorState === 'suspended'
+ )
+}
+
+export function isReadyToRecord(
+ runtime: RuntimeLike,
+ problems: RuntimeProblem[] = collectRuntimeProblems(runtime)
+): boolean {
+ return (
+ problems.length === 0 &&
+ isRuntimeConnected(runtime) &&
+ runtime.readiness.hasCompileSummary &&
+ Boolean(runtime.readiness.elfPath)
+ )
+}
+
+export function sanitizeFqbn(fqbnValue: string): string | undefined {
+ try {
+ return new FQBN(fqbnValue).sanitize().toString()
+ } catch {
+ return undefined
+ }
+}
+
+function isSelectedFqbnDifferentFromEngine(runtime: RuntimeLike): boolean {
+ const selectedFqbn = runtime.readiness.selectedBoardFqbn
+ if (!selectedFqbn) {
+ return false
+ }
+ const engineFqbn = sanitizeFqbn(runtime.config.fqbn)
+ if (!engineFqbn) {
+ return false
+ }
+ return selectedFqbn !== engineFqbn
+}
+
+function isSelectedFqbnDifferentFromBuild(runtime: RuntimeLike): boolean {
+ const engineFqbn = sanitizeFqbn(runtime.config.fqbn)
+ if (!engineFqbn) {
+ return false
+ }
+ const buildFqbn = sanitizeFqbn(runtime.readiness.buildOptions?.fqbn ?? '')
+ if (!buildFqbn) {
+ return false
+ }
+ return engineFqbn !== buildFqbn
+}
diff --git a/src/capturer/treeModel.ts b/src/capturer/treeModel.ts
new file mode 100644
index 0000000..2973c91
--- /dev/null
+++ b/src/capturer/treeModel.ts
@@ -0,0 +1,84 @@
+import type {
+ CapturerConfig,
+ CapturerEventNode,
+ CapturerFrameNode,
+ CapturerRootNode,
+ CapturerRuntime,
+} from './model'
+
+export class CapturerTreeModel {
+ private readonly rootNodesByConfigId = new Map()
+ private readonly eventNodesByKey = new Map()
+
+ getRootNodes(configs: readonly CapturerConfig[]): CapturerRootNode[] {
+ return configs.map((config) => this.getRootNode(config.id))
+ }
+
+ getRootNode(configId: string): CapturerRootNode {
+ const existing = this.rootNodesByConfigId.get(configId)
+ if (existing) {
+ return existing
+ }
+ const created: CapturerRootNode = { type: 'root', configId }
+ this.rootNodesByConfigId.set(configId, created)
+ return created
+ }
+
+ getEventNode(configId: string, signature: string): CapturerEventNode {
+ const key = this.toEventNodeKey(configId, signature)
+ const existing = this.eventNodesByKey.get(key)
+ if (existing) {
+ return existing
+ }
+ const created: CapturerEventNode = {
+ type: 'event',
+ configId,
+ signature,
+ }
+ this.eventNodesByKey.set(key, created)
+ return created
+ }
+
+ getEventNodes(
+ configId: string,
+ runtime: CapturerRuntime | undefined
+ ): CapturerEventNode[] {
+ if (!runtime) {
+ return []
+ }
+ return Array.from(runtime.eventsBySignature.values())
+ .sort((left, right) => right.createdAt - left.createdAt)
+ .map((summary) => this.getEventNode(configId, summary.signature))
+ }
+
+ getFrameNodes(
+ configId: string,
+ signature: string,
+ frameCount: number
+ ): CapturerFrameNode[] {
+ return Array.from({ length: frameCount }, (_, frameIndex) => ({
+ type: 'frame' as const,
+ configId,
+ signature,
+ frameIndex,
+ }))
+ }
+
+ removeEventNode(configId: string, signature: string): void {
+ this.eventNodesByKey.delete(this.toEventNodeKey(configId, signature))
+ }
+
+ clearConfig(configId: string): void {
+ this.rootNodesByConfigId.delete(configId)
+ const prefix = `${configId}::`
+ for (const key of this.eventNodesByKey.keys()) {
+ if (key.startsWith(prefix)) {
+ this.eventNodesByKey.delete(key)
+ }
+ }
+ }
+
+ private toEventNodeKey(configId: string, signature: string): string {
+ return `${configId}::${signature}`
+ }
+}
diff --git a/src/decodeParams.ts b/src/decodeParams.ts
index f5f8160..263e268 100644
--- a/src/decodeParams.ts
+++ b/src/decodeParams.ts
@@ -7,7 +7,7 @@ import {
} from 'trbr'
import type { BoardDetails, SketchFolder } from 'vscode-arduino-api'
-import { access } from './utils'
+import { findElfPath } from './findElfPath'
export interface DecodeParams extends TrbrDecodeParams {
fqbn: FQBN
@@ -19,9 +19,9 @@ const esp8266 = 'esp8266'
const supportedArchitectures = new Set([esp32, esp8266])
export async function createDecodeParams(
- sketchFolder: SketchFolder
+ params: Pick
): Promise {
- const { compileSummary, sketchPath, board } = sketchFolder
+ const { compileSummary, sketchPath, board } = params
if (!sketchPath) {
throw new Error('Sketch path is not set')
}
@@ -99,25 +99,8 @@ export class DecodeParamsError extends Error {
}
}
-async function findElfPath(
- sketchFolderName: string,
- buildPath: string
-): Promise {
- const [inoElfPath, cppElfPath] = await Promise.all(
- ['ino', 'cpp'].map((ext) =>
- access(path.join(buildPath, `${sketchFolderName}.${ext}.elf`))
- )
- )
- return inoElfPath ?? cppElfPath ?? undefined
-}
-
function isBoardDetails(board: SketchFolder['board']): board is BoardDetails {
return (
typeof board === 'object' && board !== null && 'buildProperties' in board
)
}
-
-/** (non-API) */
-export const __tests = {
- findElfPath,
-} as const
diff --git a/src/extension.ts b/src/extension.ts
index fa8a17f..ab19fbb 100644
--- a/src/extension.ts
+++ b/src/extension.ts
@@ -1,22 +1,67 @@
import vscode from 'vscode'
import type { ArduinoContext } from 'vscode-arduino-api'
+import { registerCapturer } from './capturer'
+import { ReplayStore } from './replay'
+import { registerReplay, replayCrashCommandId } from './replayRegistration'
import { activateDecoderTerminal } from './terminal'
-export function activate(context: vscode.ExtensionContext): void {
- findArduinoContext()
- .then((arduinoContext) => {
- if (arduinoContext) {
- activateDecoderTerminal(context, arduinoContext)
- }
- })
- .catch((err) => {
- vscode.window.showErrorMessage(
- `Failed to activate extension: ${
- err instanceof Error ? err.message : String(err)
- }`
- )
- })
+export async function activate(
+ context: vscode.ExtensionContext
+): Promise {
+ const replayStore = new ReplayStore()
+ context.subscriptions.push(replayStore)
+ registerReplay(context, replayStore)
+
+ await activateWithBoardLab(context, replayStore)
+}
+
+async function activateWithBoardLab(
+ context: vscode.ExtensionContext,
+ replayStore: ReplayStore
+): Promise {
+ try {
+ const arduinoContext = await findArduinoContext()
+ if (!arduinoContext) {
+ return
+ }
+ activateDecoderTerminal(context, arduinoContext, replayStore)
+ context.subscriptions.push(
+ registerCapturer(context, arduinoContext, {
+ onDecodedEvent: async ({ meta, params, result }) => {
+ replayStore.recordDecode(params, result, {
+ sourceKey: meta.key,
+ reason: meta.label,
+ createdAt: meta.createdAt,
+ captureSessionLabel: meta.captureSessionLabel,
+ })
+ },
+ onRemovedDecodedEvent: ({ key }) => {
+ replayStore.deleteSnapshotBySourceKey(key)
+ },
+ onRemovedCapturer: ({ configId }) => {
+ replayStore.deleteSnapshotsBySourceKeyPrefix(`${configId}:`)
+ },
+ evaluateDecodedEvent: async ({ meta, params, result }) => {
+ const snapshot = replayStore.recordDecode(params, result, {
+ sourceKey: meta.key,
+ reason: meta.label,
+ createdAt: meta.createdAt,
+ captureSessionLabel: meta.captureSessionLabel,
+ })
+ await vscode.commands.executeCommand(replayCrashCommandId, {
+ snapshotId: snapshot.id,
+ })
+ },
+ })
+ )
+ } catch (err) {
+ vscode.window.showErrorMessage(
+ `Failed to activate extension: ${
+ err instanceof Error ? err.message : String(err)
+ }`
+ )
+ }
}
async function findArduinoContext(): Promise {
diff --git a/src/findElfPath.ts b/src/findElfPath.ts
new file mode 100644
index 0000000..4f3c7bc
--- /dev/null
+++ b/src/findElfPath.ts
@@ -0,0 +1,167 @@
+import { createHash } from 'node:crypto'
+import { homedir, tmpdir } from 'node:os'
+import path from 'node:path'
+
+import { access } from './utils'
+
+export async function findElfPath(
+ sketchFolderName: string,
+ buildPath: string
+): Promise {
+ const [inoElfPath, cppElfPath] = await Promise.all(
+ ['ino', 'cpp'].map((ext) =>
+ access(path.join(buildPath, `${sketchFolderName}.${ext}.elf`))
+ )
+ )
+ return inoElfPath ?? cppElfPath ?? undefined
+}
+
+interface BuildPathProbeOptions {
+ platform?: NodeJS.Platform
+ userCacheDirPath?: string
+ tempDirPath?: string
+ sketchRealPath?: string
+}
+
+interface BuildPathProbeResult {
+ buildPath: string
+ elfPath?: string
+ hasBuildOptions: boolean
+}
+
+export function buildFolderMd5Hash(sketchPath: string): string {
+ return createHash('md5').update(sketchPath).digest('hex').toUpperCase()
+}
+
+export function resolveBuildPathCandidates(
+ sketchPath: string,
+ options: BuildPathProbeOptions = {}
+): string[] {
+ const platform = options.platform ?? process.platform
+ const platformPath = pathForPlatform(platform)
+ const basePaths = resolveBasePaths(platform, options)
+ const sketchPaths = resolveSketchPathVariants(
+ sketchPath,
+ platform,
+ options,
+ platformPath
+ )
+ const candidates: string[] = []
+ for (const basePath of basePaths) {
+ for (const sketchPathVariant of sketchPaths) {
+ const hash = buildFolderMd5Hash(sketchPathVariant)
+ candidates.push(
+ platformPath.join(basePath, 'arduino', 'sketches', hash),
+ platformPath.join(basePath, `arduino-sketch-${hash}`)
+ )
+ }
+ }
+ return dedupe(candidates)
+}
+
+// https://github.com/arduino/arduino-cli/issues/2866
+export async function resolveBuildPathFromSketchPath(
+ sketchPath: string,
+ sketchFolderName: string,
+ options: BuildPathProbeOptions = {}
+): Promise {
+ const platform = options.platform ?? process.platform
+ const platformPath = pathForPlatform(platform)
+ const candidates = resolveBuildPathCandidates(sketchPath, options)
+ for (const buildPath of candidates) {
+ const [elfPath, buildOptionsPath] = await Promise.all([
+ findElfPath(sketchFolderName, buildPath),
+ access(platformPath.join(buildPath, 'build.options.json')),
+ ])
+ if (!elfPath && !buildOptionsPath) {
+ continue
+ }
+ return {
+ buildPath,
+ elfPath,
+ hasBuildOptions: Boolean(buildOptionsPath),
+ }
+ }
+ return undefined
+}
+
+function resolveSketchPathVariants(
+ sketchPath: string,
+ platform: NodeJS.Platform,
+ options: { sketchRealPath?: string },
+ platformPath: typeof path.posix | typeof path.win32
+): string[] {
+ const variants = new Set()
+ variants.add(platformPath.normalize(sketchPath))
+ if (options.sketchRealPath) {
+ variants.add(platformPath.normalize(options.sketchRealPath))
+ }
+ if (platform === 'win32') {
+ for (const entry of [...variants]) {
+ if (isWinDrivePath(entry)) {
+ variants.add(toggleFirstCharCase(entry))
+ }
+ }
+ }
+ return [...variants]
+}
+
+function resolveBasePaths(
+ platform: NodeJS.Platform,
+ options: { userCacheDirPath?: string; tempDirPath?: string }
+): string[] {
+ const userCachePath =
+ options.userCacheDirPath ?? resolveUserCacheDir(platform)
+ const tempPath = options.tempDirPath ?? tmpdir()
+ const bases = dedupe([userCachePath, tempPath].filter(isNonEmptyString))
+ if (platform !== 'win32') {
+ return bases
+ }
+ const withCaseVariants = new Set(bases)
+ for (const base of bases) {
+ if (isWinDrivePath(base)) {
+ withCaseVariants.add(toggleFirstCharCase(base))
+ }
+ }
+ return [...withCaseVariants]
+}
+
+function resolveUserCacheDir(platform: NodeJS.Platform): string | undefined {
+ if (platform === 'win32') {
+ return process.env.LOCALAPPDATA
+ }
+ if (platform === 'darwin') {
+ return path.posix.join(homedir(), 'Library', 'Caches')
+ }
+ return process.env.XDG_CACHE_HOME ?? path.posix.join(homedir(), '.cache')
+}
+
+function isWinDrivePath(candidate: string): boolean {
+ return /^[a-zA-Z]:[\\/]/.test(candidate)
+}
+
+function toggleFirstCharCase(value: string): string {
+ if (value.length === 0) {
+ return value
+ }
+ const firstChar = value[0]
+ const toggled =
+ firstChar === firstChar.toUpperCase()
+ ? firstChar.toLowerCase()
+ : firstChar.toUpperCase()
+ return `${toggled}${value.slice(1)}`
+}
+
+function dedupe(values: string[]): string[] {
+ return [...new Set(values)]
+}
+
+function isNonEmptyString(value: string | undefined): value is string {
+ return typeof value === 'string' && value.trim().length > 0
+}
+
+function pathForPlatform(
+ platform: NodeJS.Platform
+): typeof path.posix | typeof path.win32 {
+ return platform === 'win32' ? path.win32 : path.posix
+}
diff --git a/src/replay.ts b/src/replay.ts
new file mode 100644
index 0000000..7e42b30
--- /dev/null
+++ b/src/replay.ts
@@ -0,0 +1,746 @@
+import path from 'node:path'
+
+import type {
+ LoadStackTraceParams,
+ LoadVariablesParams,
+ ReplayDataSource,
+ StackTrace,
+ StackTraceFrame,
+ VariablePayload,
+ Variables,
+} from 'error-replay-debug-adapter'
+import {
+ type DecodeResult,
+ type FrameArg,
+ type FrameVar,
+ type GDBLine,
+ type ParsedGDBLine,
+ isGDBLine,
+ isParsedGDBLine,
+} from 'trbr'
+import vscode from 'vscode'
+
+import type { DecodeParams } from './decodeParams'
+
+export interface ReplayLocation {
+ sourcePath: string
+ line: number
+ functionName?: string
+}
+
+export interface ReplaySnapshot {
+ id: string
+ sourceKey?: string
+ params: {
+ fqbn: string
+ sketchPath: string
+ targetArch: string
+ }
+ stackTrace: StackTrace
+ variables: Map
+ location?: ReplayLocation
+ title: string
+ createdAt: number
+ eventCreatedAt?: number
+ captureSessionLabel?: string
+}
+
+export interface ReplaySnapshotMeta {
+ sourceKey?: string
+ reason?: string
+ createdAt?: number
+ captureSessionLabel?: string
+}
+
+export interface ReplayLocationEntry {
+ sourcePath: string
+ line: number
+ snapshots: ReplaySnapshot[]
+}
+
+export class ReplayStore implements vscode.Disposable {
+ private snapshotId?: string
+ private snapshotSequence = 0
+ private readonly snapshotsById = new Map()
+ private readonly snapshotIdBySourceKey = new Map()
+ private readonly snapshotIdsByDocumentLine = new Map<
+ string,
+ Map>
+ >()
+
+ private readonly onDidChangeEmitter = new vscode.EventEmitter<
+ ReplaySnapshot | undefined
+ >()
+
+ readonly onDidChange = this.onDidChangeEmitter.event
+
+ dispose(): void {
+ this.onDidChangeEmitter.dispose()
+ }
+
+ get current(): ReplaySnapshot | undefined {
+ if (!this.snapshotId) {
+ return undefined
+ }
+ return this.snapshotsById.get(this.snapshotId)
+ }
+
+ recordDecode(
+ params: DecodeParams,
+ result: DecodeResult,
+ metadata: ReplaySnapshotMeta = {}
+ ): ReplaySnapshot {
+ const sourceKey = metadata.sourceKey?.trim()
+ const existingSnapshotId = sourceKey
+ ? this.snapshotIdBySourceKey.get(sourceKey)
+ : undefined
+ const previousSnapshot = existingSnapshotId
+ ? this.snapshotsById.get(existingSnapshotId)
+ : undefined
+ const snapshot = buildReplaySnapshot(params, result, {
+ id: existingSnapshotId ?? this.nextSnapshotId(),
+ sourceKey,
+ reason: metadata.reason,
+ createdAt: metadata.createdAt,
+ captureSessionLabel: metadata.captureSessionLabel,
+ })
+ if (previousSnapshot) {
+ this.deindexSnapshot(previousSnapshot)
+ }
+ this.snapshotsById.set(snapshot.id, snapshot)
+ this.indexSnapshot(snapshot)
+ if (sourceKey) {
+ this.snapshotIdBySourceKey.set(sourceKey, snapshot.id)
+ }
+ this.snapshotId = snapshot.id
+ this.onDidChangeEmitter.fire(snapshot)
+ return snapshot
+ }
+
+ clear(): void {
+ if (this.snapshotsById.size === 0) {
+ return
+ }
+ this.snapshotId = undefined
+ this.snapshotsById.clear()
+ this.snapshotIdBySourceKey.clear()
+ this.snapshotIdsByDocumentLine.clear()
+ this.onDidChangeEmitter.fire(undefined)
+ }
+
+ clearIfParamsMismatch(params: DecodeParams | Error): void {
+ const snapshot = this.current
+ if (!snapshot) {
+ return
+ }
+ const next =
+ params instanceof Error
+ ? extractReplayContextFromError(params)
+ : {
+ fqbn: params.fqbn.toString(),
+ sketchPath: params.sketchPath,
+ }
+ if (!next) {
+ return
+ }
+ if (
+ snapshot.params.fqbn !== next.fqbn ||
+ snapshot.params.sketchPath !== next.sketchPath
+ ) {
+ this.clear()
+ }
+ }
+
+ getSnapshot(snapshotId: string): ReplaySnapshot | undefined {
+ return this.snapshotsById.get(snapshotId)
+ }
+
+ setCurrentSnapshot(snapshotId: string): ReplaySnapshot | undefined {
+ const snapshot = this.snapshotsById.get(snapshotId)
+ if (!snapshot) {
+ return undefined
+ }
+ this.snapshotId = snapshotId
+ this.onDidChangeEmitter.fire(snapshot)
+ return snapshot
+ }
+
+ deleteSnapshotBySourceKey(sourceKey: string): boolean {
+ const key = sourceKey.trim()
+ if (!key) {
+ return false
+ }
+ const snapshotId = this.snapshotIdBySourceKey.get(key)
+ if (!snapshotId) {
+ return false
+ }
+ const removed = this.deleteSnapshotById(snapshotId)
+ if (removed) {
+ this.onDidChangeEmitter.fire(this.current)
+ }
+ return removed
+ }
+
+ deleteSnapshotsBySourceKeyPrefix(sourceKeyPrefix: string): number {
+ const prefix = sourceKeyPrefix.trim()
+ if (!prefix) {
+ return 0
+ }
+ const snapshotIds = new Set(
+ [...this.snapshotIdBySourceKey.entries()]
+ .filter(([sourceKey]) => sourceKey.startsWith(prefix))
+ .map(([, snapshotId]) => snapshotId)
+ )
+ if (snapshotIds.size === 0) {
+ return 0
+ }
+ let removedCount = 0
+ for (const snapshotId of snapshotIds) {
+ if (this.deleteSnapshotById(snapshotId)) {
+ removedCount += 1
+ }
+ }
+ if (removedCount > 0) {
+ this.onDidChangeEmitter.fire(this.current)
+ }
+ return removedCount
+ }
+
+ getSnapshotsAtLocation(sourcePath: string, line: number): ReplaySnapshot[] {
+ const normalizedSourcePath = normalizePathForLookup(sourcePath)
+ const byLine = this.snapshotIdsByDocumentLine.get(normalizedSourcePath)
+ if (!byLine) {
+ return []
+ }
+ const snapshotIds = byLine.get(line)
+ if (!snapshotIds) {
+ return []
+ }
+ return [...snapshotIds]
+ .map((snapshotId) => this.snapshotsById.get(snapshotId))
+ .filter((snapshot): snapshot is ReplaySnapshot => Boolean(snapshot))
+ .sort(sortSnapshotsNewestFirst)
+ }
+
+ getLocationsForDocument(sourcePath: string): ReplayLocationEntry[] {
+ const normalizedSourcePath = normalizePathForLookup(sourcePath)
+ const byLine = this.snapshotIdsByDocumentLine.get(normalizedSourcePath)
+ if (!byLine) {
+ return []
+ }
+ return [...byLine.entries()]
+ .sort(([leftLine], [rightLine]) => leftLine - rightLine)
+ .map(([line]) => ({
+ sourcePath: normalizedSourcePath,
+ line,
+ snapshots: this.getSnapshotsAtLocation(normalizedSourcePath, line),
+ }))
+ .filter((entry) => entry.snapshots.length > 0)
+ }
+
+ private nextSnapshotId(): string {
+ this.snapshotSequence += 1
+ return `esp-exception-${Date.now()}-${this.snapshotSequence}`
+ }
+
+ private indexSnapshot(snapshot: ReplaySnapshot): void {
+ if (!snapshot.location) {
+ return
+ }
+ const normalizedSourcePath = normalizePathForLookup(
+ snapshot.location.sourcePath
+ )
+ let byLine = this.snapshotIdsByDocumentLine.get(normalizedSourcePath)
+ if (!byLine) {
+ byLine = new Map>()
+ this.snapshotIdsByDocumentLine.set(normalizedSourcePath, byLine)
+ }
+ let snapshotIds = byLine.get(snapshot.location.line)
+ if (!snapshotIds) {
+ snapshotIds = new Set()
+ byLine.set(snapshot.location.line, snapshotIds)
+ }
+ snapshotIds.add(snapshot.id)
+ }
+
+ private deindexSnapshot(snapshot: ReplaySnapshot): void {
+ if (snapshot.sourceKey) {
+ const currentSnapshotId = this.snapshotIdBySourceKey.get(
+ snapshot.sourceKey
+ )
+ if (currentSnapshotId === snapshot.id) {
+ this.snapshotIdBySourceKey.delete(snapshot.sourceKey)
+ }
+ }
+ if (!snapshot.location) {
+ return
+ }
+ const normalizedSourcePath = normalizePathForLookup(
+ snapshot.location.sourcePath
+ )
+ const byLine = this.snapshotIdsByDocumentLine.get(normalizedSourcePath)
+ if (!byLine) {
+ return
+ }
+ const snapshotIds = byLine.get(snapshot.location.line)
+ if (!snapshotIds) {
+ return
+ }
+ snapshotIds.delete(snapshot.id)
+ if (snapshotIds.size === 0) {
+ byLine.delete(snapshot.location.line)
+ }
+ if (byLine.size === 0) {
+ this.snapshotIdsByDocumentLine.delete(normalizedSourcePath)
+ }
+ }
+
+ private deleteSnapshotById(snapshotId: string): boolean {
+ const snapshot = this.snapshotsById.get(snapshotId)
+ if (!snapshot) {
+ return false
+ }
+ this.deindexSnapshot(snapshot)
+ this.snapshotsById.delete(snapshotId)
+ if (this.snapshotId === snapshotId) {
+ this.snapshotId = this.selectMostRecentSnapshotId()
+ }
+ return true
+ }
+
+ private selectMostRecentSnapshotId(): string | undefined {
+ let selectedId: string | undefined
+ let selectedTime = Number.NEGATIVE_INFINITY
+ for (const snapshot of this.snapshotsById.values()) {
+ const snapshotTime = snapshot.eventCreatedAt ?? snapshot.createdAt
+ if (snapshotTime > selectedTime) {
+ selectedTime = snapshotTime
+ selectedId = snapshot.id
+ }
+ }
+ return selectedId
+ }
+}
+
+export class ReplayCodeLensProvider implements vscode.CodeLensProvider {
+ private readonly onDidChangeEmitter = new vscode.EventEmitter()
+ readonly onDidChangeCodeLenses = this.onDidChangeEmitter.event
+ private readonly storeListener: vscode.Disposable
+
+ constructor(
+ private readonly replayStore: ReplayStore,
+ private readonly commandId: string
+ ) {
+ this.storeListener = replayStore.onDidChange(() =>
+ this.onDidChangeEmitter.fire()
+ )
+ }
+
+ dispose(): void {
+ this.storeListener.dispose()
+ this.onDidChangeEmitter.dispose()
+ }
+
+ provideCodeLenses(document: vscode.TextDocument): vscode.CodeLens[] {
+ const entries = this.replayStore.getLocationsForDocument(
+ document.uri.fsPath
+ )
+ if (entries.length === 0) {
+ return []
+ }
+ return entries.map((entry) => {
+ const position = new vscode.Position(Math.max(0, entry.line - 1), 0)
+ const singleSnapshot =
+ entry.snapshots.length === 1 ? entry.snapshots[0] : undefined
+ const command: vscode.Command = {
+ title: singleSnapshot
+ ? toSingleReplayCodeLensTitle(singleSnapshot)
+ : 'Replay Crash',
+ command: this.commandId,
+ arguments: [
+ singleSnapshot
+ ? { snapshotId: singleSnapshot.id }
+ : { sourcePath: entry.sourcePath, line: entry.line },
+ ],
+ }
+ return new vscode.CodeLens(new vscode.Range(position, position), command)
+ })
+ }
+}
+
+export class ReplaySnapshotDataSource implements ReplayDataSource {
+ constructor(private readonly snapshot: ReplaySnapshot) {}
+
+ async loadStackTrace(_params: LoadStackTraceParams): Promise {
+ return this.snapshot.stackTrace
+ }
+
+ async loadVariables(
+ params: LoadVariablesParams
+ ): Promise {
+ return this.snapshot.variables.get(params.snapshotId)
+ }
+}
+
+function buildReplaySnapshot(
+ params: DecodeParams,
+ result: DecodeResult,
+ metadata: {
+ id: string
+ sourceKey?: string
+ reason?: string
+ createdAt?: number
+ captureSessionLabel?: string
+ }
+): ReplaySnapshot {
+ const createdAt = Date.now()
+ const entries = collectFrameEntries(result)
+ const variables = new Map()
+ const frames = entries.map((entry, index) => {
+ variables.set(entry.snapshotId, buildVariables(entry.line, result))
+ return buildStackTraceFrame(entry, index, params.sketchPath)
+ })
+
+ const stackTrace: StackTrace = {
+ id: `esp-exception-${createdAt}`,
+ frames,
+ eventId: `esp-exception-${createdAt}`,
+ traceId: `esp-exception-${createdAt}`,
+ timestamp: createdAt,
+ source: 'esp-exception-decoder',
+ snapshotCount: frames.length,
+ symbolicated: true,
+ architecture: params.targetArch,
+ registers: result.regs ? stringifyRegisters(result.regs) : undefined,
+ exception: buildException(result),
+ }
+
+ return {
+ id: metadata.id,
+ sourceKey: metadata.sourceKey,
+ params: {
+ fqbn: params.fqbn.toString(),
+ sketchPath: params.sketchPath,
+ targetArch: params.targetArch,
+ },
+ stackTrace,
+ variables,
+ location: resolveReplayLocation(entries, params.sketchPath),
+ title: metadata.reason?.trim() || buildReplayTitle(result),
+ createdAt,
+ eventCreatedAt: metadata.createdAt,
+ captureSessionLabel: metadata.captureSessionLabel,
+ }
+}
+
+function toSingleReplayCodeLensTitle(snapshot: ReplaySnapshot): string {
+ const titleParts = [ellipsize(extractCrashName(snapshot.title), 40)]
+ const createdAt = formatReplayTimestamp(
+ snapshot.eventCreatedAt ?? snapshot.createdAt
+ )
+ if (createdAt) {
+ titleParts.push(createdAt)
+ }
+ if (snapshot.captureSessionLabel) {
+ titleParts.push(snapshot.captureSessionLabel)
+ }
+ return `Replay Crash: ${titleParts.join(' · ')}`
+}
+
+function extractCrashName(value: string): string {
+ const trimmed = value.trim()
+ if (!trimmed) {
+ return 'Crash'
+ }
+ const separatorIndexes = [trimmed.indexOf(':'), trimmed.indexOf('·')]
+ .filter((index) => index >= 0)
+ .sort((left, right) => left - right)
+ if (separatorIndexes.length === 0) {
+ return trimmed
+ }
+ return trimmed.slice(0, separatorIndexes[0]).trim() || trimmed
+}
+
+function ellipsize(value: string, maxLength: number): string {
+ if (value.length <= maxLength) {
+ return value
+ }
+ return `${value.slice(0, Math.max(0, maxLength - 1))}…`
+}
+
+function formatReplayTimestamp(value: number): string {
+ return new Date(value).toLocaleString()
+}
+
+function normalizePathForLookup(value: string): string {
+ const normalized = path.normalize(value)
+ return process.platform === 'win32' ? normalized.toLowerCase() : normalized
+}
+
+function sortSnapshotsNewestFirst(
+ left: ReplaySnapshot,
+ right: ReplaySnapshot
+): number {
+ const leftTime = left.eventCreatedAt ?? left.createdAt
+ const rightTime = right.eventCreatedAt ?? right.createdAt
+ return rightTime - leftTime
+}
+
+type FrameLine = GDBLine | ParsedGDBLine
+
+interface FrameEntry {
+ line: FrameLine
+ snapshotId: string
+}
+
+function collectFrameEntries(result: DecodeResult): FrameEntry[] {
+ const seen = new Set()
+ const lines: FrameLine[] = []
+ const pc = result.faultInfo?.programCounter?.location
+ if (pc && isGDBLine(pc)) {
+ pushUniqueLine(lines, seen, pc)
+ }
+ for (const line of result.stacktraceLines) {
+ if (isGDBLine(line)) {
+ pushUniqueLine(lines, seen, line)
+ }
+ }
+ return lines.map((line, index) => ({
+ line,
+ snapshotId: `frame-${index}`,
+ }))
+}
+
+function pushUniqueLine(
+ lines: FrameLine[],
+ seen: Set,
+ line: FrameLine
+): void {
+ const key = `${line.regAddr}:${line.lineNumber}`
+ if (seen.has(key)) {
+ return
+ }
+ seen.add(key)
+ lines.push(line)
+}
+
+function buildStackTraceFrame(
+ entry: FrameEntry,
+ index: number,
+ sketchPath: string
+): StackTraceFrame {
+ const { line, snapshotId } = entry
+ const parsed = isParsedGDBLine(line)
+ const file = parsed ? normalizeFilePath(line.file, sketchPath) : undefined
+ const lineNumber = parsed ? parseLineNumber(line.lineNumber) : undefined
+ const lineZero =
+ lineNumber !== undefined ? Math.max(0, lineNumber - 1) : undefined
+ const tokens: StackTraceFrame['tokens'] =
+ file && lineZero !== undefined
+ ? [
+ {
+ kind: 'runtimeCoordinate',
+ value: { uri: { path: file }, location: lineZero },
+ },
+ ]
+ : []
+ const content = parsed
+ ? `${line.method} @ ${file ?? 'unknown'}:${lineNumber ?? '??'}`
+ : `${line.regAddr} ${line.lineNumber}`
+ return {
+ id: snapshotId,
+ content,
+ tokens,
+ snapshotId,
+ snapshotIndex: index + 1,
+ ...(file ? { file } : {}),
+ ...(parsed ? { function: line.method } : {}),
+ ...(lineZero !== undefined ? { line: lineZero, column: 0 } : {}),
+ }
+}
+
+function buildVariables(line: FrameLine, result: DecodeResult): Variables {
+ const locals: Record = {}
+ const args: Record = {}
+
+ if (isParsedGDBLine(line)) {
+ if (line.args) {
+ for (const arg of line.args) {
+ args[arg.name] = frameArgToPayload(arg)
+ }
+ }
+ if (line.locals) {
+ Object.assign(locals, mapFrameVars(line.locals))
+ }
+ }
+
+ const globals = isParsedGDBLine(line)
+ ? (line.globals ?? result.globals)
+ : result.globals
+ if (globals && globals.length) {
+ locals.globals = {
+ type: 'globals',
+ fields: mapFrameVars(globals),
+ }
+ }
+
+ if (result.regs) {
+ locals.registers = {
+ type: 'registers',
+ fields: mapRegisterVars(result.regs),
+ }
+ }
+
+ return { locals, arguments: args }
+}
+
+function frameArgToPayload(arg: FrameArg): VariablePayload {
+ return {
+ ...(arg.type ? { type: arg.type } : {}),
+ ...(arg.value !== undefined ? { value: arg.value } : {}),
+ }
+}
+
+function mapFrameVars(vars: FrameVar[]): Record {
+ const record: Record = {}
+ for (const variable of vars) {
+ record[variable.name] = frameVarToPayload(variable)
+ }
+ return record
+}
+
+function frameVarToPayload(variable: FrameVar): VariablePayload {
+ const childFields =
+ variable.children && variable.children.length
+ ? mapFrameVars(variable.children)
+ : undefined
+ const fields = variable.address
+ ? {
+ ...(childFields ?? {}),
+ address: {
+ type: 'address',
+ value: variable.address,
+ },
+ }
+ : childFields
+ const value =
+ variable.value !== undefined
+ ? variable.value
+ : variable.address && !variable.children?.length
+ ? variable.address
+ : undefined
+ return {
+ ...(variable.type ? { type: variable.type } : {}),
+ ...(value !== undefined ? { value } : {}),
+ ...(fields ? { fields } : {}),
+ }
+}
+
+function mapRegisterVars(
+ regs: Record
+): Record {
+ const record: Record = {}
+ for (const [name, value] of Object.entries(regs)) {
+ record[name] = { type: 'number', value: toHex(value) }
+ }
+ return record
+}
+
+function stringifyRegisters(
+ regs: Record
+): Record {
+ const record: Record = {}
+ for (const [name, value] of Object.entries(regs)) {
+ record[name] = toHex(value)
+ }
+ return record
+}
+
+function toHex(value: number): string {
+ return `0x${value.toString(16)}`
+}
+
+function buildException(result: DecodeResult): StackTrace['exception'] {
+ if (!result.faultInfo) {
+ return undefined
+ }
+ const fault = result.faultInfo
+ const message = fault.faultMessage
+ ? fault.faultMessage
+ : typeof fault.faultCode === 'number'
+ ? `Fault ${fault.faultCode}`
+ : undefined
+ return {
+ name: 'Guru Meditation',
+ message,
+ }
+}
+
+function buildReplayTitle(result: DecodeResult): string {
+ if (result.faultInfo?.faultMessage) {
+ return result.faultInfo.faultMessage
+ }
+ if (typeof result.faultInfo?.faultCode === 'number') {
+ return `Fault ${result.faultInfo.faultCode}`
+ }
+ if (result.allocInfo) {
+ return 'Memory allocation failed'
+ }
+ return 'ESP Crash'
+}
+
+function resolveReplayLocation(
+ entries: FrameEntry[],
+ sketchPath: string
+): ReplayLocation | undefined {
+ for (const entry of entries) {
+ if (!isParsedGDBLine(entry.line)) {
+ continue
+ }
+ const file = normalizeFilePath(entry.line.file, sketchPath)
+ const lineNumber = parseLineNumber(entry.line.lineNumber)
+ if (!file || lineNumber === undefined) {
+ continue
+ }
+ return {
+ sourcePath: file,
+ line: lineNumber,
+ functionName: entry.line.method,
+ }
+ }
+ return undefined
+}
+
+function parseLineNumber(value: string): number | undefined {
+ if (!value || value === '??') {
+ return undefined
+ }
+ const parsed = Number.parseInt(value, 10)
+ return Number.isNaN(parsed) ? undefined : parsed
+}
+
+function normalizeFilePath(
+ file: string,
+ sketchPath: string
+): string | undefined {
+ if (!file || file === '??') {
+ return undefined
+ }
+ if (path.isAbsolute(file)) {
+ return path.normalize(file)
+ }
+ return path.normalize(path.join(sketchPath, file))
+}
+
+function extractReplayContextFromError(
+ error: Error
+): { fqbn: string; sketchPath: string } | undefined {
+ const candidate = error as { fqbn?: unknown; sketchPath?: unknown }
+ if (
+ typeof candidate.fqbn === 'string' &&
+ typeof candidate.sketchPath === 'string'
+ ) {
+ return { fqbn: candidate.fqbn, sketchPath: candidate.sketchPath }
+ }
+ return undefined
+}
diff --git a/src/replayRegistration.ts b/src/replayRegistration.ts
new file mode 100644
index 0000000..7acc40e
--- /dev/null
+++ b/src/replayRegistration.ts
@@ -0,0 +1,162 @@
+import path from 'node:path'
+
+import { ErrorReplaySession } from 'error-replay-debug-adapter'
+import vscode from 'vscode'
+
+import {
+ ReplayCodeLensProvider,
+ type ReplaySnapshot,
+ ReplaySnapshotDataSource,
+ ReplayStore,
+} from './replay'
+
+const replayDebugType = 'esp-crash-replay'
+export const replayCrashCommandId = 'espExceptionDecoder.replayCrash'
+
+export function registerReplay(
+ context: vscode.ExtensionContext,
+ replayStore: ReplayStore
+): void {
+ let replaySessionActive = false
+
+ const codeLensProvider = new ReplayCodeLensProvider(
+ replayStore,
+ replayCrashCommandId
+ )
+
+ context.subscriptions.push(
+ vscode.languages.registerCodeLensProvider(
+ { scheme: 'file', pattern: '**/*.{ino,c,cpp,h,hpp,cc,cxx}' },
+ codeLensProvider
+ ),
+ codeLensProvider
+ )
+
+ context.subscriptions.push(
+ vscode.debug.onDidStartDebugSession((session) => {
+ if (session.type === replayDebugType) {
+ replaySessionActive = true
+ }
+ }),
+ vscode.debug.onDidTerminateDebugSession((session) => {
+ if (session.type === replayDebugType) {
+ replaySessionActive = false
+ }
+ }),
+ vscode.debug.onDidChangeActiveDebugSession((session) => {
+ if (!session || session.type !== replayDebugType) {
+ replaySessionActive = false
+ }
+ })
+ )
+
+ const factory: vscode.DebugAdapterDescriptorFactory = {
+ createDebugAdapterDescriptor(session: vscode.DebugSession) {
+ const snapshot = replayStore.current
+ if (!snapshot) {
+ throw new Error('No decoded crash data is available to replay.')
+ }
+ const dataSource = new ReplaySnapshotDataSource(snapshot)
+ return new vscode.DebugAdapterInlineImplementation(
+ new ErrorReplaySession(
+ dataSource,
+ session.workspaceFolder?.uri?.fsPath
+ ) as unknown as vscode.DebugAdapter
+ )
+ },
+ }
+
+ context.subscriptions.push(
+ vscode.debug.registerDebugAdapterDescriptorFactory(
+ replayDebugType,
+ factory
+ ),
+ vscode.commands.registerCommand(
+ replayCrashCommandId,
+ async (args?: ReplayCommandArgs) => {
+ if (replaySessionActive) {
+ vscode.window.showWarningMessage(
+ 'A crash replay session is already running.'
+ )
+ return
+ }
+ const snapshot = await resolveReplaySnapshotFromArgs(replayStore, args)
+ if (!snapshot) {
+ vscode.window.showWarningMessage('Decode a crash to start replay.')
+ return
+ }
+ replayStore.setCurrentSnapshot(snapshot.id)
+
+ const location = snapshot.location
+ const config: vscode.DebugConfiguration = {
+ type: replayDebugType,
+ name: 'ESP Crash Replay',
+ request: 'launch',
+ errorTitle: snapshot.title,
+ }
+ if (location) {
+ config.startLine = location.line
+ config.startSourcePath = location.sourcePath
+ }
+
+ const workspaceFolder =
+ vscode.workspace.workspaceFolders?.find((folder) =>
+ path
+ .normalize(snapshot.params.sketchPath)
+ .startsWith(path.normalize(folder.uri.fsPath))
+ ) ?? vscode.workspace.workspaceFolders?.[0]
+
+ await vscode.commands.executeCommand('workbench.view.debug')
+ await vscode.debug.startDebugging(workspaceFolder, config)
+ }
+ )
+ )
+}
+
+type ReplayCommandArgs = {
+ snapshotId?: string
+ sourcePath?: string
+ line?: number
+}
+
+async function resolveReplaySnapshotFromArgs(
+ replayStore: ReplayStore,
+ args?: ReplayCommandArgs
+): Promise {
+ if (typeof args?.snapshotId === 'string' && args.snapshotId.trim()) {
+ return replayStore.getSnapshot(args.snapshotId)
+ }
+ if (
+ typeof args?.sourcePath === 'string' &&
+ args.sourcePath.trim().length > 0 &&
+ typeof args?.line === 'number'
+ ) {
+ const snapshots = replayStore.getSnapshotsAtLocation(
+ args.sourcePath,
+ args.line
+ )
+ if (snapshots.length === 1) {
+ return snapshots[0]
+ }
+ if (snapshots.length > 1) {
+ const selected = await vscode.window.showQuickPick(
+ snapshots.map((snapshot) => ({
+ label: snapshot.title
+ ? `Replay Crash: ${snapshot.title}`
+ : 'Replay Crash',
+ description: new Date(
+ snapshot.eventCreatedAt ?? snapshot.createdAt
+ ).toLocaleString(),
+ detail: snapshot.captureSessionLabel,
+ snapshotId: snapshot.id,
+ })),
+ {
+ placeHolder: 'Select a crash event to replay',
+ }
+ )
+ return selected ? replayStore.getSnapshot(selected.snapshotId) : undefined
+ }
+ return undefined
+ }
+ return undefined
+}
diff --git a/src/terminal.ts b/src/terminal.ts
index 98d000c..f855ef4 100644
--- a/src/terminal.ts
+++ b/src/terminal.ts
@@ -15,6 +15,7 @@ import {
DecodeParamsError,
createDecodeParams,
} from './decodeParams'
+import type { ReplayStore } from './replay'
import { Debug } from './utils'
const terminalDebug: Debug = debug('espExceptionDecoder:terminal')
@@ -34,7 +35,8 @@ function createDebugOutput(): Debug {
export function activateDecoderTerminal(
context: vscode.ExtensionContext,
- arduinoContext: ArduinoContext
+ arduinoContext: ArduinoContext,
+ replayStore?: ReplayStore
): void {
context.subscriptions.push(
new vscode.Disposable(() => _debugOutput?.dispose()),
@@ -42,6 +44,7 @@ export function activateDecoderTerminal(
openTerminal(arduinoContext, {
show: true,
debug: createDebugOutput(),
+ replayStore,
})
)
)
@@ -49,14 +52,15 @@ export function activateDecoderTerminal(
function openTerminal(
arduinoContext: ArduinoContext,
- options: { show: boolean; debug: Debug } = {
+ options: { show: boolean; debug: Debug; replayStore?: ReplayStore } = {
show: true,
debug: terminalDebug,
}
): vscode.Terminal {
const { debug, show } = options
const terminal =
- findDecodeTerminal() ?? createDecodeTerminal(arduinoContext, debug)
+ findDecodeTerminal() ??
+ createDecodeTerminal(arduinoContext, debug, options.replayStore)
if (show) {
terminal.show()
}
@@ -72,9 +76,10 @@ function findDecodeTerminal(): vscode.Terminal | undefined {
function createDecodeTerminal(
arduinoContext: ArduinoContext,
- debug: Debug
+ debug: Debug,
+ replayStore?: ReplayStore
): vscode.Terminal {
- const pty = new DecoderTerminal(arduinoContext, debug)
+ const pty = new DecoderTerminal(arduinoContext, debug, replayStore)
const options: vscode.ExtensionTerminalOptions = {
name: decodeTerminalName,
pty,
@@ -84,10 +89,10 @@ function createDecodeTerminal(
}
const decodeTerminalTitle = 'ESP Exception Decoder'
-const decodeTerminalName = 'Exception Decoder'
+const decodeTerminalName = 'Crash Decoder'
const initializing = 'Initializing...'
const busy = 'Decoding...'
-const idle = 'Paste exception to decode...'
+const idle = 'Paste crash to decode...'
const relevantSketchChanges = new Set([
'board',
@@ -116,7 +121,8 @@ class DecoderTerminal implements vscode.Pseudoterminal {
constructor(
private readonly arduinoContext: ArduinoContext,
- private readonly debug: Debug = terminalDebug
+ private readonly debug: Debug = terminalDebug,
+ private readonly replayStore?: ReplayStore
) {
this.onDidWriteEmitter = new vscode.EventEmitter()
this.onDidCloseEmitter = new vscode.EventEmitter()
@@ -192,6 +198,9 @@ class DecoderTerminal implements vscode.Pseudoterminal {
this.abortController.abort()
decoderResult = err instanceof Error ? err : new Error(String(err))
}
+ if (!(decoderResult instanceof Error)) {
+ this.replayStore?.recordDecode(params, decoderResult)
+ }
this.updateState({ decoderResult, statusMessage: idle })
}
@@ -200,12 +209,13 @@ class DecoderTerminal implements vscode.Pseudoterminal {
try {
const resolvedSketch = this.resolveCurrentSketch(sketch)
if (!resolvedSketch) {
- throw new Error('Select a sketch folder to decode exceptions')
+ throw new Error('Select a sketch folder to decode crashes')
}
params = await createDecodeParams(resolvedSketch)
} catch (err) {
params = err instanceof Error ? err : new Error(String(err))
}
+ this.replayStore?.clearIfParamsMismatch(params)
this.updateState({ params })
}
diff --git a/src/test/fixtures/monitor.txt b/src/test/fixtures/monitor.txt
new file mode 100644
index 0000000..a20dc70
--- /dev/null
+++ b/src/test/fixtures/monitor.txt
@@ -0,0 +1,33 @@
+Guru Meditation Error: Core 1 panic'ed (StoreProhibited). Exception was unhandled.
+
+Core 1 register dump:
+PC : 0x400d15f1 PS : 0x00060b30 A0 : 0x800d1609 A1 : 0x3ffb21d0
+A2 : 0x0000002a A3 : 0x3f40018f A4 : 0x00000020 A5 : 0x0000ff00
+A6 : 0x00ff0000 A7 : 0x00000022 A8 : 0x00000000 A9 : 0x3ffb21b0
+A10 : 0x0000002c A11 : 0x3f400164 A12 : 0x00000022 A13 : 0x0000ff00
+A14 : 0x00ff0000 A15 : 0x0000002a SAR : 0x0000000c EXCCAUSE: 0x0000001d
+EXCVADDR: 0x00000000 LBEG : 0x40086161 LEND : 0x40086171 LCOUNT : 0xfffffff5
+
+Backtrace: 0x400d15ee:0x3ffb21d0 0x400d1606:0x3ffb21f0
+--- SNAP ---
+Guru Meditation Error: Core 1 panic'ed (StoreProhibited). Exception was unhandled.
+
+Core 1 register dump:
+PC : 0x400d15f1 PS : 0x00060b30 A0 : 0x800d1609 A1 : 0x3ffb21d0
+A2 : 0x0000002a A3 : 0x3f40018f A4 : 0x00000020 A5 : 0x0000ff00
+A6 : 0x00ff0000 A7 : 0x00000022 A8 : 0x00000000 A9 : 0x3ffb21b0
+A10 : 0x0000002c A11 : 0x3f400164 A12 : 0x00000022 A13 : 0x0000ff00
+A14 : 0x00ff0000 A15 : 0x0000002a SAR : 0x0000000c EXCCAUSE: 0x0000001d
+EXCVADDR: 0x00000000 LBEG : 0x40086161 LEND : 0x40086171 LCOUNT : 0xfffffff5
+
+Backtrace: 0x400d15ee:0x3ffb21d0 0x400d1606:0x3ffb21f0
+--- SNAP ---
+Core 0 panic'ed (Load access fault). Exception was unhandled.
+
+Core 0 register dump:
+MEPC : 0x4200007e RA : 0x4200007e SP : 0x3fc98300 GP : 0x3fc8d000
+TP : 0x3fc98350 T0 : 0x4005890e T1 : 0x3fc8f000 T2 : 0x00000000
+S0/FP : 0x420001ea S1 : 0x3fc8f000 A0 : 0x00000001 A1 : 0x00000001
+T3 : 0x3fc8f000 T4 : 0x00000001 T5 : 0x3fc8f000 T6 : 0x00000001
+MSTATUS : 0x00001801 MTVEC : 0x40380001 MCAUSE : 0x00000005 MTVAL : 0x00000000
+MHARTID : 0x00000000
diff --git a/src/test/runTest.ts b/src/test/runTest.ts
deleted file mode 100644
index bd4b9db..0000000
--- a/src/test/runTest.ts
+++ /dev/null
@@ -1,36 +0,0 @@
-import path from 'node:path'
-
-import { runTests } from '@vscode/test-electron'
-
-async function main() {
- try {
- // The folder containing the Extension Manifest package.json
- // Passed to `--extensionDevelopmentPath`
- const extensionDevelopmentPath = path.resolve(__dirname, '../../')
-
- // The path to test runner
- // Passed to --extensionTestsPath
- const extensionTestsPath = path.resolve(__dirname, './suite/index')
-
- const extensionTestsEnv = <{ [key: string]: string | undefined }>(
- JSON.parse(JSON.stringify(process.env))
- )
- // VS Code removes the `DEBUG` env variable
- // https://github.com/microsoft/vscode/blob/c248f9ec0cf272351175ccf934054b18ffbf18c6/src/vs/base/common/processes.ts#L141
- if (extensionTestsEnv.DEBUG) {
- extensionTestsEnv['TEST_DEBUG'] = extensionTestsEnv.DEBUG
- }
-
- // Download VS Code, unzip it and run the integration test
- await runTests({
- extensionDevelopmentPath,
- extensionTestsPath,
- extensionTestsEnv,
- })
- } catch (err) {
- console.error('Failed to run tests', err)
- process.exit(1)
- }
-}
-
-main()
diff --git a/src/test/suite/capturer.test.ts b/src/test/suite/capturer.test.ts
new file mode 100644
index 0000000..4e20b8c
--- /dev/null
+++ b/src/test/suite/capturer.test.ts
@@ -0,0 +1,952 @@
+import assert from 'node:assert/strict'
+import { promises as fs, mkdirSync, writeFileSync } from 'node:fs'
+import { tmpdir } from 'node:os'
+import path from 'node:path'
+
+import type { CapturerEvent, DecodeResult } from 'trbr'
+import vscode from 'vscode'
+
+import type {
+ BoardLabContextExt,
+ MonitorClient,
+ MonitorRuntimeState,
+ MonitorSuspensionResult,
+} from '../../boardLabExt'
+import {
+ type CapturerNode,
+ type CapturerRootNode,
+ CapturerManager,
+ __tests,
+} from '../../capturer'
+import {
+ mockArduinoContext,
+ mockBoardDetails,
+ mockCompileSummary,
+ mockSketchFolder,
+} from './mock'
+
+class TestMemento implements vscode.Memento {
+ constructor(private readonly store = new Map()) {}
+
+ get(key: string): T | undefined
+ get(key: string, defaultValue: T): T
+ get(key: string, defaultValue?: T): T | undefined {
+ if (!this.store.has(key)) {
+ return defaultValue
+ }
+ return this.store.get(key) as T
+ }
+
+ keys(): readonly string[] {
+ return Array.from(this.store.keys())
+ }
+
+ async update(key: string, value: unknown): Promise {
+ this.store.set(key, value)
+ }
+}
+
+class FakeMonitorClient implements MonitorClient {
+ readonly onDidReceiveData: vscode.Event
+ readonly onDidChangeState: vscode.Event
+ private readonly receiveEmitter = new vscode.EventEmitter()
+ private readonly stateEmitter = new vscode.EventEmitter()
+ private _state: MonitorRuntimeState = 'running'
+ disposed = false
+
+ constructor(
+ readonly port: { protocol: string; address: string },
+ state: MonitorRuntimeState = 'running'
+ ) {
+ this._state = state
+ this.onDidReceiveData = this.receiveEmitter.event
+ this.onDidChangeState = this.stateEmitter.event
+ }
+
+ get state(): MonitorRuntimeState {
+ return this._state
+ }
+
+ emitData(chunk: Uint8Array): void {
+ this.receiveEmitter.fire(chunk)
+ }
+
+ emitState(state: MonitorRuntimeState): void {
+ this._state = state
+ this.stateEmitter.fire(state)
+ }
+
+ async send(_message: string | Uint8Array): Promise {
+ // noop
+ }
+
+ dispose(): void {
+ this.disposed = true
+ this.receiveEmitter.dispose()
+ this.stateEmitter.dispose()
+ }
+}
+
+class FakeTreeView {
+ badge: vscode.ViewBadge | undefined
+
+ async reveal(
+ _element: T,
+ _options?: {
+ select?: boolean
+ focus?: boolean
+ expand?: boolean | number
+ }
+ ): Promise {
+ // noop
+ }
+
+ dispose(): void {
+ // noop
+ }
+}
+
+function mkdtempInSystemTmp(prefix: string): Promise {
+ return fs.mkdtemp(path.join(tmpdir(), prefix))
+}
+
+describe('capturer', () => {
+ it('validates capturer config input', () => {
+ const invalid = __tests.validateCapturerConfig({})
+ assert.strictEqual(invalid.ok, false)
+
+ const unsupportedArch = __tests.validateCapturerConfig({
+ port: { protocol: 'serial', address: '/dev/mock0' },
+ fqbn: 'arduino:avr:uno',
+ sketchPath: '/tmp/sketch-a',
+ })
+ assert.strictEqual(unsupportedArch.ok, false)
+
+ const valid = __tests.validateCapturerConfig({
+ port: { protocol: 'serial', address: '/dev/mock0' },
+ fqbn: 'esp32:esp32:esp32c3',
+ sketchPath: '/tmp/sketch-a',
+ })
+ assert.strictEqual(valid.ok, true)
+ assert.strictEqual(valid.ok && valid.value.fqbn, 'esp32:esp32:esp32c3')
+
+ const validWithOptions = __tests.validateCapturerConfig({
+ port: { protocol: 'serial', address: '/dev/mock0' },
+ fqbn: 'esp32:esp32:esp32da:CPUFreq=240,PartitionScheme=huge_app',
+ sketchPath: '/tmp/sketch-a',
+ })
+ assert.strictEqual(validWithOptions.ok, true)
+ assert.strictEqual(
+ validWithOptions.ok && validWithOptions.value.fqbn,
+ 'esp32:esp32:esp32da:CPUFreq=240,PartitionScheme=huge_app'
+ )
+ })
+
+ it('covers config/runtime helper utilities', () => {
+ assert.strictEqual(__tests.pathEquals('/tmp/a/../b', '/tmp/b'), true)
+ assert.strictEqual(__tests.isPortIdentifierLike({}), false)
+ assert.strictEqual(
+ __tests.isPortIdentifierLike({ protocol: 'serial', address: '/dev/1' }),
+ true
+ )
+ assert.deepStrictEqual(__tests.extractDetectedPorts(undefined), undefined)
+ assert.strictEqual(
+ __tests.arePortsEqual(
+ { protocol: 'serial', address: '/dev/1' },
+ { protocol: 'serial', address: '/dev/1' }
+ ),
+ true
+ )
+ assert.strictEqual(
+ __tests.isPortDetected([{ protocol: 'serial', address: '/dev/1' }], {
+ protocol: 'serial',
+ address: '/dev/1',
+ }),
+ true
+ )
+ assert.strictEqual(__tests.pickNonEmptyString({ k: ' v ' }, 'k'), 'v')
+ assert.strictEqual(__tests.pickNonEmptyString({ k: ' ' }, 'k'), undefined)
+ assert.strictEqual(__tests.isRecord({}), true)
+ assert.strictEqual(__tests.isRecord(null), false)
+ assert.strictEqual(
+ __tests.resolveSketchBoardName(
+ { board: { name: 'ESP', fqbn: 'esp32:esp32:esp32c3' } },
+ 'esp32:esp32:esp32c3'
+ ),
+ 'ESP'
+ )
+ assert.strictEqual(
+ __tests.resolveSketchBoardName(
+ { board: { name: 'ESP', fqbn: 'esp32:esp32:esp32da' } },
+ 'esp32:esp32:esp32c3'
+ ),
+ undefined
+ )
+ assert.strictEqual(
+ __tests.resolveConfiguredFqbn({
+ configOptions: 'CPUFreq=240,PartitionScheme=huge_app',
+ board: { fqbn: 'esp32:esp32:esp32da' },
+ }),
+ 'esp32:esp32:esp32da:CPUFreq=240,PartitionScheme=huge_app'
+ )
+ assert.deepStrictEqual(
+ __tests.toDraftFromSketch({
+ sketchPath: '/tmp/sketch-with-options',
+ configOptions: 'CPUFreq=240,PartitionScheme=huge_app',
+ board: { fqbn: 'esp32:esp32:esp32da' },
+ port: { protocol: 'serial', address: '/dev/ttyUSB0' },
+ }),
+ {
+ sketchPath: path.resolve('/tmp/sketch-with-options'),
+ fqbn: 'esp32:esp32:esp32da:CPUFreq=240,PartitionScheme=huge_app',
+ port: { protocol: 'serial', address: '/dev/ttyUSB0' },
+ }
+ )
+ })
+
+ it('covers transfer/readiness/source helpers', () => {
+ assert.strictEqual(__tests.areStringArraysEqual(['a'], ['a']), true)
+ assert.strictEqual(__tests.areStringArraysEqual(['a'], ['b']), false)
+ assert.strictEqual(
+ __tests.chunkEndsWithLineBreak(new Uint8Array([0x41, 0x0a])),
+ true
+ )
+ assert.strictEqual(
+ __tests.chunkEndsWithLineBreak(new Uint8Array([0x41])),
+ false
+ )
+
+ const runtime: {
+ processedBytes: number
+ processedFirstAt?: number
+ processedLastAt?: number
+ } = {
+ processedBytes: 10,
+ processedFirstAt: 1,
+ processedLastAt: 2,
+ }
+ __tests.resetTransferMetrics(runtime)
+ assert.deepStrictEqual(runtime, {
+ processedBytes: 0,
+ processedFirstAt: undefined,
+ processedLastAt: undefined,
+ })
+ __tests.recordTransfer(runtime, 0, 10)
+ assert.strictEqual(runtime.processedBytes, 0)
+ __tests.recordTransfer(runtime, 4, 10)
+ __tests.recordTransfer(runtime, 8, 20)
+ assert.strictEqual(runtime.processedBytes, 12)
+ assert.strictEqual(runtime.processedFirstAt, 10)
+ assert.strictEqual(runtime.processedLastAt, 20)
+
+ const leftReadiness = {
+ sketchExists: true,
+ hasCompileSummary: true,
+ elfPath: '/tmp/elf',
+ buildPath: '/tmp/build',
+ buildOptions: {
+ buildPath: '/tmp/build',
+ optionsPath: '/tmp/build/build.options.json',
+ fqbn: 'esp32:esp32:esp32c3',
+ flags: [],
+ },
+ boardName: 'ESP',
+ selectedBoardName: 'ESP',
+ selectedBoardFqbn: 'esp32:esp32:esp32c3',
+ selectedPort: { protocol: 'serial', address: '/dev/1' },
+ configuredPortDetected: true,
+ workspaceRelativeSketchPath: 'sketch',
+ }
+ assert.strictEqual(
+ __tests.isReadinessEqual(leftReadiness, { ...leftReadiness }),
+ true
+ )
+ assert.strictEqual(
+ __tests.isBuildOptionsEqual(leftReadiness.buildOptions, {
+ ...leftReadiness.buildOptions,
+ }),
+ true
+ )
+ assert.strictEqual(
+ __tests.isBuildOptionsEqual(leftReadiness.buildOptions, undefined),
+ false
+ )
+ assert.strictEqual(
+ __tests.isElfIdentityEqual(
+ {
+ path: '/tmp/elf',
+ size: 1,
+ mtimeMs: 2,
+ sha256: 'abc',
+ sessionId: 'x',
+ },
+ {
+ path: '/tmp/elf',
+ size: 1,
+ mtimeMs: 2,
+ sha256: 'abc',
+ sessionId: 'x',
+ }
+ ),
+ true
+ )
+ assert.strictEqual(
+ __tests.classifyDecodedSource('/tmp/sketch/main.cpp', '/tmp/sketch'),
+ 'sketch'
+ )
+ assert.strictEqual(
+ __tests.classifyDecodedSource('/tmp/lib/main.cpp', '/tmp/sketch'),
+ 'library'
+ )
+ assert.strictEqual(
+ __tests.classifyDecodedSource(undefined, '/tmp/sketch'),
+ 'missing'
+ )
+ assert.strictEqual(
+ __tests.isPathInside('/tmp/sketch', '/tmp/sketch/a'),
+ true
+ )
+ })
+
+ it('ignores single-line crash fragments without decode anchors', () => {
+ const truncatedEvent: CapturerEvent = {
+ id: 'event-fragment',
+ signature:
+ "xtensa|guru meditation error: core 1 panic'ed (storeprohibited). exception|fc:na|nopc",
+ kind: 'xtensa',
+ lines: [
+ "Guru Meditation Error: Core 1 panic'ed (StoreProhibited). Exception",
+ ],
+ rawText:
+ "Guru Meditation Error: Core 1 panic'ed (StoreProhibited). Exception",
+ firstSeenAt: 10,
+ lastSeenAt: 10,
+ count: 1,
+ lightweight: {
+ reasonLine:
+ "Guru Meditation Error: Core 1 panic'ed (StoreProhibited). Exception",
+ programCounter: undefined,
+ faultCode: undefined,
+ faultAddr: undefined,
+ regs: {},
+ backtraceAddrs: [],
+ },
+ fastFrames: undefined,
+ evaluated: undefined,
+ }
+
+ assert.strictEqual(__tests.shouldIgnoreCapturerEvent(truncatedEvent), true)
+
+ const completeEnoughEvent: CapturerEvent = {
+ ...truncatedEvent,
+ id: 'event-complete',
+ signature: 'sig-complete',
+ lightweight: {
+ ...truncatedEvent.lightweight,
+ programCounter: 0x400d1234,
+ },
+ }
+ assert.strictEqual(
+ __tests.shouldIgnoreCapturerEvent(completeEnoughEvent),
+ false
+ )
+
+ const multiLineEvent: CapturerEvent = {
+ ...truncatedEvent,
+ id: 'event-multi',
+ signature: 'sig-multi',
+ lines: [
+ "Guru Meditation Error: Core 1 panic'ed (StoreProhibited). Exception",
+ 'Core 1 register dump:',
+ ],
+ rawText:
+ "Guru Meditation Error: Core 1 panic'ed (StoreProhibited). Exception\nCore 1 register dump:",
+ }
+ assert.strictEqual(__tests.shouldIgnoreCapturerEvent(multiLineEvent), false)
+ })
+
+ it('resolves ELF identity with stable short hash prefix', async () => {
+ const buildPath = await mkdtempInSystemTmp('capturer-elf-identity-')
+ const elfPath = path.join(buildPath, 'sample.elf')
+ try {
+ await fs.writeFile(elfPath, 'elf-content')
+ const identity = await __tests.resolveElfIdentity(elfPath)
+ assert.ok(identity)
+ assert.strictEqual(identity.sha256Short, identity.sha256.slice(0, 8))
+ assert.strictEqual(identity.sha256Short.length, 8)
+ assert.ok(identity.sessionId.startsWith(`${identity.sha256Short}-`))
+
+ const reused = await __tests.resolveElfIdentity(elfPath, identity)
+ assert.strictEqual(reused, identity)
+ } finally {
+ await fs.rm(buildPath, { recursive: true, force: true })
+ }
+ })
+
+ it('deduplicates summaries by event signature', () => {
+ const cache = new Map>()
+ const event: CapturerEvent = {
+ id: '1',
+ signature: 'sig-1',
+ kind: 'xtensa',
+ lines: ['StoreProhibited'],
+ rawText: 'panic',
+ firstSeenAt: 10,
+ lastSeenAt: 10,
+ count: 1,
+ lightweight: {
+ reasonLine: 'StoreProhibited',
+ programCounter: 0x1234,
+ faultCode: 29,
+ faultAddr: undefined,
+ regs: {},
+ backtraceAddrs: [],
+ },
+ fastFrames: undefined,
+ evaluated: undefined,
+ }
+ __tests.upsertEventSummary(cache, event)
+ __tests.upsertEventSummary(cache, {
+ ...event,
+ id: '2',
+ count: 2,
+ lastSeenAt: 20,
+ })
+
+ assert.strictEqual(cache.size, 1)
+ const summary = cache.get('sig-1')
+ assert.ok(summary)
+ assert.strictEqual(summary.createdAt, 10)
+ assert.strictEqual(summary.count, 2)
+ assert.strictEqual(summary.lastSeenAt, 20)
+ })
+
+ it('replaces raw text with latest data for same-signature updates', () => {
+ const cache = new Map>()
+ const event: CapturerEvent = {
+ id: 'event-1',
+ signature: 'sig-1',
+ kind: 'xtensa',
+ lines: ['Guru Meditation Error'],
+ rawText:
+ "Guru Meditation Error: Core 1 panic'ed (StoreProhibited). Exception was unhandled.\n\nBacktrace: 0x1 0x2",
+ firstSeenAt: 10,
+ lastSeenAt: 10,
+ count: 1,
+ lightweight: {
+ reasonLine:
+ "Guru Meditation Error: Core 1 panic'ed (StoreProhibited). Exception was unhandled.",
+ programCounter: 0x1234,
+ faultCode: 29,
+ faultAddr: undefined,
+ regs: {},
+ backtraceAddrs: [],
+ },
+ fastFrames: undefined,
+ evaluated: undefined,
+ }
+ __tests.upsertEventSummary(cache, event)
+ __tests.upsertEventSummary(cache, {
+ ...event,
+ id: 'event-2',
+ lastSeenAt: 20,
+ rawText:
+ "Guru Meditation Error: Core 1 panic'ed (StoreProhibited). Exception was unhandled.",
+ })
+
+ const summary = cache.get('sig-1')
+ assert.ok(summary)
+ assert.strictEqual(
+ summary.rawText,
+ "Guru Meditation Error: Core 1 panic'ed (StoreProhibited). Exception was unhandled."
+ )
+ })
+
+ it('formats decoded event label with source location', () => {
+ const summary = __tests.toEventSummary({
+ id: 'event-1',
+ signature: 'sig',
+ kind: 'xtensa',
+ lines: ['Guru Meditation Error'],
+ rawText: 'Guru Meditation Error',
+ firstSeenAt: 10,
+ lastSeenAt: 20,
+ count: 1,
+ lightweight: {
+ reasonLine: 'Guru Meditation Error',
+ programCounter: 0x1234,
+ faultCode: undefined,
+ faultAddr: undefined,
+ regs: {},
+ backtraceAddrs: [],
+ },
+ fastFrames: undefined,
+ evaluated: undefined,
+ })
+ assert.strictEqual(
+ __tests.eventDisplayLabel(summary),
+ 'Guru Meditation Error'
+ )
+
+ const decoded: DecodeResult = {
+ stacktraceLines: [
+ {
+ regAddr: '0x400d1234',
+ lineNumber: '4',
+ file: '/tmp/main.ino',
+ method: 'loop',
+ },
+ ],
+ }
+ summary.decodedResult = decoded
+ assert.strictEqual(
+ __tests.eventDisplayLabel(summary),
+ 'Guru Meditation Error · main.ino'
+ )
+ })
+
+ it('loads compile options from build.options.json', async () => {
+ const buildPath = await mkdtempInSystemTmp('capturer-build-options-')
+ try {
+ await fs.writeFile(
+ path.join(buildPath, 'build.options.json'),
+ JSON.stringify(
+ {
+ fqbn: 'esp32:esp32:esp32da',
+ sketchLocation: '/tmp/sketch',
+ 'compiler.optimization_flags': '-Os',
+ 'compiler.c.extra_flags': '-Og -g3',
+ 'compiler.cpp.extra_flags': '-Og -g3',
+ customBuildProperties: '',
+ },
+ null,
+ 2
+ )
+ )
+ const compileOptions = await __tests.loadCompileBuildOptions(buildPath)
+ assert.ok(compileOptions)
+ assert.strictEqual(compileOptions.buildPath, buildPath)
+ assert.strictEqual(compileOptions.fqbn, 'esp32:esp32:esp32da')
+ assert.strictEqual(compileOptions.optimizationFlags, '-Os')
+ assert.deepStrictEqual(compileOptions.flags, [
+ 'compiler.c.extra_flags=-Og -g3',
+ 'compiler.cpp.extra_flags=-Og -g3',
+ 'compiler.optimization_flags=-Os',
+ ])
+ } finally {
+ await fs.rm(buildPath, { recursive: true, force: true })
+ }
+ })
+
+ it('loads and persists configs', async () => {
+ const sketchPath = await mkdtempInSystemTmp('capturer-load-')
+ const state = new TestMemento(
+ new Map([
+ [
+ __tests.capturerConfigStateKey,
+ [
+ {
+ sketchPath,
+ fqbn: 'esp32:esp32:esp32c3',
+ port: { protocol: 'serial', address: '/dev/load0' },
+ },
+ {
+ sketchPath: '',
+ fqbn: 'invalid',
+ },
+ ],
+ ],
+ ])
+ )
+ const context = {
+ workspaceState: state,
+ subscriptions: [],
+ } as unknown as vscode.ExtensionContext
+
+ const arduino = createBoardLabArduinoContext()
+ const manager = new CapturerManager(context, arduino)
+ const roots = manager.getChildren()
+ assert.strictEqual(roots.length, 1)
+
+ await manager.addConfig({
+ sketchPath: await mkdtempInSystemTmp('capturer-save-'),
+ fqbn: 'esp32:esp32:esp32c3',
+ port: { protocol: 'serial', address: '/dev/save0' },
+ })
+ const persisted = state.get(__tests.capturerConfigStateKey, [])
+ assert.strictEqual(persisted.length, 2)
+ manager.dispose()
+ })
+
+ it('replays monitor recording and updates tree nodes', async () => {
+ const sketchPath = await mkdtempInSystemTmp('capturer-replay-')
+ const context = {
+ workspaceState: new TestMemento(),
+ subscriptions: [],
+ } as unknown as vscode.ExtensionContext
+ const arduino = createBoardLabArduinoContext({
+ sketchPath,
+ portAddress: '/dev/replay0',
+ })
+ const manager = new CapturerManager(context, arduino)
+
+ const config = await manager.addConfig({
+ sketchPath,
+ fqbn: 'esp32:esp32:esp32c3',
+ port: { protocol: 'serial', address: '/dev/replay0' },
+ })
+ assert.ok(config)
+ const root = {
+ type: 'root',
+ configId: config.id,
+ } as const
+
+ let treeRefreshes = 0
+ const disposeChange = manager.onDidChangeTreeData(() => {
+ treeRefreshes += 1
+ })
+ try {
+ const fixturePath = path.resolve(
+ __dirname,
+ '../../../src/test/fixtures/monitor.txt'
+ )
+ await manager.replayRecording(root, vscode.Uri.file(fixturePath))
+
+ const children = manager.getChildren(root)
+ assert.ok(children.length >= 1)
+ const childItems = children.map((child) => manager.getTreeItem(child))
+ assert.ok(
+ childItems.every(
+ (item) =>
+ typeof item.description === 'string' &&
+ item.description.startsWith('Created ')
+ )
+ )
+ const firstEventItem = childItems[0]
+ assert.ok(firstEventItem.tooltip instanceof vscode.MarkdownString)
+ const firstEventTooltip = firstEventItem.tooltip.value
+ assert.ok(firstEventTooltip.includes('- Crash:'))
+ assert.ok(firstEventTooltip.includes('- Location:'))
+ assert.ok(!firstEventTooltip.includes('- Count:'))
+ assert.ok(firstEventTooltip.includes('- Build:'))
+ assert.ok(!firstEventTooltip.includes('#### Crash Summary'))
+ assert.ok(!firstEventTooltip.includes('#### Build Context'))
+ assert.ok(!firstEventTooltip.includes('- ELF Path:'))
+ const rootItem = manager.getTreeItem(root)
+ assert.ok(rootItem.tooltip instanceof vscode.MarkdownString)
+ assert.ok(treeRefreshes > 0)
+ } finally {
+ disposeChange.dispose()
+ manager.dispose()
+ }
+ })
+
+ it('shows problems when sketch is not compiled', async () => {
+ const sketchPath = await mkdtempInSystemTmp('capturer-not-compiled-')
+ const context = {
+ workspaceState: new TestMemento(),
+ subscriptions: [],
+ } as unknown as vscode.ExtensionContext
+ const arduino = createBoardLabArduinoContext({
+ sketchPath,
+ portAddress: '/dev/notcompiled0',
+ includeCompileSummary: false,
+ detectedPort: false,
+ })
+ const manager = new CapturerManager(context, arduino)
+
+ const config = await manager.addConfig({
+ sketchPath,
+ fqbn: 'esp32:esp32:esp32da',
+ port: { protocol: 'serial', address: '/dev/notcompiled0' },
+ })
+ assert.ok(config)
+ const root: CapturerRootNode = {
+ type: 'root',
+ configId: config.id,
+ }
+ const item = manager.getTreeItem(root)
+ assert.strictEqual(item.description, 'Not connected')
+ assert.ok(item.iconPath instanceof vscode.ThemeIcon)
+ assert.strictEqual(item.iconPath.id, 'error')
+ assert.ok(item.tooltip instanceof vscode.MarkdownString)
+ const markdown = item.tooltip.value
+ assert.ok(markdown.includes('Problems:'))
+ assert.ok(
+ markdown.includes('Compile summary is not available for the sketch')
+ )
+ assert.ok(!markdown.includes('Quick Fixes:'))
+ manager.dispose()
+ })
+
+ it('uses warning icon and mismatch description for fqbn mismatches', async () => {
+ const sketchPath = await mkdtempInSystemTmp('capturer-fqbn-mismatch-')
+ const context = {
+ workspaceState: new TestMemento(),
+ subscriptions: [],
+ } as unknown as vscode.ExtensionContext
+ const arduino = createBoardLabArduinoContext({
+ sketchPath,
+ portAddress: '/dev/fqbn0',
+ detectedPort: true,
+ sketchBoardFqbn: 'esp32:esp32:esp32c3',
+ buildOptionsFqbn: 'esp32:esp32:esp32c3',
+ includeElf: true,
+ })
+ const manager = new CapturerManager(context, arduino)
+ const config = await manager.addConfig({
+ sketchPath,
+ fqbn: 'esp32:esp32:esp32da',
+ port: { protocol: 'serial', address: '/dev/fqbn0' },
+ })
+ assert.ok(config)
+ const root: CapturerRootNode = {
+ type: 'root',
+ configId: config.id,
+ }
+ const item = manager.getTreeItem(root)
+ assert.strictEqual(item.description, 'Sketch FQBN mismatch')
+ assert.ok(item.iconPath instanceof vscode.ThemeIcon)
+ assert.strictEqual(item.iconPath.id, 'warning')
+ manager.dispose()
+ })
+
+ it('starts and stops monitor lifecycle', async () => {
+ const sketchPath = await mkdtempInSystemTmp('capturer-monitor-')
+ const context = {
+ workspaceState: new TestMemento(),
+ subscriptions: [],
+ } as unknown as vscode.ExtensionContext
+ let createdMonitor: FakeMonitorClient | undefined
+
+ const arduino = createBoardLabArduinoContext(
+ {
+ sketchPath,
+ portAddress: '/dev/monitor0',
+ detectedPort: true,
+ },
+ async (port) => {
+ createdMonitor = new FakeMonitorClient(port, 'running')
+ return createdMonitor
+ }
+ )
+
+ const manager = new CapturerManager(context, arduino)
+ const config = await manager.addConfig({
+ sketchPath,
+ fqbn: 'esp32:esp32:esp32c3',
+ port: { protocol: 'serial', address: '/dev/monitor0' },
+ })
+ assert.ok(config)
+ const root: CapturerRootNode = {
+ type: 'root',
+ configId: config.id,
+ }
+
+ await manager.startCapturer(root)
+ assert.ok(createdMonitor)
+ const runningItem = manager.getTreeItem(root)
+ assert.strictEqual(runningItem.description, 'Capturing crashes')
+ assert.ok(runningItem.iconPath instanceof vscode.ThemeIcon)
+ assert.strictEqual(runningItem.iconPath.id, 'pulse')
+
+ await manager.stopCapturer(root)
+ assert.strictEqual(createdMonitor.disposed, true)
+ const stoppedItem = manager.getTreeItem(root)
+ assert.strictEqual(stoppedItem.description, 'Ready')
+
+ manager.dispose()
+ })
+
+ it('updates view badge based on active capture sessions', async () => {
+ const sketchPath = await mkdtempInSystemTmp('capturer-badge-')
+ const context = {
+ workspaceState: new TestMemento(),
+ subscriptions: [],
+ } as unknown as vscode.ExtensionContext
+ const arduino = createBoardLabArduinoContext({
+ sketchPath,
+ portAddress: '/dev/badge0',
+ detectedPort: true,
+ })
+ const manager = new CapturerManager(context, arduino)
+ const treeView = new FakeTreeView()
+ manager.bindTreeView(treeView as unknown as vscode.TreeView)
+
+ const config = await manager.addConfig({
+ sketchPath,
+ fqbn: 'esp32:esp32:esp32c3',
+ port: { protocol: 'serial', address: '/dev/badge0' },
+ })
+ assert.ok(config)
+ const root: CapturerRootNode = {
+ type: 'root',
+ configId: config.id,
+ }
+
+ assert.strictEqual(treeView.badge, undefined)
+
+ await manager.startCapturer(root)
+ assert.deepStrictEqual(treeView.badge, {
+ value: 1,
+ tooltip: '1 active crash capture session',
+ })
+
+ await manager.stopCapturer(root)
+ assert.strictEqual(treeView.badge, undefined)
+
+ await manager.startCapturer(root)
+ await manager.removeConfig(root, { force: true })
+ assert.strictEqual(treeView.badge, undefined)
+
+ manager.dispose()
+ })
+
+ it('returns no child nodes when no exceptions are detected', async () => {
+ const sketchPath = await mkdtempInSystemTmp('capturer-empty-child-')
+ const context = {
+ workspaceState: new TestMemento(),
+ subscriptions: [],
+ } as unknown as vscode.ExtensionContext
+ const arduino = createBoardLabArduinoContext({
+ sketchPath,
+ portAddress: '/dev/empty0',
+ detectedPort: true,
+ })
+ const manager = new CapturerManager(context, arduino)
+ const config = await manager.addConfig({
+ sketchPath,
+ fqbn: 'esp32:esp32:esp32c3',
+ port: { protocol: 'serial', address: '/dev/empty0' },
+ })
+ assert.ok(config)
+ const root: CapturerRootNode = {
+ type: 'root',
+ configId: config.id,
+ }
+
+ const children = manager.getChildren(root)
+ assert.strictEqual(children.length, 0)
+
+ manager.dispose()
+ })
+})
+
+function createBoardLabArduinoContext(
+ options: {
+ sketchPath?: string
+ portAddress?: string
+ sketchBoardFqbn?: string
+ buildOptionsFqbn?: string
+ includeCompileSummary?: boolean
+ includeElf?: boolean
+ detectedPort?: boolean
+ } = {},
+ createMonitorClient?: (port: {
+ protocol: string
+ address: string
+ }) => Promise
+): ArduinoContextWithBoardLab {
+ const sketchPath = options.sketchPath ?? '/tmp/mock-sketch'
+ const portAddress = options.portAddress ?? '/dev/mock0'
+ const sketchBoardFqbn = options.sketchBoardFqbn ?? 'esp32:esp32:esp32c3'
+ const buildOptionsFqbn = options.buildOptionsFqbn ?? sketchBoardFqbn
+ const includeCompileSummary = options.includeCompileSummary ?? true
+ const includeElf = options.includeElf ?? includeCompileSummary
+ const buildPath = path.join(sketchPath, '.build')
+ if (includeCompileSummary) {
+ mkdirSync(buildPath, { recursive: true })
+ writeFileSync(
+ path.join(buildPath, 'build.options.json'),
+ JSON.stringify({
+ fqbn: buildOptionsFqbn,
+ 'compiler.optimization_flags': '-Os',
+ })
+ )
+ }
+ if (includeCompileSummary && includeElf) {
+ writeFileSync(
+ path.join(buildPath, `${path.basename(sketchPath)}.ino.elf`),
+ ''
+ )
+ }
+ const sketchFolder = mockSketchFolder({
+ sketchPath,
+ port: { protocol: 'serial', address: portAddress },
+ board: mockBoardDetails(sketchBoardFqbn),
+ compileSummary: includeCompileSummary
+ ? mockCompileSummary(buildPath)
+ : undefined,
+ })
+ const base = mockArduinoContext({
+ currentSketch: sketchFolder,
+ openedSketches: [sketchFolder],
+ })
+ const boardLab = base as ArduinoContextWithBoardLab
+ const detectedPorts =
+ options.detectedPort === false
+ ? ({} as BoardLabContextExt['boardsListWatcher']['detectedPorts'])
+ : ({
+ mock: {
+ port: { protocol: 'serial', address: portAddress },
+ },
+ } as unknown as BoardLabContextExt['boardsListWatcher']['detectedPorts'])
+ const detectedPortsEmitter = new vscode.EventEmitter()
+ ;(
+ boardLab as unknown as {
+ boardsListWatcher: BoardLabContextExt['boardsListWatcher']
+ }
+ ).boardsListWatcher = {
+ detectedPorts,
+ onDidChangeDetectedPorts: detectedPortsEmitter.event,
+ }
+ boardLab.createMonitorClient = async (port) => {
+ if (createMonitorClient) {
+ return createMonitorClient(port)
+ }
+ return new FakeMonitorClient(port)
+ }
+ boardLab.withMonitorSuspended = async (
+ _port: { protocol: string; address: string },
+ run: (options?: { retry?: number }) => Promise
+ ): Promise => run()
+ boardLab.pickSketch = async () => sketchFolder
+ boardLab.selectSketch = async () => sketchFolder
+ boardLab.pickBoard = async () => sketchFolder.board
+ boardLab.selectBoard = async (_currentSketchOrOptions, pickOptions) => {
+ const selection = sketchFolder.board
+ if (
+ pickOptions?.filters &&
+ Array.isArray(pickOptions.filters) &&
+ selection &&
+ 'fqbn' in selection
+ ) {
+ for (const filter of pickOptions.filters) {
+ const isVisible = await filter({
+ board: selection,
+ selection,
+ } as never)
+ if (!isVisible) {
+ return undefined
+ }
+ }
+ }
+ return selection
+ }
+ boardLab.setBoardByFqbn = async (fqbn) => {
+ const targetFqbn = typeof fqbn === 'string' ? fqbn : fqbn.toString()
+ const updatedBoard = mockBoardDetails(targetFqbn)
+ ;(
+ sketchFolder as unknown as {
+ board: ReturnType
+ }
+ ).board = updatedBoard
+ return updatedBoard
+ }
+ boardLab.pickPort = async () => sketchFolder.port
+ boardLab.selectPort = async () => sketchFolder.port
+ return boardLab
+}
+
+type ArduinoContextWithBoardLab = ReturnType &
+ BoardLabContextExt
diff --git a/src/test/suite/capturerCreationFlow.test.ts b/src/test/suite/capturerCreationFlow.test.ts
new file mode 100644
index 0000000..e7d6850
--- /dev/null
+++ b/src/test/suite/capturerCreationFlow.test.ts
@@ -0,0 +1,441 @@
+import assert from 'node:assert/strict'
+import path from 'node:path'
+
+import type { BoardLabContextExt } from '../../boardLabExt'
+import {
+ isSupportedCapturerArch,
+ isSupportedCapturerFqbn,
+ modeNeedsBoardSelection,
+ modeNeedsPortSelection,
+ resolveConfiguredFqbn,
+ resolveSketchPath,
+ resolveSketchTargetWithBoardLab,
+ toDraftFromSketch,
+ toPortIdentifier,
+ toSketchTargetSelectionModeItems,
+} from '../../boardLabTarget'
+
+describe('boardLabTarget', () => {
+ it('resolves sketch path from sketchPath and uri', () => {
+ assert.strictEqual(
+ resolveSketchPath({ sketchPath: 'workspace/sketch-a' }),
+ path.resolve('workspace/sketch-a')
+ )
+ assert.strictEqual(
+ resolveSketchPath({ uri: { fsPath: 'workspace/sketch-b' } }),
+ path.resolve('workspace/sketch-b')
+ )
+ assert.strictEqual(resolveSketchPath({}), undefined)
+ })
+
+ it('resolves configured fqbn from board fqbn + config options', () => {
+ assert.strictEqual(
+ resolveConfiguredFqbn({
+ configOptions: 'CPUFreq=240',
+ board: { fqbn: 'esp32:esp32:esp32da' },
+ }),
+ 'esp32:esp32:esp32da:CPUFreq=240'
+ )
+ assert.strictEqual(
+ resolveConfiguredFqbn({
+ board: { fqbn: 'esp32:esp32:esp32c3' },
+ }),
+ 'esp32:esp32:esp32c3'
+ )
+ assert.strictEqual(
+ resolveConfiguredFqbn({
+ fqbn: 'esp8266:esp8266:nodemcuv2',
+ }),
+ 'esp8266:esp8266:nodemcuv2'
+ )
+ assert.strictEqual(
+ resolveConfiguredFqbn({
+ configOptions: 'esp32:esp32:esp32da:CPUFreq=240',
+ }),
+ 'esp32:esp32:esp32da:CPUFreq=240'
+ )
+ assert.strictEqual(
+ resolveConfiguredFqbn({
+ configOptions: 'CPUFreq=240',
+ }),
+ undefined
+ )
+ assert.strictEqual(
+ resolveConfiguredFqbn({ configOptions: ' ' }),
+ undefined
+ )
+ })
+
+ it('normalizes direct and nested port identifiers', () => {
+ assert.deepStrictEqual(
+ toPortIdentifier({ protocol: 'serial', address: '/dev/ttyUSB0' }),
+ { protocol: 'serial', address: '/dev/ttyUSB0' }
+ )
+ assert.deepStrictEqual(
+ toPortIdentifier({
+ port: { protocol: 'network', address: '192.168.1.3:23' },
+ }),
+ { protocol: 'network', address: '192.168.1.3:23' }
+ )
+ assert.strictEqual(toPortIdentifier({ protocol: 'serial' }), undefined)
+ })
+
+ it('builds capturer draft from sketch state when complete', () => {
+ const draft = toDraftFromSketch({
+ sketchPath: 'workspace/sketch-with-target',
+ configOptions: 'CPUFreq=240,PartitionScheme=huge_app',
+ board: { fqbn: 'esp32:esp32:esp32da' },
+ port: { protocol: 'serial', address: '/dev/ttyUSB0' },
+ })
+
+ assert.deepStrictEqual(draft, {
+ sketchPath: path.resolve('workspace/sketch-with-target'),
+ fqbn: 'esp32:esp32:esp32da:CPUFreq=240,PartitionScheme=huge_app',
+ port: { protocol: 'serial', address: '/dev/ttyUSB0' },
+ })
+ assert.strictEqual(
+ toDraftFromSketch({
+ sketchPath: 'workspace/sketch-without-port',
+ board: { fqbn: 'esp32:esp32:esp32c3' },
+ }),
+ undefined
+ )
+ })
+
+ it('returns creation modes based on available board/port', () => {
+ const toModes = (value: unknown) =>
+ toSketchTargetSelectionModeItems(value).map((item) => item.mode)
+
+ assert.deepStrictEqual(
+ toModes({
+ board: { fqbn: 'esp32:esp32:esp32c3' },
+ port: { protocol: 'serial', address: '/dev/mock0' },
+ }),
+ ['use-sketch-target', 'select-board', 'select-port', 'select-both']
+ )
+ assert.deepStrictEqual(
+ toModes({
+ board: { fqbn: 'esp32:esp32:esp32c3' },
+ }),
+ ['use-board-select-port', 'select-board-select-port']
+ )
+ assert.deepStrictEqual(
+ toModes({
+ port: { protocol: 'serial', address: '/dev/mock0' },
+ }),
+ ['use-port-select-board', 'select-board-select-port']
+ )
+ assert.deepStrictEqual(
+ toModes({
+ board: { fqbn: 'arduino:avr:uno' },
+ port: { protocol: 'serial', address: '/dev/mock0' },
+ }),
+ ['use-port-select-board', 'select-board-select-port']
+ )
+ assert.deepStrictEqual(toModes({}), ['select-both'])
+ })
+
+ it('maps mode to required board and port selections', () => {
+ const matrix = [
+ {
+ mode: 'use-sketch-target',
+ needsBoard: false,
+ needsPort: false,
+ },
+ {
+ mode: 'select-board',
+ needsBoard: true,
+ needsPort: false,
+ },
+ {
+ mode: 'select-port',
+ needsBoard: false,
+ needsPort: true,
+ },
+ {
+ mode: 'select-both',
+ needsBoard: true,
+ needsPort: true,
+ },
+ {
+ mode: 'use-board-select-port',
+ needsBoard: false,
+ needsPort: true,
+ },
+ {
+ mode: 'use-port-select-board',
+ needsBoard: true,
+ needsPort: false,
+ },
+ {
+ mode: 'select-board-select-port',
+ needsBoard: true,
+ needsPort: true,
+ },
+ ] as const
+
+ for (const entry of matrix) {
+ assert.strictEqual(modeNeedsBoardSelection(entry.mode), entry.needsBoard)
+ assert.strictEqual(modeNeedsPortSelection(entry.mode), entry.needsPort)
+ }
+ })
+
+ it('resolves sketch target from boardlab selections with select->pick fallback', async () => {
+ const calls: string[] = []
+ const boardLab = {
+ pickSketch: async () => {
+ calls.push('pickSketch')
+ return {
+ sketchPath: 'workspace/sketch-fallback',
+ }
+ },
+ selectBoard: async () => {
+ calls.push('selectBoard')
+ return undefined
+ },
+ pickBoard: async () => {
+ calls.push('pickBoard')
+ return { fqbn: 'esp32:esp32:esp32da' }
+ },
+ selectPort: async () => {
+ calls.push('selectPort')
+ return undefined
+ },
+ pickPort: async () => {
+ calls.push('pickPort')
+ return { protocol: 'serial', address: '/dev/ttyUSB-fallback' }
+ },
+ } as unknown as Pick<
+ BoardLabContextExt,
+ 'pickSketch' | 'selectBoard' | 'pickBoard' | 'selectPort' | 'pickPort'
+ >
+ const draft = await resolveSketchTargetWithBoardLab({
+ boardLab,
+ openedSketches: [
+ {
+ sketchPath: 'workspace/sketch-fallback',
+ },
+ ],
+ pickMode: async () => undefined,
+ })
+
+ assert.deepStrictEqual(draft, {
+ sketchPath: path.resolve('workspace/sketch-fallback'),
+ fqbn: 'esp32:esp32:esp32da',
+ port: { protocol: 'serial', address: '/dev/ttyUSB-fallback' },
+ })
+ assert.deepStrictEqual(calls, [
+ 'pickSketch',
+ 'selectBoard',
+ 'pickBoard',
+ 'selectPort',
+ 'pickPort',
+ ])
+ })
+
+ it('uses refreshed opened sketch values when select commands update sketch state', async () => {
+ const openedSketch = {
+ sketchPath: path.resolve('workspace/sketch-updated'),
+ configOptions: undefined,
+ board: undefined,
+ port: undefined,
+ } as {
+ sketchPath: string
+ configOptions?: string
+ board?: { fqbn: string }
+ port?: { protocol: string; address: string }
+ }
+ const calls: string[] = []
+ const boardLab = {
+ pickSketch: async () => {
+ calls.push('pickSketch')
+ return {
+ sketchPath: 'workspace/sketch-updated',
+ }
+ },
+ selectBoard: async () => {
+ calls.push('selectBoard')
+ openedSketch.configOptions = 'CPUFreq=240,PartitionScheme=huge_app'
+ openedSketch.board = { fqbn: 'esp32:esp32:esp32da' }
+ return {
+ board: { fqbn: 'esp32:esp32:esp32da' },
+ }
+ },
+ pickBoard: async () => {
+ calls.push('pickBoard')
+ return undefined
+ },
+ selectPort: async () => {
+ calls.push('selectPort')
+ openedSketch.port = {
+ protocol: 'serial',
+ address: '/dev/ttyUSB-updated',
+ }
+ return {
+ port: openedSketch.port,
+ }
+ },
+ pickPort: async () => {
+ calls.push('pickPort')
+ return undefined
+ },
+ } as unknown as Pick<
+ BoardLabContextExt,
+ 'pickSketch' | 'selectBoard' | 'pickBoard' | 'selectPort' | 'pickPort'
+ >
+ const draft = await resolveSketchTargetWithBoardLab({
+ boardLab,
+ openedSketches: [openedSketch],
+ pickMode: async () => undefined,
+ })
+
+ assert.deepStrictEqual(draft, {
+ sketchPath: path.resolve('workspace/sketch-updated'),
+ fqbn: 'esp32:esp32:esp32da:CPUFreq=240,PartitionScheme=huge_app',
+ port: { protocol: 'serial', address: '/dev/ttyUSB-updated' },
+ })
+ assert.deepStrictEqual(calls, ['pickSketch', 'selectBoard', 'selectPort'])
+ })
+
+ it('reports error when picked sketch path cannot be resolved', async () => {
+ const errors: string[] = []
+ const boardLab = {
+ pickSketch: async () => ({}),
+ selectBoard: async () => undefined,
+ pickBoard: async () => undefined,
+ selectPort: async () => undefined,
+ pickPort: async () => undefined,
+ } as unknown as Pick<
+ BoardLabContextExt,
+ 'pickSketch' | 'selectBoard' | 'pickBoard' | 'selectPort' | 'pickPort'
+ >
+ const draft = await resolveSketchTargetWithBoardLab({
+ boardLab,
+ openedSketches: [],
+ pickMode: async () => undefined,
+ onError: (message) => errors.push(message),
+ })
+
+ assert.strictEqual(draft, undefined)
+ assert.deepStrictEqual(errors, [
+ 'Could not resolve the sketch path for target selection.',
+ ])
+ })
+
+ it('passes board filters and rejects unsupported board architectures', async () => {
+ const errors: string[] = []
+ const calls: string[] = []
+ const boardLab = {
+ pickSketch: async () => {
+ calls.push('pickSketch')
+ return {
+ sketchPath: 'workspace/sketch-unsupported-board',
+ }
+ },
+ selectBoard: async (_sketch: unknown, boardOptions?: unknown) => {
+ calls.push('selectBoard')
+ const options = boardOptions as {
+ filters?: Array<(candidate: unknown) => boolean | Promise>
+ }
+ assert.ok(Array.isArray(options.filters))
+ const filter = options.filters?.[0]
+ assert.strictEqual(typeof filter, 'function')
+ const allowed = await filter?.({
+ board: { fqbn: 'arduino:avr:uno' },
+ selection: { fqbn: 'arduino:avr:uno' },
+ })
+ assert.strictEqual(allowed, false)
+ return { fqbn: 'arduino:avr:uno' }
+ },
+ pickBoard: async () => {
+ calls.push('pickBoard')
+ return undefined
+ },
+ selectPort: async () => {
+ calls.push('selectPort')
+ return { protocol: 'serial', address: '/dev/never-used' }
+ },
+ pickPort: async () => {
+ calls.push('pickPort')
+ return undefined
+ },
+ } as unknown as Pick<
+ BoardLabContextExt,
+ 'pickSketch' | 'selectBoard' | 'pickBoard' | 'selectPort' | 'pickPort'
+ >
+ const draft = await resolveSketchTargetWithBoardLab({
+ boardLab,
+ openedSketches: [
+ {
+ sketchPath: 'workspace/sketch-unsupported-board',
+ },
+ ],
+ pickMode: async () => undefined,
+ onError: (message) => errors.push(message),
+ })
+
+ assert.strictEqual(isSupportedCapturerArch('esp32'), true)
+ assert.strictEqual(isSupportedCapturerArch('esp8266'), true)
+ assert.strictEqual(isSupportedCapturerArch('avr'), false)
+ assert.strictEqual(isSupportedCapturerFqbn('esp32:esp32:esp32'), true)
+ assert.strictEqual(
+ isSupportedCapturerFqbn('esp8266:esp8266:nodemcuv2'),
+ true
+ )
+ assert.strictEqual(isSupportedCapturerFqbn('arduino:avr:uno'), false)
+ assert.strictEqual(draft, undefined)
+ assert.deepStrictEqual(calls, ['pickSketch', 'selectBoard'])
+ assert.deepStrictEqual(errors, [
+ 'Only ESP32 and ESP8266 boards are supported for crash capture.',
+ ])
+ })
+
+ it('treats unsupported sketch board as missing and guides to pick supported board', async () => {
+ const calls: string[] = []
+ const draft = await resolveSketchTargetWithBoardLab({
+ boardLab: {
+ pickSketch: async () => {
+ calls.push('pickSketch')
+ return {
+ sketchPath: 'workspace/sketch-avr',
+ }
+ },
+ selectBoard: async () => {
+ calls.push('selectBoard')
+ return { fqbn: 'esp32:esp32:esp32da' }
+ },
+ pickBoard: async () => {
+ calls.push('pickBoard')
+ return undefined
+ },
+ selectPort: async () => {
+ calls.push('selectPort')
+ return undefined
+ },
+ pickPort: async () => {
+ calls.push('pickPort')
+ return undefined
+ },
+ } as unknown as Pick<
+ BoardLabContextExt,
+ 'pickSketch' | 'selectBoard' | 'pickBoard' | 'selectPort' | 'pickPort'
+ >,
+ openedSketches: [
+ {
+ sketchPath: 'workspace/sketch-avr',
+ board: { fqbn: 'arduino:avr:uno' },
+ port: { protocol: 'serial', address: '/dev/mock0' },
+ configOptions: 'arduino:avr:uno',
+ },
+ ],
+ pickMode: async (items) => items[0],
+ })
+
+ assert.deepStrictEqual(draft, {
+ sketchPath: path.resolve('workspace/sketch-avr'),
+ fqbn: 'esp32:esp32:esp32da',
+ port: { protocol: 'serial', address: '/dev/mock0' },
+ })
+ assert.deepStrictEqual(calls, ['pickSketch', 'selectBoard'])
+ })
+})
diff --git a/src/test/suite/decodeParams.test.ts b/src/test/suite/decodeParams.test.ts
index 00e84ac..f5a7584 100644
--- a/src/test/suite/decodeParams.test.ts
+++ b/src/test/suite/decodeParams.test.ts
@@ -4,16 +4,10 @@ import path from 'node:path'
import temp from 'temp'
-import {
- DecodeParamsError,
- __tests,
- createDecodeParams,
-} from '../../decodeParams'
+import { DecodeParamsError, createDecodeParams } from '../../decodeParams'
import { isWindows } from '../../utils'
import { mockBoardDetails, mockCompileSummary, mockSketchFolder } from './mock'
-const { findElfPath } = __tests
-
describe('decodeParams', () => {
let tracked: typeof temp
before(() => (tracked = temp.track()))
@@ -113,7 +107,7 @@ describe('decodeParams', () => {
board: mockBoardDetails(fqbn),
compileSummary: {
...mockCompileSummary('/tmp/ignored'),
- buildPath: undefined as unknown,
+ buildPath: undefined as unknown as string,
},
})
),
@@ -204,51 +198,4 @@ describe('decodeParams', () => {
assert.strictEqual(actual.fqbn.toString(), fqbn)
})
})
-
- describe('findElfPath', () => {
- it('should not find the elf path when the sketch folder name does not match', async () => {
- const buildPath = tracked.mkdirSync()
- const sketchName = 'my_sketch'
- const invalidName = 'MySketch'
- await fs.writeFile(path.join(buildPath, `${invalidName}.ino.elf`), '')
- await fs.writeFile(path.join(buildPath, `${invalidName}.cpp.elf`), '')
- const actual = await findElfPath(sketchName, buildPath)
- assert.strictEqual(actual, undefined)
- })
-
- it("should not find the elf path when neither 'ino.elf' nor 'cpp.elf' exist", async () => {
- const buildPath = tracked.mkdirSync()
- const sketchName = 'my_sketch'
- const actual = await findElfPath(sketchName, buildPath)
- assert.strictEqual(actual, undefined)
- })
-
- it("should find 'ino.elf' path", async () => {
- const buildPath = tracked.mkdirSync()
- const sketchName = 'my_sketch'
- const expected = path.join(buildPath, `${sketchName}.ino.elf`)
- await fs.writeFile(expected, '')
- const actual = await findElfPath(sketchName, buildPath)
- assert.strictEqual(actual, expected)
- })
-
- it("should find 'cpp.elf' path", async () => {
- const buildPath = tracked.mkdirSync()
- const sketchName = 'my_sketch'
- const expected = path.join(buildPath, `${sketchName}.cpp.elf`)
- await fs.writeFile(expected, '')
- const actual = await findElfPath(sketchName, buildPath)
- assert.strictEqual(actual, expected)
- })
-
- it("should prefer 'ino.elf' over 'cpp.elf' when both paths exist", async () => {
- const buildPath = tracked.mkdirSync()
- const sketchName = 'my_sketch'
- const expected = path.join(buildPath, `${sketchName}.ino.elf`)
- await fs.writeFile(expected, '')
- await fs.writeFile(path.join(buildPath, `${sketchName}.cpp.elf`), '')
- const actual = await findElfPath(sketchName, buildPath)
- assert.strictEqual(actual, expected)
- })
- })
})
diff --git a/src/test/suite/findElfPath.test.ts b/src/test/suite/findElfPath.test.ts
new file mode 100644
index 0000000..49d74db
--- /dev/null
+++ b/src/test/suite/findElfPath.test.ts
@@ -0,0 +1,179 @@
+import assert from 'node:assert/strict'
+import { promises as fs } from 'node:fs'
+import path from 'node:path'
+
+import temp from 'temp'
+
+import {
+ buildFolderMd5Hash,
+ findElfPath,
+ resolveBuildPathCandidates,
+ resolveBuildPathFromSketchPath,
+} from '../../findElfPath'
+
+describe('findElfPath', () => {
+ let tracked: typeof temp
+ before(() => (tracked = temp.track()))
+ after(() => tracked.cleanupSync())
+
+ it('should not find the elf path when the sketch folder name does not match', async () => {
+ const buildPath = tracked.mkdirSync()
+ const sketchName = 'my_sketch'
+ const invalidName = 'MySketch'
+ await fs.writeFile(path.join(buildPath, `${invalidName}.ino.elf`), '')
+ await fs.writeFile(path.join(buildPath, `${invalidName}.cpp.elf`), '')
+ const actual = await findElfPath(sketchName, buildPath)
+ assert.strictEqual(actual, undefined)
+ })
+
+ it("should not find the elf path when neither 'ino.elf' nor 'cpp.elf' exist", async () => {
+ const buildPath = tracked.mkdirSync()
+ const sketchName = 'my_sketch'
+ const actual = await findElfPath(sketchName, buildPath)
+ assert.strictEqual(actual, undefined)
+ })
+
+ it("should find 'ino.elf' path", async () => {
+ const buildPath = tracked.mkdirSync()
+ const sketchName = 'my_sketch'
+ const expected = path.join(buildPath, `${sketchName}.ino.elf`)
+ await fs.writeFile(expected, '')
+ const actual = await findElfPath(sketchName, buildPath)
+ assert.strictEqual(actual, expected)
+ })
+
+ it("should find 'cpp.elf' path", async () => {
+ const buildPath = tracked.mkdirSync()
+ const sketchName = 'my_sketch'
+ const expected = path.join(buildPath, `${sketchName}.cpp.elf`)
+ await fs.writeFile(expected, '')
+ const actual = await findElfPath(sketchName, buildPath)
+ assert.strictEqual(actual, expected)
+ })
+
+ it("should prefer 'ino.elf' over 'cpp.elf' when both paths exist", async () => {
+ const buildPath = tracked.mkdirSync()
+ const sketchName = 'my_sketch'
+ const expected = path.join(buildPath, `${sketchName}.ino.elf`)
+ await fs.writeFile(expected, '')
+ await fs.writeFile(path.join(buildPath, `${sketchName}.cpp.elf`), '')
+ const actual = await findElfPath(sketchName, buildPath)
+ assert.strictEqual(actual, expected)
+ })
+
+ it('resolves fallback build path when build.options.json exists', async () => {
+ const basePath = tracked.mkdirSync()
+ const sketchPath = '/tmp/workspace/esp32backtracetest'
+ const sketchFolderName = 'esp32backtracetest'
+ const [candidatePath] = resolveBuildPathCandidates(sketchPath, {
+ userCacheDirPath: basePath,
+ tempDirPath: basePath,
+ })
+ assert.ok(candidatePath)
+ await fs.mkdir(candidatePath, { recursive: true })
+ await fs.writeFile(path.join(candidatePath, 'build.options.json'), '{}')
+
+ const actual = await resolveBuildPathFromSketchPath(
+ sketchPath,
+ sketchFolderName,
+ {
+ userCacheDirPath: basePath,
+ tempDirPath: basePath,
+ }
+ )
+
+ assert.ok(actual)
+ assert.strictEqual(actual.buildPath, candidatePath)
+ assert.strictEqual(actual.elfPath, undefined)
+ assert.strictEqual(actual.hasBuildOptions, true)
+ })
+
+ it('resolves fallback build path when elf exists', async () => {
+ const basePath = tracked.mkdirSync()
+ const sketchPath = '/tmp/workspace/esp32backtracetest'
+ const sketchFolderName = 'esp32backtracetest'
+ const [candidatePath] = resolveBuildPathCandidates(sketchPath, {
+ userCacheDirPath: basePath,
+ tempDirPath: basePath,
+ })
+ assert.ok(candidatePath)
+ await fs.mkdir(candidatePath, { recursive: true })
+ const elfPath = path.join(candidatePath, `${sketchFolderName}.ino.elf`)
+ await fs.writeFile(elfPath, '')
+
+ const actual = await resolveBuildPathFromSketchPath(
+ sketchPath,
+ sketchFolderName,
+ {
+ userCacheDirPath: basePath,
+ tempDirPath: basePath,
+ }
+ )
+
+ assert.ok(actual)
+ assert.strictEqual(actual.buildPath, candidatePath)
+ assert.strictEqual(actual.elfPath, elfPath)
+ assert.strictEqual(actual.hasBuildOptions, false)
+ })
+
+ it('generates windows hash candidates for both drive-letter casings', () => {
+ const sketchPath = 'C:\\ws\\esp32backtracetest'
+ const sketchPathOtherCase = 'c:\\ws\\esp32backtracetest'
+ const basePath = 'C:\\Users\\per\\AppData\\Local\\Temp'
+ const basePathOtherCase = 'c:\\Users\\per\\AppData\\Local\\Temp'
+
+ const candidates = resolveBuildPathCandidates(sketchPath, {
+ platform: 'win32',
+ userCacheDirPath: basePath,
+ tempDirPath: basePath,
+ })
+
+ const expectedPrimary = path.win32.join(
+ basePath,
+ 'arduino',
+ 'sketches',
+ buildFolderMd5Hash(sketchPath)
+ )
+ const expectedBaseOtherCase = path.win32.join(
+ basePathOtherCase,
+ 'arduino',
+ 'sketches',
+ buildFolderMd5Hash(sketchPath)
+ )
+ const expectedSketchOtherCase = path.win32.join(
+ basePath,
+ 'arduino',
+ 'sketches',
+ buildFolderMd5Hash(sketchPathOtherCase)
+ )
+
+ assert.ok(candidates.includes(expectedPrimary))
+ assert.ok(candidates.includes(expectedBaseOtherCase))
+ assert.ok(candidates.includes(expectedSketchOtherCase))
+ })
+
+ it('normalizes windows slash styles and emits both folder schemes', () => {
+ const sketchPath = 'D:/a/esp-exception-decoder/esp-exception-decoder/sketch'
+ const normalizedSketchPath = path.win32.normalize(sketchPath)
+ const basePath = 'D:\\Users\\runneradmin\\AppData\\Local\\Temp'
+ const candidates = resolveBuildPathCandidates(sketchPath, {
+ platform: 'win32',
+ userCacheDirPath: basePath,
+ tempDirPath: basePath,
+ })
+ const hash = buildFolderMd5Hash(normalizedSketchPath)
+ const sketchesFolderCandidate = path.win32.join(
+ basePath,
+ 'arduino',
+ 'sketches',
+ hash
+ )
+ const flatFolderCandidate = path.win32.join(
+ basePath,
+ `arduino-sketch-${hash}`
+ )
+
+ assert.ok(candidates.includes(sketchesFolderCandidate))
+ assert.ok(candidates.includes(flatFolderCandidate))
+ })
+})
diff --git a/src/test/suite/fsProviders.test.ts b/src/test/suite/fsProviders.test.ts
new file mode 100644
index 0000000..84c8339
--- /dev/null
+++ b/src/test/suite/fsProviders.test.ts
@@ -0,0 +1,98 @@
+import assert from 'node:assert/strict'
+import * as fs from 'node:fs/promises'
+import os from 'node:os'
+import path from 'node:path'
+
+import vscode from 'vscode'
+
+import {
+ CrashReportContentProvider,
+ ReadonlyFsProvider,
+ crashReportScheme,
+ readonlyLibraryScheme,
+ toReadonlyLibraryUri,
+ toSourcePathFromReadonlyUri,
+} from '../../capturer/fsProviders'
+
+describe('fsProviders', () => {
+ it('creates and serves crash report content and trims old entries', () => {
+ const provider = new CrashReportContentProvider()
+ const missingUri = vscode.Uri.from({
+ scheme: crashReportScheme,
+ path: '/missing.md',
+ })
+ assert.equal(
+ provider.provideTextDocumentContent(missingUri),
+ '# Report is no longer available.'
+ )
+
+ const first = provider.createReportUri('My Report', '# hello')
+ assert.equal(first.scheme, crashReportScheme)
+ assert.equal(provider.provideTextDocumentContent(first), '# hello')
+
+ for (let i = 0; i < 130; i += 1) {
+ provider.createReportUri(`R ${i}`, `# ${i}`)
+ }
+
+ assert.equal(
+ provider.provideTextDocumentContent(first),
+ '# Report is no longer available.'
+ )
+ provider.dispose()
+ })
+
+ it('converts readonly uri roundtrip and falls back to uri path', () => {
+ const sourcePath = '/tmp/library/main.cpp'
+ const uri = toReadonlyLibraryUri(sourcePath)
+ assert.equal(uri.scheme, readonlyLibraryScheme)
+ assert.equal(toSourcePathFromReadonlyUri(uri), sourcePath)
+
+ const fallback = vscode.Uri.from({
+ scheme: readonlyLibraryScheme,
+ path: '/tmp/no-query.cpp',
+ })
+ assert.equal(toSourcePathFromReadonlyUri(fallback), '/tmp/no-query.cpp')
+ })
+
+ it('reads readonly files and blocks write operations', async () => {
+ const root = await fs.mkdtemp(path.join(os.tmpdir(), 'capturer-fs-'))
+ const subDir = path.join(root, 'sub')
+ const filePath = path.join(root, 'a.txt')
+ await fs.mkdir(subDir)
+ await fs.writeFile(filePath, 'hello')
+
+ // best-effort symbolic link coverage for Dirent.isSymbolicLink().
+ try {
+ await fs.symlink(filePath, path.join(root, 'a-link.txt'))
+ } catch {
+ // symlink may be unavailable in some environments
+ }
+
+ const provider = new ReadonlyFsProvider()
+ const watched = provider.watch()
+
+ const stat = await provider.stat(toReadonlyLibraryUri(filePath))
+ assert.equal(stat.type, vscode.FileType.File)
+ const content = await provider.readFile(toReadonlyLibraryUri(filePath))
+ assert.equal(new TextDecoder().decode(content), 'hello')
+
+ const dirEntries = await provider.readDirectory(toReadonlyLibraryUri(root))
+ assert.equal(
+ dirEntries.some(([name]) => name === 'a.txt'),
+ true
+ )
+ assert.equal(
+ dirEntries.some(([name]) => name === 'sub'),
+ true
+ )
+
+ assert.throws(() => (provider as any).createDirectory())
+ assert.throws(() => (provider as any).writeFile())
+ assert.throws(() => (provider as any).delete())
+ assert.throws(() => (provider as any).rename())
+
+ watched.dispose()
+ provider.dispose()
+ await fs.rm(root, { recursive: true, force: true })
+ })
+})
diff --git a/src/test/suite/index.ts b/src/test/suite/index.ts
deleted file mode 100644
index 92d933c..0000000
--- a/src/test/suite/index.ts
+++ /dev/null
@@ -1,110 +0,0 @@
-/* eslint-disable import/newline-after-import */
-// Enable `debug` coloring in VS Code _Debug Console_
-// https://github.com/debug-js/debug/issues/641#issuecomment-490706752
-import path from 'node:path'
-
-import debug from 'debug'
-import { glob } from 'glob'
-import Mocha, { MochaOptions } from 'mocha'
-;(process as any).browser = true
-;(global as any).window = { process: { type: 'renderer' } }
-
-const tty = require('node:tty')
-
-const NYC = require('nyc')
-const baseConfig = require('@istanbuljs/nyc-config-typescript')
-
-if (!tty.getWindowSize) {
- tty.getWindowSize = (): number[] => {
- return [80, 75]
- }
-}
-
-export async function run(): Promise {
- // nyc setup
-
- let nyc: any | undefined
- if (runTestCoverage()) {
- nyc = new NYC({
- ...baseConfig,
- cwd: path.join(__dirname, '..', '..', '..'),
- reporter: ['text'],
- all: true,
- silent: false,
- instrument: true,
- hookRequire: true,
- hookRunInContext: true,
- hookRunInThisContext: true,
- include: ['out/**/*.js'],
- exclude: ['out/test/**'],
- })
-
- await nyc.reset()
- await nyc.wrap()
- Object.keys(require.cache)
- .filter((f) => nyc.exclude.shouldInstrument(f))
- .forEach((m) => {
- console.warn('Module loaded before NYC, invalidating:', m)
- delete require.cache[m]
- require(m)
- })
- }
-
- // mocha setup
- const options: MochaOptions = {
- ui: 'bdd',
- color: true,
- timeout: noTestTimeout() ? 0 : 2_000,
- }
- // The debugger cannot be enabled with the `DEBUG` env variable.
- // https://github.com/microsoft/vscode/blob/c248f9ec0cf272351175ccf934054b18ffbf18c6/src/vs/base/common/processes.ts#L141
- if (typeof process.env['TEST_DEBUG'] === 'string') {
- debug.enable(process.env['TEST_DEBUG'])
- debug.selectColor(process.env['TEST_DEBUG'])
- }
- const mocha = new Mocha(options)
- const testsRoot = path.resolve(__dirname, '..')
-
- const files = await glob('**/*.test.js', { cwd: testsRoot })
- files.forEach((file) => mocha.addFile(path.resolve(testsRoot, file)))
- const failures = await new Promise((resolve) => mocha.run(resolve))
-
- if (nyc) {
- // write coverage
- await nyc.writeCoverageFile()
- console.log(await captureStdout(nyc.report.bind(nyc)))
- }
-
- if (failures > 0) {
- throw new Error(`${failures} tests failed.`)
- }
-}
-
-function noTestTimeout(): boolean {
- return (
- typeof process.env.NO_TEST_TIMEOUT === 'string' &&
- /true/i.test(process.env.NO_TEST_TIMEOUT)
- )
-}
-
-function runTestCoverage(): boolean {
- return !(
- typeof process.env.NO_TEST_COVERAGE === 'string' &&
- /true/i.test(process.env.NO_TEST_COVERAGE)
- )
-}
-
-async function captureStdout(task: () => Promise): Promise {
- const originalWrite = process.stdout.write
- let buffer = ''
- process.stdout.write = (s) => {
- buffer = buffer + s
- return true
- }
- try {
- await task()
- } finally {
- process.stdout.write = originalWrite
- }
- return buffer
-}
diff --git a/src/test/suite/markdown.test.ts b/src/test/suite/markdown.test.ts
new file mode 100644
index 0000000..25807ec
--- /dev/null
+++ b/src/test/suite/markdown.test.ts
@@ -0,0 +1,442 @@
+import assert from 'node:assert/strict'
+
+import type { DecodeResult } from 'trbr'
+
+import {
+ collectDecodedStackFrames,
+ countHeavyVars,
+ eventDisplayLabel,
+ formatLocalTimestamp,
+ frameToDescription,
+ frameToLabel,
+ toCapturerStateDumpMarkdown,
+ toEventReportDocumentMarkdown,
+ toEventTreeItemMarkdown,
+ toParsedFrameLocation,
+ toRootTreeItemMarkdown,
+} from '../../capturer/markdown'
+import type {
+ CapturerEventSummary,
+ CapturerReadiness,
+ CapturerRuntime,
+} from '../../capturer/model'
+
+function createReadiness(
+ overrides: Partial = {}
+): CapturerReadiness {
+ return {
+ sketchExists: true,
+ hasCompileSummary: true,
+ elfPath: '/tmp/sketch/build/sketch.ino.elf',
+ buildOptions: {
+ buildPath: '/tmp/sketch/build',
+ optionsPath: '/tmp/sketch/build/build.options.json',
+ fqbn: 'esp32:esp32:esp32c3',
+ optimizationFlags: '-Og -g3',
+ flags: [],
+ },
+ boardName: 'ESP32C3 Dev Module',
+ selectedBoardName: 'ESP32C3 Dev Module',
+ selectedBoardFqbn: 'esp32:esp32:esp32c3',
+ configuredPortDetected: true,
+ ...overrides,
+ }
+}
+
+function createRuntime(
+ overrides: Partial = {},
+ readinessOverrides: Partial = {}
+): CapturerRuntime {
+ return {
+ config: {
+ id: 'serial:///dev/mock0|esp32:esp32:esp32c3|/tmp/sketch',
+ fqbn: 'esp32:esp32:esp32c3',
+ sketchPath: '/tmp/sketch',
+ port: {
+ protocol: 'serial',
+ address: '/dev/mock0',
+ },
+ },
+ capturer: {} as any,
+ eventsBySignature: new Map(),
+ eventsById: new Map(),
+ unsubscribeDetected: () => {
+ // noop
+ },
+ unsubscribeUpdated: () => {
+ // noop
+ },
+ monitorSubscriptions: [],
+ sourceAvailabilityByPath: new Map(),
+ lastChunkEndedWithLineBreak: true,
+ processedBytes: 0,
+ readiness: createReadiness(readinessOverrides),
+ ...overrides,
+ } as CapturerRuntime
+}
+
+function createDecoded(): DecodeResult {
+ return {
+ faultInfo: {
+ faultMessage:
+ "Guru Meditation Error: Core 1 panic'ed (StoreProhibited). Exception was unhandled.",
+ coreId: 1,
+ programCounter: {
+ location: {
+ regAddr: '0x400d1111',
+ lineNumber: '7',
+ file: '/tmp/sketch/pc.cpp',
+ method: 'pc',
+ } as any,
+ },
+ },
+ globals: [{ name: 'g', value: '1' }] as any,
+ stacktraceLines: [
+ {
+ regAddr: '0x400d1234',
+ lineNumber: '4',
+ file: '/tmp/sketch/main.ino',
+ method: 'loop',
+ args: [{ name: 'a' }, { name: 'b' }],
+ locals: [{ name: 'l' }],
+ globals: [{ name: 'gg' }],
+ } as any,
+ {
+ regAddr: '0x400d0000',
+ lineNumber: '0',
+ } as any,
+ ],
+ } as any
+}
+
+function createEvent(
+ overrides: Partial = {}
+): CapturerEventSummary {
+ return {
+ eventId: 'event-1',
+ signature: 'sig-1',
+ reason:
+ "Guru Meditation Error: Core 1 panic'ed (StoreProhibited). Exception was unhandled.",
+ kind: 'xtensa',
+ createdAt: 1_000,
+ captureSessionId: 'session-1',
+ captureSessionLabel: 'abcd1234 @ 1/1/2026',
+ count: 2,
+ firstSeenAt: 1_000,
+ lastSeenAt: 2_000,
+ rawText: 'Guru Meditation Error\n```raw```',
+ programCounter: 0x400d1234,
+ faultCode: 29,
+ decodeState: 'decoded',
+ decodedResult: createDecoded(),
+ lastDecodeDurationMs: 1234,
+ lastEvaluateDurationMs: 321,
+ lastDecodeHeavyVarsCount: 7,
+ ...overrides,
+ }
+}
+
+describe('markdown', () => {
+ it('renders root markdown with problems when compile output is missing', () => {
+ const runtime = createRuntime(
+ {},
+ {
+ hasCompileSummary: false,
+ elfPath: undefined,
+ configuredPortDetected: false,
+ }
+ )
+ const markdown = toRootTreeItemMarkdown(runtime).value
+ assert.equal(markdown.includes('Quick Fixes:'), false)
+ assert.equal(markdown.includes('Problems:'), true)
+ assert.equal(markdown.includes('No device found.'), true)
+ assert.equal(
+ markdown.includes(
+ 'Use $(light-bulb) `Quick Fixes...` from the view item menu to resolve issues.'
+ ),
+ true
+ )
+ })
+
+ it('renders compact root markdown with status and board comparison', () => {
+ const ready = createRuntime()
+ const readyMarkdown = toRootTreeItemMarkdown(ready).value
+ assert.equal(readyMarkdown.includes('$(pass)'), true)
+ assert.equal(readyMarkdown.includes('- Capturer Board:'), true)
+ assert.equal(readyMarkdown.includes('- Sketch Board:'), true)
+ assert.equal(readyMarkdown.includes('Compile Details:'), false)
+ assert.equal(readyMarkdown.includes('Optimization Flags'), false)
+ assert.equal(readyMarkdown.includes('ELF SHA256:'), false)
+ assert.equal(readyMarkdown.includes('- Sketch:'), false)
+ assert.equal(
+ readyMarkdown.includes('`/tmp/sketch/build/sketch.ino.elf`'),
+ false
+ )
+
+ const capturing = createRuntime({
+ monitor: {} as any,
+ monitorState: 'running',
+ })
+ assert.equal(
+ toRootTreeItemMarkdown(capturing).value.includes('$(pulse)'),
+ true
+ )
+
+ const suspended = createRuntime({
+ monitor: {} as any,
+ monitorState: 'suspended',
+ })
+ assert.equal(
+ toRootTreeItemMarkdown(suspended).value.includes('$(sync~spin)'),
+ true
+ )
+
+ const warning = createRuntime(
+ {},
+ { selectedBoardFqbn: 'esp32:esp32:esp32da' }
+ )
+ const warningMarkdown = toRootTreeItemMarkdown(warning).value
+ assert.equal(warningMarkdown.includes('$(warning)'), true)
+ assert.equal(warningMarkdown.includes('Quick Fixes:'), false)
+ assert.equal(
+ warningMarkdown.includes(
+ 'Use $(light-bulb) `Quick Fixes...` from the view item menu to resolve issues.'
+ ),
+ true
+ )
+ assert.equal(readyMarkdown.includes('Quick Fixes:'), false)
+ assert.equal(
+ readyMarkdown.includes(
+ 'Use $(light-bulb) `Quick Fixes...` from the view item menu to resolve issues.'
+ ),
+ false
+ )
+
+ const withElfIdentity = createRuntime({
+ elfIdentity: {
+ path: '/tmp/sketch/build/sketch.ino.elf',
+ size: 10,
+ mtimeMs: 2_000,
+ sha256: '95a6c833',
+ sha256Short: '95a6c833',
+ sessionId: 'session',
+ },
+ })
+ const withElfMarkdown = toRootTreeItemMarkdown(withElfIdentity).value
+ assert.equal(withElfMarkdown.includes('ELF SHA256:'), false)
+ assert.equal(withElfMarkdown.includes('ELF Modified:'), false)
+
+ const withSketchConfigOptions = createRuntime(
+ {},
+ {
+ selectedBoardFqbn:
+ 'esp32:esp32:esp32da:CPUFreq=240,PartitionScheme=huge_app',
+ }
+ )
+ const withSketchConfigMarkdown = toRootTreeItemMarkdown(
+ withSketchConfigOptions
+ ).value
+ assert.equal(
+ withSketchConfigMarkdown.includes(
+ 'esp32:esp32:esp32da:CPUFreq=240,PartitionScheme=huge_app'
+ ),
+ true
+ )
+
+ const withBothFqbnMismatches = createRuntime(
+ {
+ config: {
+ ...createRuntime().config,
+ fqbn: 'esp32:esp32:esp32da',
+ },
+ },
+ {
+ selectedBoardFqbn: 'esp32:esp32:esp32c3',
+ buildOptions: {
+ buildPath: '/tmp/sketch/build',
+ optionsPath: '/tmp/sketch/build/build.options.json',
+ fqbn: 'esp32:esp32:esp32c3',
+ optimizationFlags: '-Og -g3',
+ flags: [],
+ },
+ }
+ )
+ const bothMismatchMarkdown = toRootTreeItemMarkdown(
+ withBothFqbnMismatches
+ ).value
+ assert.equal(bothMismatchMarkdown.includes('Problems:'), true)
+ assert.equal(
+ bothMismatchMarkdown.includes(
+ 'Selected sketch FQBN differs from capturer FQBN'
+ ),
+ true
+ )
+ assert.equal(
+ bothMismatchMarkdown.includes('Build FQBN differs from capturer FQBN'),
+ true
+ )
+ assert.equal(
+ bothMismatchMarkdown.includes(
+ 'Use $(light-bulb) `Quick Fixes...` from the view item menu to resolve issues.'
+ ),
+ true
+ )
+ })
+
+ it('renders compact event markdown tooltip', () => {
+ const runtime = createRuntime()
+ const summary = createEvent()
+ const markdown = toEventTreeItemMarkdown(runtime, summary).value
+ const tooltipContent = markdown.split('\n----\n')[0]
+ const tooltipLines = tooltipContent
+ .split('\n')
+ .map((line) => line.trim())
+ .filter((line) => line.length > 0)
+
+ assert.equal(markdown.includes('#### Crash Summary'), false)
+ assert.equal(markdown.includes('#### Build Context'), false)
+ assert.equal(markdown.includes('#### Captured Crash'), false)
+ assert.equal(markdown.includes('Decoded Exception'), false)
+ assert.equal(markdown.includes('- Crash:'), true)
+ assert.equal(markdown.includes('- Location:'), true)
+ assert.equal(
+ markdown.includes('- Location: `main.ino:4` in `loop (a, b)`'),
+ true
+ )
+ assert.equal(markdown.includes('- Build:'), true)
+ assert.equal(markdown.includes('- Repeats:'), false)
+ assert.equal(markdown.includes('- Count:'), false)
+ assert.equal(tooltipLines.length <= 9, true)
+ })
+
+ it('renders compact tooltip fallback when location is unknown', () => {
+ const runtime = createRuntime()
+ const summary = createEvent({
+ decodedResult: undefined,
+ decodeState: 'detected',
+ })
+ const markdown = toEventTreeItemMarkdown(runtime, summary).value
+ assert.equal(markdown.includes('- Location: unknown (decode needed)'), true)
+ })
+
+ it('renders full event report markdown with payload and session details', () => {
+ const runtime = createRuntime({
+ elfIdentity: {
+ path: '/tmp/sketch/build/sketch.ino.elf',
+ size: 10,
+ mtimeMs: 2_000,
+ sha256: '95a6c833',
+ sha256Short: '95a6c833',
+ sessionId: 'session',
+ },
+ })
+ const summary = createEvent({ decodeError: 'decode fail' })
+ const markdown = toEventReportDocumentMarkdown(runtime, summary)
+ assert.equal(markdown.includes('# ESP Crash Event Report'), true)
+ assert.equal(markdown.includes('#### Captured Crash'), true)
+ assert.equal(markdown.includes('#### Decoded Stacktrace'), true)
+ assert.equal(markdown.includes('Decode Error'), true)
+ assert.equal(markdown.includes('Session Details'), true)
+ assert.equal(markdown.includes('Capturer Board'), true)
+ assert.equal(markdown.includes('Sketch Board'), true)
+ assert.equal(markdown.includes('Sketch Folder'), true)
+ assert.equal(markdown.includes('Build FQBN'), true)
+ assert.equal(markdown.includes('Optimization Flags'), true)
+ assert.equal(markdown.includes('ELF SHA256'), true)
+ assert.equal(markdown.includes('Program Counter'), true)
+ assert.equal(markdown.includes('``\\`raw``\\`'), true)
+ })
+
+ it('escapes backslashes and backticks in inline code spans', () => {
+ const runtime = createRuntime(
+ {
+ config: {
+ id: 'serial://C:\\tmp\\runtime`id',
+ fqbn: 'esp32:esp32:esp32c3',
+ sketchPath: 'C:\\tmp\\sketch`name',
+ port: {
+ protocol: 'serial',
+ address: 'COM5',
+ },
+ } as any,
+ },
+ {
+ elfPath: 'C:\\tmp\\build\\sketch`name.ino.elf',
+ }
+ )
+ const summary = createEvent({
+ signature: 'sig\\path`tick',
+ captureSessionId: 'session\\id`1',
+ })
+
+ const markdown = toEventReportDocumentMarkdown(runtime, summary)
+ assert.equal(
+ markdown.includes('`C:\\\\tmp\\\\build\\\\sketch\\`name.ino.elf`'),
+ true
+ )
+ assert.equal(markdown.includes('`sig\\\\path\\`tick`'), true)
+ assert.equal(markdown.includes('`session\\\\id\\`1`'), true)
+ })
+
+ it('renders capturer state dump for empty and populated events', () => {
+ const runtime = createRuntime({
+ processedBytes: 16 * 1024,
+ processedFirstAt: 1_000,
+ processedLastAt: 3_000,
+ monitorState: 'running',
+ elfIdentity: {
+ path: '/tmp/sketch/build/sketch.ino.elf',
+ size: 123,
+ mtimeMs: 2_000,
+ sha256: 'abcdef',
+ sha256Short: 'abcdef',
+ sessionId: 'session',
+ },
+ })
+ const emptyDump = toCapturerStateDumpMarkdown(
+ runtime,
+ { lines: [], bytes: [], byteLength: 32 } as any,
+ []
+ )
+ assert.equal(emptyDump.includes('_No crash events were detected._'), true)
+
+ const fullDump = toCapturerStateDumpMarkdown(
+ runtime,
+ { lines: ['a', 'b'], bytes: [], byteLength: 2 * 1024 * 1024 } as any,
+ [createEvent()]
+ )
+ assert.equal(fullDump.includes('MiB'), true)
+ assert.equal(fullDump.includes('KiB'), true)
+ assert.equal(fullDump.includes('Bandwidth:'), true)
+ assert.equal(fullDump.includes('## Distinct Events'), true)
+ })
+
+ it('formats event labels, frames and parsed locations', () => {
+ const event = createEvent({ decodedResult: undefined })
+ assert.equal(eventDisplayLabel(event), event.reason)
+
+ const decoded = createDecoded()
+ event.decodedResult = decoded
+ assert.equal(eventDisplayLabel(event), 'Guru Meditation Error · main.ino')
+
+ assert.equal(collectDecodedStackFrames(undefined).length, 0)
+ assert.equal(collectDecodedStackFrames(decoded).length, 2)
+ assert.equal(countHeavyVars(decoded), 3)
+
+ const first = collectDecodedStackFrames(decoded)[0]
+ assert.equal(frameToLabel(first).includes('loop (a, b)'), true)
+ assert.equal(frameToDescription(first), 'main.ino:4')
+ assert.deepEqual(toParsedFrameLocation(first), {
+ file: '/tmp/sketch/main.ino',
+ line: 4,
+ })
+ assert.equal(frameToLabel(undefined), 'Unknown frame')
+ assert.equal(frameToDescription(undefined), undefined)
+ assert.equal(toParsedFrameLocation(undefined), undefined)
+ assert.equal(
+ toParsedFrameLocation({ regAddr: '0x1', lineNumber: '0' } as any),
+ undefined
+ )
+ assert.equal(typeof formatLocalTimestamp(0), 'string')
+ })
+})
diff --git a/src/test/suite/runtimeModel.test.ts b/src/test/suite/runtimeModel.test.ts
new file mode 100644
index 0000000..e9c4ac7
--- /dev/null
+++ b/src/test/suite/runtimeModel.test.ts
@@ -0,0 +1,293 @@
+import assert from 'node:assert/strict'
+
+import {
+ collectRuntimeProblems,
+ eventContextValue,
+ getRuntimeStatus,
+ isReadyToRecord,
+ isRuntimeConnected,
+ resolveRootDescription,
+ rootContextValue,
+ sanitizeFqbn,
+ toRootLabel,
+ type RuntimeLike,
+} from '../../capturer/runtimeModel'
+
+function createRuntime(
+ overrides: Partial = {},
+ readinessOverrides: Partial = {}
+): RuntimeLike {
+ const base: RuntimeLike = {
+ config: {
+ id: 'serial:///dev/mock0|esp32:esp32:esp32c3|/tmp/sketch',
+ fqbn: 'esp32:esp32:esp32c3',
+ sketchPath: '/tmp/sketch',
+ port: {
+ protocol: 'serial',
+ address: '/dev/mock0',
+ },
+ },
+ readiness: {
+ sketchExists: true,
+ hasCompileSummary: true,
+ elfPath: '/tmp/sketch/build/sketch.ino.elf',
+ boardName: 'ESP32C3 Dev Module',
+ selectedBoardName: 'ESP32C3 Dev Module',
+ selectedBoardFqbn: 'esp32:esp32:esp32c3',
+ configuredPortDetected: true,
+ ...readinessOverrides,
+ },
+ monitorState: undefined,
+ eventsBySignature: new Map(),
+ ...overrides,
+ }
+ return base
+}
+
+describe('runtimeModel', () => {
+ it('returns ready status and root description when runtime is healthy', () => {
+ const runtime = createRuntime()
+ assert.equal(getRuntimeStatus(runtime), 'Ready')
+ assert.equal(resolveRootDescription(runtime), 'Ready')
+ assert.equal(isReadyToRecord(runtime), true)
+ })
+
+ it('returns capturing status and context when monitor is running', () => {
+ const runtime = createRuntime({ monitor: {}, monitorState: 'running' })
+ assert.equal(getRuntimeStatus(runtime), 'Capturing crashes')
+ assert.equal(resolveRootDescription(runtime), 'Capturing crashes')
+ assert.equal(rootContextValue(runtime), 'espCapturerRootCapturing')
+ })
+
+ it('returns disconnected description when configured port is not detected', () => {
+ const runtime = createRuntime(
+ {},
+ {
+ configuredPortDetected: false,
+ }
+ )
+ assert.equal(getRuntimeStatus(runtime), 'Disconnected')
+ assert.equal(resolveRootDescription(runtime), 'Not connected')
+ assert.equal(isRuntimeConnected(runtime), false)
+ })
+
+ it('returns invalid fqbn status and problem', () => {
+ const runtime = createRuntime({
+ config: {
+ ...createRuntime().config,
+ fqbn: 'invalid-fqbn',
+ },
+ })
+ const problems = collectRuntimeProblems(runtime)
+ assert.equal(
+ problems.some((problem) => problem.code === 'invalid-fqbn'),
+ true
+ )
+ assert.equal(getRuntimeStatus(runtime), 'Invalid FQBN')
+ })
+
+ it('returns warnings when sketch and build fqbn differ from capturer fqbn', () => {
+ const runtime = createRuntime(
+ {
+ config: {
+ ...createRuntime().config,
+ fqbn: 'esp32:esp32:esp32da',
+ },
+ },
+ {
+ selectedBoardFqbn: 'esp32:esp32:esp32c3',
+ buildOptions: {
+ fqbn: 'esp32:esp32:esp32c3',
+ },
+ }
+ )
+ const problems = collectRuntimeProblems(runtime)
+ assert.equal(
+ problems.some(
+ (problem) => problem.code === 'fqbn-mismatch-selected-engine'
+ ),
+ true
+ )
+ assert.equal(
+ problems.some(
+ (problem) => problem.code === 'fqbn-mismatch-selected-build'
+ ),
+ true
+ )
+ assert.equal(getRuntimeStatus(runtime), 'Warning')
+ assert.equal(resolveRootDescription(runtime), 'Sketch FQBN mismatch')
+ })
+
+ it('returns build mismatch warning when build fqbn differs from capturer fqbn', () => {
+ const runtime = createRuntime(
+ {},
+ {
+ buildOptions: {
+ fqbn: 'esp32:esp32:esp32da',
+ },
+ }
+ )
+ const problems = collectRuntimeProblems(runtime)
+ assert.equal(
+ problems.some(
+ (problem) => problem.code === 'fqbn-mismatch-selected-engine'
+ ),
+ false
+ )
+ assert.equal(
+ problems.some(
+ (problem) => problem.code === 'fqbn-mismatch-selected-build'
+ ),
+ true
+ )
+ assert.equal(resolveRootDescription(runtime), 'Build FQBN mismatch')
+ })
+
+ it('uses expected root context values based on state and event count', () => {
+ const ready = createRuntime()
+ assert.equal(rootContextValue(ready), 'espCapturerRootReady')
+
+ const readyWithEvents = createRuntime({
+ eventsBySignature: new Map([['sig', {}]]),
+ })
+ assert.equal(
+ rootContextValue(readyWithEvents),
+ 'espCapturerRootReadyHasEvents'
+ )
+
+ const stoppedWithEvents = createRuntime(
+ {
+ monitor: undefined,
+ monitorState: 'disconnected',
+ eventsBySignature: new Map([['sig', {}]]),
+ },
+ { configuredPortDetected: false }
+ )
+ assert.equal(
+ rootContextValue(stoppedWithEvents),
+ 'espCapturerRootStoppedHasEvents'
+ )
+
+ const stoppedFixable = createRuntime(
+ {},
+ {
+ hasCompileSummary: false,
+ }
+ )
+ assert.equal(
+ rootContextValue(stoppedFixable),
+ 'espCapturerRootStoppedFixableCompile'
+ )
+
+ const stoppedFixableWithEvents = createRuntime(
+ {
+ eventsBySignature: new Map([['sig', {}]]),
+ },
+ {
+ hasCompileSummary: false,
+ }
+ )
+ assert.equal(
+ rootContextValue(stoppedFixableWithEvents),
+ 'espCapturerRootStoppedFixableCompileHasEvents'
+ )
+
+ const capturingFixable = createRuntime(
+ {
+ monitor: {},
+ monitorState: 'running',
+ },
+ {
+ selectedBoardFqbn: 'esp32:esp32:esp32da',
+ }
+ )
+ assert.equal(
+ rootContextValue(capturingFixable),
+ 'espCapturerRootCapturingFixableSync'
+ )
+ })
+
+ it('maps event context values by decode state', () => {
+ assert.equal(eventContextValue(undefined), 'espCapturerEvent')
+ assert.equal(
+ eventContextValue({ decodeState: 'detected' }),
+ 'espCapturerEventDetected'
+ )
+ assert.equal(
+ eventContextValue({ decodeState: 'decoding' }),
+ 'espCapturerEventDecoding'
+ )
+ assert.equal(
+ eventContextValue({ decodeState: 'decoded' }),
+ 'espCapturerEventDecoded'
+ )
+ assert.equal(
+ eventContextValue({ decodeState: 'evaluating' }),
+ 'espCapturerEventEvaluating'
+ )
+ assert.equal(
+ eventContextValue({ decodeState: 'evaluated' }),
+ 'espCapturerEventEvaluated'
+ )
+ })
+
+ it('formats root label and sanitizes fqbn', () => {
+ const runtime = createRuntime()
+ assert.equal(
+ toRootLabel(runtime),
+ '/dev/mock0 · sketch · ESP32C3 Dev Module'
+ )
+ const runtimeWithoutBoardName = createRuntime(
+ {},
+ {
+ boardName: undefined,
+ selectedBoardName: undefined,
+ }
+ )
+ assert.equal(
+ toRootLabel(runtimeWithoutBoardName),
+ '/dev/mock0 · sketch · esp32c3'
+ )
+ const runtimeWithMismatchedSelectedBoard = createRuntime(
+ {
+ config: {
+ ...createRuntime().config,
+ fqbn: 'esp32:esp32:esp32da',
+ },
+ },
+ {
+ boardName: undefined,
+ selectedBoardName: 'ESP32C3 Dev Module',
+ selectedBoardFqbn: 'esp32:esp32:esp32c3',
+ }
+ )
+ assert.equal(
+ toRootLabel(runtimeWithMismatchedSelectedBoard),
+ '/dev/mock0 · sketch · esp32da'
+ )
+ assert.equal(sanitizeFqbn('esp32:esp32:esp32c3'), 'esp32:esp32:esp32c3')
+ assert.equal(sanitizeFqbn('invalid'), undefined)
+ })
+
+ it('returns generic error status for runtime-error and resolves description message', () => {
+ const runtime = createRuntime({
+ lastError: 'monitor failed',
+ })
+ assert.equal(getRuntimeStatus(runtime), 'Error')
+ assert.equal(resolveRootDescription(runtime), 'monitor failed')
+ })
+
+ it('handles explicit connected/disconnected runtime connectivity', () => {
+ const connected = createRuntime(
+ { monitorState: 'connected' },
+ { configuredPortDetected: undefined }
+ )
+ assert.equal(isRuntimeConnected(connected), true)
+
+ const disconnected = createRuntime(
+ { monitorState: 'disconnected' },
+ { configuredPortDetected: undefined }
+ )
+ assert.equal(isRuntimeConnected(disconnected), false)
+ })
+})
diff --git a/src/test/suite/treeModel.test.ts b/src/test/suite/treeModel.test.ts
new file mode 100644
index 0000000..c598867
--- /dev/null
+++ b/src/test/suite/treeModel.test.ts
@@ -0,0 +1,84 @@
+import assert from 'node:assert/strict'
+
+import type { CapturerConfig, CapturerRuntime } from '../../capturer/model'
+import { CapturerTreeModel } from '../../capturer/treeModel'
+
+function makeConfig(id: string): CapturerConfig {
+ return {
+ id,
+ fqbn: 'esp32:esp32:esp32c3',
+ sketchPath: `/tmp/${id}`,
+ port: { protocol: 'serial', address: `/dev/${id}` },
+ }
+}
+
+function makeRuntime(
+ signatures: Array<{ signature: string; createdAt: number }>
+) {
+ return {
+ eventsBySignature: new Map(
+ signatures.map((item) => [
+ item.signature,
+ { signature: item.signature, createdAt: item.createdAt },
+ ])
+ ),
+ } as unknown as CapturerRuntime
+}
+
+describe('treeModel', () => {
+ it('creates stable root and event node identities', () => {
+ const model = new CapturerTreeModel()
+ const rootA = model.getRootNode('a')
+ const rootA2 = model.getRootNode('a')
+ assert.strictEqual(rootA, rootA2)
+
+ const event = model.getEventNode('a', 'sig')
+ const event2 = model.getEventNode('a', 'sig')
+ assert.strictEqual(event, event2)
+ })
+
+ it('returns root nodes and sorted event nodes', () => {
+ const model = new CapturerTreeModel()
+ const configs = [makeConfig('a'), makeConfig('b')]
+ const roots = model.getRootNodes(configs)
+ assert.deepEqual(
+ roots.map((root) => root.configId),
+ ['a', 'b']
+ )
+
+ const empty = model.getEventNodes('a', undefined)
+ assert.deepEqual(empty, [])
+
+ const runtime = makeRuntime([
+ { signature: 'older', createdAt: 1 },
+ { signature: 'newer', createdAt: 2 },
+ ])
+ const events = model.getEventNodes('a', runtime)
+ assert.deepEqual(
+ events.map((event) => event.signature),
+ ['newer', 'older']
+ )
+ })
+
+ it('creates frame nodes and clears/removes cached event nodes', () => {
+ const model = new CapturerTreeModel()
+ const frames = model.getFrameNodes('a', 'sig', 3)
+ assert.equal(frames.length, 3)
+ assert.deepEqual(
+ frames.map((frame) => frame.frameIndex),
+ [0, 1, 2]
+ )
+
+ const beforeRemove = model.getEventNode('a', 'sig-a')
+ model.removeEventNode('a', 'sig-a')
+ const afterRemove = model.getEventNode('a', 'sig-a')
+ assert.notStrictEqual(beforeRemove, afterRemove)
+
+ const keep = model.getEventNode('b', 'sig-b')
+ model.getEventNode('a', 'sig-c')
+ model.clearConfig('a')
+ const recreated = model.getEventNode('a', 'sig-c')
+ assert.notStrictEqual(recreated, beforeRemove)
+ assert.strictEqual(model.getEventNode('b', 'sig-b'), keep)
+ })
+})
diff --git a/webpack.config.js b/webpack.config.js
index 665a89e..417ae8b 100644
--- a/webpack.config.js
+++ b/webpack.config.js
@@ -13,6 +13,13 @@ const extensionConfig = {
path: path.resolve(__dirname, 'dist'),
filename: 'extension.js',
libraryTarget: 'commonjs2',
+ devtoolModuleFilenameTemplate: (info) => {
+ const normalizedPath = info.absoluteResourcePath.replace(/\\/g, '/')
+ return normalizedPath.startsWith('/')
+ ? `file://${normalizedPath}`
+ : `file:///${normalizedPath}`
+ },
+ devtoolFallbackModuleFilenameTemplate: 'file:///[absolute-resource-path]',
},
externals: {
vscode: 'commonjs vscode',