diff --git a/.github/ci-modules.yml b/.github/ci-modules.yml index 0171fffa..763f98cd 100644 --- a/.github/ci-modules.yml +++ b/.github/ci-modules.yml @@ -16,6 +16,7 @@ rust: vscode: - "editors/vscode/**" + - "packages/vide-extension-shared/**" - ".github/ci-modules.yml" - ".github/workflows/ci.yml" @@ -33,7 +34,13 @@ package: - "editors/vscode/scripts/**" - "editors/vscode/src/**" - "editors/vscode/syntaxes/**" + - "packages/vide-extension-shared/**" + - "playground/package.json" + - "playground/package-lock.json" + - "playground/scripts/build-vide-wasm.mjs" + - "playground/scripts/script-utils.mjs" - ".github/actions/setup-rust/**" - ".github/actions/setup-sccache/**" + - ".github/actions/setup-emscripten/**" - ".github/ci-modules.yml" - ".github/workflows/ci.yml" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7a0efccf..3ed630fb 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,6 +16,9 @@ concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: ${{ github.ref != 'refs/heads/master' }} +env: + EMSDK_VERSION: 5.0.2 + jobs: changes: name: Detect changes @@ -112,6 +115,77 @@ jobs: - name: Test extension run: npm test + vscode-web-smoke: + name: VS Code Web Smoke + needs: changes + if: github.event_name == 'workflow_dispatch' || needs.changes.outputs.vscode == 'true' || needs.changes.outputs.package == 'true' + runs-on: ubuntu-latest + defaults: + run: + working-directory: editors/vscode + steps: + - uses: actions/checkout@v4 + - name: Restore playground WASM cache + id: playground-wasm-cache + uses: actions/cache/restore@v4 + with: + path: playground/public/wasm + key: playground-wasm-${{ runner.os }}-${{ env.EMSDK_VERSION }}-${{ hashFiles('Cargo.toml', 'Cargo.lock', 'build.rs', 'rust-toolchain.toml', 'src/**', 'crates/**', 'playground/scripts/build-vide-wasm.mjs', 'playground/scripts/script-utils.mjs', '.github/actions/setup-emscripten/**', '.github/actions/setup-rust/**') }} + - name: Verify cached playground WASM + if: steps.playground-wasm-cache.outputs.cache-hit == 'true' + working-directory: . + run: | + test -s playground/public/wasm/vide-lsp.js + test -s playground/public/wasm/vide-core.js + test -s playground/public/wasm/vide-core.wasm + - name: Install Rust + if: steps.playground-wasm-cache.outputs.cache-hit != 'true' + uses: ./.github/actions/setup-rust + with: + components: rustfmt + - name: Setup sccache + if: steps.playground-wasm-cache.outputs.cache-hit != 'true' + uses: ./.github/actions/setup-sccache + with: + cmake-launcher: "true" + - name: Rust Cache + if: steps.playground-wasm-cache.outputs.cache-hit != 'true' + uses: Swatinem/rust-cache@v2 + continue-on-error: true + with: + shared-key: vscode-web-wasm-${{ runner.os }}-${{ env.EMSDK_VERSION }} + key: wasm-src-${{ hashFiles('Cargo.toml', 'Cargo.lock', 'build.rs', 'rust-toolchain.toml', 'src/**', 'crates/**', 'playground/scripts/build-vide-wasm.mjs', 'playground/scripts/script-utils.mjs', '.github/actions/setup-emscripten/**', '.github/actions/setup-rust/**') }} + cache-workspace-crates: true + cache-on-failure: true + - name: Install Emscripten SDK + if: steps.playground-wasm-cache.outputs.cache-hit != 'true' + uses: ./.github/actions/setup-emscripten + with: + version: ${{ env.EMSDK_VERSION }} + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: 22 + cache: npm + cache-dependency-path: editors/vscode/package-lock.json + - name: Install dependencies + run: npm ci + - name: Build playground WASM + if: steps.playground-wasm-cache.outputs.cache-hit != 'true' + working-directory: playground + run: | + source "${EMSDK}/emsdk_env.sh" + npm run build:wasm + - name: Save playground WASM cache + if: steps.playground-wasm-cache.outputs.cache-hit != 'true' + uses: actions/cache/save@v4 + continue-on-error: true + with: + path: playground/public/wasm + key: ${{ steps.playground-wasm-cache.outputs.cache-primary-key }} + - name: Web smoke test + run: npm run test:web + dev-package: name: Dev Package (${{ matrix.target }}) needs: changes @@ -221,6 +295,94 @@ jobs: if-no-files-found: error retention-days: 14 + dev-web-package: + name: Dev Package (web) + needs: changes + if: github.event_name != 'pull_request' && (github.event_name == 'workflow_dispatch' || needs.changes.outputs.package == 'true') + runs-on: ubuntu-latest + defaults: + run: + working-directory: editors/vscode + steps: + - uses: actions/checkout@v4 + - name: Restore playground WASM cache + id: playground-wasm-cache + uses: actions/cache/restore@v4 + with: + path: playground/public/wasm + key: playground-wasm-${{ runner.os }}-${{ env.EMSDK_VERSION }}-${{ hashFiles('Cargo.toml', 'Cargo.lock', 'build.rs', 'rust-toolchain.toml', 'src/**', 'crates/**', 'playground/scripts/build-vide-wasm.mjs', 'playground/scripts/script-utils.mjs', '.github/actions/setup-emscripten/**', '.github/actions/setup-rust/**') }} + - name: Verify cached playground WASM + if: steps.playground-wasm-cache.outputs.cache-hit == 'true' + working-directory: . + run: | + test -s playground/public/wasm/vide-lsp.js + test -s playground/public/wasm/vide-core.js + test -s playground/public/wasm/vide-core.wasm + - name: Install Rust + if: steps.playground-wasm-cache.outputs.cache-hit != 'true' + uses: ./.github/actions/setup-rust + with: + components: rustfmt + - name: Setup sccache + if: steps.playground-wasm-cache.outputs.cache-hit != 'true' + uses: ./.github/actions/setup-sccache + with: + cmake-launcher: "true" + - name: Rust Cache + if: steps.playground-wasm-cache.outputs.cache-hit != 'true' + uses: Swatinem/rust-cache@v2 + continue-on-error: true + with: + shared-key: vscode-web-wasm-${{ runner.os }}-${{ env.EMSDK_VERSION }} + key: wasm-src-${{ hashFiles('Cargo.toml', 'Cargo.lock', 'build.rs', 'rust-toolchain.toml', 'src/**', 'crates/**', 'playground/scripts/build-vide-wasm.mjs', 'playground/scripts/script-utils.mjs', '.github/actions/setup-emscripten/**', '.github/actions/setup-rust/**') }} + cache-workspace-crates: true + cache-on-failure: true + - name: Install Emscripten SDK + if: steps.playground-wasm-cache.outputs.cache-hit != 'true' + uses: ./.github/actions/setup-emscripten + with: + version: ${{ env.EMSDK_VERSION }} + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: 22 + cache: npm + cache-dependency-path: editors/vscode/package-lock.json + - name: Install dependencies + run: npm ci + - name: Build playground WASM + if: steps.playground-wasm-cache.outputs.cache-hit != 'true' + working-directory: playground + run: | + source "${EMSDK}/emsdk_env.sh" + npm run build:wasm + - name: Save playground WASM cache + if: steps.playground-wasm-cache.outputs.cache-hit != 'true' + uses: actions/cache/save@v4 + continue-on-error: true + with: + path: playground/public/wasm + key: ${{ steps.playground-wasm-cache.outputs.cache-primary-key }} + - name: Set build metadata + id: build-metadata + shell: bash + working-directory: . + run: | + commit_hash="$(git rev-parse --short "$GITHUB_SHA")" + echo "commit_hash=${commit_hash}" >> "$GITHUB_OUTPUT" + echo "VIDE_EXTENSION_BUILD_KIND=nightly" >> "$GITHUB_ENV" + echo "VIDE_EXTENSION_COMMIT_HASH=${commit_hash}" >> "$GITHUB_ENV" + echo "VIDE_EXTENSION_BUILD_DATE=$(date -u +'%Y%m%dT%H%M%SZ')" >> "$GITHUB_ENV" + - name: Package extension + run: npm run package:web + - name: Upload dev VSIX + uses: actions/upload-artifact@v4 + with: + name: vide-vscode-dev-web-${{ steps.build-metadata.outputs.commit_hash }} + path: editors/vscode/vide-vscode-web.vsix + if-no-files-found: error + retention-days: 14 + dev-alpine-package: name: Dev Package (${{ matrix.target }}) needs: changes diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 8ab3eb9d..dfa137f7 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -13,6 +13,9 @@ concurrency: group: ${{ github.workflow }}-${{ github.event_name == 'workflow_dispatch' && 'beta' || github.ref }} cancel-in-progress: false +env: + EMSDK_VERSION: 5.0.2 + jobs: changelog: name: Check changelog @@ -253,9 +256,110 @@ jobs: path: vide-${{ matrix.target }} if-no-files-found: error + build-web-package: + name: Package web + needs: changelog + runs-on: ubuntu-latest + defaults: + run: + working-directory: editors/vscode + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Restore playground WASM cache + id: playground-wasm-cache + uses: actions/cache/restore@v4 + with: + path: playground/public/wasm + key: playground-wasm-${{ runner.os }}-${{ env.EMSDK_VERSION }}-${{ hashFiles('Cargo.toml', 'Cargo.lock', 'build.rs', 'rust-toolchain.toml', 'src/**', 'crates/**', 'playground/scripts/build-vide-wasm.mjs', 'playground/scripts/script-utils.mjs', '.github/actions/setup-emscripten/**', '.github/actions/setup-rust/**') }} + + - name: Verify cached playground WASM + if: steps.playground-wasm-cache.outputs.cache-hit == 'true' + working-directory: . + run: | + test -s playground/public/wasm/vide-lsp.js + test -s playground/public/wasm/vide-core.js + test -s playground/public/wasm/vide-core.wasm + + - name: Setup Rust + if: steps.playground-wasm-cache.outputs.cache-hit != 'true' + uses: ./.github/actions/setup-rust + with: + components: rustfmt + + - name: Setup sccache + if: steps.playground-wasm-cache.outputs.cache-hit != 'true' + uses: ./.github/actions/setup-sccache + with: + cmake-launcher: "true" + + - name: Rust Cache + if: steps.playground-wasm-cache.outputs.cache-hit != 'true' + uses: Swatinem/rust-cache@v2 + continue-on-error: true + with: + shared-key: release-web-wasm-${{ runner.os }}-${{ env.EMSDK_VERSION }} + key: wasm-src-${{ hashFiles('Cargo.toml', 'Cargo.lock', 'build.rs', 'rust-toolchain.toml', 'src/**', 'crates/**', 'playground/scripts/build-vide-wasm.mjs', 'playground/scripts/script-utils.mjs', '.github/actions/setup-emscripten/**', '.github/actions/setup-rust/**') }} + cache-workspace-crates: true + cache-on-failure: true + + - name: Install Emscripten SDK + if: steps.playground-wasm-cache.outputs.cache-hit != 'true' + uses: ./.github/actions/setup-emscripten + with: + version: ${{ env.EMSDK_VERSION }} + + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: 22 + cache: npm + cache-dependency-path: editors/vscode/package-lock.json + + - name: Install JS dependencies + run: npm ci + + - name: Build playground WASM + if: steps.playground-wasm-cache.outputs.cache-hit != 'true' + working-directory: playground + run: | + source "${EMSDK}/emsdk_env.sh" + npm run build:wasm + + - name: Save playground WASM cache + if: steps.playground-wasm-cache.outputs.cache-hit != 'true' + uses: actions/cache/save@v4 + continue-on-error: true + with: + path: playground/public/wasm + key: ${{ steps.playground-wasm-cache.outputs.cache-primary-key }} + + - name: Set build metadata + shell: bash + working-directory: . + run: | + if [ "${GITHUB_EVENT_NAME}" = "workflow_dispatch" ]; then + echo "VIDE_EXTENSION_BUILD_KIND=beta" >> "$GITHUB_ENV" + echo "VIDE_EXTENSION_COMMIT_HASH=$(git rev-parse --short "$GITHUB_SHA")" >> "$GITHUB_ENV" + echo "VIDE_EXTENSION_BUILD_DATE=$(date -u +'%Y%m%dT%H%M%SZ')" >> "$GITHUB_ENV" + else + echo "VIDE_EXTENSION_BUILD_KIND=stable" >> "$GITHUB_ENV" + fi + + - name: Build VSIX + run: npm run package:web + + - name: Upload VSIX + uses: actions/upload-artifact@v4 + with: + name: vide-vscode-${{ github.event_name == 'workflow_dispatch' && 'beta' || 'stable' }}-web + path: editors/vscode/vide-vscode-web.vsix + if-no-files-found: error + publish: name: Publish ${{ github.event_name == 'workflow_dispatch' && 'beta' || 'stable' }} release - needs: [build-and-package, build-alpine-package] + needs: [build-and-package, build-alpine-package, build-web-package] runs-on: ubuntu-latest env: RELEASE_TAG: ${{ github.event_name == 'workflow_dispatch' && 'beta' || github.ref_name }} @@ -297,7 +401,7 @@ jobs: publish-marketplace: name: Publish VS Code Marketplace if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v') - needs: [build-and-package, publish] + needs: [build-and-package, build-web-package, publish] runs-on: ubuntu-latest defaults: run: diff --git a/editors/vscode/.gitignore b/editors/vscode/.gitignore index 6b0aba77..53ef7f10 100644 --- a/editors/vscode/.gitignore +++ b/editors/vscode/.gitignore @@ -5,6 +5,7 @@ out/ README.md build-info.json .vscode-test/ +.vscode-test-web/ *.log .DS_Store bun.lock diff --git a/editors/vscode/.vscodeignore b/editors/vscode/.vscodeignore index 274cc2fc..79fe1112 100644 --- a/editors/vscode/.vscodeignore +++ b/editors/vscode/.vscodeignore @@ -4,10 +4,12 @@ node_modules/** out/** src/** test/** +test-web/** .gitignore .yarnrc vsc-extension-quickstart.md **/tsconfig.json +**/tsconfig.*.json **/.eslintrc.json **/*.map **/*.ts @@ -16,3 +18,4 @@ pnpm-lock.yaml bun.lock scripts/** server/*/ +dist/test-web/** diff --git a/editors/vscode/l10n/bundle.l10n.zh-cn.json b/editors/vscode/l10n/bundle.l10n.zh-cn.json index 43cb26e1..57c847f1 100644 --- a/editors/vscode/l10n/bundle.l10n.zh-cn.json +++ b/editors/vscode/l10n/bundle.l10n.zh-cn.json @@ -77,5 +77,6 @@ "Failed to run Qihe analysis: {0}": "无法运行 Qihe 分析:{0}", "Unable to update Vide diagnostic rules: {0}": "无法更新 Vide 诊断规则:{0}", "Restart": "重启", - "Vide server configuration changed. Restart the language server to apply it.": "Vide 服务器配置已更改。请重启语言服务器以应用更改。" + "Vide server configuration changed. Restart the language server to apply it.": "Vide 服务器配置已更改。请重启语言服务器以应用更改。", + "{0} is not available in vscode.dev yet.": "{0} 暂时无法在 vscode.dev 中使用。" } diff --git a/editors/vscode/package-lock.json b/editors/vscode/package-lock.json index d6e1413f..8e02334c 100644 --- a/editors/vscode/package-lock.json +++ b/editors/vscode/package-lock.json @@ -12,11 +12,14 @@ "vscode-languageclient": "^9.0.1" }, "devDependencies": { + "@types/mocha": "^10.0.10", "@types/node": "22.15.1", "@types/vscode": "1.101.0", "@vscode/l10n-dev": "^0.0.35", + "@vscode/test-web": "^0.0.80", "@vscode/vsce": "^3.6.2", "esbuild": "^0.28.0", + "mocha": "^11.7.6", "speedscope": "1.25.0", "tsx": "^4.22.0", "typescript": "^5.9.3" @@ -722,6 +725,43 @@ "node": ">=18" } }, + "node_modules/@koa/cors": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@koa/cors/-/cors-5.0.0.tgz", + "integrity": "sha512-x/iUDjcS90W69PryLDIMgFyV21YLTnG9zOpPXS7Bkt2b8AsY3zZsIpOLBkYr9fBcF3HbkKaER5hOBZLfpLgYNw==", + "dev": true, + "license": "MIT", + "dependencies": { + "vary": "^1.1.2" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@koa/router": { + "version": "15.5.0", + "resolved": "https://registry.npmjs.org/@koa/router/-/router-15.5.0.tgz", + "integrity": "sha512-KSC0oG/5t6ITu5wqX4lJseA/dngoj14hEaohrLZEXtlUT2RRyJvwaJ0KV+5uQoaWrY3A8ClHOrBEU4g8dujn8Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.4.3", + "http-errors": "^2.0.1", + "koa-compose": "^4.1.0", + "path-to-regexp": "^8.4.2" + }, + "engines": { + "node": ">= 20" + }, + "peerDependencies": { + "koa": "^2.0.0 || ^3.0.0" + }, + "peerDependenciesMeta": { + "koa": { + "optional": false + } + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -771,6 +811,20 @@ "node": ">=14" } }, + "node_modules/@playwright/browser-chromium": { + "version": "1.60.0", + "resolved": "https://registry.npmjs.org/@playwright/browser-chromium/-/browser-chromium-1.60.0.tgz", + "integrity": "sha512-0ND2pbNWKJYwhlA1LNaDC3DP2x7eguQkQF7Ga7XAlJV0AFieqYNRw/E+gaY9BpSFr1TYwfwXQv1bHq5AK9nbvA==", + "dev": true, + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "playwright-core": "1.60.0" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/@secretlint/config-creator": { "version": "10.2.2", "resolved": "https://registry.npmjs.org/@secretlint/config-creator/-/config-creator-10.2.2.tgz", @@ -1042,6 +1096,13 @@ "@textlint/ast-node-types": "15.7.0" } }, + "node_modules/@types/mocha": { + "version": "10.0.10", + "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-10.0.10.tgz", + "integrity": "sha512-xPyYSz1cMPnJQhl0CLMH68j3gprKZaTjG3s5Vi+fDgx+uhG9NOXwbVt52eFS8ECyXhyKcjDLCBEqBExKuiZb7Q==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/node": { "version": "22.15.1", "resolved": "https://registry.npmjs.org/@types/node/-/node-22.15.1.tgz", @@ -1272,6 +1333,64 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, + "node_modules/@vscode/test-web": { + "version": "0.0.80", + "resolved": "https://registry.npmjs.org/@vscode/test-web/-/test-web-0.0.80.tgz", + "integrity": "sha512-QgsRPp8IuPcdbUdVtqQkFN/sGd+6cr6g0ENEVFOHwJbQqZtJSiZD5w0e6tgmoeq9nBjNqZCq87OO1GkwFHkPyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@koa/cors": "^5.0.0", + "@koa/router": "^15.3.0", + "@playwright/browser-chromium": "^1.58.2", + "gunzip-maybe": "^1.4.2", + "http-proxy-agent": "^7.0.2", + "https-proxy-agent": "^7.0.6", + "koa": "^3.1.2", + "koa-morgan": "^1.0.1", + "koa-mount": "^4.2.0", + "koa-static": "^5.0.0", + "minimist": "^1.2.8", + "playwright": "^1.58.2", + "tar-fs": "^3.1.1", + "tinyglobby": "^0.2.15", + "vscode-uri": "^3.1.0" + }, + "bin": { + "vscode-test-web": "out/server/index.js" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@vscode/test-web/node_modules/tar-fs": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.1.2.tgz", + "integrity": "sha512-QGxxTxxyleAdyM3kpFs14ymbYmNFrfY+pHj7Z8FgtbZ7w2//VAgLMac7sT6nRpIHjppXO2AwwEOg0bPFVRcmXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "pump": "^3.0.0", + "tar-stream": "^3.1.5" + }, + "optionalDependencies": { + "bare-fs": "^4.0.1", + "bare-path": "^3.0.0" + } + }, + "node_modules/@vscode/test-web/node_modules/tar-stream": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.2.0.tgz", + "integrity": "sha512-ojzvCvVaNp6aOTFmG7jaRD0meowIAuPc3cMMhSgKiVWws1GyHbGd/xvnyuRKcKlMpt3qvxx6r0hreCNITP9hIg==", + "dev": true, + "license": "MIT", + "dependencies": { + "b4a": "^1.6.4", + "bare-fs": "^4.5.5", + "fast-fifo": "^1.2.0", + "streamx": "^2.15.0" + } + }, "node_modules/@vscode/vsce": { "version": "3.9.1", "resolved": "https://registry.npmjs.org/@vscode/vsce/-/vsce-3.9.1.tgz", @@ -1464,6 +1583,20 @@ "win32" ] }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/agent-base": { "version": "7.1.4", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", @@ -1571,12 +1704,124 @@ "typed-rest-client": "^1.8.4" } }, + "node_modules/b4a": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.8.1.tgz", + "integrity": "sha512-aiqre1Nr0B/6DgE2N5vwTc+2/oQZ4Wh1t4NznYY4E00y8LCt6NqdRv81so00oo27D8MVKTpUa/MwUUtBLXCoDw==", + "dev": true, + "license": "Apache-2.0", + "peerDependencies": { + "react-native-b4a": "*" + }, + "peerDependenciesMeta": { + "react-native-b4a": { + "optional": true + } + } + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "license": "MIT" }, + "node_modules/bare-events": { + "version": "2.8.3", + "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.8.3.tgz", + "integrity": "sha512-HdUm8EMQBLaJvGUdidNNbqpA1kYkwNcb+MYxkxCLAPJGQzlv9J0C24h8V65Z4c5GLd/JEALDvpFCQgpLJqc0zw==", + "dev": true, + "license": "Apache-2.0", + "peerDependencies": { + "bare-abort-controller": "*" + }, + "peerDependenciesMeta": { + "bare-abort-controller": { + "optional": true + } + } + }, + "node_modules/bare-fs": { + "version": "4.7.1", + "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-4.7.1.tgz", + "integrity": "sha512-WDRsyVN52eAx/lBamKD6uyw8H4228h/x0sGGGegOamM2cd7Pag88GfMQalobXI+HaEUxpCkbKQUDOQqt9wawRw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "bare-events": "^2.5.4", + "bare-path": "^3.0.0", + "bare-stream": "^2.6.4", + "bare-url": "^2.2.2", + "fast-fifo": "^1.3.2" + }, + "engines": { + "bare": ">=1.16.0" + }, + "peerDependencies": { + "bare-buffer": "*" + }, + "peerDependenciesMeta": { + "bare-buffer": { + "optional": true + } + } + }, + "node_modules/bare-os": { + "version": "3.9.1", + "resolved": "https://registry.npmjs.org/bare-os/-/bare-os-3.9.1.tgz", + "integrity": "sha512-6M5XjcnsygQNPMCMPXSK379xrJFiZ/AEMNBmFEmQW8d/789VQATvriyi5r0HYTL9TkQ26rn3kgdTG3aisbrXkQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "bare": ">=1.14.0" + } + }, + "node_modules/bare-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bare-path/-/bare-path-3.0.0.tgz", + "integrity": "sha512-tyfW2cQcB5NN8Saijrhqn0Zh7AnFNsnczRcuWODH0eYAXBsJ5gVxAUuNr7tsHSC6IZ77cA0SitzT+s47kot8Mw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "bare-os": "^3.0.1" + } + }, + "node_modules/bare-stream": { + "version": "2.13.1", + "resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-2.13.1.tgz", + "integrity": "sha512-Vp0cnjYyrEC4whYTymQ+YZi6pBpfiICZO3cfRG8sy67ZNWe951urv1x4eW1BKNngw3U+3fPYb5JQvHbCtxH7Ow==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "streamx": "^2.25.0", + "teex": "^1.0.1" + }, + "peerDependencies": { + "bare-abort-controller": "*", + "bare-buffer": "*", + "bare-events": "*" + }, + "peerDependenciesMeta": { + "bare-abort-controller": { + "optional": true + }, + "bare-buffer": { + "optional": true + }, + "bare-events": { + "optional": true + } + } + }, + "node_modules/bare-url": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/bare-url/-/bare-url-2.4.3.tgz", + "integrity": "sha512-Kccpc7ACfXaxfeInfqKcZtW4pT5YBn1mesc4sCsun6sRwtbJ4h+sNOaksUpYEJUKfN65YWC6Bw2OJEFiKxq8nQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "bare-path": "^3.0.0" + } + }, "node_modules/base64-js": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", @@ -1599,6 +1844,26 @@ "license": "MIT", "optional": true }, + "node_modules/basic-auth": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz", + "integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "5.1.2" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/basic-auth/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true, + "license": "MIT" + }, "node_modules/binaryextensions": { "version": "6.11.0", "resolved": "https://registry.npmjs.org/binaryextensions/-/binaryextensions-6.11.0.tgz", @@ -1666,6 +1931,23 @@ "node": ">=8" } }, + "node_modules/browser-stdout": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", + "dev": true, + "license": "ISC" + }, + "node_modules/browserify-zlib": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.1.4.tgz", + "integrity": "sha512-19OEpq7vWgsH6WkvkBJQDFvJS1uPcbFOQ4v9CU839dO+ZZXUZO6XpE6hNCqvlIIj+4fZvRiJ6DsAQ382GwiyTQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "pako": "~0.2.0" + } + }, "node_modules/buffer": { "version": "5.7.1", "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", @@ -1709,6 +1991,13 @@ "dev": true, "license": "BSD-3-Clause" }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true, + "license": "MIT" + }, "node_modules/bundle-name": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-4.1.0.tgz", @@ -1756,6 +2045,19 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -1817,6 +2119,22 @@ "url": "https://github.com/sponsors/fb55" } }, + "node_modules/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/chownr": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", @@ -1923,6 +2241,51 @@ "dev": true, "license": "MIT" }, + "node_modules/content-disposition": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.1.tgz", + "integrity": "sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookies": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/cookies/-/cookies-0.9.1.tgz", + "integrity": "sha512-TG2hpqe4ELx54QER/S3HQ9SRVnQnGBtKUz5bLQWtYAQ+o6GpgMs6sYUvaiJjVxb+UXwhRhAEP3m7LbsIZ77Hmw==", + "dev": true, + "license": "MIT", + "dependencies": { + "depd": "~2.0.0", + "keygrip": "~1.1.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "dev": true, + "license": "MIT" + }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -1986,6 +2349,19 @@ } } }, + "node_modules/decamelize": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", + "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/decompress-response": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", @@ -2003,6 +2379,13 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/deep-equal": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.0.1.tgz", + "integrity": "sha512-bHtC0iYvWhyaTzvV3CZgPeZQqCOBGyGsVV7v4eevpdkLHfiSrXUdBG+qAuSz4RI70sszvjQ1QSZ98An1yNwpSw==", + "dev": true, + "license": "MIT" + }, "node_modules/deep-extend": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", @@ -2077,6 +2460,34 @@ "node": ">=0.4.0" } }, + "node_modules/delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, "node_modules/detect-libc": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", @@ -2088,6 +2499,16 @@ "node": ">=8" } }, + "node_modules/diff": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-7.0.0.tgz", + "integrity": "sha512-PJWHUb1RFevKCwaFA9RlG5tCd+FO5iRh9A8HEtkmBH2Li03iJriB6m6JIN4rGz3K3JLawI7/veA1xzRKP6ISBw==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, "node_modules/dom-serializer": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", @@ -2162,6 +2583,52 @@ "node": ">= 0.4" } }, + "node_modules/duplexify": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.7.1.tgz", + "integrity": "sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g==", + "dev": true, + "license": "MIT", + "dependencies": { + "end-of-stream": "^1.0.0", + "inherits": "^2.0.1", + "readable-stream": "^2.0.0", + "stream-shift": "^1.0.0" + } + }, + "node_modules/duplexify/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/duplexify/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true, + "license": "MIT" + }, + "node_modules/duplexify/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, "node_modules/eastasianwidth": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", @@ -2196,13 +2663,30 @@ "url": "https://bevry.me/fund" } }, - "node_modules/emoji-regex": { - "version": "8.0.0", + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "dev": true, + "license": "MIT" + }, + "node_modules/emoji-regex": { + "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "dev": true, "license": "MIT" }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/encoding-sniffer": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/encoding-sniffer/-/encoding-sniffer-0.2.1.tgz", @@ -2223,7 +2707,6 @@ "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", "dev": true, "license": "MIT", - "optional": true, "dependencies": { "once": "^1.4.0" } @@ -2355,6 +2838,36 @@ "node": ">=6" } }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "dev": true, + "license": "MIT" + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/events-universal": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/events-universal/-/events-universal-1.0.1.tgz", + "integrity": "sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "bare-events": "^2.7.0" + } + }, "node_modules/expand-template": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", @@ -2373,6 +2886,13 @@ "dev": true, "license": "MIT" }, + "node_modules/fast-fifo": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz", + "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==", + "dev": true, + "license": "MIT" + }, "node_modules/fast-glob": { "version": "3.3.3", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", @@ -2430,6 +2950,23 @@ "node": ">=8" } }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/flat": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", @@ -2474,6 +3011,16 @@ "node": ">= 6" } }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/fs-constants": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", @@ -2707,6 +3254,24 @@ "dev": true, "license": "ISC" }, + "node_modules/gunzip-maybe": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/gunzip-maybe/-/gunzip-maybe-1.4.2.tgz", + "integrity": "sha512-4haO1M4mLO91PW57BMsDFf75UmwoRX0GkdD+Faw+Lr+r/OZrOCS0pIBwOL1xCKQqnQzbNFGgK2V2CpBUPeFNTw==", + "dev": true, + "license": "MIT", + "dependencies": { + "browserify-zlib": "^0.1.4", + "is-deflate": "^1.0.0", + "is-gzip": "^1.0.0", + "peek-stream": "^1.1.0", + "pumpify": "^1.3.3", + "through2": "^2.0.3" + }, + "bin": { + "gunzip-maybe": "bin.js" + } + }, "node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -2815,6 +3380,78 @@ "url": "https://github.com/fb55/entities?sponsor=1" } }, + "node_modules/http-assert": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/http-assert/-/http-assert-1.5.0.tgz", + "integrity": "sha512-uPpH7OKX4H25hBmU6G1jWNaqJGpTXxey+YOUizJUAgu0AjLUeC8D73hTrhvDS5D+GJN1DN1+hhc/eF/wpxtp0w==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-equal": "~1.0.1", + "http-errors": "~1.8.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/http-assert/node_modules/depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/http-assert/node_modules/http-errors": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz", + "integrity": "sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "depd": "~1.1.2", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/http-assert/node_modules/statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/http-proxy-agent": { "version": "7.0.2", "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", @@ -2906,8 +3543,7 @@ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", "dev": true, - "license": "ISC", - "optional": true + "license": "ISC" }, "node_modules/ini": { "version": "1.3.8", @@ -2917,6 +3553,13 @@ "license": "ISC", "optional": true }, + "node_modules/is-deflate": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-deflate/-/is-deflate-1.0.0.tgz", + "integrity": "sha512-YDoFpuZWu1VRXlsnlYMzKyVRITXj7Ej/V9gXQ2/pAe7X1J7M/RNOqaIYi6qUn+B7nGyB9pDXrv02dsB58d2ZAQ==", + "dev": true, + "license": "MIT" + }, "node_modules/is-docker": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", @@ -2966,6 +3609,16 @@ "node": ">=0.10.0" } }, + "node_modules/is-gzip": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-gzip/-/is-gzip-1.0.0.tgz", + "integrity": "sha512-rcfALRIb1YewtnksfRIHGcIY93QnK8BIQ/2c9yDYcG/Y6+vRoJuTWBmmSEbyLLYtXm7q35pHOHbZFQBaLrhlWQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/is-inside-container": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", @@ -2995,6 +3648,39 @@ "node": ">=0.12.0" } }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-plain-obj": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", + "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-wsl": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.1.tgz", @@ -3011,6 +3697,13 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "dev": true, + "license": "MIT" + }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", @@ -3158,6 +3851,19 @@ "safe-buffer": "^5.0.1" } }, + "node_modules/keygrip": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/keygrip/-/keygrip-1.1.0.tgz", + "integrity": "sha512-iYSchDJ+liQ8iwbSI2QqsQOvqv58eJCEanyJPJi+Khyu8smkcKSFUCbPwzFcL7YVtZ6eONjqRX/38caJ7QjRAQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "tsscmp": "1.0.6" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/keytar": { "version": "7.9.0", "resolved": "https://registry.npmjs.org/keytar/-/keytar-7.9.0.tgz", @@ -3171,6 +3877,170 @@ "prebuild-install": "^7.0.1" } }, + "node_modules/koa": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/koa/-/koa-3.2.1.tgz", + "integrity": "sha512-e7IpWJrnanNUroVK2taAgMxoEZvHLXdQiNjeExSu/DEIWm83jaKGBgb7tLmu2rMYpA027qFB3iLR/k3AVpFRnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "accepts": "^1.3.8", + "content-disposition": "~1.0.1", + "content-type": "^1.0.5", + "cookies": "~0.9.1", + "delegates": "^1.0.0", + "destroy": "^1.2.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "fresh": "~0.5.2", + "http-assert": "^1.5.0", + "http-errors": "^2.0.0", + "koa-compose": "^4.1.0", + "mime-types": "^3.0.1", + "on-finished": "^2.4.1", + "parseurl": "^1.3.3", + "statuses": "^2.0.1", + "type-is": "^2.0.1", + "vary": "^1.1.2" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/koa-compose": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/koa-compose/-/koa-compose-4.1.0.tgz", + "integrity": "sha512-8ODW8TrDuMYvXRwra/Kh7/rJo9BtOfPc6qO8eAfC80CnCvSjSl0bkRM24X6/XBBEyj0v1nRUQ1LyOy3dbqOWXw==", + "dev": true, + "license": "MIT" + }, + "node_modules/koa-morgan": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/koa-morgan/-/koa-morgan-1.0.1.tgz", + "integrity": "sha512-JOUdCNlc21G50afBXfErUrr1RKymbgzlrO5KURY+wmDG1Uvd2jmxUJcHgylb/mYXy2SjiNZyYim/ptUBGsIi3A==", + "dev": true, + "license": "MIT", + "dependencies": { + "morgan": "^1.6.1" + } + }, + "node_modules/koa-mount": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/koa-mount/-/koa-mount-4.2.0.tgz", + "integrity": "sha512-2iHQc7vbA9qLeVq5gKAYh3m5DOMMlMfIKjW/REPAS18Mf63daCJHHVXY9nbu7ivrnYn5PiPC4CE523Tf5qvjeQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.0.1", + "koa-compose": "^4.1.0" + }, + "engines": { + "node": ">= 7.6.0" + } + }, + "node_modules/koa-send": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/koa-send/-/koa-send-5.0.1.tgz", + "integrity": "sha512-tmcyQ/wXXuxpDxyNXv5yNNkdAMdFRqwtegBXUaowiQzUKqJehttS0x2j0eOZDQAyloAth5w6wwBImnFzkUz3pQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.1.1", + "http-errors": "^1.7.3", + "resolve-path": "^1.4.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/koa-send/node_modules/depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/koa-send/node_modules/http-errors": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz", + "integrity": "sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "depd": "~1.1.2", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/koa-send/node_modules/statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/koa-static": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/koa-static/-/koa-static-5.0.0.tgz", + "integrity": "sha512-UqyYyH5YEXaJrf9S8E23GoJFQZXkBVJ9zYYMPGz919MSX1KuvAcycIuS0ci150HCoPf4XQVhQ84Qf8xRPWxFaQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^3.1.0", + "koa-send": "^5.0.0" + }, + "engines": { + "node": ">= 7.6.0" + } + }, + "node_modules/koa-static/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/koa/node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/koa/node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/leven": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", @@ -3191,6 +4061,22 @@ "uc.micro": "^2.0.0" } }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/lodash": { "version": "4.18.1", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", @@ -3254,10 +4140,27 @@ "dev": true, "license": "MIT" }, - "node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "node_modules/log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", "dev": true, "license": "ISC", "dependencies": { @@ -3302,6 +4205,16 @@ "dev": true, "license": "MIT" }, + "node_modules/media-typer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", + "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", @@ -3395,7 +4308,6 @@ "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", "dev": true, "license": "MIT", - "optional": true, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -3418,6 +4330,281 @@ "license": "MIT", "optional": true }, + "node_modules/mocha": { + "version": "11.7.6", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-11.7.6.tgz", + "integrity": "sha512-nS9xOGbw2I3cjCpxwZAEJ9xK9lmJ08vEkQvLtz4du9ZrF9UrjRpeJGiIgl2Z+Qs++pmB4ecDe48Fwsh+j+j7xA==", + "dev": true, + "license": "MIT", + "dependencies": { + "browser-stdout": "^1.3.1", + "chokidar": "^4.0.1", + "debug": "^4.3.5", + "diff": "^7.0.0", + "escape-string-regexp": "^4.0.0", + "find-up": "^5.0.0", + "glob": "^10.4.5", + "he": "^1.2.0", + "is-path-inside": "^3.0.3", + "js-yaml": "^4.1.0", + "log-symbols": "^4.1.0", + "minimatch": "^9.0.5", + "ms": "^2.1.3", + "picocolors": "^1.1.1", + "serialize-javascript": "^6.0.2", + "strip-json-comments": "^3.1.1", + "supports-color": "^8.1.1", + "workerpool": "^9.2.0", + "yargs": "^17.7.2", + "yargs-parser": "^21.1.1", + "yargs-unparser": "^2.0.0" + }, + "bin": { + "_mocha": "bin/_mocha", + "mocha": "bin/mocha.js" + }, + "engines": { + "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/brace-expansion": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.1.tgz", + "integrity": "sha512-WR1cURNjuvBLMZBMbqM0UoE+WAfdUcEV1ccD8PVBVOI+Z3ND4+SZbN8RsfT2bMuG1qwz5RFvPukSZm5fF2D5eA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "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": { + "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/mocha/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/mocha/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/mocha/node_modules/minimatch": { + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.2" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/mocha/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/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/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "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", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "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/morgan": { + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.10.1.tgz", + "integrity": "sha512-223dMRJtI/l25dJKWpgij2cMtywuG/WiUKXdvwfbhGKBhy1puASqXwFzmWZ7+K73vUPoR7SS2Qz2cI/g9MKw0A==", + "dev": true, + "license": "MIT", + "dependencies": { + "basic-auth": "~2.0.1", + "debug": "2.6.9", + "depd": "~2.0.0", + "on-finished": "~2.3.0", + "on-headers": "~1.1.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/morgan/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/morgan/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true, + "license": "MIT" + }, + "node_modules/morgan/node_modules/on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==", + "dev": true, + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -3440,6 +4627,16 @@ "license": "MIT", "optional": true }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/node-abi": { "version": "3.92.0", "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.92.0.tgz", @@ -3561,13 +4758,35 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "dev": true, + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/on-headers": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.1.0.tgz", + "integrity": "sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", "dev": true, "license": "ISC", - "optional": true, "dependencies": { "wrappy": "1" } @@ -3591,6 +4810,38 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/p-map": { "version": "7.0.4", "resolved": "https://registry.npmjs.org/p-map/-/p-map-7.0.4.tgz", @@ -3611,6 +4862,13 @@ "dev": true, "license": "BlueOak-1.0.0" }, + "node_modules/pako": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/pako/-/pako-0.2.9.tgz", + "integrity": "sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA==", + "dev": true, + "license": "MIT" + }, "node_modules/parse-json": { "version": "8.3.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-8.3.0.tgz", @@ -3702,6 +4960,36 @@ "url": "https://github.com/fb55/entities?sponsor=1" } }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", @@ -3739,6 +5027,17 @@ "node": "20 || >=22" } }, + "node_modules/path-to-regexp": { + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.4.2.tgz", + "integrity": "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==", + "dev": true, + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/path-type": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-6.0.0.tgz", @@ -3752,6 +5051,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/peek-stream": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/peek-stream/-/peek-stream-1.1.3.tgz", + "integrity": "sha512-FhJ+YbOSBb9/rIl2ZeE/QHEsWn7PqNYt8ARAY3kIgNGOk13g9FGyIY6JIl/xB/3TFRVoTv5as0l11weORrTekA==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "duplexify": "^3.5.0", + "through2": "^2.0.3" + } + }, "node_modules/pend": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", @@ -3773,10 +5084,57 @@ "dev": true, "license": "MIT", "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/playwright": { + "version": "1.60.0", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.60.0.tgz", + "integrity": "sha512-hheHdokM8cdqCb0lcE3s+zT4t4W+vvjpGxsZlDnikarzx8tSzMebh3UiFtgqwFwnTnjYQcsyMF8ei2mCO/tpeA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright-core": "1.60.0" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/playwright-core": { + "version": "1.60.0", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.60.0.tgz", + "integrity": "sha512-9bW6zvX/m0lEbgTKJ6YppOKx8H3VOPBMOCFh2irXFOT4BbHgrx5hPjwJYLT40Lu+4qtD36qKc/Hn56StUW57IA==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/playwright/node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, "node_modules/pluralize": { @@ -3818,6 +5176,13 @@ "node": ">=10" } }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "dev": true, + "license": "MIT" + }, "node_modules/pseudo-localization": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/pseudo-localization/-/pseudo-localization-2.4.0.tgz", @@ -3854,7 +5219,29 @@ "integrity": "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==", "dev": true, "license": "MIT", - "optional": true, + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/pumpify": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/pumpify/-/pumpify-1.5.1.tgz", + "integrity": "sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "duplexify": "^3.6.0", + "inherits": "^2.0.3", + "pump": "^2.0.0" + } + }, + "node_modules/pumpify/node_modules/pump": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz", + "integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==", + "dev": true, + "license": "MIT", "dependencies": { "end-of-stream": "^1.1.0", "once": "^1.3.1" @@ -3907,6 +5294,16 @@ ], "license": "MIT" }, + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, "node_modules/rc": { "version": "1.2.8", "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", @@ -3999,6 +5396,20 @@ "node": ">= 6" } }, + "node_modules/readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -4019,6 +5430,70 @@ "node": ">=0.10.0" } }, + "node_modules/resolve-path": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/resolve-path/-/resolve-path-1.4.0.tgz", + "integrity": "sha512-i1xevIst/Qa+nA9olDxLWnLk8YZbi8R/7JPbCMcgyWaFR6bKWaexgJgEB5oc2PKMjYdrHynyz0NY+if+H98t1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "http-errors": "~1.6.2", + "path-is-absolute": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/resolve-path/node_modules/depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/resolve-path/node_modules/http-errors": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", + "integrity": "sha512-lks+lVC8dgGyh97jxvxeYTWQFvh4uw4yC12gVl63Cg30sjPX4wuGcdkICVXDAESr6OJGjqGA8Iz5mkeN6zlD7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.0", + "statuses": ">= 1.4.0 < 2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/resolve-path/node_modules/inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==", + "dev": true, + "license": "ISC" + }, + "node_modules/resolve-path/node_modules/setprototypeof": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", + "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/resolve-path/node_modules/statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/reusify": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", @@ -4139,6 +5614,23 @@ "node": ">=10" } }, + "node_modules/serialize-javascript": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", + "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "randombytes": "^2.1.0" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "dev": true, + "license": "ISC" + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -4399,6 +5891,35 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/stream-shift": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.3.tgz", + "integrity": "sha512-76ORR0DO1o1hlKwTbi/DM3EXWGf3ZJYO8cXX5RJwnul2DEg2oyoZyjLNoQM8WsvZiFKCRfC1O0J7iCvie3RZmQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/streamx": { + "version": "2.26.0", + "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.26.0.tgz", + "integrity": "sha512-VvNG1K72Po/xwJzxZFnZ++Tbrv4lwSptsbkFuzXCJAYZvCK5nnxsvXU6ajqkv7chyiI1Y0YXq2Jh8Iy8Y7NF/A==", + "dev": true, + "license": "MIT", + "dependencies": { + "events-universal": "^1.0.0", + "fast-fifo": "^1.3.2", + "text-decoder": "^1.1.0" + } + }, "node_modules/string_decoder": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", @@ -4650,6 +6171,16 @@ "node": ">=6" } }, + "node_modules/teex": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/teex/-/teex-1.0.1.tgz", + "integrity": "sha512-eYE6iEI62Ni1H8oIa7KlDU6uQBtqr4Eajni3wX7rpfXD8ysFx8z0+dri+KWEPWpBsxXfxu58x/0jvTVT1ekOSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "streamx": "^2.12.5" + } + }, "node_modules/terminal-link": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/terminal-link/-/terminal-link-4.0.0.tgz", @@ -4667,6 +6198,16 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/text-decoder": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.7.tgz", + "integrity": "sha512-vlLytXkeP4xvEq2otHeJfSQIRyWxo/oZGEbXrtEEF9Hnmrdly59sUbzZ/QgyWuLYHctCHxFF4tRQZNQ9k60ExQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "b4a": "^1.6.4" + } + }, "node_modules/text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", @@ -4690,6 +6231,98 @@ "url": "https://bevry.me/fund" } }, + "node_modules/through2": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } + }, + "node_modules/through2/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/through2/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true, + "license": "MIT" + }, + "node_modules/through2/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.16", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz", + "integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyglobby/node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/tmp": { "version": "0.2.7", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.7.tgz", @@ -4713,6 +6346,16 @@ "node": ">=8.0" } }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, "node_modules/tslib": { "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", @@ -4720,6 +6363,16 @@ "dev": true, "license": "0BSD" }, + "node_modules/tsscmp": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/tsscmp/-/tsscmp-1.0.6.tgz", + "integrity": "sha512-LxhtAkPDTkVCMQjt2h6eBVY28KCjikZqZfMcC15YBeNjkgUpdCfBu5HoiOTDu86v6smE8yOjyEktJ8hlbANHQA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.6.x" + } + }, "node_modules/tsx": { "version": "4.22.0", "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.22.0.tgz", @@ -4776,6 +6429,66 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/type-is": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.1.0.tgz", + "integrity": "sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==", + "dev": true, + "license": "MIT", + "dependencies": { + "content-type": "^2.0.0", + "media-typer": "^1.1.0", + "mime-types": "^3.0.0" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/type-is/node_modules/content-type": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.0.0.tgz", + "integrity": "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/type-is/node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/type-is/node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/typed-rest-client": { "version": "1.8.11", "resolved": "https://registry.npmjs.org/typed-rest-client/-/typed-rest-client-1.8.11.tgz", @@ -4868,8 +6581,7 @@ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", "dev": true, - "license": "MIT", - "optional": true + "license": "MIT" }, "node_modules/validate-npm-package-license": { "version": "3.0.4", @@ -4882,6 +6594,16 @@ "spdx-expression-parse": "^3.0.0" } }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/version-range": { "version": "4.15.0", "resolved": "https://registry.npmjs.org/version-range/-/version-range-4.15.0.tgz", @@ -4955,6 +6677,13 @@ "integrity": "sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg==", "license": "MIT" }, + "node_modules/vscode-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.1.0.tgz", + "integrity": "sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==", + "dev": true, + "license": "MIT" + }, "node_modules/web-tree-sitter": { "version": "0.20.8", "resolved": "https://registry.npmjs.org/web-tree-sitter/-/web-tree-sitter-0.20.8.tgz", @@ -5002,6 +6731,13 @@ "node": ">= 8" } }, + "node_modules/workerpool": { + "version": "9.3.4", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-9.3.4.tgz", + "integrity": "sha512-TmPRQYYSAnnDiEB0P/Ytip7bFGvqnSU6I2BcuSw7Hx+JSg/DsUi5ebYfc8GYaSdpuvOcEs6dXxPurOYpe9QFwg==", + "dev": true, + "license": "Apache-2.0" + }, "node_modules/wrap-ansi": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", @@ -5090,8 +6826,7 @@ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "dev": true, - "license": "ISC", - "optional": true + "license": "ISC" }, "node_modules/wsl-utils": { "version": "0.1.0", @@ -5133,6 +6868,16 @@ "node": ">=4.0" } }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.4" + } + }, "node_modules/y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", @@ -5179,6 +6924,22 @@ "node": ">=12" } }, + "node_modules/yargs-unparser": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", + "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", + "dev": true, + "license": "MIT", + "dependencies": { + "camelcase": "^6.0.0", + "decamelize": "^4.0.0", + "flat": "^5.0.2", + "is-plain-obj": "^2.1.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/yauzl": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-3.3.0.tgz", @@ -5202,6 +6963,19 @@ "dependencies": { "buffer-crc32": "~0.2.3" } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } } } } diff --git a/editors/vscode/package.json b/editors/vscode/package.json index 3323aae1..aa843d11 100644 --- a/editors/vscode/package.json +++ b/editors/vscode/package.json @@ -28,6 +28,7 @@ ], "activationEvents": [], "main": "./dist/extension.js", + "browser": "./dist/browser/extension.js", "l10n": "./l10n", "contributes": { "languages": [ @@ -390,19 +391,26 @@ "editor/title": [ { "command": "vide.runQiheAnalysis", - "when": "resourceLangId == verilog || resourceLangId == systemverilog", + "when": "!isWeb && (resourceLangId == verilog || resourceLangId == systemverilog)", "group": "navigation@10" } ] } }, "scripts": { - "bundle": "esbuild src/extension.ts --bundle --platform=node --target=node22 --format=cjs --external:vscode --outfile=dist/extension.js && npm run copy-speedscope", + "bundle:node": "esbuild src/extension.ts --bundle --platform=node --target=node22 --format=cjs --external:vscode --outfile=dist/extension.js && npm run copy-speedscope", + "bundle:browser:extension": "esbuild src/browser/extension.ts --bundle --platform=browser --target=es2022 --format=cjs --external:vscode --outfile=dist/browser/extension.js", + "bundle:browser:worker": "esbuild src/browser/worker.ts --bundle --platform=browser --target=es2022 --format=iife --outfile=dist/browser/vide-lsp.worker.js", + "bundle:browser": "npm run bundle:browser:extension && npm run bundle:browser:worker && npm run copy-web-assets", + "bundle:test-web": "esbuild test-web/suite/index.ts --bundle --platform=browser --target=es2022 --format=cjs --external:vscode --outfile=dist/test-web/suite/index.js", "clean": "tsx scripts/clean.ts", "copy-speedscope": "tsx scripts/copy-speedscope-assets.ts", - "typecheck": "tsc -p . --noEmit", - "compile": "npm run clean && npm run typecheck && npm run bundle", + "copy-web-assets": "tsx scripts/copy-web-assets.ts", + "typecheck": "tsc -p . --noEmit && tsc -p tsconfig.browser.json --noEmit && tsc -p tsconfig.test-web.json --noEmit", + "compile": "npm run clean && npm run typecheck && npm run bundle:node", + "compile:web": "npm run clean && npm run typecheck && npm run bundle:node && npm run bundle:browser", "test": "npm run compile && node --import tsx --test \"test/**/*.test.ts\"", + "test:web": "npm run bundle:browser && npm run bundle:test-web && tsx test-web/runTests.ts", "package:debug": "npm run compile && tsx scripts/package.ts --debug", "package:alpine-arm64": "npm run compile && tsx scripts/package.ts alpine-arm64", "package:alpine-x64": "npm run compile && tsx scripts/package.ts alpine-x64", @@ -410,6 +418,7 @@ "package:darwin-x64": "npm run compile && tsx scripts/package.ts darwin-x64", "package:linux-x64": "npm run compile && tsx scripts/package.ts linux-x64", "package:linux-arm64": "npm run compile && tsx scripts/package.ts linux-arm64", + "package:web": "npm run compile:web && tsx scripts/package.ts web", "package:win32-x64": "npm run compile && tsx scripts/package.ts win32-x64", "package:win32-arm64": "npm run compile && tsx scripts/package.ts win32-arm64", "install-extension": "tsx scripts/install-extension.ts" @@ -418,11 +427,14 @@ "vscode-languageclient": "^9.0.1" }, "devDependencies": { + "@types/mocha": "^10.0.10", "@types/node": "22.15.1", "@types/vscode": "1.101.0", "@vscode/l10n-dev": "^0.0.35", + "@vscode/test-web": "^0.0.80", "@vscode/vsce": "^3.6.2", "esbuild": "^0.28.0", + "mocha": "^11.7.6", "speedscope": "1.25.0", "tsx": "^4.22.0", "typescript": "^5.9.3" diff --git a/editors/vscode/scripts/copy-web-assets.ts b/editors/vscode/scripts/copy-web-assets.ts new file mode 100644 index 00000000..ff7508e7 --- /dev/null +++ b/editors/vscode/scripts/copy-web-assets.ts @@ -0,0 +1,28 @@ +import * as fs from "node:fs"; +import * as path from "node:path"; + +const vscodeDir = path.resolve(__dirname, ".."); +const repoRoot = path.resolve(vscodeDir, "..", ".."); +const sourceDir = path.join(repoRoot, "playground", "public", "wasm"); +const targetDir = path.join(vscodeDir, "dist", "browser", "wasm"); +const requiredFiles = ["vide-lsp.js", "vide-core.js", "vide-core.wasm"]; + +if (!fs.existsSync(sourceDir)) { + throw new Error( + `Missing ${sourceDir}. Run \`npm --prefix playground run build:wasm\` first.`, + ); +} + +for (const fileName of requiredFiles) { + const sourcePath = path.join(sourceDir, fileName); + if (!fs.existsSync(sourcePath)) { + throw new Error( + `Missing ${sourcePath}. Run \`npm --prefix playground run build:wasm\` first.`, + ); + } +} + +fs.mkdirSync(targetDir, { recursive: true }); +for (const fileName of requiredFiles) { + fs.copyFileSync(path.join(sourceDir, fileName), path.join(targetDir, fileName)); +} diff --git a/editors/vscode/scripts/package.ts b/editors/vscode/scripts/package.ts index 3da20b3d..d1a71fed 100644 --- a/editors/vscode/scripts/package.ts +++ b/editors/vscode/scripts/package.ts @@ -12,9 +12,11 @@ import { const vscodeDir = findExtensionRoot(__dirname); const repoRoot = path.resolve(vscodeDir, '..', '..'); const binName = 'vide'; +const webTarget = 'web'; type BuildProfile = 'debug' | 'release'; type ServerMode = 'build' | 'prebuilt'; +type PackageTarget = PlatformFolder | typeof webTarget; const cargoTargets: Partial> = { 'alpine-arm64': 'aarch64-unknown-linux-musl', @@ -202,7 +204,7 @@ function optionalEnv(name: string): string | undefined { return value ? value : undefined; } -function writeBuildInfo(target: PlatformFolder, profile: BuildProfile): void { +function writeBuildInfo(target: PackageTarget, profile: BuildProfile): void { const buildInfo = { version: readExtensionVersion(), target, @@ -224,7 +226,7 @@ function parseServerMode(value: string): ServerMode { throw new Error(`unsupported server mode: ${value}`); } -function parseArgs(): { target: PlatformFolder; profile: BuildProfile; serverMode: ServerMode } { +function parseArgs(): { target: PackageTarget; profile: BuildProfile; serverMode: ServerMode } { const args = process.argv.slice(2); let profile: BuildProfile = 'release'; let serverMode: ServerMode = 'build'; @@ -243,10 +245,13 @@ function parseArgs(): { target: PlatformFolder; profile: BuildProfile; serverMod } target ??= hostPlatformFolder(); + if (target === webTarget) { + return { target, profile, serverMode }; + } if (!isPlatformFolder(target)) { throw new Error( `unsupported target platform: ${target}\n` + - `supported targets: ${SUPPORTED_PLATFORM_FOLDERS.join(', ')}`, + `supported targets: ${[...SUPPORTED_PLATFORM_FOLDERS, webTarget].join(', ')}`, ); } @@ -254,30 +259,40 @@ function parseArgs(): { target: PlatformFolder; profile: BuildProfile; serverMod } function packageExtension( - target: PlatformFolder, + target: PackageTarget, profile: BuildProfile, serverMode: ServerMode, ): string { syncReadmeFromRepoRoot(); writeBuildInfo(target, profile); + const debugSuffix = profile === 'debug' ? '-debug' : ''; + const vsixOut = `vide-vscode-${target}${debugSuffix}.vsix`; + const vsceBin = path.join(vscodeDir, 'node_modules', '@vscode', 'vsce', 'vsce'); + + if (target === webTarget) { + cleanRuntimeServerFiles(); + run( + process.execPath, + [vsceBin, 'package', '--target', target, '--out', vsixOut], + vscodeDir, + sanitizedVsceEnv(), + ); + return path.join(vscodeDir, vsixOut); + } + const binFile = binaryFileForTarget(target); const targetServerPath = ensureTargetServerBinary(target, binFile, profile, serverMode); cleanRuntimeServerFiles(); const runtimeServerPath = stageRuntimeServer(targetServerPath, target, binFile); - const debugSuffix = profile === 'debug' ? '-debug' : ''; - const vsixOut = `vide-vscode-${target}${debugSuffix}.vsix`; - const vsceBin = path.join(vscodeDir, 'node_modules', '@vscode', 'vsce', 'vsce'); try { - run(process.execPath, [ - vsceBin, - 'package', - '--target', - target, - '--out', - vsixOut, - ], vscodeDir, sanitizedVsceEnv()); + run( + process.execPath, + [vsceBin, 'package', '--target', target, '--out', vsixOut], + vscodeDir, + sanitizedVsceEnv(), + ); } finally { fs.rmSync(runtimeServerPath, { force: true }); } diff --git a/editors/vscode/src/browser/client.ts b/editors/vscode/src/browser/client.ts new file mode 100644 index 00000000..feadfd31 --- /dev/null +++ b/editors/vscode/src/browser/client.ts @@ -0,0 +1,221 @@ +import * as vscode from "vscode"; +import { + BaseLanguageClient, + BrowserMessageReader, + BrowserMessageWriter, + CloseAction, + ErrorAction, + type LanguageClientOptions, + type MessageTransports, +} from "vscode-languageclient/browser"; + +import { videInitializationOptions } from "../../../../packages/vide-extension-shared/src/config/initialization-options"; +import type { + LspTraceEntry, + WorkerRequest, + WorkerResponse, + WorkerStatus, +} from "../../../../packages/vide-extension-shared/src/browser/types"; +import { + BROWSER_WORKSPACE_FOLDER_NAME, + type BrowserWorkspaceSnapshot, +} from "./workspaceSnapshot"; + +const CLIENT_DISPOSED_MESSAGE = "Vide browser client has been disposed."; + +export class VideBrowserClient { + private readonly worker: Worker; + private languageClient?: VideLanguageClient; + private workerReadyStatus?: WorkerStatus; + private disposed = false; + + onStatus: (status: WorkerStatus) => void = () => undefined; + onServerCapabilities: (capabilities: unknown) => void = () => undefined; + onTrace: (entry: LspTraceEntry) => void = () => undefined; + onLog: (message: string, level: "info" | "warn" | "error") => void = + () => undefined; + + constructor( + private readonly context: vscode.ExtensionContext, + private readonly snapshot: BrowserWorkspaceSnapshot, + ) { + const workerUri = vscode.Uri.joinPath( + context.extensionUri, + "dist", + "browser", + "vide-lsp.worker.js", + ); + this.worker = new Worker(workerUri.toString(true)); + this.worker.addEventListener("message", (event: MessageEvent) => { + this.handleMessage(event.data); + }); + } + + start(): void { + const channel = new MessageChannel(); + this.languageClient = new VideLanguageClient(this.clientOptions(), { + reader: new BrowserMessageReader(channel.port1), + writer: new BrowserMessageWriter(channel.port1), + }); + this.post( + { + kind: "boot", + wasmBaseUrl: vscode.Uri.joinPath( + this.context.extensionUri, + "dist", + "browser", + "wasm", + ).toString(), + rootUri: this.snapshot.rootUri, + workspaceRootUris: this.snapshot.workspaceRootUris, + workspaceFiles: this.snapshot.workspaceFiles, + lspPort: channel.port2, + }, + [channel.port2], + ); + } + + onNotification( + method: string, + handler: (params: unknown) => void, + ): vscode.Disposable { + this.requireLanguageClient().onNotification(method, handler); + return new vscode.Disposable(() => undefined); + } + + request(method: string, params?: unknown): Promise { + if (this.disposed) { + return Promise.reject(new Error(CLIENT_DISPOSED_MESSAGE)); + } + return this.requireLanguageClient().sendRequest(method, params); + } + + initializeServerInfo(): + | { name?: string; version?: string } + | undefined { + return this.languageClient?.initializeResult?.serverInfo; + } + + dispose(): void { + if (this.disposed) { + return; + } + this.post({ kind: "stop" }); + this.disposed = true; + void this.languageClient?.dispose(500).catch(() => undefined); + this.worker.terminate(); + } + + private post(message: WorkerRequest, transfer: Transferable[] = []): void { + if (this.disposed) { + return; + } + this.worker.postMessage(message, transfer); + } + + private requireLanguageClient(): VideLanguageClient { + if (!this.languageClient || this.disposed) { + throw new Error(CLIENT_DISPOSED_MESSAGE); + } + return this.languageClient; + } + + private clientOptions(): LanguageClientOptions { + return { + documentSelector: [ + { language: "verilog" }, + { language: "systemverilog" }, + ], + workspaceFolder: { + index: 0, + name: BROWSER_WORKSPACE_FOLDER_NAME, + uri: vscode.Uri.parse(this.snapshot.rootUri), + }, + initializationOptions: videInitializationOptions( + vscode.workspace.getConfiguration("vide"), + ), + diagnosticPullOptions: { + onChange: false, + onSave: false, + onTabs: false, + }, + errorHandler: { + error: (error) => { + this.onLog(error.message, "error"); + return { action: ErrorAction.Shutdown }; + }, + closed: () => ({ action: CloseAction.DoNotRestart }), + }, + middleware: { + handleDiagnostics: (uri, diagnostics, next) => { + next(uri, diagnostics); + }, + workspace: { + configuration: () => [], + }, + }, + }; + } + + private handleMessage(message: WorkerResponse): void { + switch (message.kind) { + case "status": + if (message.status.ready) { + this.workerReadyStatus = message.status; + void this.startLanguageClient(); + } else { + this.onStatus(message.status); + } + break; + case "trace": + this.onTrace(message.entry); + break; + case "log": + this.onLog(message.message, message.level); + break; + } + } + + private async startLanguageClient(): Promise { + const languageClient = this.languageClient; + const workerReadyStatus = this.workerReadyStatus; + if ( + !languageClient || + !workerReadyStatus || + this.disposed || + languageClient.isRunning() + ) { + return; + } + + try { + await languageClient.start(); + this.onServerCapabilities( + languageClient.initializeResult?.capabilities ?? null, + ); + this.onStatus(workerReadyStatus); + } catch (error) { + this.onStatus({ + engine: "unavailable", + ready: false, + detail: + error instanceof Error + ? error.message + : "Vide language client failed to start.", + }); + } + } +} + +class VideLanguageClient extends BaseLanguageClient { + constructor( + clientOptions: LanguageClientOptions, + private readonly messageTransports: MessageTransports, + ) { + super("vide", "Vide", clientOptions); + } + + protected createMessageTransports(): Promise { + return Promise.resolve(this.messageTransports); + } +} diff --git a/editors/vscode/src/browser/extension.ts b/editors/vscode/src/browser/extension.ts new file mode 100644 index 00000000..57f950f0 --- /dev/null +++ b/editors/vscode/src/browser/extension.ts @@ -0,0 +1,346 @@ +import * as vscode from "vscode"; + +import { registerDiagnosticActions } from "../diagnosticActions"; +import { + PROJECT_CONFIG_FILE_NAME, + PROJECT_SOURCE_FILE_GLOB, + isProjectConfigFileName, + isProjectSourceFileName, +} from "../projectConfigCommon"; +import { + projectStatusNotification, + reloadWorkspaceCommand, + showOutputCommand, + showStatusCommand, + VideStatusController, +} from "../videStatus"; +import type { ServerStatus } from "../status"; +import { VideBrowserClient } from "./client"; +import { + buildBrowserWorkspaceSnapshot, + createProjectConfigAtRoot, + shouldRestartForWatchedUri, +} from "./workspaceSnapshot"; + +const restartServerCommand = "vide.restartServer"; +const showServerVersionCommand = "vide.showServerVersion"; +const runQiheAnalysisCommand = "vide.runQiheAnalysis"; +const profileDiagnosticsCommand = "vide.profileDiagnostics"; +const languageServerOutputChannelName = "Vide Language Server"; + +interface ExtensionBuildInfo { + kind?: string; + commitHash?: string; + buildDate?: string; +} + +let client: VideBrowserClient | undefined; +let outputChannel: vscode.OutputChannel | undefined; +let videStatusController: VideStatusController | undefined; +let restartChain: Promise = Promise.resolve(); +let workspaceRestartTimer: ReturnType | undefined; + +function log(message: string): void { + outputChannel?.appendLine(message); +} + +function requireOutputChannel(): vscode.OutputChannel { + if (!outputChannel) { + throw new Error(vscode.l10n.t("Vide output channel has not been initialized.")); + } + return outputChannel; +} + +function showOutput(): void { + requireOutputChannel().show(true); +} + +async function showLanguageServerErrorMessage(message: string): Promise { + const showOutputAction = vscode.l10n.t("Show Output"); + const selection = await vscode.window.showErrorMessage( + message, + showOutputAction, + ); + if (selection === showOutputAction) { + showOutput(); + } +} + +function updateServerStatus(status: ServerStatus, detail?: string): void { + videStatusController?.updateServerStatus(status, detail); +} + +function extensionVersion(context: vscode.ExtensionContext): string { + const packageJson = context.extension.packageJSON as { version?: unknown }; + return typeof packageJson.version === "string" && packageJson.version.length > 0 + ? packageJson.version + : "unknown"; +} + +async function extensionBuildInfo( + context: vscode.ExtensionContext, +): Promise { + try { + const bytes = await vscode.workspace.fs.readFile( + vscode.Uri.joinPath(context.extensionUri, "build-info.json"), + ); + return JSON.parse(new TextDecoder("utf-8").decode(bytes)) as ExtensionBuildInfo; + } catch { + return undefined; + } +} + +async function extensionBuildLabel( + context: vscode.ExtensionContext, +): Promise { + const version = extensionVersion(context); + const buildInfo = await extensionBuildInfo(context); + const details = [ + buildInfo?.kind, + buildInfo?.commitHash, + buildInfo?.buildDate, + ].filter((part): part is string => typeof part === "string" && part.length > 0); + return details.length > 0 ? `${version} (${details.join(", ")})` : version; +} + +async function startClient(context: vscode.ExtensionContext): Promise { + updateServerStatus("starting"); + log("[INFO] Building browser workspace snapshot..."); + + try { + const snapshot = await buildBrowserWorkspaceSnapshot(log); + const browserClient = new VideBrowserClient(context, snapshot); + client = browserClient; + + browserClient.onStatus = (status) => { + if (client !== browserClient) { + return; + } + updateServerStatus(status.ready ? "ready" : "error", status.detail); + }; + browserClient.onServerCapabilities = () => undefined; + browserClient.onLog = (message, level) => { + if (client !== browserClient) { + return; + } + log(`[${level.toUpperCase()}] ${message}`); + }; + browserClient.onTrace = (entry) => { + if (client !== browserClient) { + return; + } + log(`[TRACE] ${entry.direction} ${entry.method} ${entry.detail}`); + }; + + browserClient.start(); + browserClient.onNotification(projectStatusNotification, (params) => { + if (client !== browserClient) { + return; + } + videStatusController?.handleProjectNotification(params); + }); + log("[INFO] Browser language client booted."); + } catch (error) { + client = undefined; + const message = + error instanceof Error + ? error.message + : "Failed to start the Vide browser extension."; + log(`[ERROR] ${message}`); + updateServerStatus("error", message); + await showLanguageServerErrorMessage( + vscode.l10n.t("Failed to start Vide Language Server: {0}", message), + ); + } +} + +async function stopClient(): Promise { + if (!client) { + updateServerStatus("stopped"); + return; + } + + updateServerStatus("stopping"); + client.dispose(); + client = undefined; + updateServerStatus("stopped"); +} + +function queueRestart( + context: vscode.ExtensionContext, + reason: string, +): Promise { + restartChain = restartChain + .catch(() => undefined) + .then(async () => { + log(`[INFO] Restarting browser language client: ${reason}`); + await stopClient(); + await startClient(context); + }); + return restartChain; +} + +function scheduleWorkspaceRestart( + context: vscode.ExtensionContext, + reason: string, +): void { + if (workspaceRestartTimer) { + clearTimeout(workspaceRestartTimer); + } + workspaceRestartTimer = setTimeout(() => { + workspaceRestartTimer = undefined; + void queueRestart(context, reason); + }, 250); +} + +async function createProjectConfigsFromRootUris( + context: vscode.ExtensionContext, + rootUris: readonly string[], +): Promise { + const created: vscode.Uri[] = []; + for (const rootUri of rootUris) { + created.push(await createProjectConfigAtRoot(rootUri)); + } + + await queueRestart(context, "project manifest created"); + + const action = vscode.l10n.t("Open Manifest"); + const selection = await vscode.window.showInformationMessage( + created.length === 1 + ? vscode.l10n.t("Created {0}.", PROJECT_CONFIG_FILE_NAME) + : vscode.l10n.t( + "Created {0} in {1} workspace folders.", + PROJECT_CONFIG_FILE_NAME, + created.length, + ), + action, + ); + if (selection === action && created[0]) { + const document = await vscode.workspace.openTextDocument(created[0]); + await vscode.window.showTextDocument(document); + } +} + +async function showServerVersion( + context: vscode.ExtensionContext, +): Promise { + const buildLabel = await extensionBuildLabel(context); + const serverInfo = client?.initializeServerInfo(); + const serverLabel = serverInfo + ? `${serverInfo.name ?? "Vide"} ${serverInfo.version ?? ""}`.trim() + : "unavailable"; + await vscode.window.showInformationMessage( + vscode.l10n.t("Vide extension: {0}; server: {1}", buildLabel, serverLabel), + ); +} + +async function showUnavailableInBrowser(feature: string): Promise { + await vscode.window.showInformationMessage( + vscode.l10n.t("{0} is not available in vscode.dev yet.", feature), + ); +} + +function registerWorkspaceWatchers( + context: vscode.ExtensionContext, +): void { + const sourceWatcher = vscode.workspace.createFileSystemWatcher( + PROJECT_SOURCE_FILE_GLOB, + ); + const manifestWatcher = vscode.workspace.createFileSystemWatcher( + `**/${PROJECT_CONFIG_FILE_NAME}`, + ); + + const handleSourceEvent = (uri: vscode.Uri, label: string) => { + if (!shouldRestartForWatchedUri(uri)) { + return; + } + const openDocument = vscode.workspace.textDocuments.find( + (document) => document.uri.toString() === uri.toString(), + ); + if ( + openDocument && + isProjectSourceFileName(openDocument.fileName) && + !isProjectConfigFileName(openDocument.fileName) + ) { + return; + } + log(`[INFO] Workspace ${label}: ${uri.toString()}`); + scheduleWorkspaceRestart(context, `${label}: ${uri.toString()}`); + }; + + sourceWatcher.onDidCreate((uri) => handleSourceEvent(uri, "source created")); + sourceWatcher.onDidDelete((uri) => handleSourceEvent(uri, "source deleted")); + sourceWatcher.onDidChange((uri) => handleSourceEvent(uri, "source changed")); + + manifestWatcher.onDidCreate((uri) => handleSourceEvent(uri, "manifest created")); + manifestWatcher.onDidDelete((uri) => handleSourceEvent(uri, "manifest deleted")); + manifestWatcher.onDidChange((uri) => handleSourceEvent(uri, "manifest changed")); + + context.subscriptions.push(sourceWatcher, manifestWatcher); +} + +export async function activate( + context: vscode.ExtensionContext, +): Promise { + outputChannel = vscode.window.createOutputChannel(languageServerOutputChannelName); + context.subscriptions.push(outputChannel); + + videStatusController = new VideStatusController({ + createManifest: (rootUris) => createProjectConfigsFromRootUris(context, rootUris), + profileDiagnostics: () => showUnavailableInBrowser("Diagnostics profiling"), + reloadProject: () => queueRestart(context, "reload project"), + restartServer: () => queueRestart(context, "restart command"), + showOutput, + log, + }); + context.subscriptions.push(videStatusController); + updateServerStatus("stopped"); + + log("[INFO] Vide browser extension activating..."); + log(`[INFO] Extension version: ${await extensionBuildLabel(context)}`); + log(`[INFO] VS Code version: ${vscode.version}`); + + context.subscriptions.push( + vscode.commands.registerCommand(showOutputCommand, () => showOutput()), + vscode.commands.registerCommand(showStatusCommand, async () => { + await videStatusController?.show(); + }), + vscode.commands.registerCommand(restartServerCommand, async () => { + await queueRestart(context, "restart command"); + }), + vscode.commands.registerCommand(reloadWorkspaceCommand, async () => { + await queueRestart(context, "reload project command"); + }), + vscode.commands.registerCommand(showServerVersionCommand, async () => { + await showServerVersion(context); + }), + vscode.commands.registerCommand(runQiheAnalysisCommand, async () => { + await showUnavailableInBrowser("Qihe analysis"); + }), + vscode.commands.registerCommand(profileDiagnosticsCommand, async () => { + await showUnavailableInBrowser("Diagnostics profiling"); + }), + ); + + registerDiagnosticActions(context); + registerWorkspaceWatchers(context); + + context.subscriptions.push( + vscode.workspace.onDidChangeConfiguration((event) => { + if (event.affectsConfiguration("vide")) { + scheduleWorkspaceRestart(context, "Vide configuration changed"); + } + }), + ); + + await queueRestart(context, "activation"); + log("[INFO] Vide browser extension activated."); +} + +export async function deactivate(): Promise { + if (workspaceRestartTimer) { + clearTimeout(workspaceRestartTimer); + workspaceRestartTimer = undefined; + } + await stopClient(); +} diff --git a/editors/vscode/src/browser/worker.ts b/editors/vscode/src/browser/worker.ts new file mode 100644 index 00000000..860f6c07 --- /dev/null +++ b/editors/vscode/src/browser/worker.ts @@ -0,0 +1,3 @@ +import { installVideWorkerRuntime } from "../../../../packages/vide-extension-shared/src/browser/worker-runtime"; + +installVideWorkerRuntime(); diff --git a/editors/vscode/src/browser/workspaceSnapshot.ts b/editors/vscode/src/browser/workspaceSnapshot.ts new file mode 100644 index 00000000..10b41082 --- /dev/null +++ b/editors/vscode/src/browser/workspaceSnapshot.ts @@ -0,0 +1,157 @@ +import * as vscode from "vscode"; + +import type { WorkerWorkspaceFile } from "../../../../packages/vide-extension-shared/src/browser/types"; +import { + DEFAULT_PROJECT_CONFIG_TEXT, + PROJECT_CONFIG_FILE_NAME, + PROJECT_SOURCE_FILE_GLOB, + isProjectConfigFileName, + isProjectSourceFileName, +} from "../projectConfigCommon"; + +const textDecoder = new TextDecoder("utf-8"); + +export const BROWSER_WORKSPACE_ROOT_URI = "file:///workspace"; +export const BROWSER_WORKSPACE_FOLDER_NAME = "workspace"; + +export interface BrowserWorkspaceSnapshot { + rootUri: string; + workspaceRootUris: string[]; + workspaceFiles: WorkerWorkspaceFile[]; +} + +export async function buildBrowserWorkspaceSnapshot( + log: (message: string) => void, +): Promise { + const workspaceFolders = vscode.workspace.workspaceFolders ?? []; + const filesByUri = new Map(); + + for (const [index, folder] of workspaceFolders.entries()) { + for (const pattern of [ + PROJECT_SOURCE_FILE_GLOB, + `**/${PROJECT_CONFIG_FILE_NAME}`, + ]) { + const matches = await vscode.workspace.findFiles( + new vscode.RelativePattern(folder, pattern), + ); + for (const uri of matches) { + const syntheticPath = syntheticPathForUri( + workspaceFolders, + folder, + index, + uri, + ); + const content = await readWorkspaceText(uri); + filesByUri.set(uri.toString(), { + path: syntheticPath, + text: content, + uri: uri.toString(), + }); + } + } + } + + for (const document of vscode.workspace.textDocuments) { + if (!shouldMirrorWorkspaceDocument(document)) { + continue; + } + const folder = vscode.workspace.getWorkspaceFolder(document.uri); + if (!folder) { + continue; + } + const index = workspaceFolders.findIndex( + (candidate) => candidate.uri.toString() === folder.uri.toString(), + ); + if (index < 0) { + continue; + } + filesByUri.set(document.uri.toString(), { + path: syntheticPathForUri(workspaceFolders, folder, index, document.uri), + text: document.getText(), + uri: document.uri.toString(), + }); + } + + log( + `[INFO] Prepared browser workspace snapshot with ${filesByUri.size} mirrored files.`, + ); + + return { + rootUri: BROWSER_WORKSPACE_ROOT_URI, + workspaceRootUris: workspaceFolders.map((folder) => folder.uri.toString()), + workspaceFiles: [...filesByUri.values()], + }; +} + +export async function createProjectConfigAtRoot(rootUri: string): Promise { + const root = vscode.Uri.parse(rootUri); + const configUri = vscode.Uri.joinPath(root, PROJECT_CONFIG_FILE_NAME); + + try { + await vscode.workspace.fs.stat(configUri); + return configUri; + } catch { + await vscode.workspace.fs.writeFile( + configUri, + new TextEncoder().encode(DEFAULT_PROJECT_CONFIG_TEXT), + ); + return configUri; + } +} + +export function shouldMirrorWorkspaceDocument( + document: Pick, +): boolean { + const fileName = baseName(document.fileName || document.uri.path); + return ( + isProjectSourceFileName(fileName) || isProjectConfigFileName(fileName) + ); +} + +export function shouldRestartForWatchedUri(uri: vscode.Uri): boolean { + const fileName = baseName(uri.path); + return ( + isProjectSourceFileName(fileName) || isProjectConfigFileName(fileName) + ); +} + +function syntheticPathForUri( + workspaceFolders: readonly vscode.WorkspaceFolder[], + folder: vscode.WorkspaceFolder, + index: number, + uri: vscode.Uri, +): string { + const relativePath = relativePathWithinFolder(folder, uri); + return workspaceFolders.length === 1 + ? relativePath + : `root-${index}/${relativePath}`; +} + +function relativePathWithinFolder( + folder: vscode.WorkspaceFolder, + uri: vscode.Uri, +): string { + const folderPath = folder.uri.path.replace(/\/+$/, ""); + const filePath = uri.path; + const relative = + filePath === folderPath + ? "" + : filePath.startsWith(`${folderPath}/`) + ? filePath.slice(folderPath.length + 1) + : filePath.replace(/^\/+/, ""); + return relative + .split("/") + .filter(Boolean) + .map((segment) => decodeURIComponent(segment)) + .join("/"); +} + +async function readWorkspaceText(uri: vscode.Uri): Promise { + return textDecoder.decode(await vscode.workspace.fs.readFile(uri)); +} + +function baseName(path: string): string { + const normalized = path.replace(/\\/g, "/").replace(/\/+$/, ""); + const slashIndex = normalized.lastIndexOf("/"); + return slashIndex >= 0 ? normalized.slice(slashIndex + 1) : normalized; +} diff --git a/editors/vscode/src/projectConfig.ts b/editors/vscode/src/projectConfig.ts index c1afb3cb..fe476c18 100644 --- a/editors/vscode/src/projectConfig.ts +++ b/editors/vscode/src/projectConfig.ts @@ -1,49 +1,8 @@ import * as path from 'node:path'; -import { - PROJECT_CONFIG_SCHEMA_PATH, - PROJECT_CONFIG_SCHEMA_URL, - PROJECT_CONFIG_SCHEMA_VERSION, -} from './generated/projectConfigSchema'; +export * from './projectConfigCommon'; -export { - PROJECT_CONFIG_SCHEMA_PATH, - PROJECT_CONFIG_SCHEMA_URL, - PROJECT_CONFIG_SCHEMA_VERSION, -} from './generated/projectConfigSchema'; - -export const PROJECT_CONFIG_FILE_NAME = 'vide.toml'; -export const PROJECT_CONFIG_FILE_NAMES = [PROJECT_CONFIG_FILE_NAME] as const; -export const PROJECT_SOURCE_FILE_EXTENSIONS = [ - '.v', - '.sv', - '.vh', - '.svh', - '.svi', -] as const; -export const PROJECT_SOURCE_FILE_GLOB = '**/*.{v,sv,vh,svh,svi}'; - -export const DEFAULT_PROJECT_CONFIG_TEXT = `#:schema ${PROJECT_CONFIG_SCHEMA_URL} -sources = [] - -# include_dirs = ["include"] -# defines = ["SYNTHESIS"] -# top_modules = ["top"] -# libraries = ["../common_cells"] -# exclude = ["build/**"] -`; - -export function isProjectConfigFileName(fileName: string): boolean { - return PROJECT_CONFIG_FILE_NAMES.includes( - fileName as (typeof PROJECT_CONFIG_FILE_NAMES)[number], - ); -} - -export function isProjectSourceFileName(fileName: string): boolean { - return PROJECT_SOURCE_FILE_EXTENSIONS.includes( - path.extname(fileName).toLowerCase() as (typeof PROJECT_SOURCE_FILE_EXTENSIONS)[number], - ); -} +import { PROJECT_CONFIG_FILE_NAME } from './projectConfigCommon'; export function getProjectConfigPath( workspaceFolderPath: string, diff --git a/editors/vscode/src/projectConfigCommon.ts b/editors/vscode/src/projectConfigCommon.ts new file mode 100644 index 00000000..cc7c103e --- /dev/null +++ b/editors/vscode/src/projectConfigCommon.ts @@ -0,0 +1,52 @@ +import { + PROJECT_CONFIG_SCHEMA_PATH, + PROJECT_CONFIG_SCHEMA_URL, + PROJECT_CONFIG_SCHEMA_VERSION, +} from "./generated/projectConfigSchema"; + +export { + PROJECT_CONFIG_SCHEMA_PATH, + PROJECT_CONFIG_SCHEMA_URL, + PROJECT_CONFIG_SCHEMA_VERSION, +} from "./generated/projectConfigSchema"; + +export const PROJECT_CONFIG_FILE_NAME = "vide.toml"; +export const PROJECT_CONFIG_FILE_NAMES = [PROJECT_CONFIG_FILE_NAME] as const; +export const PROJECT_SOURCE_FILE_EXTENSIONS = [ + ".v", + ".sv", + ".vh", + ".svh", + ".svi", +] as const; +export const PROJECT_SOURCE_FILE_GLOB = "**/*.{v,sv,vh,svh,svi}"; + +export const DEFAULT_PROJECT_CONFIG_TEXT = `#:schema ${PROJECT_CONFIG_SCHEMA_URL} +sources = [] + +# include_dirs = ["include"] +# defines = ["SYNTHESIS"] +# top_modules = ["top"] +# libraries = ["../common_cells"] +# exclude = ["build/**"] +`; + +export function isProjectConfigFileName(fileName: string): boolean { + return PROJECT_CONFIG_FILE_NAMES.includes( + fileName as (typeof PROJECT_CONFIG_FILE_NAMES)[number], + ); +} + +export function isProjectSourceFileName(fileName: string): boolean { + return PROJECT_SOURCE_FILE_EXTENSIONS.includes( + lowerCaseExtension(fileName) as (typeof PROJECT_SOURCE_FILE_EXTENSIONS)[number], + ); +} + +function lowerCaseExtension(fileName: string): string { + const normalized = fileName.replace(/\\/g, "/"); + const lastSlash = normalized.lastIndexOf("/"); + const baseName = lastSlash >= 0 ? normalized.slice(lastSlash + 1) : normalized; + const extensionIndex = baseName.lastIndexOf("."); + return extensionIndex >= 0 ? baseName.slice(extensionIndex).toLowerCase() : ""; +} diff --git a/editors/vscode/src/videStatus.ts b/editors/vscode/src/videStatus.ts index 2d54c4ba..690c4ca7 100644 --- a/editors/vscode/src/videStatus.ts +++ b/editors/vscode/src/videStatus.ts @@ -1,8 +1,6 @@ -import * as path from 'node:path'; - import * as vscode from 'vscode'; -import { PROJECT_CONFIG_FILE_NAME } from './projectConfig'; +import { PROJECT_CONFIG_FILE_NAME } from './projectConfigCommon'; import { asProjectStatus, getVideStatusPresentation, @@ -294,7 +292,7 @@ async function openProjectManifest(status: ProjectStatus): Promise { status.manifestUris.map((uri) => { const displayPath = uriDisplayPath(uri); return { - label: path.basename(displayPath), + label: baseName(displayPath), description: displayPath, uri, }; @@ -309,3 +307,9 @@ async function openProjectManifest(status: ProjectStatus): Promise { await openUri(selected.uri); } + +function baseName(value: string): string { + const normalized = value.replace(/\\/g, '/').replace(/\/+$/, ''); + const slashIndex = normalized.lastIndexOf('/'); + return slashIndex >= 0 ? normalized.slice(slashIndex + 1) : normalized; +} diff --git a/editors/vscode/test-web/runTests.ts b/editors/vscode/test-web/runTests.ts new file mode 100644 index 00000000..4f22836c --- /dev/null +++ b/editors/vscode/test-web/runTests.ts @@ -0,0 +1,24 @@ +import * as path from "node:path"; + +import { runTests } from "@vscode/test-web"; + +const extensionDevelopmentPath = path.resolve(__dirname, ".."); +const extensionTestsPath = path.join( + extensionDevelopmentPath, + "dist", + "test-web", + "suite", + "index.js", +); + +async function main(): Promise { + await runTests({ + browserType: "chromium", + extensionDevelopmentPath, + extensionTestsPath, + headless: true, + quality: "stable", + }); +} + +void main(); diff --git a/editors/vscode/test-web/suite/activation.test.ts b/editors/vscode/test-web/suite/activation.test.ts new file mode 100644 index 00000000..54b77d2f --- /dev/null +++ b/editors/vscode/test-web/suite/activation.test.ts @@ -0,0 +1,29 @@ +import * as vscode from "vscode"; + +const EXTENSION_ID = "pascal-lab.vide-ide"; + +suite("Vide web extension smoke", () => { + test("activates in the web host", async () => { + const extension = vscode.extensions.getExtension(EXTENSION_ID); + if (!extension) { + throw new Error(`extension ${EXTENSION_ID} is not registered`); + } + + // Browser-host extensions activate on language contribution; opening a + // SystemVerilog document triggers it. + const document = await vscode.workspace.openTextDocument({ + language: "systemverilog", + content: "module smoke; endmodule\n", + }); + await vscode.window.showTextDocument(document); + + const deadline = Date.now() + 20_000; + while (!extension.isActive && Date.now() < deadline) { + await new Promise((resolve) => setTimeout(resolve, 100)); + } + + if (!extension.isActive) { + throw new Error(`extension ${EXTENSION_ID} did not activate within 20s`); + } + }); +}); diff --git a/editors/vscode/test-web/suite/index.ts b/editors/vscode/test-web/suite/index.ts new file mode 100644 index 00000000..1a9b6bc2 --- /dev/null +++ b/editors/vscode/test-web/suite/index.ts @@ -0,0 +1,28 @@ +import "mocha/mocha"; + +declare const mocha: { + setup(options: Record): void; + run(callback: (failures: number) => void): void; +}; + +mocha.setup({ + ui: "tdd", + reporter: undefined, + timeout: 30_000, +}); + +// Keep this after mocha.setup; ESM imports would be hoisted too early. +declare const require: (id: string) => unknown; +require("./activation.test"); + +export function run(): Promise { + return new Promise((resolve, reject) => { + mocha.run((failures) => { + if (failures > 0) { + reject(new Error(`${failures} test(s) failed.`)); + } else { + resolve(); + } + }); + }); +} diff --git a/editors/vscode/tsconfig.browser.json b/editors/vscode/tsconfig.browser.json new file mode 100644 index 00000000..351d3285 --- /dev/null +++ b/editors/vscode/tsconfig.browser.json @@ -0,0 +1,19 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "ES2022", + "moduleResolution": "Bundler" + }, + "include": [ + "../../packages/vide-extension-shared/src/**/*.ts", + "src/browser/**/*.ts", + "src/diagnosticActions.ts", + "src/diagnosticRules.ts", + "src/generated/**/*.ts", + "src/initializationOptions.ts", + "src/projectConfigCommon.ts", + "src/status.ts", + "src/videStatus.ts" + ], + "exclude": [] +} diff --git a/editors/vscode/tsconfig.json b/editors/vscode/tsconfig.json index 7e5f7130..c13bc05f 100644 --- a/editors/vscode/tsconfig.json +++ b/editors/vscode/tsconfig.json @@ -3,7 +3,6 @@ "target": "ES2022", "module": "Node16", "moduleResolution": "Node16", - "rootDir": ".", "outDir": "out", "strict": true, "esModuleInterop": true, @@ -11,8 +10,14 @@ "skipLibCheck": true }, "include": [ + "../../packages/vide-extension-shared/src/**/*.ts", "scripts/**/*.ts", "src/**/*.ts", - "test/**/*.ts" + "test/**/*.ts", + "test-web/runTests.ts" + ], + "exclude": [ + "src/browser/**/*.ts", + "test-web/suite/**/*.ts" ] } diff --git a/editors/vscode/tsconfig.test-web.json b/editors/vscode/tsconfig.test-web.json new file mode 100644 index 00000000..97a1c101 --- /dev/null +++ b/editors/vscode/tsconfig.test-web.json @@ -0,0 +1,13 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "ES2022", + "moduleResolution": "Bundler", + "lib": ["ES2022", "DOM", "DOM.Iterable"], + "types": ["mocha"] + }, + "include": [ + "test-web/suite/**/*.ts" + ], + "exclude": [] +} diff --git a/packages/vide-extension-shared/src/browser/document-selector.ts b/packages/vide-extension-shared/src/browser/document-selector.ts new file mode 100644 index 00000000..e91c4bd5 --- /dev/null +++ b/packages/vide-extension-shared/src/browser/document-selector.ts @@ -0,0 +1,13 @@ +export function videDocumentSelector(rootUri: string): Array<{ + scheme: "file"; + language: "verilog" | "systemverilog"; + pattern?: string; +}> { + const rootPath = new URL(rootUri).pathname.replace(/\/+$/, ""); + const pattern = rootPath ? `${decodeURIComponent(rootPath)}/**` : undefined; + + return [ + { scheme: "file", language: "systemverilog", pattern }, + { scheme: "file", language: "verilog", pattern }, + ]; +} diff --git a/packages/vide-extension-shared/src/browser/lsp-protocol.ts b/packages/vide-extension-shared/src/browser/lsp-protocol.ts new file mode 100644 index 00000000..aa826f5f --- /dev/null +++ b/packages/vide-extension-shared/src/browser/lsp-protocol.ts @@ -0,0 +1,36 @@ +export interface WasmEngine { + send(message: LspMessage): LspMessage[]; + poll(): LspMessage[]; + writeFile(path: string, text: string): void; + reset(): void; +} + +export type LspMessage = LspRequest | LspNotification | LspResponse; + +export interface LspRequest { + jsonrpc: "2.0"; + id: number | string; + method: string; + params?: unknown; +} + +export interface LspNotification { + jsonrpc: "2.0"; + method: string; + params?: unknown; +} + +export interface LspResponse { + jsonrpc: "2.0"; + id: number | string; + result?: unknown; + error?: { + code: number; + message: string; + data?: unknown; + }; +} + +export function isRecord(value: unknown): value is Record { + return typeof value === "object" && value !== null; +} diff --git a/packages/vide-extension-shared/src/browser/types.ts b/packages/vide-extension-shared/src/browser/types.ts new file mode 100644 index 00000000..5985f3a8 --- /dev/null +++ b/packages/vide-extension-shared/src/browser/types.ts @@ -0,0 +1,34 @@ +export interface LspTraceEntry { + id: number; + direction: "client" | "server"; + method: string; + detail: string; +} + +export interface WorkerStatus { + engine: "wasm" | "unavailable"; + ready: boolean; + detail: string; +} + +export interface WorkerWorkspaceFile { + path: string; + text: string; + uri?: string; +} + +export type WorkerRequest = + | { + kind: "boot"; + wasmBaseUrl: string; + rootUri: string; + workspaceRootUris?: string[]; + workspaceFiles: WorkerWorkspaceFile[]; + lspPort: MessagePort; + } + | { kind: "stop" }; + +export type WorkerResponse = + | { kind: "status"; status: WorkerStatus } + | { kind: "trace"; entry: LspTraceEntry } + | { kind: "log"; level: "info" | "warn" | "error"; message: string }; diff --git a/packages/vide-extension-shared/src/browser/worker-runtime.ts b/packages/vide-extension-shared/src/browser/worker-runtime.ts new file mode 100644 index 00000000..3380f3e4 --- /dev/null +++ b/packages/vide-extension-shared/src/browser/worker-runtime.ts @@ -0,0 +1,534 @@ +import type { WorkerRequest, WorkerResponse, WorkerStatus, WorkerWorkspaceFile } from "./types"; +import { isRecord, type LspMessage, type LspNotification, type WasmEngine } from "./lsp-protocol"; + +const REQUEST_TIMEOUT_MS = 15_000; +const POLL_INTERVAL_MS = 16; +const RUN_QIHE_ANALYSIS_COMMAND = "vide.server.runQiheAnalysis"; + +interface PendingLspRequest { + method: string; + timeout: number; +} + +let engine: WasmEngine | null = null; +let lspPort: MessagePort | undefined; +let status: WorkerStatus = { + engine: "unavailable", + ready: false, + detail: "Vide WASM engine has not been loaded.", +}; +let traceId = 1; +let pollTimer: number | undefined; +let rootUri = "file:///workspace"; +let externalWorkspaceRootUris: string[] = []; +const pendingLspRequests = new Map(); +const workspaceTextByPath = new Map(); +const externalToSyntheticUris = new Map(); +const syntheticToExternalUris = new Map(); + +export function installVideWorkerRuntime(): void { + self.addEventListener("message", (event: MessageEvent) => { + void handleRequest(event.data).catch((error: unknown) => { + post({ + kind: "log", + level: "error", + message: + error instanceof Error ? error.message : "Vide worker request failed.", + }); + }); + }); +} + +async function handleRequest(message: WorkerRequest): Promise { + switch (message.kind) { + case "boot": + trace("client", "boot", `${message.workspaceFiles.length} workspace files`); + await boot( + message.wasmBaseUrl, + message.rootUri, + message.workspaceRootUris ?? [], + message.workspaceFiles, + message.lspPort, + ); + post({ kind: "status", status }); + break; + case "stop": + stopEngine(); + break; + } +} + +async function boot( + wasmBaseUrl: string, + requestedRootUri: string, + requestedWorkspaceRootUris: string[], + workspaceFiles: WorkerWorkspaceFile[], + requestedLspPort: MessagePort, +): Promise { + try { + stopEngine(); + rootUri = normalizeRootUri(requestedRootUri); + externalWorkspaceRootUris = requestedWorkspaceRootUris + .map(normalizeRootUri) + .sort((left, right) => right.length - left.length); + workspaceTextByPath.clear(); + for (const [index, workspaceRootUri] of requestedWorkspaceRootUris.entries()) { + registerUriMapping( + normalizeRootUri(workspaceRootUri), + syntheticRootUriForIndex(requestedWorkspaceRootUris.length, index), + ); + } + for (const file of workspaceFiles) { + const normalizedPath = normalizeWorkspacePath(file.path); + const syntheticUri = workspaceUri(normalizedPath); + const externalUri = normalizeRootUri(file.uri ?? syntheticUri); + registerUriMapping(externalUri, syntheticUri); + workspaceTextByPath.set(normalizedPath, file.text); + } + lspPort = requestedLspPort; + lspPort.onmessage = (event: MessageEvent) => handleLspMessage(event.data); + lspPort.start(); + engine = await loadWasmEngine(wasmBaseUrl, rootUri, workspaceFiles); + status = { + engine: "wasm", + ready: true, + detail: "Vide WASM engine loaded.", + }; + trace("server", "ready", status.detail); + } catch (error) { + stopEngine(); + status = { + engine: "unavailable", + ready: false, + detail: + error instanceof Error ? error.message : "Vide WASM is not available.", + }; + post({ + kind: "log", + level: "error", + message: `${status.detail} Run npm run build:wasm before using the browser runtime.`, + }); + } +} + +async function loadWasmEngine( + wasmBaseUrl: string, + requestedRootUri: string, + workspaceFiles: WorkerWorkspaceFile[], +): Promise { + const baseUrl = new URL( + wasmBaseUrl.endsWith("/") ? wasmBaseUrl : `${wasmBaseUrl}/`, + self.location.href, + ); + const moduleUrl = new URL("vide-lsp.js", baseUrl); + moduleUrl.search = baseUrl.search; + const loaded = (await import(/* @vite-ignore */ moduleUrl.href)) as { + createVideLspEngine?: (options: { + wasmBaseUrl: string; + rootUri: string; + workspaceFiles: WorkerWorkspaceFile[]; + }) => Promise; + }; + + if (!loaded.createVideLspEngine) { + throw new Error("Vide WASM adapter did not export createVideLspEngine()."); + } + + return loaded.createVideLspEngine({ + wasmBaseUrl: baseUrl.href, + rootUri: requestedRootUri, + workspaceFiles, + }); +} + +function handleLspMessage(message: LspMessage): void { + const translatedMessage = rewriteMessageUris( + message, + translateClientUriToSynthetic, + ); + traceLspMessage("client", translatedMessage); + if (!engine) { + respondWithError(translatedMessage, status.detail); + return; + } + + trackClientRequest(translatedMessage); + applyWorkspaceTextSideEffect(translatedMessage); + + try { + const emitted = engine.send(translatedMessage); + processEmittedMessages(emitted); + schedulePump(); + } catch (error) { + clearClientRequest(translatedMessage); + respondWithError( + translatedMessage, + error instanceof Error ? error.message : "Vide LSP request failed.", + ); + } +} + +function processEmittedMessages(emitted: LspMessage[]): void { + for (const rawMessage of emitted) { + const message = disableUnsupportedBrowserCapabilities(rawMessage); + const translatedMessage = rewriteMessageUris( + message, + translateSyntheticUriToClient, + ); + traceLspMessage("server", translatedMessage); + clearClientRequest(translatedMessage); + postLsp(translatedMessage); + } +} + +function disableUnsupportedBrowserCapabilities(message: LspMessage): LspMessage { + if (!isInitializeResponse(message)) { + return message; + } + + const capabilities = message.result.capabilities; + delete capabilities.documentFormattingProvider; + delete capabilities.documentRangeFormattingProvider; + delete capabilities.documentOnTypeFormattingProvider; + + const executeCommandProvider = recordValue(capabilities.executeCommandProvider); + if (executeCommandProvider && Array.isArray(executeCommandProvider.commands)) { + executeCommandProvider.commands = executeCommandProvider.commands.filter( + (command) => command !== RUN_QIHE_ANALYSIS_COMMAND, + ); + } + + return message; +} + +function isInitializeResponse( + message: LspMessage, +): message is LspMessage & { result: { capabilities: Record } } { + if (!("id" in message) || "method" in message || !isRecord(message.result)) { + return false; + } + const pending = pendingLspRequests.get(message.id); + return pending?.method === "initialize" && isRecord(message.result.capabilities); +} + +function pollLsp(): void { + if (!engine) { + return; + } + processEmittedMessages(engine.poll()); +} + +function schedulePump(): void { + if (pollTimer !== undefined || pendingLspRequests.size === 0) { + return; + } + + pollTimer = self.setTimeout(() => { + pollTimer = undefined; + try { + pollLsp(); + schedulePump(); + } catch (error) { + failPendingRequests( + error instanceof Error ? error.message : "Vide LSP polling failed.", + ); + } + }, POLL_INTERVAL_MS); +} + +function trackClientRequest(message: LspMessage): void { + if (!("id" in message) || !("method" in message)) { + return; + } + + const id = message.id; + const method = message.method; + const timeout = self.setTimeout(() => { + const pending = pendingLspRequests.get(id); + if (!pending) { + return; + } + pendingLspRequests.delete(id); + trace("server", pending.method, "request timed out"); + postLsp({ + jsonrpc: "2.0", + id, + error: { + code: -32001, + message: `Vide LSP did not respond to ${pending.method}.`, + }, + }); + }, REQUEST_TIMEOUT_MS); + + pendingLspRequests.set(id, { method, timeout }); +} + +function clearClientRequest(message: LspMessage): void { + if (!("id" in message) || "method" in message) { + return; + } + const pending = pendingLspRequests.get(message.id); + if (!pending) { + return; + } + self.clearTimeout(pending.timeout); + pendingLspRequests.delete(message.id); +} + +function failPendingRequests(message: string): void { + for (const [id, pending] of pendingLspRequests) { + self.clearTimeout(pending.timeout); + postLsp({ + jsonrpc: "2.0", + id, + error: { code: -32001, message }, + }); + } + pendingLspRequests.clear(); +} + +function respondWithError(message: LspMessage, errorMessage: string): void { + if (!("id" in message)) { + post({ kind: "log", level: "error", message: errorMessage }); + return; + } + postLsp({ + jsonrpc: "2.0", + id: message.id, + error: { code: -32001, message: errorMessage }, + }); +} + +function postLsp(message: LspMessage): void { + lspPort?.postMessage(message); +} + +function writeWorkspaceFile(path: string, text: string): void { + const normalized = normalizeWorkspacePath(path); + workspaceTextByPath.set(normalized, text); + requireEngine().writeFile(normalized, text); +} + +function applyWorkspaceTextSideEffect(message: LspMessage): void { + if (!("method" in message)) { + return; + } + + if (message.method === "textDocument/didOpen") { + const params = lspParams(message); + const textDocument = recordValue(params?.textDocument); + const path = workspacePathFromUri(textDocument?.uri); + if (path && typeof textDocument?.text === "string") { + writeWorkspaceFile(path, textDocument.text); + } + return; + } + + if (message.method === "textDocument/didChange") { + const params = lspParams(message); + const textDocument = recordValue(params?.textDocument); + const path = workspacePathFromUri(textDocument?.uri); + const contentChanges = Array.isArray(params?.contentChanges) + ? params.contentChanges + : []; + if (!path || contentChanges.length === 0) { + return; + } + let text = workspaceTextByPath.get(path) ?? ""; + for (const change of contentChanges.map(recordValue)) { + if (!change || typeof change.text !== "string") { + continue; + } + text = change.range + ? applyRangedTextChange(text, recordValue(change.range), change.text) + : change.text; + } + writeWorkspaceFile(path, text); + } +} + +function applyRangedTextChange( + text: string, + range: Record | undefined, + replacement: string, +): string { + const start = recordValue(range?.start); + const end = recordValue(range?.end); + const startOffset = offsetAt(text, Number(start?.line), Number(start?.character)); + const endOffset = offsetAt(text, Number(end?.line), Number(end?.character)); + return `${text.slice(0, startOffset)}${replacement}${text.slice(endOffset)}`; +} + +function offsetAt(text: string, line: number, character: number): number { + if (!Number.isFinite(line) || !Number.isFinite(character) || line <= 0) { + return Math.max( + 0, + Math.min(text.length, Number.isFinite(character) ? character : 0), + ); + } + + let offset = 0; + for ( + let currentLine = 0; + currentLine < line && offset < text.length; + currentLine += 1 + ) { + const nextNewline = text.indexOf("\n", offset); + if (nextNewline < 0) { + return text.length; + } + offset = nextNewline + 1; + } + + return Math.max(0, Math.min(text.length, offset + character)); +} + +function lspParams( + message: LspNotification, +): Record | undefined { + return recordValue(message.params); +} + +function workspacePathFromUri(uri: unknown): string | undefined { + if (typeof uri !== "string") { + return undefined; + } + const prefix = `${rootUri}/`; + if (!uri.startsWith(prefix)) { + return undefined; + } + return normalizeWorkspacePath(decodeURIComponent(uri.slice(prefix.length))); +} + +function requireEngine(): WasmEngine { + if (!engine) { + throw new Error(status.detail); + } + return engine; +} + +function stopEngine(): void { + failPendingRequests("Vide LSP is stopping."); + if (pollTimer !== undefined) { + self.clearTimeout(pollTimer); + pollTimer = undefined; + } + engine?.reset(); + engine = null; + lspPort?.close(); + lspPort = undefined; + externalWorkspaceRootUris = []; + externalToSyntheticUris.clear(); + syntheticToExternalUris.clear(); + workspaceTextByPath.clear(); +} + +function traceLspMessage(direction: "client" | "server", message: LspMessage): void { + if ("method" in message) { + const detail = "params" in message ? summarizeJson(message.params) : ""; + trace(direction, message.method, detail); + } else if ("id" in message) { + trace( + direction, + `response#${String(message.id)}`, + message.error ? message.error.message : "ok", + ); + } +} + +function trace(direction: "client" | "server", method: string, detail: string): void { + post({ + kind: "trace", + entry: { id: traceId++, direction, method, detail }, + }); +} + +function summarizeJson(value: unknown): string { + const text = JSON.stringify(value); + return text.length > 160 ? `${text.slice(0, 157)}...` : text; +} + +function post(response: WorkerResponse): void { + self.postMessage(response); +} + +function registerUriMapping(externalUri: string, syntheticUri: string): void { + externalToSyntheticUris.set(externalUri, syntheticUri); + syntheticToExternalUris.set(syntheticUri, externalUri); +} + +function translateClientUriToSynthetic(uri: string): string { + const direct = externalToSyntheticUris.get(uri); + if (direct) { + return direct; + } + + for (const workspaceRoot of externalWorkspaceRootUris) { + if (uri === workspaceRoot || uri.startsWith(`${workspaceRoot}/`)) { + const relative = decodeURIComponent( + uri === workspaceRoot ? "" : uri.slice(workspaceRoot.length + 1), + ); + const syntheticUri = workspaceUri(relative); + registerUriMapping(uri, syntheticUri); + return syntheticUri; + } + } + + return uri; +} + +function translateSyntheticUriToClient(uri: string): string { + return syntheticToExternalUris.get(uri) ?? uri; +} + +function rewriteMessageUris( + value: T, + translate: (uri: string) => string, +): T { + if (Array.isArray(value)) { + return value.map((item) => rewriteMessageUris(item, translate)) as T; + } + + if (!isRecord(value)) { + return value; + } + + const output: Record = {}; + for (const [key, item] of Object.entries(value)) { + if (typeof item === "string" && (key === "uri" || key.endsWith("Uri"))) { + output[key] = translate(item); + } else if ( + Array.isArray(item) && + key.endsWith("Uris") && + item.every((entry) => typeof entry === "string") + ) { + output[key] = item.map((entry) => translate(entry)); + } else { + output[key] = rewriteMessageUris(item, translate); + } + } + return output as T; +} + +function normalizeRootUri(uri: string): string { + return uri.replace(/\/+$/, ""); +} + +function syntheticRootUriForIndex(rootCount: number, index: number): string { + return rootCount === 1 ? rootUri : workspaceUri(`root-${index}`); +} + +function normalizeWorkspacePath(path: string): string { + return path.replace(/\\/g, "/").replace(/^\/+/, ""); +} + +function workspaceUri(path: string): string { + return `${rootUri}/${normalizeWorkspacePath(path) + .split("/") + .map(encodeURIComponent) + .join("/")}`; +} + +function recordValue(value: unknown): Record | undefined { + return isRecord(value) ? value : undefined; +} diff --git a/packages/vide-extension-shared/src/config/initialization-options.ts b/packages/vide-extension-shared/src/config/initialization-options.ts new file mode 100644 index 00000000..f8986a0d --- /dev/null +++ b/packages/vide-extension-shared/src/config/initialization-options.ts @@ -0,0 +1,133 @@ +export type ConfigurationReader = { + get(section: string): T | undefined; +}; + +function setting( + config: ConfigurationReader | undefined, + section: string, + fallback: T, +): T { + return config?.get(section) ?? fallback; +} + +export function videInitializationOptions( + config?: ConfigurationReader, +): Record { + return { + files: { + excludeDirs: setting(config, "files.excludeDirs", []), + watcher: setting(config, "files.watcher", "client"), + }, + workspace: { + auto: { + reload: setting(config, "workspace.auto.reload", true), + }, + }, + scope: { + visibility: setting(config, "scope.visibility", "private"), + }, + formatter: { + provider: setting(config, "formatter.provider", "verible"), + path: setting(config, "formatter.path", null), + args: setting(config, "formatter.args", ["--failsafe_success=false"]), + }, + formatting: { + on: { + enter: setting(config, "formatting.on.enter", true), + }, + in: { + comments: setting(config, "formatting.in.comments", true), + }, + indent: { + width: setting(config, "formatting.indent.width", 4), + }, + }, + inlayHints: { + port: { + connection: { + enable: setting(config, "inlayHints.port.connection.enable", true), + }, + }, + parameter: { + assignment: { + enable: setting(config, "inlayHints.parameter.assignment.enable", true), + }, + }, + end: { + structure: { + enable: setting(config, "inlayHints.end.structure.enable", true), + }, + }, + }, + lens: { + instantiations: { + enable: setting(config, "lens.instantiations.enable", true), + }, + }, + semantic: { + tokens: { + port: { + clk: { + rst: { + enable: setting(config, "semantic.tokens.port.clk.rst.enable", true), + }, + }, + input: { + output: { + enable: setting( + config, + "semantic.tokens.port.input.output.enable", + true, + ), + }, + }, + }, + }, + }, + diagnostics: { + enable: setting(config, "diagnostics.enable", true), + update: setting(config, "diagnostics.update", "onSave"), + parse: { + enable: setting(config, "diagnostics.parse.enable", true), + }, + semantic: { + enable: setting(config, "diagnostics.semantic.enable", true), + }, + slang: { + warnings: setting(config, "diagnostics.slang.warnings", []), + rules: setting(config, "diagnostics.slang.rules", []), + }, + }, + signature: { + help: { + params: { + only: setting(config, "signature.help.params.only", false), + }, + }, + }, + qihe: { + command: setting(config, "qihe.command", "qihe"), + autoConfigureArgsFromManifest: setting( + config, + "qihe.autoConfigureArgsFromManifest", + true, + ), + compileArgs: setting(config, "qihe.compileArgs", []), + runArgs: setting(config, "qihe.runArgs", ["-g", "std"]), + }, + }; +} + +export function videDiagnosticsProfilingInitializationOptions( + config?: ConfigurationReader, +): Record { + const options = videInitializationOptions(config); + + return { + ...options, + files: { + ...(options.files as Record), + watcher: "server", + }, + }; +}