diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 6aada44..cc2949a 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -172,10 +172,6 @@ jobs: target: aarch64-unknown-linux-gnu bun_target: bun-linux-arm64 artifact_name: operator-linux-arm64 - - os: macos-15-intel - target: x86_64-apple-darwin - bun_target: bun-darwin-x64 - artifact_name: operator-macos-x86_64 - os: macos-14 target: aarch64-apple-darwin bun_target: bun-darwin-arm64 @@ -184,10 +180,6 @@ jobs: target: x86_64-pc-windows-msvc bun_target: bun-windows-x64 artifact_name: operator-windows-x86_64.exe - - os: windows-11-arm - target: aarch64-pc-windows-msvc - bun_target: bun-windows-arm64 - artifact_name: operator-windows-arm64.exe runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/opr8r.yaml b/.github/workflows/opr8r.yaml index 9707359..0be9696 100644 --- a/.github/workflows/opr8r.yaml +++ b/.github/workflows/opr8r.yaml @@ -106,9 +106,6 @@ jobs: - target: aarch64-unknown-linux-gnu os: ubuntu-24.04-arm artifact: opr8r-linux-arm64 - - target: x86_64-apple-darwin - os: macos-13 - artifact: opr8r-macos-x86_64 - target: aarch64-apple-darwin os: macos-14 artifact: opr8r-macos-arm64 @@ -116,11 +113,6 @@ jobs: os: windows-latest artifact: opr8r-windows-x86_64 extension: .exe - - target: aarch64-pc-windows-msvc - os: windows-latest - artifact: opr8r-windows-arm64 - extension: .exe - cross_compile: true runs-on: ${{ matrix.os }} defaults: run: @@ -142,41 +134,18 @@ jobs: restore-keys: | opr8r-${{ matrix.artifact }}-cargo- - - name: Install cross-compile target (Windows ARM64) - if: matrix.cross_compile == true - run: rustup target add ${{ matrix.target }} - - name: Build release binary - run: cargo build --release --target ${{ matrix.target }} - if: matrix.cross_compile == true - - - name: Build release binary (native) run: cargo build --release - if: matrix.cross_compile != true - name: Strip binary (Linux/macOS) if: runner.os != 'Windows' - run: | - if [ "${{ matrix.cross_compile }}" = "true" ]; then - strip target/${{ matrix.target }}/release/opr8r || true - else - strip target/release/opr8r || true - fi - - - name: Upload artifact (native) - if: matrix.cross_compile != true - uses: actions/upload-artifact@v4 - with: - name: ${{ matrix.artifact }} - path: opr8r/target/release/opr8r${{ matrix.extension || '' }} - retention-days: 30 + run: strip target/release/opr8r || true - - name: Upload artifact (cross-compiled) - if: matrix.cross_compile == true + - name: Upload artifact uses: actions/upload-artifact@v4 with: name: ${{ matrix.artifact }} - path: opr8r/target/${{ matrix.target }}/release/opr8r${{ matrix.extension || '' }} + path: opr8r/target/release/opr8r${{ matrix.extension || '' }} retention-days: 30 release: @@ -228,10 +197,8 @@ jobs: ### Downloads - **Linux x86_64**: `opr8r-linux-x86_64` - **Linux ARM64**: `opr8r-linux-arm64` - - **macOS Intel**: `opr8r-macos-x86_64` - **macOS Apple Silicon**: `opr8r-macos-arm64` - **Windows x86_64**: `opr8r-windows-x86_64.exe` - - **Windows ARM64**: `opr8r-windows-arm64.exe` files: release/* draft: false prerelease: false diff --git a/.github/workflows/vscode-extension.yaml b/.github/workflows/vscode-extension.yaml index b50f57e..f17e649 100644 --- a/.github/workflows/vscode-extension.yaml +++ b/.github/workflows/vscode-extension.yaml @@ -67,16 +67,11 @@ jobs: opr8r_artifact: opr8r-linux-x86_64 - vscode_target: linux-arm64 opr8r_artifact: opr8r-linux-arm64 - - vscode_target: darwin-x64 - opr8r_artifact: opr8r-macos-x86_64 - vscode_target: darwin-arm64 opr8r_artifact: opr8r-macos-arm64 - vscode_target: win32-x64 opr8r_artifact: opr8r-windows-x86_64 opr8r_extension: .exe - - vscode_target: win32-arm64 - opr8r_artifact: opr8r-windows-arm64 - opr8r_extension: .exe runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 diff --git a/README.md b/README.md index 6528fb5..d78e5df 100644 --- a/README.md +++ b/README.md @@ -78,11 +78,6 @@ curl -L https://github.com/untra/operator/releases/latest/download/operator-maco chmod +x operator sudo mv operator /usr/local/bin/ -# macOS Intel -curl -L https://github.com/untra/operator/releases/latest/download/operator-macos-x86_64 -o operator -chmod +x operator -sudo mv operator /usr/local/bin/ - # Linux x86_64 curl -L https://github.com/untra/operator/releases/latest/download/operator-linux-x86_64 -o operator chmod +x operator diff --git a/docs/downloads/index.md b/docs/downloads/index.md index 966b650..571ce19 100644 --- a/docs/downloads/index.md +++ b/docs/downloads/index.md @@ -19,11 +19,9 @@ Download Operator! for your platform. Curren | Platform | Architecture | Download | |----------|--------------|----------| | macOS | ARM64 (Apple Silicon) | [operator-macos-arm64]({{ site.github.repo }}/releases/download/v{{ site.version }}/operator-macos-arm64) | -| macOS | x86_64 (Intel) | [operator-macos-x86_64]({{ site.github.repo }}/releases/download/v{{ site.version }}/operator-macos-x86_64) | | Linux | ARM64 | [operator-linux-arm64]({{ site.github.repo }}/releases/download/v{{ site.version }}/operator-linux-arm64) | | Linux | x86_64 | [operator-linux-x86_64]({{ site.github.repo }}/releases/download/v{{ site.version }}/operator-linux-x86_64) | | Windows | x86_64 | [operator-windows-x86_64.exe]({{ site.github.repo }}/releases/download/v{{ site.version }}/operator-windows-x86_64.exe) | -| Windows | ARM64 | [operator-windows-arm64.exe]({{ site.github.repo }}/releases/download/v{{ site.version }}/operator-windows-arm64.exe) | ## Backstage Server @@ -32,7 +30,6 @@ Optional companion server for web-based project monitoring dashboard. | Platform | Architecture | Download | |----------|--------------|----------| | macOS | ARM64 | [backstage-server-bun-darwin-arm64]({{ site.github.repo }}/releases/download/v{{ site.version }}/backstage-server-bun-darwin-arm64) | -| macOS | x64 | [backstage-server-bun-darwin-x64]({{ site.github.repo }}/releases/download/v{{ site.version }}/backstage-server-bun-darwin-x64) | | Linux | ARM64 | [backstage-server-bun-linux-arm64]({{ site.github.repo }}/releases/download/v{{ site.version }}/backstage-server-bun-linux-arm64) | | Linux | x64 | [backstage-server-bun-linux-x64]({{ site.github.repo }}/releases/download/v{{ site.version }}/backstage-server-bun-linux-x64) | | Windows | x64 | [backstage-server-bun-windows-x64]({{ site.github.repo }}/releases/download/v{{ site.version }}/backstage-server-bun-windows-x64) | diff --git a/tests/kanban_integration.rs b/tests/kanban_integration.rs index 12abc5f..d56a8d0 100644 --- a/tests/kanban_integration.rs +++ b/tests/kanban_integration.rs @@ -32,20 +32,38 @@ use operator::api::providers::kanban::{ CreateIssueRequest, JiraProvider, KanbanProvider, LinearProvider, UpdateStatusRequest, }; use std::env; +use tokio::sync::OnceCell; + +// Cached credential validation results +static JIRA_CREDENTIALS_VALID: OnceCell = OnceCell::const_new(); +static LINEAR_CREDENTIALS_VALID: OnceCell = OnceCell::const_new(); // ─── Configuration Helpers ─────────────────────────────────────────────────── -/// Check if Jira credentials are configured +/// Check if Jira credentials are configured (non-empty env vars) fn jira_configured() -> bool { - env::var("OPERATOR_JIRA_DOMAIN").is_ok() - && env::var("OPERATOR_JIRA_EMAIL").is_ok() - && env::var("OPERATOR_JIRA_API_KEY").is_ok() - && env::var("OPERATOR_JIRA_TEST_PROJECT").is_ok() + env::var("OPERATOR_JIRA_DOMAIN") + .map(|s| !s.is_empty()) + .unwrap_or(false) + && env::var("OPERATOR_JIRA_EMAIL") + .map(|s| !s.is_empty()) + .unwrap_or(false) + && env::var("OPERATOR_JIRA_API_KEY") + .map(|s| !s.is_empty()) + .unwrap_or(false) + && env::var("OPERATOR_JIRA_TEST_PROJECT") + .map(|s| !s.is_empty()) + .unwrap_or(false) } -/// Check if Linear credentials are configured +/// Check if Linear credentials are configured (non-empty env vars) fn linear_configured() -> bool { - env::var("OPERATOR_LINEAR_API_KEY").is_ok() && env::var("OPERATOR_LINEAR_TEST_TEAM").is_ok() + env::var("OPERATOR_LINEAR_API_KEY") + .map(|s| !s.is_empty()) + .unwrap_or(false) + && env::var("OPERATOR_LINEAR_TEST_TEAM") + .map(|s| !s.is_empty()) + .unwrap_or(false) } /// Get the Jira test project key @@ -79,13 +97,86 @@ fn find_terminal_status(statuses: &[String]) -> Option { None } -/// Macro to skip test if provider is not configured +/// Validate Jira credentials by testing the connection. +/// Result is cached for the duration of the test run. +async fn jira_credentials_valid() -> bool { + if !jira_configured() { + return false; + } + + *JIRA_CREDENTIALS_VALID + .get_or_init(|| async { + match JiraProvider::from_env() { + Ok(provider) => match provider.test_connection().await { + Ok(valid) => { + if !valid { + eprintln!( + "Jira credentials validation failed: connection test returned false" + ); + } + valid + } + Err(e) => { + eprintln!("Jira credentials validation failed: {}", e); + false + } + }, + Err(e) => { + eprintln!("Jira provider initialization failed: {}", e); + false + } + } + }) + .await +} + +/// Validate Linear credentials by testing the connection. +/// Result is cached for the duration of the test run. +async fn linear_credentials_valid() -> bool { + if !linear_configured() { + return false; + } + + *LINEAR_CREDENTIALS_VALID + .get_or_init(|| async { + match LinearProvider::from_env() { + Ok(provider) => match provider.test_connection().await { + Ok(valid) => { + if !valid { + eprintln!( + "Linear credentials validation failed: connection test returned false" + ); + } + valid + } + Err(e) => { + eprintln!("Linear credentials validation failed: {}", e); + false + } + }, + Err(e) => { + eprintln!("Linear provider initialization failed: {}", e); + false + } + } + }) + .await +} + +/// Macro to skip test if provider is not configured or credentials are invalid macro_rules! skip_if_not_configured { - ($configured:expr, $provider:expr) => { + ($configured:expr, $valid:expr, $provider:expr) => { if !$configured { eprintln!("Skipping test: {} credentials not configured", $provider); return; } + if !$valid.await { + eprintln!( + "Skipping test: {} credentials invalid or expired", + $provider + ); + return; + } }; } @@ -100,7 +191,7 @@ mod jira_tests { #[tokio::test] async fn test_connection() { - skip_if_not_configured!(jira_configured(), "Jira"); + skip_if_not_configured!(jira_configured(), jira_credentials_valid(), "Jira"); let provider = get_provider(); let result = provider.test_connection().await; @@ -110,7 +201,7 @@ mod jira_tests { #[tokio::test] async fn test_list_projects() { - skip_if_not_configured!(jira_configured(), "Jira"); + skip_if_not_configured!(jira_configured(), jira_credentials_valid(), "Jira"); let provider = get_provider(); let projects = provider @@ -131,7 +222,7 @@ mod jira_tests { #[tokio::test] async fn test_list_users() { - skip_if_not_configured!(jira_configured(), "Jira"); + skip_if_not_configured!(jira_configured(), jira_credentials_valid(), "Jira"); let provider = get_provider(); let project = jira_test_project(); @@ -150,7 +241,7 @@ mod jira_tests { #[tokio::test] async fn test_list_statuses() { - skip_if_not_configured!(jira_configured(), "Jira"); + skip_if_not_configured!(jira_configured(), jira_credentials_valid(), "Jira"); let provider = get_provider(); let project = jira_test_project(); @@ -165,7 +256,7 @@ mod jira_tests { #[tokio::test] async fn test_get_issue_types() { - skip_if_not_configured!(jira_configured(), "Jira"); + skip_if_not_configured!(jira_configured(), jira_credentials_valid(), "Jira"); let provider = get_provider(); let project = jira_test_project(); @@ -183,7 +274,7 @@ mod jira_tests { #[tokio::test] async fn test_list_issues() { - skip_if_not_configured!(jira_configured(), "Jira"); + skip_if_not_configured!(jira_configured(), jira_credentials_valid(), "Jira"); let provider = get_provider(); let project = jira_test_project(); @@ -217,7 +308,7 @@ mod jira_tests { #[tokio::test] async fn test_create_issue() { - skip_if_not_configured!(jira_configured(), "Jira"); + skip_if_not_configured!(jira_configured(), jira_credentials_valid(), "Jira"); let provider = get_provider(); let project = jira_test_project(); @@ -248,7 +339,7 @@ mod jira_tests { #[tokio::test] async fn test_update_issue_status() { - skip_if_not_configured!(jira_configured(), "Jira"); + skip_if_not_configured!(jira_configured(), jira_credentials_valid(), "Jira"); let provider = get_provider(); let project = jira_test_project(); @@ -320,7 +411,7 @@ mod linear_tests { #[tokio::test] async fn test_connection() { - skip_if_not_configured!(linear_configured(), "Linear"); + skip_if_not_configured!(linear_configured(), linear_credentials_valid(), "Linear"); let provider = get_provider(); let result = provider.test_connection().await; @@ -330,7 +421,7 @@ mod linear_tests { #[tokio::test] async fn test_list_projects() { - skip_if_not_configured!(linear_configured(), "Linear"); + skip_if_not_configured!(linear_configured(), linear_credentials_valid(), "Linear"); let provider = get_provider(); let teams = provider.list_projects().await.expect("Should list teams"); @@ -348,7 +439,7 @@ mod linear_tests { #[tokio::test] async fn test_list_users() { - skip_if_not_configured!(linear_configured(), "Linear"); + skip_if_not_configured!(linear_configured(), linear_credentials_valid(), "Linear"); let provider = get_provider(); let team = linear_test_team(); @@ -368,7 +459,7 @@ mod linear_tests { #[tokio::test] async fn test_list_statuses() { - skip_if_not_configured!(linear_configured(), "Linear"); + skip_if_not_configured!(linear_configured(), linear_credentials_valid(), "Linear"); let provider = get_provider(); let team = linear_test_team(); @@ -383,7 +474,7 @@ mod linear_tests { #[tokio::test] async fn test_get_issue_types() { - skip_if_not_configured!(linear_configured(), "Linear"); + skip_if_not_configured!(linear_configured(), linear_credentials_valid(), "Linear"); let provider = get_provider(); let team = linear_test_team(); @@ -401,7 +492,7 @@ mod linear_tests { #[tokio::test] async fn test_list_issues() { - skip_if_not_configured!(linear_configured(), "Linear"); + skip_if_not_configured!(linear_configured(), linear_credentials_valid(), "Linear"); let provider = get_provider(); let team = linear_test_team(); @@ -427,7 +518,7 @@ mod linear_tests { #[tokio::test] async fn test_create_issue() { - skip_if_not_configured!(linear_configured(), "Linear"); + skip_if_not_configured!(linear_configured(), linear_credentials_valid(), "Linear"); let provider = get_provider(); let team = linear_test_team(); @@ -477,7 +568,7 @@ mod linear_tests { #[tokio::test] async fn test_update_issue_status() { - skip_if_not_configured!(linear_configured(), "Linear"); + skip_if_not_configured!(linear_configured(), linear_credentials_valid(), "Linear"); let provider = get_provider(); let team = linear_test_team(); @@ -598,11 +689,11 @@ mod linear_tests { #[tokio::test] async fn test_provider_interface_consistency() { // This test verifies both providers implement the same interface - let jira_ok = jira_configured(); - let linear_ok = linear_configured(); + let jira_ok = jira_configured() && jira_credentials_valid().await; + let linear_ok = linear_configured() && linear_credentials_valid().await; if !jira_ok && !linear_ok { - eprintln!("Skipping: No providers configured"); + eprintln!("Skipping: No providers configured or credentials invalid"); return; } diff --git a/vscode-extension/package-lock.json b/vscode-extension/package-lock.json index 9952850..865f5f0 100644 --- a/vscode-extension/package-lock.json +++ b/vscode-extension/package-lock.json @@ -24,6 +24,7 @@ "eslint": "^8.56.0", "glob": "^10.3.10", "mocha": "^10.2.0", + "nyc": "^17.1.0", "sinon": "^17.0.1", "source-map-support": "^0.5.21", "typescript": "^5.3.3" @@ -207,7 +208,6 @@ "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/helper-validator-identifier": "^7.27.1", "js-tokens": "^4.0.0", @@ -223,7 +223,6 @@ "integrity": "sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=6.9.0" } @@ -234,7 +233,6 @@ "integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", @@ -265,8 +263,7 @@ "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@babel/core/node_modules/semver": { "version": "6.3.1", @@ -274,7 +271,6 @@ "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, "license": "ISC", - "peer": true, "bin": { "semver": "bin/semver.js" } @@ -285,7 +281,6 @@ "integrity": "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/parser": "^7.28.5", "@babel/types": "^7.28.5", @@ -303,7 +298,6 @@ "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/compat-data": "^7.27.2", "@babel/helper-validator-option": "^7.27.1", @@ -321,7 +315,6 @@ "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", "dev": true, "license": "ISC", - "peer": true, "dependencies": { "yallist": "^3.0.2" } @@ -332,7 +325,6 @@ "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, "license": "ISC", - "peer": true, "bin": { "semver": "bin/semver.js" } @@ -342,8 +334,7 @@ "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", "dev": true, - "license": "ISC", - "peer": true + "license": "ISC" }, "node_modules/@babel/helper-globals": { "version": "7.28.0", @@ -351,7 +342,6 @@ "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=6.9.0" } @@ -362,7 +352,6 @@ "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/traverse": "^7.27.1", "@babel/types": "^7.27.1" @@ -377,7 +366,6 @@ "integrity": "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/helper-module-imports": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1", @@ -396,7 +384,6 @@ "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=6.9.0" } @@ -407,7 +394,6 @@ "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=6.9.0" } @@ -418,7 +404,6 @@ "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=6.9.0" } @@ -429,7 +414,6 @@ "integrity": "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/template": "^7.27.2", "@babel/types": "^7.28.4" @@ -444,7 +428,6 @@ "integrity": "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/types": "^7.28.5" }, @@ -461,7 +444,6 @@ "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/parser": "^7.27.2", @@ -477,7 +459,6 @@ "integrity": "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", @@ -497,7 +478,6 @@ "integrity": "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" @@ -718,7 +698,6 @@ "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", "dev": true, "license": "ISC", - "peer": true, "dependencies": { "camelcase": "^5.3.1", "find-up": "^4.1.0", @@ -736,7 +715,6 @@ "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "sprintf-js": "~1.0.2" } @@ -747,7 +725,6 @@ "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=6" } @@ -758,7 +735,6 @@ "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "locate-path": "^5.0.0", "path-exists": "^4.0.0" @@ -773,7 +749,6 @@ "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "argparse": "^1.0.7", "esprima": "^4.0.0" @@ -788,7 +763,6 @@ "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "p-locate": "^4.1.0" }, @@ -802,7 +776,6 @@ "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "p-try": "^2.0.0" }, @@ -819,7 +792,6 @@ "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "p-limit": "^2.2.0" }, @@ -833,7 +805,6 @@ "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=8" } @@ -870,7 +841,6 @@ "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" @@ -882,7 +852,6 @@ "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" @@ -1621,7 +1590,6 @@ "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "clean-stack": "^2.0.0", "indent-string": "^4.0.0" @@ -1700,7 +1668,6 @@ "integrity": "sha512-7yeyCEurROLQJFv5Xj4lEGTy0borxepjFv1g22oAdqFu//SrAlDl1O1Nxx15SH1RoliUml6p8dwJW9jvZughhg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "default-require-extensions": "^3.0.0" }, @@ -1713,8 +1680,7 @@ "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz", "integrity": "sha512-Xg+9RwCg/0p32teKdGMPTPnVXKD0w3DfHnFTficozsAgsvq2XenPJq/MYpzzQ/v8zrOyJn6Ds39VA4JIDwFfqw==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/argparse": { "version": "2.0.1", @@ -1786,7 +1752,6 @@ "integrity": "sha512-B0xUquLkiGLgHhpPBqvl7GWegWBUNuujQ6kXd/r1U38ElPT6Ok8KZ8e+FpUGEc2ZoRQUzq/aUnaKFc/svWUGSg==", "dev": true, "license": "Apache-2.0", - "peer": true, "bin": { "baseline-browser-mapping": "dist/cli.js" } @@ -1890,7 +1855,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", @@ -2021,7 +1985,6 @@ "integrity": "sha512-kpqOvwXnjjN44D89K5ccQC+RUrsy7jB/XLlRrx0D7/2HNcTPqzsb6XgYoErwko6QsV184CA2YgS1fxDiiDZMWA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "hasha": "^5.0.0", "make-dir": "^3.0.0", @@ -2038,7 +2001,6 @@ "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "semver": "^6.0.0" }, @@ -2055,7 +2017,6 @@ "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, "license": "ISC", - "peer": true, "bin": { "semver": "bin/semver.js" } @@ -2133,8 +2094,7 @@ "url": "https://github.com/sponsors/ai" } ], - "license": "CC-BY-4.0", - "peer": true + "license": "CC-BY-4.0" }, "node_modules/chalk": { "version": "2.4.2", @@ -2247,7 +2207,6 @@ "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=6" } @@ -2427,8 +2386,7 @@ "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/concat-map": { "version": "0.0.1", @@ -2442,8 +2400,7 @@ "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/core-util-is": { "version": "1.0.3", @@ -2599,7 +2556,6 @@ "integrity": "sha512-eXTJmRbm2TIt9MgWTsOH1wEuhew6XGZcMeGKCtLedIg/NCsg1iBePXkceTdK4Fii7pzmN9tGsZhKzZ4h7O/fxw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "strip-bom": "^4.0.0" }, @@ -2776,8 +2732,7 @@ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.267.tgz", "integrity": "sha512-0Drusm6MVRXSOJpGbaSVgcQsuB4hEkMpHXaVstcPmhu5LIedxs1xNK/nIxmQIU/RPC0+1/o0AVZfBTkTNJOdUw==", "dev": true, - "license": "ISC", - "peer": true + "license": "ISC" }, "node_modules/emoji-regex": { "version": "9.2.2", @@ -2878,8 +2833,7 @@ "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz", "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/escalade": { "version": "3.2.0", @@ -3138,7 +3092,6 @@ "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", "dev": true, "license": "BSD-2-Clause", - "peer": true, "bin": { "esparse": "bin/esparse.js", "esvalidate": "bin/esvalidate.js" @@ -3294,7 +3247,6 @@ "integrity": "sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "commondir": "^1.0.1", "make-dir": "^3.0.2", @@ -3313,7 +3265,6 @@ "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "semver": "^6.0.0" }, @@ -3330,7 +3281,6 @@ "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, "license": "ISC", - "peer": true, "bin": { "semver": "bin/semver.js" } @@ -3437,8 +3387,7 @@ "url": "https://feross.org/support" } ], - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/fs-constants": { "version": "1.0.0", @@ -3486,7 +3435,6 @@ "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=6.9.0" } @@ -3545,7 +3493,6 @@ "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=8.0.0" } @@ -3677,8 +3624,7 @@ "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", "dev": true, - "license": "ISC", - "peer": true + "license": "ISC" }, "node_modules/graphemer": { "version": "1.4.0", @@ -3732,7 +3678,6 @@ "integrity": "sha512-Hrp5vIK/xr5SkeN2onO32H0MgNZ0f17HRNH39WfL0SYUNOTZ5Lz1TJ8Pajo/87dYGEFlLMm7mIc/k/s6Bvz9HQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "is-stream": "^2.0.0", "type-fest": "^0.8.0" @@ -3750,7 +3695,6 @@ "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", "dev": true, "license": "(MIT OR CC0-1.0)", - "peer": true, "engines": { "node": ">=8" } @@ -3944,7 +3888,6 @@ "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=8" } @@ -4106,7 +4049,6 @@ "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=8" }, @@ -4119,8 +4061,7 @@ "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/is-unicode-supported": { "version": "0.1.0", @@ -4141,7 +4082,6 @@ "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -4192,7 +4132,6 @@ "integrity": "sha512-Pt/uge1Q9s+5VAZ+pCo16TYMWPBIl+oaNIjgLQxcX0itS6ueeaA+pEfThZpH8WxhFgCiEb8sAJY6MdUKgiIWaQ==", "dev": true, "license": "BSD-3-Clause", - "peer": true, "dependencies": { "append-transform": "^2.0.0" }, @@ -4206,7 +4145,6 @@ "integrity": "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==", "dev": true, "license": "BSD-3-Clause", - "peer": true, "dependencies": { "@babel/core": "^7.23.9", "@babel/parser": "^7.23.9", @@ -4224,7 +4162,6 @@ "integrity": "sha512-NkwHbo3E00oybX6NGJi6ar0B29vxyvNwoC7eJ4G4Yq28UfY758Hgn/heV8VRFhevPED4LXfFz0DQ8z/0kw9zMg==", "dev": true, "license": "ISC", - "peer": true, "dependencies": { "archy": "^1.0.0", "cross-spawn": "^7.0.3", @@ -4281,7 +4218,6 @@ "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", "dev": true, "license": "BSD-3-Clause", - "peer": true, "dependencies": { "debug": "^4.1.1", "istanbul-lib-coverage": "^3.0.0", @@ -4326,8 +4262,7 @@ "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/js-yaml": { "version": "4.1.1", @@ -4348,7 +4283,6 @@ "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", "dev": true, "license": "MIT", - "peer": true, "bin": { "jsesc": "bin/jsesc" }, @@ -4383,7 +4317,6 @@ "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", "dev": true, "license": "MIT", - "peer": true, "bin": { "json5": "lib/cli.js" }, @@ -4552,8 +4485,7 @@ "resolved": "https://registry.npmjs.org/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz", "integrity": "sha512-uHaJFihxmJcEX3kT4I23ABqKKalJ/zDrDg0lsFtc1h+3uw49SIJ5beyhx5ExVRti3AvKoOJngIj7xz3oylPdWQ==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/lodash.includes": { "version": "4.3.0", @@ -5196,7 +5128,6 @@ "integrity": "sha512-RM5oyBy45cLEoHqCeh+MNuFAxO0vTFBLskvQbOKnEE7YTTSN4tbN8QWDIPQ6L+WvKsB/qLEGpYe2ZZ9d4W9OIQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "process-on-spawn": "^1.0.0" }, @@ -5209,8 +5140,7 @@ "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/normalize-path": { "version": "3.0.0", @@ -5241,7 +5171,6 @@ "integrity": "sha512-U42vQ4czpKa0QdI1hu950XuNhYqgoM+ZF1HT+VuUHL9hPfDPVvNQyltmMqdE9bUHMVa+8yNbc3QKTj8zQhlVxQ==", "dev": true, "license": "ISC", - "peer": true, "dependencies": { "@istanbuljs/load-nyc-config": "^1.0.0", "@istanbuljs/schema": "^0.1.2", @@ -5284,7 +5213,6 @@ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "color-convert": "^2.0.1" }, @@ -5301,7 +5229,6 @@ "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -5313,7 +5240,6 @@ "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=6" } @@ -5324,7 +5250,6 @@ "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", "dev": true, "license": "ISC", - "peer": true, "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.0", @@ -5337,7 +5262,6 @@ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "color-name": "~1.1.4" }, @@ -5350,8 +5274,7 @@ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/nyc/node_modules/decamelize": { "version": "1.2.0", @@ -5359,7 +5282,6 @@ "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -5369,8 +5291,7 @@ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/nyc/node_modules/find-up": { "version": "4.1.0", @@ -5378,7 +5299,6 @@ "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "locate-path": "^5.0.0", "path-exists": "^4.0.0" @@ -5394,7 +5314,6 @@ "deprecated": "Glob versions prior to v9 are no longer supported", "dev": true, "license": "ISC", - "peer": true, "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -5416,7 +5335,6 @@ "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "p-locate": "^4.1.0" }, @@ -5430,7 +5348,6 @@ "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "semver": "^6.0.0" }, @@ -5447,7 +5364,6 @@ "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, "license": "ISC", - "peer": true, "dependencies": { "brace-expansion": "^1.1.7" }, @@ -5461,7 +5377,6 @@ "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "p-try": "^2.0.0" }, @@ -5478,7 +5393,6 @@ "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "p-limit": "^2.2.0" }, @@ -5492,7 +5406,6 @@ "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=8" } @@ -5503,7 +5416,6 @@ "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, "license": "ISC", - "peer": true, "bin": { "semver": "bin/semver.js" } @@ -5513,8 +5425,7 @@ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", "dev": true, - "license": "ISC", - "peer": true + "license": "ISC" }, "node_modules/nyc/node_modules/string-width": { "version": "4.2.3", @@ -5522,7 +5433,6 @@ "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -5538,7 +5448,6 @@ "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", "dev": true, "license": "ISC", - "peer": true, "dependencies": { "@istanbuljs/schema": "^0.1.2", "glob": "^7.1.4", @@ -5554,7 +5463,6 @@ "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", @@ -5569,8 +5477,7 @@ "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", "dev": true, - "license": "ISC", - "peer": true + "license": "ISC" }, "node_modules/nyc/node_modules/yargs": { "version": "15.4.1", @@ -5578,7 +5485,6 @@ "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "cliui": "^6.0.0", "decamelize": "^1.2.0", @@ -5602,7 +5508,6 @@ "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", "dev": true, "license": "ISC", - "peer": true, "dependencies": { "camelcase": "^5.0.0", "decamelize": "^1.2.0" @@ -5859,7 +5764,6 @@ "integrity": "sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "aggregate-error": "^3.0.0" }, @@ -5873,7 +5777,6 @@ "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=6" } @@ -5884,7 +5787,6 @@ "integrity": "sha512-whdkPIooSu/bASggZ96BWVvZTRMOFxnyUG5PnTSGKoJE2gd5mbVNmR2Nj20QFzxYYgAXpoqC+AiXzl+UMRh7zQ==", "dev": true, "license": "ISC", - "peer": true, "dependencies": { "graceful-fs": "^4.1.15", "hasha": "^5.0.0", @@ -6078,8 +5980,7 @@ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", "dev": true, - "license": "ISC", - "peer": true + "license": "ISC" }, "node_modules/picomatch": { "version": "2.3.1", @@ -6100,7 +6001,6 @@ "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "find-up": "^4.0.0" }, @@ -6114,7 +6014,6 @@ "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "locate-path": "^5.0.0", "path-exists": "^4.0.0" @@ -6129,7 +6028,6 @@ "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "p-locate": "^4.1.0" }, @@ -6143,7 +6041,6 @@ "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "p-try": "^2.0.0" }, @@ -6160,7 +6057,6 @@ "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "p-limit": "^2.2.0" }, @@ -6219,7 +6115,6 @@ "integrity": "sha512-JOnOPQ/8TZgjs1JIH/m9ni7FfimjNa/PRx7y/Wb5qdItsnhO0jE4AT7fC0HjC28DUQWDr50dwSYZLdRMlqDq3Q==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "fromentries": "^1.2.0" }, @@ -6379,7 +6274,6 @@ "integrity": "sha512-gUAyHVHPPC5wdqX/LG4LWtRYtgjxyX78oanFNTMMyFEfOqdC54s3eE82imuWKbOeqYht2CrNf64Qb8vgmmtZGA==", "dev": true, "license": "ISC", - "peer": true, "dependencies": { "es6-error": "^4.0.1" }, @@ -6402,8 +6296,7 @@ "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", "dev": true, - "license": "ISC", - "peer": true + "license": "ISC" }, "node_modules/resolve-from": { "version": "4.0.0", @@ -6609,8 +6502,7 @@ "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", "dev": true, - "license": "ISC", - "peer": true + "license": "ISC" }, "node_modules/setimmediate": { "version": "1.0.5", @@ -6859,7 +6751,6 @@ "integrity": "sha512-EeajNjfN9zMnULLwhZZQU3GWBoFNkbngTUPfaawT4RkMiviTxcX0qfhVbGey39mfctfDHkWtuecgQ8NJcyQWHg==", "dev": true, "license": "ISC", - "peer": true, "dependencies": { "foreground-child": "^2.0.0", "is-windows": "^1.0.2", @@ -6878,7 +6769,6 @@ "integrity": "sha512-dCIq9FpEcyQyXKCkyzmlPTFNgrCzPudOe+mhvJU5zAtlBnGVy2yKxtfsxK2tQBThwq225jcvBjpw1Gr40uzZCA==", "dev": true, "license": "ISC", - "peer": true, "dependencies": { "cross-spawn": "^7.0.0", "signal-exit": "^3.0.2" @@ -6893,7 +6783,6 @@ "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "semver": "^6.0.0" }, @@ -6910,7 +6799,6 @@ "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, "license": "ISC", - "peer": true, "bin": { "semver": "bin/semver.js" } @@ -6920,16 +6808,14 @@ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", "dev": true, - "license": "ISC", - "peer": true + "license": "ISC" }, "node_modules/sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", "dev": true, - "license": "BSD-3-Clause", - "peer": true + "license": "BSD-3-Clause" }, "node_modules/stdin-discarder": { "version": "0.2.2", @@ -7064,7 +6950,6 @@ "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=8" } @@ -7302,7 +7187,6 @@ "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "is-typedarray": "^1.0.0" } @@ -7372,7 +7256,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" @@ -7485,8 +7368,7 @@ "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.1.tgz", "integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==", "dev": true, - "license": "ISC", - "peer": true + "license": "ISC" }, "node_modules/word-wrap": { "version": "1.2.5", @@ -7655,7 +7537,6 @@ "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", "dev": true, "license": "ISC", - "peer": true, "dependencies": { "imurmurhash": "^0.1.4", "is-typedarray": "^1.0.0", @@ -7668,8 +7549,7 @@ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", "dev": true, - "license": "ISC", - "peer": true + "license": "ISC" }, "node_modules/wsl-utils": { "version": "0.1.0", diff --git a/vscode-extension/package.json b/vscode-extension/package.json index 5646e0e..bc7488c 100644 --- a/vscode-extension/package.json +++ b/vscode-extension/package.json @@ -229,13 +229,13 @@ "pretest": "npm run compile && npm run lint", "lint": "eslint src --ext ts", "test": "vscode-test", - "test:coverage": "c8 npm run test", - "coverage:report": "c8 report --reporter=lcov", + "test:coverage": "npm run test", "package": "vsce package", "publish": "vsce publish" }, "devDependencies": { "@istanbuljs/nyc-config-typescript": "^1.0.2", + "nyc": "^17.1.0", "@types/glob": "^8.1.0", "@types/mocha": "^10.0.6", "@types/node": "20.x", diff --git a/vscode-extension/src/operator-binary.ts b/vscode-extension/src/operator-binary.ts index 1539cbd..1e8f685 100644 --- a/vscode-extension/src/operator-binary.ts +++ b/vscode-extension/src/operator-binary.ts @@ -28,11 +28,11 @@ export function getExtensionVersion(): string { * * Supported platforms: * - darwin + arm64 -> operator-macos-arm64 - * - darwin + x64 -> operator-macos-x86_64 * - linux + arm64 -> operator-linux-arm64 * - linux + x64 -> operator-linux-x86_64 * - win32 + x64 -> operator-windows-x86_64.exe - * - win32 + arm64 -> operator-windows-arm64.exe + * + * Unsupported platforms fall back to system PATH lookup */ function getArtifactName(): string { const platform = process.platform; // 'darwin', 'linux', 'win32' diff --git a/vscode-extension/test/suite/index.ts b/vscode-extension/test/suite/index.ts index 73bdda0..192a81a 100644 --- a/vscode-extension/test/suite/index.ts +++ b/vscode-extension/test/suite/index.ts @@ -2,15 +2,46 @@ import * as path from 'path'; import Mocha from 'mocha'; import { glob } from 'glob'; +// NYC for coverage instrumentation inside VS Code process +// eslint-disable-next-line @typescript-eslint/no-require-imports +const NYC = require('nyc'); + export async function run(): Promise { + const testsRoot = path.resolve(__dirname, '.'); + const workspaceRoot = path.join(__dirname, '..', '..', '..'); + + // Setup NYC for coverage inside VS Code process + const nyc = new NYC({ + cwd: workspaceRoot, + reporter: ['text', 'lcov', 'html'], + all: true, + silent: false, + instrument: true, + hookRequire: true, + hookRunInContext: true, + hookRunInThisContext: true, + include: ['out/src/**/*.js'], + exclude: ['out/test/**', 'out/src/generated/**'], + reportDir: path.join(workspaceRoot, 'coverage'), + }); + + await nyc.reset(); + await nyc.wrap(); + + // Re-require already-loaded modules for instrumentation + Object.keys(require.cache) + .filter((f) => nyc.exclude.shouldInstrument(f)) + .forEach((m) => { + delete require.cache[m]; + require(m); + }); + // Create the mocha test const mocha = new Mocha({ ui: 'tdd', color: true, }); - const testsRoot = path.resolve(__dirname, '.'); - const files = await glob('**/**.test.js', { cwd: testsRoot }); // Add files to the test suite @@ -18,7 +49,14 @@ export async function run(): Promise { // Run the mocha test return new Promise((resolve, reject) => { - mocha.run((failures) => { + mocha.run(async (failures) => { + // Write coverage data + await nyc.writeCoverageFile(); + + // Generate and display coverage report + console.log('\n--- Coverage Report ---'); + await captureStdout(nyc.report.bind(nyc)); + if (failures > 0) { reject(new Error(`${failures} tests failed.`)); } else { @@ -27,3 +65,16 @@ export async function run(): Promise { }); }); } + +async function captureStdout(fn: () => Promise): Promise { + const originalWrite = process.stdout.write.bind(process.stdout); + let buffer = ''; + process.stdout.write = (s: string): boolean => { + buffer += s; + originalWrite(s); + return true; + }; + await fn(); + process.stdout.write = originalWrite; + return buffer; +} diff --git a/vscode-extension/test/suite/operator-binary.test.ts b/vscode-extension/test/suite/operator-binary.test.ts new file mode 100644 index 0000000..a7e5942 --- /dev/null +++ b/vscode-extension/test/suite/operator-binary.test.ts @@ -0,0 +1,424 @@ +/** + * Tests for operator-binary.ts + * + * Tests the operator binary discovery, download URL generation, + * version checking, and path resolution functions. + */ + +import * as assert from 'assert'; +import * as sinon from 'sinon'; +import * as vscode from 'vscode'; +import * as path from 'path'; +import * as fs from 'fs/promises'; +import * as os from 'os'; +import { + getExtensionVersion, + getDownloadUrl, + getStoragePath, + getOperatorPath, + isOperatorAvailable, + getOperatorVersion, +} from '../../src/operator-binary'; + +suite('Operator Binary Test Suite', () => { + let sandbox: sinon.SinonSandbox; + + // Platform-specific binary name + const binaryName = process.platform === 'win32' ? 'operator.exe' : 'operator'; + + setup(() => { + sandbox = sinon.createSandbox(); + }); + + teardown(() => { + sandbox.restore(); + }); + + suite('getExtensionVersion()', () => { + test('returns version from extension packageJSON', () => { + sandbox.stub(vscode.extensions, 'getExtension').returns({ + packageJSON: { version: '1.2.3' }, + } as vscode.Extension); + + const version = getExtensionVersion(); + assert.strictEqual(version, '1.2.3'); + }); + + test('falls back to 0.2.0 when extension not found', () => { + sandbox.stub(vscode.extensions, 'getExtension').returns(undefined); + + const version = getExtensionVersion(); + assert.strictEqual(version, '0.2.0'); + }); + + test('falls back to 0.2.0 when packageJSON has no version', () => { + sandbox.stub(vscode.extensions, 'getExtension').returns({ + packageJSON: {}, + } as vscode.Extension); + + const version = getExtensionVersion(); + assert.strictEqual(version, '0.2.0'); + }); + }); + + suite('getDownloadUrl()', () => { + test('generates correct URL format with explicit version', () => { + const url = getDownloadUrl('1.0.0'); + + assert.ok(url.startsWith('https://github.com/untra/operator/releases/download/v1.0.0/')); + assert.ok(url.includes('operator-')); + }); + + test('uses extension version when none provided', () => { + sandbox.stub(vscode.extensions, 'getExtension').returns({ + packageJSON: { version: '2.3.4' }, + } as vscode.Extension); + + const url = getDownloadUrl(); + + assert.ok(url.includes('/v2.3.4/')); + }); + + test('includes platform-specific binary name', () => { + const url = getDownloadUrl('1.0.0'); + + // Check that it includes platform-specific naming + if (process.platform === 'darwin') { + assert.ok(url.includes('operator-macos-'), `Expected macos in URL: ${url}`); + } else if (process.platform === 'linux') { + assert.ok(url.includes('operator-linux-'), `Expected linux in URL: ${url}`); + } else if (process.platform === 'win32') { + assert.ok(url.includes('operator-windows-'), `Expected windows in URL: ${url}`); + assert.ok(url.endsWith('.exe'), 'Windows URL should end with .exe'); + } + }); + + test('includes architecture-specific binary name', () => { + const url = getDownloadUrl('1.0.0'); + + // Check architecture naming + if (process.arch === 'arm64') { + assert.ok(url.includes('-arm64'), `Expected arm64 in URL: ${url}`); + } else if (process.arch === 'x64') { + assert.ok(url.includes('-x86_64'), `Expected x86_64 in URL: ${url}`); + } + }); + }); + + suite('getStoragePath()', () => { + test('returns correct path for Unix platforms', () => { + // Skip on Windows + if (process.platform === 'win32') { + return; + } + + const mockContext = { + globalStorageUri: { fsPath: '/home/user/.vscode/extensions/storage' }, + } as unknown as vscode.ExtensionContext; + + const storagePath = getStoragePath(mockContext); + + assert.strictEqual(storagePath, '/home/user/.vscode/extensions/storage/operator'); + }); + + test('returns correct path with .exe for Windows', () => { + // We test the logic by checking that Windows would get .exe + const mockContext = { + globalStorageUri: { fsPath: 'C:\\Users\\user\\.vscode\\storage' }, + } as unknown as vscode.ExtensionContext; + + const storagePath = getStoragePath(mockContext); + + if (process.platform === 'win32') { + assert.ok(storagePath.endsWith('operator.exe')); + } else { + assert.ok(storagePath.endsWith('operator')); + assert.ok(!storagePath.endsWith('.exe')); + } + }); + }); + + suite('getOperatorPath()', () => { + let tempDir: string; + + setup(async () => { + tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'operator-binary-test-')); + }); + + teardown(async () => { + try { + await fs.rm(tempDir, { recursive: true }); + } catch { + // Ignore cleanup errors + } + }); + + test('returns configured path when set and file exists', async () => { + // Create a mock operator binary + const operatorPath = path.join(tempDir, 'my-operator'); + await fs.writeFile(operatorPath, '#!/bin/bash\necho "operator"'); + await fs.chmod(operatorPath, 0o755); + + // Mock config to return the path + const configStub = sandbox.stub(vscode.workspace, 'getConfiguration'); + configStub.returns({ + get: (key: string) => { + if (key === 'operatorPath') { + return operatorPath; + } + return undefined; + }, + } as unknown as vscode.WorkspaceConfiguration); + + const mockContext = { + globalStorageUri: { fsPath: path.join(tempDir, 'storage') }, + } as unknown as vscode.ExtensionContext; + + const result = await getOperatorPath(mockContext); + assert.strictEqual(result, operatorPath); + }); + + test('returns storage path when config empty but storage binary exists', async () => { + // Create storage directory and binary + const storagePath = path.join(tempDir, 'storage'); + await fs.mkdir(storagePath, { recursive: true }); + const binaryPath = path.join(storagePath, binaryName); + await fs.writeFile(binaryPath, '#!/bin/bash\necho "operator"'); + await fs.chmod(binaryPath, 0o755); + + // Mock config to return empty + const configStub = sandbox.stub(vscode.workspace, 'getConfiguration'); + configStub.returns({ + get: () => '', + } as unknown as vscode.WorkspaceConfiguration); + + const mockContext = { + globalStorageUri: { fsPath: storagePath }, + } as unknown as vscode.ExtensionContext; + + const result = await getOperatorPath(mockContext); + assert.strictEqual(result, binaryPath); + }); + + test('looks up in PATH for non-existent storage (integration)', async () => { + // This test actually invokes the PATH lookup + // It tests that when config and storage are empty, we call which/where + + // Mock config to return empty + const configStub = sandbox.stub(vscode.workspace, 'getConfiguration'); + configStub.returns({ + get: () => '', + } as unknown as vscode.WorkspaceConfiguration); + + const mockContext = { + globalStorageUri: { fsPath: path.join(tempDir, 'nonexistent-storage') }, + } as unknown as vscode.ExtensionContext; + + // This will actually call which/where - operator may or may not be in PATH + // We're testing that the function completes without error + const result = await getOperatorPath(mockContext); + + // Result could be a path or undefined - we just verify the function works + assert.ok(result === undefined || typeof result === 'string'); + }); + + test('ignores configured path when file does not exist', async () => { + // Mock config to return a non-existent path + const configStub = sandbox.stub(vscode.workspace, 'getConfiguration'); + configStub.returns({ + get: (key: string) => { + if (key === 'operatorPath') { + return '/nonexistent/path/operator'; + } + return undefined; + }, + } as unknown as vscode.WorkspaceConfiguration); + + const mockContext = { + globalStorageUri: { fsPath: path.join(tempDir, 'nonexistent-storage') }, + } as unknown as vscode.ExtensionContext; + + // Will fall through to PATH lookup since config path doesn't exist + const result = await getOperatorPath(mockContext); + + // Should not return the non-existent config path + assert.notStrictEqual(result, '/nonexistent/path/operator'); + }); + }); + + suite('getOperatorVersion()', () => { + let tempDir: string; + + setup(async () => { + tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'operator-version-test-')); + }); + + teardown(async () => { + try { + await fs.rm(tempDir, { recursive: true }); + } catch { + // Ignore cleanup errors + } + }); + + test('parses version from "operator X.Y.Z" format', async () => { + // Skip on Windows - shell scripts don't work the same way + if (process.platform === 'win32') { + return; + } + + // Create a mock operator binary that outputs version + const operatorPath = path.join(tempDir, 'operator'); + await fs.writeFile(operatorPath, '#!/bin/bash\necho "operator 0.1.14"'); + await fs.chmod(operatorPath, 0o755); + + const version = await getOperatorVersion(operatorPath); + assert.strictEqual(version, '0.1.14'); + }); + + test('returns trimmed output when no match pattern', async () => { + // Skip on Windows + if (process.platform === 'win32') { + return; + } + + const operatorPath = path.join(tempDir, 'operator'); + await fs.writeFile(operatorPath, '#!/bin/bash\necho "1.2.3"'); + await fs.chmod(operatorPath, 0o755); + + const version = await getOperatorVersion(operatorPath); + assert.strictEqual(version, '1.2.3'); + }); + + test('returns undefined on non-zero exit code', async () => { + // Skip on Windows + if (process.platform === 'win32') { + return; + } + + const operatorPath = path.join(tempDir, 'operator'); + await fs.writeFile(operatorPath, '#!/bin/bash\nexit 1'); + await fs.chmod(operatorPath, 0o755); + + const version = await getOperatorVersion(operatorPath); + assert.strictEqual(version, undefined); + }); + + test('returns undefined for non-existent binary', async () => { + const version = await getOperatorVersion('/nonexistent/path/operator'); + assert.strictEqual(version, undefined); + }); + + test('returns undefined on empty output', async () => { + // Skip on Windows + if (process.platform === 'win32') { + return; + } + + const operatorPath = path.join(tempDir, 'operator'); + await fs.writeFile(operatorPath, '#!/bin/bash\necho ""'); + await fs.chmod(operatorPath, 0o755); + + const version = await getOperatorVersion(operatorPath); + // Empty output after trim is falsy, so returns undefined + assert.strictEqual(version, undefined); + }); + + test('handles version with additional text', async () => { + // Skip on Windows + if (process.platform === 'win32') { + return; + } + + const operatorPath = path.join(tempDir, 'operator'); + await fs.writeFile(operatorPath, '#!/bin/bash\necho "operator 2.0.0-beta.1"'); + await fs.chmod(operatorPath, 0o755); + + const version = await getOperatorVersion(operatorPath); + assert.strictEqual(version, '2.0.0-beta.1'); + }); + }); + + suite('isOperatorAvailable()', () => { + let tempDir: string; + + setup(async () => { + tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'operator-avail-test-')); + }); + + teardown(async () => { + try { + await fs.rm(tempDir, { recursive: true }); + } catch { + // Ignore cleanup errors + } + }); + + test('returns true when operator is found in storage', async () => { + // Create storage directory and binary + const storagePath = path.join(tempDir, 'storage'); + await fs.mkdir(storagePath, { recursive: true }); + const binaryPath = path.join(storagePath, binaryName); + await fs.writeFile(binaryPath, '#!/bin/bash\necho "operator"'); + await fs.chmod(binaryPath, 0o755); + + // Mock config to return empty + const configStub = sandbox.stub(vscode.workspace, 'getConfiguration'); + configStub.returns({ + get: () => '', + } as unknown as vscode.WorkspaceConfiguration); + + const mockContext = { + globalStorageUri: { fsPath: storagePath }, + } as unknown as vscode.ExtensionContext; + + const result = await isOperatorAvailable(mockContext); + assert.strictEqual(result, true); + }); + + test('returns true when operator is in configured path', async () => { + // Create a mock operator binary + const operatorPath = path.join(tempDir, 'my-operator'); + await fs.writeFile(operatorPath, '#!/bin/bash\necho "operator"'); + await fs.chmod(operatorPath, 0o755); + + // Mock config to return the path + const configStub = sandbox.stub(vscode.workspace, 'getConfiguration'); + configStub.returns({ + get: (key: string) => { + if (key === 'operatorPath') { + return operatorPath; + } + return undefined; + }, + } as unknown as vscode.WorkspaceConfiguration); + + const mockContext = { + globalStorageUri: { fsPath: path.join(tempDir, 'storage') }, + } as unknown as vscode.ExtensionContext; + + const result = await isOperatorAvailable(mockContext); + assert.strictEqual(result, true); + }); + + test('returns false when operator is not found anywhere', async () => { + // Mock config to return empty + const configStub = sandbox.stub(vscode.workspace, 'getConfiguration'); + configStub.returns({ + get: () => '', + } as unknown as vscode.WorkspaceConfiguration); + + // Use a unique temp storage path where operator won't exist + const mockContext = { + globalStorageUri: { fsPath: path.join(tempDir, 'empty-storage-' + Date.now()) }, + } as unknown as vscode.ExtensionContext; + + const result = await isOperatorAvailable(mockContext); + + // If operator is not in PATH either, this should be false + // Note: if operator IS in PATH on the test machine, this could be true + // We're mainly testing that the function executes without error + assert.ok(typeof result === 'boolean'); + }); + }); +});