From 5ecc7cbc757beca7232b3451e11f982cb3d19464 Mon Sep 17 00:00:00 2001 From: Steven Enamakel <31011319+senamakel@users.noreply.github.com> Date: Fri, 27 Mar 2026 13:58:29 -0700 Subject: [PATCH] refactor: consolidate CLI binaries and streamline CI workflows (#44) * chore: update dependencies and remove unused packages - Removed several unused dependencies from package.json, including `@tauri-apps/plugin-shell`, `@types/react-router-dom`, `immer`, `qrcode.react`, and `@testing-library/user-event`. - Updated the Vite configuration to exclude `telegram` from the optimizeDeps include list. - Cleaned up yarn.lock by removing references to deleted packages. - Deleted outdated GitHub Actions workflows for macOS ARM64 build, package and publish, and update changelog. * refactor: consolidate CLI binaries and update configurations - Renamed the main binary from `openhuman-core` to `openhuman` for consistency across the project. - Removed the `openhuman-cli` and `openhuman-core` binaries, consolidating functionality into the new `openhuman` binary. - Updated build configurations in GitHub Actions and Tauri to reflect the new binary name and paths. - Adjusted the Tailwind and Vite configurations to point to the correct HTML and source directories. - Introduced a new method in the Rust core server for handling JSON-RPC calls, enhancing the API structure. * chore: update subproject commit reference in skills * chore: update Rust test workflow and improve CLI command formatting - Modified the GitHub Actions workflow to include a new job for running Rust tests in the workspace. - Updated the `test:rust` command in `package.json` to run tests for the entire workspace. - Refactored CLI command definitions in `openhuman.rs` for improved readability by consolidating struct fields into single lines. * chore: enhance E2E testing scripts and workflows - Updated `.prettierignore` to exclude the `rust-core` directory. - Added new E2E testing commands in `package.json` for running all flows and improved formatting commands to include `cargo fmt`. - Enhanced GitHub Actions workflows to streamline E2E testing, including new scripts for running individual specs and all flows sequentially. - Refactored existing E2E scripts to utilize a common function for running tests, improving maintainability and readability. - Introduced a new script for resolving Node.js and Appium dependencies, ensuring compatibility for E2E tests. - Cleaned up and simplified existing E2E scripts by removing redundant code and consolidating functionality. * chore: update GitHub Actions workflows for Tauri build process - Modified build and release workflows to reference the correct target directory for Rust binaries, ensuring compatibility with the Cargo workspace structure. - Adjusted caching configuration in the test workflow for consistency and improved readability. - Standardized environment variable formatting in the test workflow to enhance clarity. * fix: standardize string formatting in GitHub Actions workflows - Updated the test workflow to use single quotes for cache configuration and environment variables, enhancing consistency across the file. --- .github/workflows/build.yml | 7 +- .github/workflows/macos-arm64-build.yml | 125 ------ .github/workflows/package-and-publish.yml | 499 ---------------------- .github/workflows/release.yml | 58 ++- .github/workflows/test.yml | 84 ++++ .github/workflows/update-changelog.yml | 197 --------- .husky/pre-push | 2 +- .prettierignore | 1 + package.json | 17 +- rust-core/src/bin/openhuman-cli.rs | 388 ----------------- rust-core/src/bin/openhuman-core.rs | 7 - rust-core/src/bin/openhuman.rs | 334 +++++++++++++++ rust-core/src/core_server.rs | 14 + rust-core/src/openhuman/service/mod.rs | 13 +- rust-core/src/runtime/utils.rs | 2 +- scripts/e2e-auth.sh | 90 +--- scripts/e2e-build.sh | 20 +- scripts/e2e-crypto-payment.sh | 90 +--- scripts/e2e-gmail.sh | 90 +--- scripts/e2e-login.sh | 93 +--- scripts/e2e-notion.sh | 90 +--- scripts/e2e-payment.sh | 90 +--- scripts/e2e-resolve-node-appium.sh | 38 ++ scripts/e2e-run-all-flows.sh | 26 ++ scripts/e2e-run-spec.sh | 68 +++ scripts/e2e-telegram.sh | 90 +--- skills | 2 +- src-tauri/src/core_process.rs | 23 +- src-tauri/tauri.conf.json | 2 +- index.html => src/index.html | 2 +- src/polyfills.ts | 2 +- src/test/setup.ts | 2 - tailwind.config.js | 2 +- vite.config.ts | 8 +- yarn.lock | 312 +------------- 35 files changed, 692 insertions(+), 2196 deletions(-) delete mode 100644 .github/workflows/macos-arm64-build.yml delete mode 100644 .github/workflows/package-and-publish.yml delete mode 100644 .github/workflows/update-changelog.yml delete mode 100644 rust-core/src/bin/openhuman-cli.rs delete mode 100644 rust-core/src/bin/openhuman-core.rs create mode 100644 rust-core/src/bin/openhuman.rs create mode 100755 scripts/e2e-resolve-node-appium.sh create mode 100755 scripts/e2e-run-all-flows.sh create mode 100755 scripts/e2e-run-spec.sh rename index.html => src/index.html (84%) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 2cbe9838f..99ed92be7 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -73,13 +73,14 @@ jobs: run: cd skills && yarn install --frozen-lockfile - name: Build sidecar core binary - run: cargo build --manifest-path rust-core/Cargo.toml --release --target x86_64-unknown-linux-gnu --bin openhuman-core + run: cargo build --manifest-path rust-core/Cargo.toml --release --target x86_64-unknown-linux-gnu --bin openhuman - name: Stage sidecar for Tauri bundler run: | mkdir -p src-tauri/binaries - cp rust-core/target/x86_64-unknown-linux-gnu/release/openhuman-core src-tauri/binaries/openhuman-core-x86_64-unknown-linux-gnu - chmod +x src-tauri/binaries/openhuman-core-x86_64-unknown-linux-gnu + # Workspace root target/ (not rust-core/target/) — see root Cargo.toml [workspace] + cp target/x86_64-unknown-linux-gnu/release/openhuman src-tauri/binaries/openhuman-x86_64-unknown-linux-gnu + chmod +x src-tauri/binaries/openhuman-x86_64-unknown-linux-gnu - name: Build Tauri app run: | diff --git a/.github/workflows/macos-arm64-build.yml b/.github/workflows/macos-arm64-build.yml deleted file mode 100644 index 6b1ec26bc..000000000 --- a/.github/workflows/macos-arm64-build.yml +++ /dev/null @@ -1,125 +0,0 @@ -# Standalone macOS Apple Silicon (aarch64) Tauri build — no prepare-release / tagging. -# -# GitHub: workflow_dispatch checks out the ref. Local act: ACT=true skips checkout (use -b bind + run -# ./scripts/run-macos-arm64-build.sh so submodules are initialized before act copies the tree). -name: macOS ARM64 build - -on: - workflow_dispatch: - -permissions: - contents: read - -jobs: - build-macos-arm64: - name: Build macOS aarch64 (signed) - runs-on: macos-latest - env: - GITHUB_TOKEN: ${{ secrets.XGH_TOKEN || github.token }} - steps: - - name: Checkout repository - if: ${{ env.ACT != 'true' }} - uses: actions/checkout@v4 - - - name: Set Xcode version - uses: maxim-lobanov/setup-xcode@v1 - with: - xcode-version: latest-stable - - - name: Setup Node.js 24.x - uses: actions/setup-node@v4 - with: - node-version: 24.x - cache: yarn - - - name: Install Rust stable - uses: dtolnay/rust-toolchain@stable - with: - targets: aarch64-apple-darwin - - - name: Cargo.lock fingerprint (deps only) - id: cargo-lock-fingerprint - shell: bash - run: | - echo "hash=$(tail -n +8 src-tauri/Cargo.lock | openssl dgst -sha256 | awk '{print $2}')" >> "$GITHUB_OUTPUT" - - - name: Cache Cargo registry and git sources - uses: actions/cache@v4 - with: - path: | - ~/.cargo/registry - ~/.cargo/git - key: ${{ runner.os }}-cargo-registry-${{ steps.cargo-lock-fingerprint.outputs.hash }} - restore-keys: | - ${{ runner.os }}-cargo-registry- - - - name: Install dependencies - run: yarn install --frozen-lockfile - - - name: Install skills dependencies - run: cd skills && yarn install --frozen-lockfile - - - name: Build skills - run: cd skills && yarn build - - - name: Define Tauri configuration overrides - id: config-overrides - uses: actions/github-script@v7 - env: - BASE_URL: ${{ vars.BASE_URL }} - UPDATER_PUBLIC_KEY: ${{ secrets.UPDATER_PUBLIC_KEY }} - WITH_UPDATER: 'true' - with: - github-token: ${{ secrets.XGH_TOKEN || github.token }} - script: | - const workspacePath = process.env.GITHUB_WORKSPACE.replace(/\\/g, '/'); - const prefix = workspacePath.startsWith('/') ? 'file://' : 'file:///'; - const moduleUrl = `${prefix}${workspacePath}/scripts/prepareTauriConfig.js`; - const { default: prepareTauriConfig } = await import(moduleUrl); - const config = prepareTauriConfig(); - core.setOutput('json', JSON.stringify(config)); - - - name: Build frontend - run: yarn build - env: - NODE_ENV: production - VITE_BACKEND_URL: ${{ vars.VITE_BACKEND_URL }} - VITE_SENTRY_DSN: ${{ vars.VITE_SENTRY_DSN }} - VITE_DEBUG: ${{ vars.VITE_DEBUG }} - - - name: Build sidecar core binary - shell: bash - run: | - cargo build \ - --manifest-path rust-core/Cargo.toml \ - --release \ - --target aarch64-apple-darwin \ - --bin openhuman-core - - - name: Stage sidecar for Tauri bundler - shell: bash - run: | - mkdir -p src-tauri/binaries - cp rust-core/target/aarch64-apple-darwin/release/openhuman-core \ - src-tauri/binaries/openhuman-core-aarch64-apple-darwin - chmod +x src-tauri/binaries/openhuman-core-aarch64-apple-darwin - - - name: Build, package (no GitHub release upload) - uses: tauri-apps/tauri-action@v0.6.2 - env: - APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE_BASE64 }} - APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }} - APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }} - APPLE_ID: ${{ secrets.APPLE_ID }} - APPLE_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }} - APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }} - BASE_URL: ${{ vars.BASE_URL }} - TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }} - TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }} - WITH_UPDATER: 'true' - MACOSX_DEPLOYMENT_TARGET: '10.15' - with: - args: -c ${{ steps.config-overrides.outputs.json }} --target aarch64-apple-darwin -- -- --bin OpenHuman - includeDebug: false - includeRelease: true - includeUpdaterJson: false diff --git a/.github/workflows/package-and-publish.yml b/.github/workflows/package-and-publish.yml deleted file mode 100644 index 7a6a3cfcd..000000000 --- a/.github/workflows/package-and-publish.yml +++ /dev/null @@ -1,499 +0,0 @@ -# Terms: -# "build" - Compile web project using webpack. -# "package" - Produce a distributive package for a specific platform as a workflow artifact. -# "publish" - Send a package to corresponding store and GitHub release page. -# "release" - build + package + publish -# -# Jobs in this workflow will skip the "publish" step when `SHOULD_PUBLISH` is not set. - -name: Package and publish (Legacy) - -on: - workflow_dispatch: - # pull_request: - # branches: - # - main - -env: - IS_PR: ${{ github.event_name == 'pull_request' }} - SHOULD_PUBLISH: ${{ github.event_name != 'pull_request' && github.ref == 'refs/heads/main' }} - PUBLISH_REPO: openhumanxyz/openhuman - XGH_TOKEN: ${{ secrets.XGH_TOKEN }} - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - -jobs: - get-version: - runs-on: ubuntu-latest - outputs: - package-version: ${{ steps.extract-version.outputs.package-version }} - tag-name: ${{ steps.extract-version.outputs.tag-name }} - should-publish: ${{ steps.extract-version.outputs.should-publish }} - release-name: ${{ steps.extract-version.outputs.release-name }} - steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 1 - ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.ref || github.ref }} - token: ${{ secrets.XGH_TOKEN_READ }} - - - name: Setup Node.js - uses: actions/setup-node@v4 - with: - node-version: 20.x - - - name: Extract version and tag - id: extract-version - run: | - PACKAGE_VERSION=$(node -p "require('./package.json').version") - TAG_NAME="v${PACKAGE_VERSION}" - RELEASE_NAME="OpenHuman v${PACKAGE_VERSION}" - echo "package-version=$PACKAGE_VERSION" >> $GITHUB_OUTPUT - echo "tag-name=$TAG_NAME" >> $GITHUB_OUTPUT - echo "release-name=$RELEASE_NAME" >> $GITHUB_OUTPUT - echo "should-publish=$SHOULD_PUBLISH" >> $GITHUB_OUTPUT - echo "Extracted version: $PACKAGE_VERSION" - echo "Generated tag: $TAG_NAME" - echo "Generated release name: $RELEASE_NAME" - - check-version: - runs-on: ubuntu-latest - needs: get-version - environment: ${{ github.ref == 'refs/heads/main' && 'Production' || '' }} - outputs: - should-skip: ${{ steps.check-release.outputs.should-skip }} - steps: - - name: Check if release already exists - id: check-release - env: - PACKAGE_VERSION: ${{ needs.get-version.outputs.package-version }} - TAG_NAME: ${{ needs.get-version.outputs.tag-name }} - run: | - # For non-main branches or when publishing is disabled, always continue - if [ -z "$SHOULD_PUBLISH" ]; then - echo "🚧 Publishing disabled (non-main branch)" - echo "should-skip=false" >> $GITHUB_OUTPUT - exit 0 - fi - - echo "Checking if release already exists for tag: $TAG_NAME" - - RESPONSE=$(curl -s -w "\n%{http_code}" -H "Authorization: Bearer $XGH_TOKEN" \ - -H "Accept: application/vnd.github.v3+json" \ - "https://api.github.com/repos/$PUBLISH_REPO/releases/tags/$TAG_NAME") - HTTP_CODE=$(echo "$RESPONSE" | tail -n1) - BODY=$(echo "$RESPONSE" | sed '$d') - - if [ "$HTTP_CODE" = "404" ]; then - echo "🆕 No release found for version $PACKAGE_VERSION, will create new release" - echo "should-skip=false" >> $GITHUB_OUTPUT - elif [ "$HTTP_CODE" != "200" ]; then - echo "⚠️ Warning: Failed to check release (HTTP $HTTP_CODE). Response: $BODY" - echo "Continuing anyway..." - echo "should-skip=false" >> $GITHUB_OUTPUT - elif echo "$BODY" | jq -e '.tag_name' > /dev/null; then - IS_DRAFT=$(echo "$BODY" | jq -r '.draft') - if [ "$IS_DRAFT" = "false" ]; then - echo "✅ Published release already exists for version $PACKAGE_VERSION" - echo "should-skip=true" >> $GITHUB_OUTPUT - else - echo "📝 Draft release exists for version $PACKAGE_VERSION, will continue" - echo "should-skip=false" >> $GITHUB_OUTPUT - fi - else - echo "🆕 No release found for version $PACKAGE_VERSION, will create new release" - echo "should-skip=false" >> $GITHUB_OUTPUT - fi - - create-release: - runs-on: ubuntu-latest - needs: [get-version, check-version] - environment: ${{ github.ref == 'refs/heads/main' && 'Production' || '' }} - if: github.ref == 'refs/heads/main' && needs.get-version.outputs.should-publish == 'true' && needs.check-version.outputs.should-skip != 'true' - permissions: - contents: write - outputs: - releaseId: ${{ steps.create-release.outputs.releaseId }} - steps: - - name: Create draft release - id: create-release - env: - PACKAGE_VERSION: ${{ needs.get-version.outputs.package-version }} - TAG_NAME: ${{ needs.get-version.outputs.tag-name }} - RELEASE_NAME: ${{ needs.get-version.outputs.release-name }} - XGH_TOKEN: ${{ secrets.XGH_TOKEN }} - run: | - echo "Creating draft release for tag: $TAG_NAME" - echo "Repository: $PUBLISH_REPO" - RELEASE_BODY="See the assets below to download this version." - RESPONSE=$(curl -s -w "\n%{http_code}" -X POST \ - -H "Authorization: Bearer $XGH_TOKEN" \ - -H "Accept: application/vnd.github.v3+json" \ - -d '{"tag_name": "'"$TAG_NAME"'", "name": "'"$RELEASE_NAME"'", "draft": true, "body": "'"$RELEASE_BODY"'"}' \ - "https://api.github.com/repos/$PUBLISH_REPO/releases") - HTTP_CODE=$(echo "$RESPONSE" | tail -n1) - BODY=$(echo "$RESPONSE" | sed '$d') - echo "HTTP Status Code: $HTTP_CODE" - echo "Response body: $BODY" - if [ "$HTTP_CODE" != "201" ]; then - echo "Error: Failed to create release. HTTP $HTTP_CODE" - echo "Response: $BODY" - exit 1 - fi - RELEASE_ID=$(echo "$BODY" | jq -r '.id') - echo "Extracted Release ID: $RELEASE_ID" - if [ "$RELEASE_ID" = "null" ] || [ -z "$RELEASE_ID" ]; then - echo "Error: Failed to extract release ID. Response was: $BODY" - exit 1 - fi - echo "releaseId=$RELEASE_ID" >> $GITHUB_OUTPUT - - package-tauri: - name: Build, package and publish Tauri - needs: [get-version, check-version, create-release] - if: ${{ !failure() && !cancelled() && needs.check-version.outputs.should-skip != 'true' }} - environment: ${{ github.ref == 'refs/heads/main' && 'Production' || '' }} - env: - GITHUB_TOKEN: ${{ secrets.XGH_TOKEN }} - permissions: - contents: write - strategy: - fail-fast: false - matrix: - settings: - - platform: 'macos-latest' - args: '--target aarch64-apple-darwin' - target: 'aarch64-apple-darwin' - - platform: 'macos-latest' - args: '--target x86_64-apple-darwin' - target: 'x86_64-apple-darwin' - - platform: 'ubuntu-22.04' - args: '--target x86_64-unknown-linux-gnu' - target: 'x86_64-unknown-linux-gnu' - runs-on: ${{ matrix.settings.platform }} - steps: - - name: Checkout - uses: actions/checkout@v4 - with: - fetch-depth: 1 - submodules: true - token: ${{ secrets.XGH_TOKEN_READ }} - ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.ref || github.ref }} - - - name: Set Xcode version - if: matrix.settings.platform == 'macos-latest' - uses: maxim-lobanov/setup-xcode@v1 - with: - xcode-version: latest-stable - - - name: Setup Node.js 24.x - uses: actions/setup-node@v4 - with: - node-version: 24.x - cache: 'yarn' - - - name: Install Rust stable - uses: dtolnay/rust-toolchain@stable - with: - targets: ${{ matrix.settings.platform == 'macos-latest' && 'aarch64-apple-darwin,x86_64-apple-darwin' || '' }} - - - name: Install Tauri dependencies (ubuntu only) - if: matrix.settings.platform == 'ubuntu-22.04' - run: | - sudo apt-get update - sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf - - # OpenMP is now disabled via Cargo.toml (default-features = false for llama-cpp-2) - # This avoids cross-compilation issues where Homebrew ARM64 OpenMP can't be used for x86_64 builds - - - name: Cache Cargo registry - uses: actions/cache@v4 - with: - path: | - ~/.cargo/registry - ~/.cargo/git - key: ${{ runner.os }}-cargo-${{ hashFiles('src-tauri/Cargo.lock') }} - restore-keys: | - ${{ runner.os }}-cargo- - - - name: Cache Cargo build artifacts - uses: actions/cache@v4 - with: - path: src-tauri/target - key: ${{ runner.os }}-cargo-target-${{ matrix.settings.platform }}-${{ hashFiles('src-tauri/Cargo.lock') }} - restore-keys: | - ${{ runner.os }}-cargo-target-${{ matrix.settings.platform }}- - - - name: Cache node modules - id: yarn-cache - uses: actions/cache@v4 - with: - path: node_modules - key: ${{ runner.os }}-build-${{ hashFiles('**/yarn.lock') }} - restore-keys: | - ${{ runner.os }}-build- - - - name: Install dependencies - if: steps.yarn-cache.outputs.cache-hit != 'true' - run: yarn install --frozen-lockfile - - - name: Cache skills node modules - id: skills-yarn-cache - uses: actions/cache@v4 - with: - path: skills/node_modules - key: ${{ runner.os }}-skills-${{ hashFiles('skills/yarn.lock') }} - restore-keys: | - ${{ runner.os }}-skills- - - - name: Install skills dependencies - if: steps.skills-yarn-cache.outputs.cache-hit != 'true' - run: cd skills && yarn install --frozen-lockfile - - - name: Build skills - run: cd skills && yarn build - - - name: Extract repository owner and name - id: repository-info - if: needs.get-version.outputs.should-publish != '' - shell: bash - run: | - echo "owner=${PUBLISH_REPO%%/*}" >> $GITHUB_OUTPUT - echo "repo=${PUBLISH_REPO#*/}" >> $GITHUB_OUTPUT - - - name: Define Tauri configuration overrides - id: config-overrides - uses: actions/github-script@v7 - env: - BASE_URL: ${{ vars.BASE_URL }} - UPDATER_PUBLIC_KEY: ${{ secrets.UPDATER_PUBLIC_KEY }} - WITH_UPDATER: ${{ needs.get-version.outputs.should-publish != '' && 'true' || 'false' }} - with: - script: | - const workspacePath = process.env.GITHUB_WORKSPACE.replace(/\\/g, '/'); - const prefix = workspacePath.startsWith('/') ? 'file://' : 'file:///'; - const moduleUrl = `${prefix}${workspacePath}/scripts/prepareTauriConfig.js`; - const { default: prepareTauriConfig } = await import(moduleUrl) - const config = prepareTauriConfig(); - - const configJson = JSON.stringify(config); - console.log(configJson); - - core.setOutput("json", configJson); - - - name: Build frontend - run: yarn build - env: - NODE_ENV: production - VITE_BACKEND_URL: ${{ vars.VITE_BACKEND_URL }} - VITE_SENTRY_DSN: ${{ secrets.VITE_SENTRY_DSN }} - VITE_DEBUG: ${{ vars.VITE_DEBUG }} - - - name: Build sidecar core binary - shell: bash - run: | - cargo build \ - --manifest-path rust-core/Cargo.toml \ - --release \ - --target "$MATRIX_TARGET" \ - --bin openhuman-core - env: - MATRIX_TARGET: ${{ matrix.settings.target }} - - - name: Stage sidecar for Tauri bundler - shell: bash - run: | - mkdir -p src-tauri/binaries - SOURCE="rust-core/target/$MATRIX_TARGET/release/openhuman-core" - DEST="src-tauri/binaries/openhuman-core-$MATRIX_TARGET" - cp "$SOURCE" "$DEST" - chmod +x "$DEST" - env: - MATRIX_TARGET: ${{ matrix.settings.target }} - - - name: Build, package and publish - uses: tauri-apps/tauri-action@v0.6.2 - id: build-tauri - env: - APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE_BASE64 }} - APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }} - APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }} - APPLE_ID: ${{ secrets.APPLE_ID }} - APPLE_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }} - APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }} - BASE_URL: ${{ vars.BASE_URL }} - TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.UPDATER_PRIVATE_KEY }} - TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.UPDATER_PRIVATE_KEY_PASSWORD }} - WITH_UPDATER: ${{ needs.get-version.outputs.should-publish != '' && 'true' || 'false' }} - # macOS 10.15+ required for std::filesystem used by llama.cpp - MACOSX_DEPLOYMENT_TARGET: ${{ matrix.settings.platform == 'macos-latest' && '10.15' || '' }} - with: - args: '-c ${{ steps.config-overrides.outputs.json }} ${{ matrix.settings.args }} -- -- --bin OpenHuman' - includeDebug: ${{ needs.get-version.outputs.should-publish == '' && inputs.forceRelease != 'true' }} - includeRelease: ${{ needs.get-version.outputs.should-publish != '' || inputs.forceRelease == 'true' }} - # TDLib dylibs are now bundled natively via build.rs + tauri.conf.json macOS.frameworks - # No post-build scripts needed - Tauri bundler handles copying and signing - releaseId: ${{ needs.create-release.outputs.releaseId }} - owner: openhumanxyz - repo: openhuman - - - name: Get file info - id: file-info - shell: bash - run: | - FULL_PATH=$(echo "${{ fromJSON(steps.build-tauri.outputs.artifactPaths)[0] }}") - FILENAME=$(basename "$FULL_PATH") - NAME_WITH_EXT="${FILENAME%.*}" - VERSION="${{ needs.get-version.outputs.package-version }}" - TAG_NAME="${{ needs.get-version.outputs.tag-name }}" - - # Remove version/tag patterns from name - # Patterns to remove: v0.14.0-1, 0.14.0-1, v0.14.0, 0.14.0, etc. - # Package managers add release numbers like -1, -2, etc. - NAME="$NAME_WITH_EXT" - - # Remove version-release pattern (0.14.0-1, 0.14.0-2, etc.) - escape dots for regex - VERSION_ESCAPED=$(echo "$VERSION" | sed 's/\./\\./g') - NAME=$(echo "$NAME" | sed -E "s/[_-]?${VERSION_ESCAPED}-[0-9]+[_-]?//g") - - # Remove tag-release pattern (v0.14.0-1, v0.14.0-2, etc.) - TAG_ESCAPED=$(echo "$TAG_NAME" | sed 's/\./\\./g') - NAME=$(echo "$NAME" | sed -E "s/[_-]?${TAG_ESCAPED}-[0-9]+[_-]?//g") - - # Remove tag name (v0.14.0) with various separators - NAME=$(echo "$NAME" | sed -E "s/[_-]?${TAG_ESCAPED}[_-]?//g") - - # Remove version (0.14.0) with various separators - NAME=$(echo "$NAME" | sed -E "s/[_-]?${VERSION_ESCAPED}[_-]?//g") - - # Remove version with dots replaced by separators (0_14_0, 0-14-0) - VERSION_DASH=$(echo "$VERSION" | tr '.' '-') - VERSION_UNDERSCORE=$(echo "$VERSION" | tr '.' '_') - NAME=$(echo "$NAME" | sed -E "s/[_-]?v?${VERSION_DASH}[_-]?//g") - NAME=$(echo "$NAME" | sed -E "s/[_-]?v?${VERSION_UNDERSCORE}[_-]?//g") - - # Clean up any double separators or trailing/leading separators - NAME=$(echo "$NAME" | sed -E 's/[_-]{2,}/_/g' | sed 's/^[_-]//' | sed 's/[_-]$//') - - FILE_PATH=$(cd "$(dirname "$FULL_PATH")" && pwd) - # Extract architecture from target (e.g., aarch64-apple-darwin -> aarch64) - ARCHITECTURE=$(echo "${{ matrix.settings.target }}" | cut -d'-' -f1) - echo "name=$NAME" >> $GITHUB_OUTPUT - echo "filename=$FILENAME" >> $GITHUB_OUTPUT - echo "architecture=$ARCHITECTURE" >> $GITHUB_OUTPUT - echo "path=$FILE_PATH" >> $GITHUB_OUTPUT - - # MacOS release — upload the DMG after TDLib bundling and re-signing - - name: Upload release asset (MacOS) - if: matrix.settings.platform == 'macos-latest' && needs.get-version.outputs.should-publish != '' - shell: bash - run: | - SANITIZED_FILENAME=$(echo "${{ steps.file-info.outputs.name }}" | sed 's/ /./g') - PUBLISH_FILE_NAME="$SANITIZED_FILENAME-${{ steps.file-info.outputs.architecture }}.dmg" - FILE_PATH="${{ steps.file-info.outputs.path }}/${{ steps.file-info.outputs.filename }}" - RELEASE_ID="${{ needs.create-release.outputs.releaseId }}" - curl -X POST -H "Authorization: Bearer $XGH_TOKEN" \ - -H "Content-Type: application/octet-stream" \ - --data-binary "@$FILE_PATH" \ - "https://uploads.github.com/repos/$PUBLISH_REPO/releases/$RELEASE_ID/assets?name=$PUBLISH_FILE_NAME" - - - name: Upload artifact (MacOS) - if: matrix.settings.platform == 'macos-latest' - uses: actions/upload-artifact@v4 - with: - name: ${{ steps.file-info.outputs.name }}-${{ steps.file-info.outputs.architecture }}.dmg - path: ${{ steps.file-info.outputs.path }}/${{ steps.file-info.outputs.filename }} - - # Linux release - - name: Upload Linux artifact (Linux) - if: matrix.settings.platform == 'ubuntu-22.04' - uses: actions/upload-artifact@v4 - with: - name: ${{ steps.file-info.outputs.filename }} - path: ${{ steps.file-info.outputs.path }}/${{ steps.file-info.outputs.filename }} - - publish-release: - runs-on: ubuntu-latest - # needs: [get-version, check-version, create-release, package-tauri, package-android] - needs: [get-version, check-version, create-release, package-tauri] - environment: ${{ github.ref == 'refs/heads/main' && 'Production' || '' }} - if: github.ref == 'refs/heads/main' && needs.get-version.outputs.should-publish == 'true' && needs.check-version.outputs.should-skip != 'true' - permissions: - contents: write - env: - RELEASE_ID: ${{ needs.create-release.outputs.releaseId }} - XGH_TOKEN: ${{ secrets.XGH_TOKEN }} - steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 1 - ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.ref || github.ref }} - - - name: Publish release - run: | - RESPONSE=$(curl -s -w "\n%{http_code}" -X PATCH \ - -H "Authorization: Bearer $XGH_TOKEN" \ - -H "Accept: application/vnd.github.v3+json" \ - -d '{"draft": false}' \ - "https://api.github.com/repos/$PUBLISH_REPO/releases/$RELEASE_ID") - HTTP_CODE=$(echo "$RESPONSE" | tail -n1) - BODY=$(echo "$RESPONSE" | sed '$d') - if [ "$HTTP_CODE" != "200" ]; then - echo "Error: Failed to publish release. HTTP $HTTP_CODE" - echo "Response: $BODY" - exit 1 - fi - echo "Release published successfully" - - - name: Update Gist with JSON - run: | - ASSET_ID=$(curl -s -H "Authorization: Bearer $XGH_TOKEN" \ - -H "Accept: application/vnd.github.v3+json" \ - "https://api.github.com/repos/$PUBLISH_REPO/releases/$RELEASE_ID/assets" | jq -r '.[] | select(.name == "latest.json") | .id') - JSON_CONTENT=$(curl -sSL -H "Accept: application/octet-stream" \ - -H "Authorization: Bearer $XGH_TOKEN" \ - "https://api.github.com/repos/$PUBLISH_REPO/releases/assets/$ASSET_ID") - GIST_CONTENT=$(jq -n --arg json "$JSON_CONTENT" '{"files":{"updater.json":{"content":$json}}}') - curl -X PATCH -H "Authorization: Bearer $XGH_TOKEN" \ - -H "Accept: application/vnd.github.v3+json" \ - -d "$GIST_CONTENT" \ - "https://api.github.com/gists/$UPDATER_GIST_ID" - - delete-release-on-failure: - name: Delete draft release on build/publish failure - runs-on: ubuntu-latest - needs: [get-version, create-release, package-tauri, publish-release] - if: failure() && needs.create-release.result == 'success' && needs.create-release.outputs.releaseId != '' - permissions: - contents: write - env: - RELEASE_ID: ${{ needs.create-release.outputs.releaseId }} - TAG_NAME: ${{ needs.get-version.outputs.tag-name }} - XGH_TOKEN: ${{ secrets.XGH_TOKEN }} - steps: - - name: Delete draft release - run: | - echo "Deleting draft release $RELEASE_ID after build or publish failure" - RESPONSE=$(curl -s -w "\n%{http_code}" -X DELETE \ - -H "Authorization: Bearer $XGH_TOKEN" \ - -H "Accept: application/vnd.github.v3+json" \ - "https://api.github.com/repos/$PUBLISH_REPO/releases/$RELEASE_ID") - HTTP_CODE=$(echo "$RESPONSE" | tail -n1) - BODY=$(echo "$RESPONSE" | sed '$d') - if [ "$HTTP_CODE" = "204" ] || [ "$HTTP_CODE" = "404" ]; then - echo "Release deleted (or already gone)" - else - echo "Warning: Delete release returned HTTP $HTTP_CODE. Response: $BODY" - fi - - name: Delete tag - run: | - echo "Deleting tag $TAG_NAME" - RESPONSE=$(curl -s -w "\n%{http_code}" -X DELETE \ - -H "Authorization: Bearer $XGH_TOKEN" \ - -H "Accept: application/vnd.github.v3+json" \ - "https://api.github.com/repos/$PUBLISH_REPO/git/refs/tags/$TAG_NAME") - HTTP_CODE=$(echo "$RESPONSE" | tail -n1) - if [ "$HTTP_CODE" = "204" ] || [ "$HTTP_CODE" = "404" ]; then - echo "Tag deleted (or already gone)" - else - echo "Warning: Delete tag returned HTTP $HTTP_CODE" - fi diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 098d442b8..fd68aeb5e 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -305,27 +305,57 @@ jobs: VITE_SENTRY_DSN: ${{ vars.VITE_SENTRY_DSN }} VITE_DEBUG: ${{ vars.VITE_DEBUG }} + - name: Resolve core manifest and binary names + id: core-paths + shell: bash + run: | + if [ -f "openhuman_core/Cargo.toml" ]; then + CORE_DIR="openhuman_core" + elif [ -f "rust-core/Cargo.toml" ]; then + CORE_DIR="rust-core" + else + echo "No core Cargo manifest found (expected openhuman_core/Cargo.toml or rust-core/Cargo.toml)" + exit 1 + fi + + SIDE_CAR_BASE="$(node -e "const fs=require('fs');const c=JSON.parse(fs.readFileSync('src-tauri/tauri.conf.json','utf8'));const b=(c.bundle&&Array.isArray(c.bundle.externalBin)&&c.bundle.externalBin[0])||'binaries/openhuman';process.stdout.write(String(b).split('/').pop());")" + CORE_BIN_NAME="${SIDE_CAR_BASE}" + + echo "core_dir=$CORE_DIR" >> "$GITHUB_OUTPUT" + echo "core_manifest=$CORE_DIR/Cargo.toml" >> "$GITHUB_OUTPUT" + # Cargo workspace: release artifacts are under repo root target/, not /target/ + echo "core_target_dir=target/$MATRIX_TARGET/release" >> "$GITHUB_OUTPUT" + echo "core_bin_name=$CORE_BIN_NAME" >> "$GITHUB_OUTPUT" + echo "sidecar_base=$SIDE_CAR_BASE" >> "$GITHUB_OUTPUT" + env: + MATRIX_TARGET: ${{ matrix.settings.target }} + - name: Build sidecar core binary shell: bash run: | cargo build \ - --manifest-path rust-core/Cargo.toml \ + --manifest-path "$CORE_MANIFEST" \ --release \ --target "$MATRIX_TARGET" \ - --bin openhuman-core + --bin "$CORE_BIN_NAME" env: MATRIX_TARGET: ${{ matrix.settings.target }} + CORE_MANIFEST: ${{ steps.core-paths.outputs.core_manifest }} + CORE_BIN_NAME: ${{ steps.core-paths.outputs.core_bin_name }} - name: Stage sidecar for Tauri bundler shell: bash run: | mkdir -p src-tauri/binaries - SOURCE="rust-core/target/$MATRIX_TARGET/release/openhuman-core" - DEST="src-tauri/binaries/openhuman-core-$MATRIX_TARGET" + SOURCE="$CORE_TARGET_DIR/$CORE_BIN_NAME" + DEST="src-tauri/binaries/$SIDECAR_BASE-$MATRIX_TARGET" cp "$SOURCE" "$DEST" chmod +x "$DEST" env: MATRIX_TARGET: ${{ matrix.settings.target }} + CORE_TARGET_DIR: ${{ steps.core-paths.outputs.core_target_dir }} + CORE_BIN_NAME: ${{ steps.core-paths.outputs.core_bin_name }} + SIDECAR_BASE: ${{ steps.core-paths.outputs.sidecar_base }} - name: Build, package and upload to release uses: tauri-apps/tauri-action@v0.6.2 @@ -364,32 +394,36 @@ jobs: APP_PATH="src-tauri/target/${{ matrix.settings.target }}/release/bundle/macos/OpenHuman.app" echo "Inspecting bundle at: $APP_PATH" ls -la "$APP_PATH/Contents/MacOS" - ls -la "$APP_PATH/Contents/Resources" | grep openhuman-core || true - if [ -f "$APP_PATH/Contents/MacOS/openhuman-core" ]; then + ls -la "$APP_PATH/Contents/Resources" | grep openhuman || true + if [ -f "$APP_PATH/Contents/MacOS/openhuman" ]; then echo "Unexpected standalone core binary found in MacOS dir" exit 1 fi - if ! ls "$APP_PATH/Contents/Resources"/openhuman-core-* >/dev/null 2>&1; then + if ! ls "$APP_PATH/Contents/Resources"/openhuman-* >/dev/null 2>&1; then echo "Sidecar core binary missing from app resources" exit 1 fi - - name: Build standalone CLI binary + - name: Build standalone core CLI binary shell: bash run: | - cargo build --manifest-path rust-core/Cargo.toml --release --target "$MATRIX_TARGET" --bin openhuman-cli + cargo build --manifest-path "$CORE_MANIFEST" --release --target "$MATRIX_TARGET" --bin "$CORE_BIN_NAME" env: MATRIX_TARGET: ${{ matrix.settings.target }} + CORE_MANIFEST: ${{ steps.core-paths.outputs.core_manifest }} + CORE_BIN_NAME: ${{ steps.core-paths.outputs.core_bin_name }} - - name: Resolve standalone CLI artifact path + - name: Resolve standalone core CLI artifact path id: cli-paths shell: bash run: | - BASE_DIR="rust-core/target/$MATRIX_TARGET/release" + BASE_DIR="$CORE_TARGET_DIR" echo "base_dir=$BASE_DIR" >> "$GITHUB_OUTPUT" - echo "cli_path=$BASE_DIR/openhuman-cli" >> "$GITHUB_OUTPUT" + echo "cli_path=$BASE_DIR/$CORE_BIN_NAME" >> "$GITHUB_OUTPUT" env: MATRIX_TARGET: ${{ matrix.settings.target }} + CORE_TARGET_DIR: ${{ steps.core-paths.outputs.core_target_dir }} + CORE_BIN_NAME: ${{ steps.core-paths.outputs.core_bin_name }} - name: Upload standalone CLI artifacts uses: actions/upload-artifact@v4 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 83d910cc0..8d88795ac 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -54,3 +54,87 @@ jobs: name: coverage-report path: coverage retention-days: 7 + + rust-tests: + name: Run Rust Tests + runs-on: ubuntu-22.04 + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 1 + + - name: Install Rust stable + uses: dtolnay/rust-toolchain@stable + with: + targets: x86_64-unknown-linux-gnu + + - name: Install Tauri build dependencies + run: | + sudo apt-get update + sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf + + # Match build.yml: lockfile body only (skip workspace version header noise) + - name: Cargo.lock fingerprint (deps only) + id: cargo-lock-fingerprint + shell: bash + run: | + echo "hash=$(tail -n +8 Cargo.lock | openssl dgst -sha256 | awk '{print $2}')" >> "$GITHUB_OUTPUT" + + - name: Cache Cargo registry and git sources + uses: actions/cache@v4 + with: + path: | + ~/.cargo/registry + ~/.cargo/git + key: ${{ runner.os }}-rust-test-cargo-${{ steps.cargo-lock-fingerprint.outputs.hash }} + restore-keys: | + ${{ runner.os }}-rust-test-cargo- + + - name: Test rust-core (openhuman-core) + run: cargo test -p openhuman-core + + - name: Test src-tauri (OpenHuman) + run: cargo test -p OpenHuman + + e2e-macos: + name: E2E (macOS / Appium) + runs-on: macos-latest + timeout-minutes: 90 + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 1 + submodules: recursive + + - name: Setup Node.js 24.x + uses: actions/setup-node@v4 + with: + node-version: 24.x + cache: 'yarn' + + - name: Install Rust stable + uses: dtolnay/rust-toolchain@stable + + - name: Install dependencies + run: yarn install --frozen-lockfile + + - name: Install skills dependencies + run: cd skills && yarn install --frozen-lockfile + + - name: Ensure .env exists for E2E build + run: touch .env + + - name: Install Appium and mac2 driver + run: | + npm install -g appium + appium driver install mac2 + + - name: Build E2E app bundle + run: yarn test:e2e:build + env: + E2E_SKIP_CARGO_CLEAN: '1' + + - name: Run all E2E flows + run: yarn test:e2e:all:flows diff --git a/.github/workflows/update-changelog.yml b/.github/workflows/update-changelog.yml deleted file mode 100644 index 5276cca39..000000000 --- a/.github/workflows/update-changelog.yml +++ /dev/null @@ -1,197 +0,0 @@ -name: Update Changelog - -on: - push: - branches: - - main - -permissions: - contents: write - -concurrency: - group: update-changelog - cancel-in-progress: false - -jobs: - update-changelog: - runs-on: ubuntu-latest - # Skip if commit message contains [skip ci] or is a version bump commit - if: "!contains(github.event.head_commit.message, '[skip ci]') && !contains(github.event.head_commit.message, 'chore: bump version')" - - steps: - - name: Checkout code - uses: actions/checkout@v4 - with: - fetch-depth: 0 - token: ${{ secrets.GITHUB_TOKEN }} - - - name: Configure Git - run: | - git config user.name "github-actions[bot]" - git config user.email "github-actions[bot]@users.noreply.github.com" - - - name: Fetch tags - run: | - git fetch origin --tags - - - name: Calculate next version - id: next-version - run: | - # Get the latest version tag (format: v1.2.3) - LATEST_TAG=$(git tag -l "v*" | sort -V | tail -n 1 || echo "") - - if [ -z "$LATEST_TAG" ]; then - # No tags found, start with v1.0.0 - NEXT_VERSION="v1.0.0" - LATEST_TAG="(none)" - else - # Extract version number (remove 'v' prefix) - VERSION="${LATEST_TAG#v}" - - # Split into major.minor.patch - IFS='.' read -r MAJOR MINOR PATCH <<< "$VERSION" - - # Ensure we have valid numbers (default to 0 if empty) - MAJOR=${MAJOR:-0} - MINOR=${MINOR:-0} - PATCH=${PATCH:-0} - - # Increment minor version and reset patch to 0 - MINOR=$((MINOR + 1)) - PATCH=0 - - NEXT_VERSION="v${MAJOR}.${MINOR}.${PATCH}" - fi - - echo "Latest tag: $LATEST_TAG" - echo "Predicted next version: $NEXT_VERSION" - echo "version=$NEXT_VERSION" >> $GITHUB_OUTPUT - - - name: Get commits since last tag - id: get-commits - run: | - # Get the latest actual tag (not the predicted one) - LATEST_TAG=$(git tag -l "v*" | sort -V | tail -n 1 || echo "") - - # Get commits since the last tag (or all commits if no tag exists) - if [ -n "$LATEST_TAG" ] && git rev-parse "$LATEST_TAG" >/dev/null 2>&1; then - # Tag exists, get commits since that tag - git log "$LATEST_TAG"..HEAD --pretty=format:"- %s (%h)" --no-merges > commits.txt || echo "- $(git log -1 --pretty=format:'%s (%h)')" > commits.txt - else - # No tag exists, get all commits - git log --pretty=format:"- %s (%h)" --no-merges > commits.txt || echo "- $(git log -1 --pretty=format:'%s (%h)')" > commits.txt - fi - - # If file is empty, use current commit - if [ ! -s commits.txt ]; then - echo "- $(git log -1 --pretty=format:'%s (%h)')" > commits.txt - fi - - - name: Get current date - id: date - run: | - echo "date=$(date +'%Y-%m-%d')" >> $GITHUB_OUTPUT - - - name: Update CHANGELOG.md - run: | - CHANGELOG_PATH="publish/CHANGELOG.md" - NEXT_VERSION="${{ steps.next-version.outputs.version }}" - RELEASE_DATE="${{ steps.date.outputs.date }}" - - # Create changelog directory if it doesn't exist - mkdir -p publish - - # Check if changelog exists and has content - if [ ! -f "$CHANGELOG_PATH" ] || [ ! -s "$CHANGELOG_PATH" ]; then - # Create new changelog with header - cat > "$CHANGELOG_PATH" <<'EOF' - # Changelog - - All notable changes to this project will be documented in this file. - - The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), - and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). - - ## [Unreleased] - - ### Added - - Initial changelog - - EOF - fi - - # Check if this version already exists in changelog - if grep -q "## \[$NEXT_VERSION\]" "$CHANGELOG_PATH"; then - echo "Version $NEXT_VERSION already exists in CHANGELOG.md. Skipping update." - exit 0 - fi - - # Use Python to update the changelog - python3 </dev/null; cargo test --manifest-path src-tauri/Cargo.toml", + "test:rust": "source $HOME/.cargo/env 2>/dev/null; cargo test --workspace", "test:e2e:build": "bash scripts/e2e-build.sh", "test:e2e:login": "bash scripts/e2e-login.sh", "test:e2e:auth": "bash scripts/e2e-auth.sh", "test:e2e": "yarn test:e2e:build && yarn test:e2e:login && yarn test:e2e:auth", + "test:e2e:all:flows": "bash scripts/e2e-run-all-flows.sh", + "test:e2e:all": "yarn test:e2e:build && yarn test:e2e:all:flows", "test:all": "yarn test:coverage && yarn test:rust && yarn test:e2e", - "format": "prettier --write .", - "format:check": "prettier --check .", + "format": "prettier --write . && cargo fmt --all", + "format:check": "prettier --check . && cargo fmt --all --check", "lint": "eslint . --ext .ts,.tsx", "lint:fix": "eslint . --ext .ts,.tsx --fix", "skills:build": "cd skills && yarn build", @@ -51,16 +53,12 @@ "@tauri-apps/plugin-deep-link": "^2", "@tauri-apps/plugin-opener": "^2", "@tauri-apps/plugin-os": "^2.3.2", - "@tauri-apps/plugin-shell": "~2", - "@types/react-router-dom": "^5.3.3", "@types/three": "^0.183.1", "buffer": "^6.0.3", "debug": "^4.4.3", - "immer": "^11.1.3", "lottie-react": "^2.4.1", "os-browserify": "^0.3.0", "process": "^0.11.10", - "qrcode.react": "^4.2.0", "react": "^19.1.0", "react-dom": "^19.1.0", "react-markdown": "^10.1.0", @@ -69,10 +67,8 @@ "redux-logger": "^3.0.6", "redux-persist": "^6.0.0", "socket.io-client": "^4.8.3", - "telegram": "^2.26.22", "three": "^0.183.2", - "util": "^0.12.5", - "zustand": "^5.0.10" + "util": "^0.12.5" }, "devDependencies": { "@eslint/js": "^9.39.2", @@ -82,7 +78,6 @@ "@testing-library/dom": "^10.4.1", "@testing-library/jest-dom": "^6.9.1", "@testing-library/react": "^16.3.2", - "@testing-library/user-event": "^14.6.1", "@trivago/prettier-plugin-sort-imports": "^6.0.2", "@types/debug": "^4.1.12", "@types/node": "^25.0.10", diff --git a/rust-core/src/bin/openhuman-cli.rs b/rust-core/src/bin/openhuman-cli.rs deleted file mode 100644 index fe8779861..000000000 --- a/rust-core/src/bin/openhuman-cli.rs +++ /dev/null @@ -1,388 +0,0 @@ -use clap::{Args, Parser, Subcommand}; -use serde::{Deserialize, Serialize}; -use serde_json::json; - -#[derive(Debug, Serialize)] -struct RpcRequest { - jsonrpc: &'static str, - id: u64, - method: String, - params: serde_json::Value, -} - -#[derive(Debug, Deserialize)] -struct RpcResponse { - result: Option, - error: Option, -} - -#[derive(Debug, Deserialize)] -struct RpcError { - code: i64, - message: String, - data: Option, -} - -#[derive(Debug, Parser)] -#[command(name = "openhuman-cli")] -#[command(about = "CLI for the OpenHuman core RPC server")] -struct Cli { - /// Core RPC endpoint URL - #[arg(long, global = true)] - rpc_url: Option, - - #[command(subcommand)] - command: Command, -} - -#[derive(Debug, Subcommand)] -enum Command { - /// Check core health - Ping, - /// Print core version - Version, - /// Get health snapshot - Health, - /// Get runtime flags - RuntimeFlags, - - /// Config operations - Config { - #[command(subcommand)] - command: ConfigCommand, - }, - - /// Service operations - Service { - #[command(subcommand)] - command: ServiceCommand, - }, - - /// Doctor operations - Doctor { - #[command(subcommand)] - command: DoctorCommand, - }, - - /// Integrations operations - Integrations { - #[command(subcommand)] - command: IntegrationsCommand, - }, - - /// Send one-shot agent message - AgentChat(AgentChatArgs), - - /// Hardware operations - Hardware { - #[command(subcommand)] - command: HardwareCommand, - }, - - /// Encrypt a secret - Encrypt { plaintext: String }, - - /// Decrypt a secret - Decrypt { ciphertext: String }, - - /// Toggle browser allow-all runtime flag - BrowserAllowAll { - #[arg(long)] - enabled: bool, - }, - - /// Refresh model catalog - ModelsRefresh { - #[arg(long)] - provider: Option, - #[arg(long, default_value_t = false)] - force: bool, - }, - - /// Migrate OpenClaw memory - MigrateOpenclaw { - #[arg(long)] - source_workspace: Option, - #[arg(long, default_value_t = true)] - dry_run: bool, - }, -} - -#[derive(Debug, Subcommand)] -enum ConfigCommand { - /// Get full config snapshot - Get, -} - -#[derive(Debug, Subcommand)] -enum ServiceCommand { - Install, - Start, - Stop, - Status, - Reinstall, - Uninstall, -} - -#[derive(Debug, Subcommand)] -enum DoctorCommand { - /// Run doctor checks - Report, - /// Probe model catalog - Models { - #[arg(long)] - provider: Option, - #[arg(long, default_value_t = true)] - use_cache: bool, - }, -} - -#[derive(Debug, Subcommand)] -enum IntegrationsCommand { - /// List integrations - List, - /// Get one integration info - Info { - #[arg(long)] - name: String, - }, -} - -#[derive(Debug, Subcommand)] -enum HardwareCommand { - /// Discover connected hardware - Discover, - /// Introspect one device path - Introspect { - #[arg(long)] - path: String, - }, -} - -#[derive(Debug, Args)] -struct AgentChatArgs { - message: String, - #[arg(long)] - provider: Option, - #[arg(long)] - model: Option, - #[arg(long)] - temperature: Option, -} - -fn endpoint(cli: &Cli) -> String { - if let Some(url) = &cli.rpc_url { - return url.clone(); - } - std::env::var("OPENHUMAN_CORE_RPC_URL") - .unwrap_or_else(|_| "http://127.0.0.1:7788/rpc".to_string()) -} - -fn call(url: &str, method: &str, params: serde_json::Value) -> Result { - let req = RpcRequest { - jsonrpc: "2.0", - id: 1, - method: method.to_string(), - params, - }; - - let client = reqwest::blocking::Client::new(); - let resp = client - .post(url) - .json(&req) - .send() - .map_err(|e| format!("request failed: {e}"))?; - - let payload: RpcResponse = resp.json().map_err(|e| format!("invalid response: {e}"))?; - - if let Some(err) = payload.error { - return Err(format!( - "rpc error {}: {}{}", - err.code, - err.message, - err.data.map(|d| format!(" ({d})")).unwrap_or_default() - )); - } - - Ok(payload.result.unwrap_or(serde_json::Value::Null)) -} - -fn execute(cli: Cli) -> Result { - let url = endpoint(&cli); - - match cli.command { - Command::Ping => call(&url, "core.ping", serde_json::json!({})), - Command::Version => call(&url, "core.version", serde_json::json!({})), - Command::Health => call(&url, "openhuman.health_snapshot", serde_json::json!({})), - Command::RuntimeFlags => call(&url, "openhuman.get_runtime_flags", serde_json::json!({})), - Command::Config { command } => match command { - ConfigCommand::Get => call(&url, "openhuman.get_config", serde_json::json!({})), - }, - Command::Service { command } => match command { - ServiceCommand::Install => local_service_install(), - ServiceCommand::Start => local_service_start(), - ServiceCommand::Stop => local_service_stop(), - ServiceCommand::Status => local_service_status(), - ServiceCommand::Reinstall => { - let _ = local_service_uninstall()?; - local_service_install() - } - ServiceCommand::Uninstall => local_service_uninstall(), - }, - Command::Doctor { command } => match command { - DoctorCommand::Report => call(&url, "openhuman.doctor_report", serde_json::json!({})), - DoctorCommand::Models { - provider, - use_cache, - } => call( - &url, - "openhuman.doctor_models", - serde_json::json!({ - "provider_override": provider, - "use_cache": use_cache, - }), - ), - }, - Command::Integrations { command } => match command { - IntegrationsCommand::List => { - call(&url, "openhuman.list_integrations", serde_json::json!({})) - } - IntegrationsCommand::Info { name } => call( - &url, - "openhuman.get_integration_info", - serde_json::json!({ "name": name }), - ), - }, - Command::AgentChat(args) => call( - &url, - "openhuman.agent_chat", - serde_json::json!({ - "message": args.message, - "provider_override": args.provider, - "model_override": args.model, - "temperature": args.temperature, - }), - ), - Command::Hardware { command } => match command { - HardwareCommand::Discover => { - call(&url, "openhuman.hardware_discover", serde_json::json!({})) - } - HardwareCommand::Introspect { path } => call( - &url, - "openhuman.hardware_introspect", - serde_json::json!({ "path": path }), - ), - }, - Command::Encrypt { plaintext } => call( - &url, - "openhuman.encrypt_secret", - serde_json::json!({ "plaintext": plaintext }), - ), - Command::Decrypt { ciphertext } => call( - &url, - "openhuman.decrypt_secret", - serde_json::json!({ "ciphertext": ciphertext }), - ), - Command::BrowserAllowAll { enabled } => call( - &url, - "openhuman.set_browser_allow_all", - serde_json::json!({ "enabled": enabled }), - ), - Command::ModelsRefresh { provider, force } => call( - &url, - "openhuman.models_refresh", - serde_json::json!({ - "provider_override": provider, - "force": force, - }), - ), - Command::MigrateOpenclaw { - source_workspace, - dry_run, - } => call( - &url, - "openhuman.migrate_openclaw", - serde_json::json!({ - "source_workspace": source_workspace, - "dry_run": dry_run, - }), - ), - } -} - -fn load_config() -> Result { - let runtime = tokio::runtime::Builder::new_current_thread() - .enable_all() - .build() - .map_err(|e| format!("failed to build runtime: {e}"))?; - runtime - .block_on(openhuman_core::openhuman::config::Config::load_or_init()) - .map_err(|e| format!("failed to load config: {e}")) -} - -fn status_value( - status: openhuman_core::openhuman::service::ServiceStatus, -) -> Result { - serde_json::to_value(status).map_err(|e| format!("failed to serialize service status: {e}")) -} - -fn command_response_json( - status: openhuman_core::openhuman::service::ServiceStatus, - log: &str, -) -> Result { - Ok(json!({ - "result": status_value(status)?, - "logs": [log], - })) -} - -fn local_service_install() -> Result { - let config = load_config()?; - let status = openhuman_core::openhuman::service::install(&config) - .map_err(|e| format!("service install failed: {e}"))?; - command_response_json(status, "service install completed") -} - -fn local_service_start() -> Result { - let config = load_config()?; - let status = openhuman_core::openhuman::service::start(&config) - .map_err(|e| format!("service start failed: {e}"))?; - command_response_json(status, "service start completed") -} - -fn local_service_stop() -> Result { - let config = load_config()?; - let status = openhuman_core::openhuman::service::stop(&config) - .map_err(|e| format!("service stop failed: {e}"))?; - command_response_json(status, "service stop completed") -} - -fn local_service_status() -> Result { - let config = load_config()?; - let status = openhuman_core::openhuman::service::status(&config) - .map_err(|e| format!("service status failed: {e}"))?; - command_response_json(status, "service status fetched") -} - -fn local_service_uninstall() -> Result { - let config = load_config()?; - let status = openhuman_core::openhuman::service::uninstall(&config) - .map_err(|e| format!("service uninstall failed: {e}"))?; - command_response_json(status, "service uninstall completed") -} - -fn main() { - let cli = Cli::parse(); - match execute(cli) { - Ok(value) => { - println!( - "{}", - serde_json::to_string_pretty(&value).unwrap_or_else(|_| "null".to_string()) - ); - } - Err(err) => { - eprintln!("{err}"); - std::process::exit(1); - } - } -} diff --git a/rust-core/src/bin/openhuman-core.rs b/rust-core/src/bin/openhuman-core.rs deleted file mode 100644 index a23cbb3fe..000000000 --- a/rust-core/src/bin/openhuman-core.rs +++ /dev/null @@ -1,7 +0,0 @@ -fn main() { - let args: Vec = std::env::args().skip(1).collect(); - if let Err(err) = openhuman_core::core_server::run_from_cli_args(&args) { - eprintln!("openhuman-core failed: {err}"); - std::process::exit(1); - } -} diff --git a/rust-core/src/bin/openhuman.rs b/rust-core/src/bin/openhuman.rs new file mode 100644 index 000000000..32bd30b97 --- /dev/null +++ b/rust-core/src/bin/openhuman.rs @@ -0,0 +1,334 @@ +use clap::{Args, Parser, Subcommand}; +use serde_json::json; + +#[derive(Debug, Parser)] +#[command(name = "openhuman")] +#[command(about = "OpenHuman core CLI")] +struct Cli { + #[command(subcommand)] + command: Command, +} + +#[derive(Debug, Subcommand)] +enum Command { + /// Run JSON-RPC server + Serve { + #[arg(long)] + port: Option, + }, + /// Check core health + Ping, + /// Print core version + Version, + /// Get health snapshot + Health, + /// Get runtime flags + RuntimeFlags, + /// Get security policy info + SecurityPolicy, + /// Generic JSON-RPC style method call + Call { + #[arg(long)] + method: String, + #[arg(long, default_value = "{}")] + params: String, + }, + /// Config operations + Config { + #[command(subcommand)] + command: ConfigCommand, + }, + /// Service operations + Service { + #[command(subcommand)] + command: ServiceCommand, + }, + /// Doctor operations + Doctor { + #[command(subcommand)] + command: DoctorCommand, + }, + /// Integrations operations + Integrations { + #[command(subcommand)] + command: IntegrationsCommand, + }, + /// Send one-shot agent message + AgentChat(AgentChatArgs), + /// Hardware operations + Hardware { + #[command(subcommand)] + command: HardwareCommand, + }, + /// Encrypt a secret + Encrypt { plaintext: String }, + /// Decrypt a secret + Decrypt { ciphertext: String }, + /// Toggle browser allow-all runtime flag + BrowserAllowAll { + #[arg(long)] + enabled: bool, + }, + /// Refresh model catalog + ModelsRefresh { + #[arg(long)] + provider: Option, + #[arg(long, default_value_t = false)] + force: bool, + }, + /// Migrate OpenClaw memory + MigrateOpenclaw { + #[arg(long)] + source_workspace: Option, + #[arg(long, default_value_t = true)] + dry_run: bool, + }, +} + +#[derive(Debug, Subcommand)] +enum ConfigCommand { + /// Get full config snapshot + Get, + /// Update model settings with a JSON object + UpdateModel { + #[arg(long)] + json: String, + }, + /// Update memory settings with a JSON object + UpdateMemory { + #[arg(long)] + json: String, + }, + /// Update gateway settings with a JSON object + UpdateGateway { + #[arg(long)] + json: String, + }, + /// Update runtime settings with a JSON object + UpdateRuntime { + #[arg(long)] + json: String, + }, + /// Update browser settings with a JSON object + UpdateBrowser { + #[arg(long)] + json: String, + }, + /// Replace tunnel settings with a JSON object + UpdateTunnel { + #[arg(long)] + json: String, + }, +} + +#[derive(Debug, Subcommand)] +enum ServiceCommand { + Install, + Start, + Stop, + Status, + Reinstall, + Uninstall, +} + +#[derive(Debug, Subcommand)] +enum DoctorCommand { + /// Run doctor checks + Report, + /// Probe model catalog + Models { + #[arg(long)] + provider: Option, + #[arg(long, default_value_t = true)] + use_cache: bool, + }, +} + +#[derive(Debug, Subcommand)] +enum IntegrationsCommand { + /// List integrations + List, + /// Get one integration info + Info { + #[arg(long)] + name: String, + }, +} + +#[derive(Debug, Subcommand)] +enum HardwareCommand { + /// Discover connected hardware + Discover, + /// Introspect one device path + Introspect { + #[arg(long)] + path: String, + }, +} + +#[derive(Debug, Args)] +struct AgentChatArgs { + message: String, + #[arg(long)] + provider: Option, + #[arg(long)] + model: Option, + #[arg(long)] + temperature: Option, +} + +fn parse_json_arg(raw: &str) -> Result { + serde_json::from_str(raw).map_err(|e| format!("invalid JSON for --json/--params: {e}")) +} + +async fn call_local(method: &str, params: serde_json::Value) -> Result { + openhuman_core::core_server::call_method(method, params).await +} + +async fn execute(cli: Cli) -> Result { + match cli.command { + Command::Serve { port } => openhuman_core::core_server::run_server(port) + .await + .map(|_| serde_json::Value::Null) + .map_err(|e| format!("serve failed: {e}")), + Command::Ping => call_local("core.ping", json!({})).await, + Command::Version => call_local("core.version", json!({})).await, + Command::Health => call_local("openhuman.health_snapshot", json!({})).await, + Command::RuntimeFlags => call_local("openhuman.get_runtime_flags", json!({})).await, + Command::SecurityPolicy => call_local("openhuman.security_policy_info", json!({})).await, + Command::Call { method, params } => call_local(&method, parse_json_arg(¶ms)?).await, + Command::Config { command } => match command { + ConfigCommand::Get => call_local("openhuman.get_config", json!({})).await, + ConfigCommand::UpdateModel { json } => { + call_local("openhuman.update_model_settings", parse_json_arg(&json)?).await + } + ConfigCommand::UpdateMemory { json } => { + call_local("openhuman.update_memory_settings", parse_json_arg(&json)?).await + } + ConfigCommand::UpdateGateway { json } => { + call_local("openhuman.update_gateway_settings", parse_json_arg(&json)?).await + } + ConfigCommand::UpdateRuntime { json } => { + call_local("openhuman.update_runtime_settings", parse_json_arg(&json)?).await + } + ConfigCommand::UpdateBrowser { json } => { + call_local("openhuman.update_browser_settings", parse_json_arg(&json)?).await + } + ConfigCommand::UpdateTunnel { json } => { + call_local("openhuman.update_tunnel_settings", parse_json_arg(&json)?).await + } + }, + Command::Service { command } => match command { + ServiceCommand::Install => call_local("openhuman.service_install", json!({})).await, + ServiceCommand::Start => call_local("openhuman.service_start", json!({})).await, + ServiceCommand::Stop => call_local("openhuman.service_stop", json!({})).await, + ServiceCommand::Status => call_local("openhuman.service_status", json!({})).await, + ServiceCommand::Reinstall => { + call_local("openhuman.service_uninstall", json!({})).await?; + call_local("openhuman.service_install", json!({})).await + } + ServiceCommand::Uninstall => call_local("openhuman.service_uninstall", json!({})).await, + }, + Command::Doctor { command } => match command { + DoctorCommand::Report => call_local("openhuman.doctor_report", json!({})).await, + DoctorCommand::Models { + provider, + use_cache, + } => { + call_local( + "openhuman.doctor_models", + json!({ + "provider_override": provider, + "use_cache": use_cache, + }), + ) + .await + } + }, + Command::Integrations { command } => match command { + IntegrationsCommand::List => call_local("openhuman.list_integrations", json!({})).await, + IntegrationsCommand::Info { name } => { + call_local("openhuman.get_integration_info", json!({ "name": name })).await + } + }, + Command::AgentChat(args) => { + call_local( + "openhuman.agent_chat", + json!({ + "message": args.message, + "provider_override": args.provider, + "model_override": args.model, + "temperature": args.temperature, + }), + ) + .await + } + Command::Hardware { command } => match command { + HardwareCommand::Discover => call_local("openhuman.hardware_discover", json!({})).await, + HardwareCommand::Introspect { path } => { + call_local("openhuman.hardware_introspect", json!({ "path": path })).await + } + }, + Command::Encrypt { plaintext } => { + call_local( + "openhuman.encrypt_secret", + json!({ "plaintext": plaintext }), + ) + .await + } + Command::Decrypt { ciphertext } => { + call_local( + "openhuman.decrypt_secret", + json!({ "ciphertext": ciphertext }), + ) + .await + } + Command::BrowserAllowAll { enabled } => { + call_local( + "openhuman.set_browser_allow_all", + json!({ "enabled": enabled }), + ) + .await + } + Command::ModelsRefresh { provider, force } => { + call_local( + "openhuman.models_refresh", + json!({ + "provider_override": provider, + "force": force, + }), + ) + .await + } + Command::MigrateOpenclaw { + source_workspace, + dry_run, + } => { + call_local( + "openhuman.migrate_openclaw", + json!({ + "source_workspace": source_workspace, + "dry_run": dry_run, + }), + ) + .await + } + } +} + +#[tokio::main] +async fn main() { + let cli = Cli::parse(); + match execute(cli).await { + Ok(value) => { + println!( + "{}", + serde_json::to_string_pretty(&value).unwrap_or_else(|_| "null".to_string()) + ); + } + Err(err) => { + eprintln!("{err}"); + std::process::exit(1); + } + } +} diff --git a/rust-core/src/core_server.rs b/rust-core/src/core_server.rs index 8c955dae0..c914056c7 100644 --- a/rust-core/src/core_server.rs +++ b/rust-core/src/core_server.rs @@ -640,6 +640,20 @@ async fn dispatch( } } +pub async fn call_method( + method: &str, + params: serde_json::Value, +) -> Result { + dispatch( + AppState { + core_version: env!("CARGO_PKG_VERSION").to_string(), + }, + method, + params, + ) + .await +} + async fn health_handler() -> impl IntoResponse { (StatusCode::OK, Json(json!({ "ok": true }))) } diff --git a/rust-core/src/openhuman/service/mod.rs b/rust-core/src/openhuman/service/mod.rs index 6d1cdbd44..d5a738ff8 100644 --- a/rust-core/src/openhuman/service/mod.rs +++ b/rust-core/src/openhuman/service/mod.rs @@ -16,13 +16,14 @@ fn windows_task_name() -> &'static str { } fn daemon_program_args(exe: &std::path::Path) -> Vec { - let file_name = exe - .file_name() - .and_then(|n| n.to_str()) - .unwrap_or_default() - .to_ascii_lowercase(); + let raw_file_name = exe.file_name().and_then(|n| n.to_str()).unwrap_or_default(); + let file_name = raw_file_name.to_ascii_lowercase(); + let standalone_core_binary = file_name.contains("openhuman-core") + || file_name.starts_with("openhuman-") + || raw_file_name == "openhuman" + || raw_file_name == "openhuman.exe"; - if file_name.contains("openhuman-core") { + if standalone_core_binary { vec!["serve".to_string()] } else { vec!["core".to_string(), "serve".to_string()] diff --git a/rust-core/src/runtime/utils.rs b/rust-core/src/runtime/utils.rs index 5d98ba08f..11889ed7f 100644 --- a/rust-core/src/runtime/utils.rs +++ b/rust-core/src/runtime/utils.rs @@ -119,7 +119,7 @@ mod tests { assert_eq!(result.unwrap(), "test"); } - #[tokio::test] + #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn test_safe_async_execute_with_runtime() { let result = safe_async_execute(async { "test".to_string() }); assert!(result.is_ok()); diff --git a/scripts/e2e-auth.sh b/scripts/e2e-auth.sh index d6666eab0..27cca1429 100755 --- a/scripts/e2e-auth.sh +++ b/scripts/e2e-auth.sh @@ -1,90 +1,4 @@ #!/usr/bin/env bash -# -# Run E2E auth & access control tests only. -# -# Starts Appium, cleans app caches, runs the auth-access-control spec, -# then tears everything down. Each flow script is self-contained so -# specs don't pollute each other's Redux Persist state. -# -# Usage: -# ./scripts/e2e-auth.sh -# APPIUM_PORT=4723 ./scripts/e2e-auth.sh -# +# Run E2E auth & access control tests only. See scripts/e2e-run-spec.sh. set -euo pipefail - -APPIUM_PORT="${APPIUM_PORT:-4723}" -E2E_MOCK_PORT="${E2E_MOCK_PORT:-18473}" -SPEC="test/e2e/specs/auth-access-control.spec.ts" - -export VITE_BACKEND_URL="http://127.0.0.1:${E2E_MOCK_PORT}" -export BACKEND_URL="http://127.0.0.1:${E2E_MOCK_PORT}" - -# Clean cached app data for a fresh state — Redux Persist would otherwise -# remember the JWT from a previous run and skip the login flow. -echo "Cleaning cached app data..." -rm -rf ~/Library/WebKit/com.openhuman.app -rm -rf ~/Library/Caches/com.openhuman.app -rm -rf "$HOME/Library/Application Support/com.openhuman.app" - -# Verify the frontend dist has the mock server URL baked in. -DIST_JS="$(ls dist/assets/index-*.js 2>/dev/null | head -1)" -if [ -z "$DIST_JS" ]; then - echo "ERROR: No frontend bundle found at dist/assets/index-*.js." >&2 - echo " Run 'yarn test:e2e:build' to build the app before running E2E tests." >&2 - exit 1 -fi -if ! grep -q "127.0.0.1:${E2E_MOCK_PORT}" "$DIST_JS"; then - echo "ERROR: frontend bundle does NOT contain mock server URL (127.0.0.1:${E2E_MOCK_PORT})." >&2 - echo " Run 'yarn test:e2e:build' to rebuild with the mock URL." >&2 - exit 1 -fi -echo "Verified: frontend bundle contains mock server URL." - -# --- Resolve Node 24 via nvm --------------------------------------------------- -export NVM_DIR="${NVM_DIR:-$HOME/.nvm}" -# shellcheck source=/dev/null -[ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh" - -NODE24="$(nvm which 24 2>/dev/null || true)" -if [ -z "$NODE24" ] || [ ! -x "$NODE24" ]; then - echo "ERROR: Node 24 is required for appium v3. Install it with: nvm install 24" >&2 - exit 1 -fi - -APPIUM_BIN="$(dirname "$NODE24")/appium" -if [ ! -x "$APPIUM_BIN" ]; then - echo "ERROR: appium not found at $APPIUM_BIN. Install it with: nvm use 24 && npm i -g appium" >&2 - exit 1 -fi - -# --- Start Appium in the background ------------------------------------------- -APPIUM_LOG="/tmp/appium-e2e-auth.log" -NODE_VER=$("$NODE24" --version) -echo "Starting Appium on port $APPIUM_PORT (Node $NODE_VER)..." -echo " Appium logs: $APPIUM_LOG" -"$NODE24" "$APPIUM_BIN" --port "$APPIUM_PORT" --relaxed-security > "$APPIUM_LOG" 2>&1 & -APPIUM_PID=$! - -cleanup() { - echo "Stopping Appium (pid $APPIUM_PID)..." - kill "$APPIUM_PID" 2>/dev/null || true - wait "$APPIUM_PID" 2>/dev/null || true -} -trap cleanup EXIT - -# Wait for Appium to be ready -for i in $(seq 1 30); do - if curl -sf "http://127.0.0.1:$APPIUM_PORT/status" >/dev/null 2>&1; then - echo "Appium is ready." - break - fi - if [ "$i" -eq 30 ]; then - echo "ERROR: Appium did not start within 30 seconds." >&2 - exit 1 - fi - sleep 1 -done - -# --- Run WebDriverIO ---------------------------------------------------------- -echo "Running E2E auth flow tests ($SPEC)..." -npx wdio run test/wdio.conf.ts --spec "$SPEC" +exec "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/e2e-run-spec.sh" "test/e2e/specs/auth-access-control.spec.ts" "auth" diff --git a/scripts/e2e-build.sh b/scripts/e2e-build.sh index 3522fafcb..c438b8f35 100755 --- a/scripts/e2e-build.sh +++ b/scripts/e2e-build.sh @@ -14,16 +14,22 @@ export VITE_BACKEND_URL="http://127.0.0.1:${E2E_MOCK_PORT:-18473}" echo "Building E2E app bundle with VITE_BACKEND_URL=$VITE_BACKEND_URL" -# Clean Rust build cache so frontend assets get re-embedded -cargo clean --manifest-path src-tauri/Cargo.toml +if [ -z "${E2E_SKIP_CARGO_CLEAN:-}" ]; then + cargo clean --manifest-path src-tauri/Cargo.toml +else + echo "Skipping cargo clean (E2E_SKIP_CARGO_CLEAN is set)." +fi -# Load .env for other vars (Telegram API keys, etc.) -source scripts/load-dotenv.sh +if [ -f .env ]; then + # shellcheck source=/dev/null + source scripts/load-dotenv.sh +else + echo "No .env file — skipping load-dotenv (optional for CI)." +fi -# Re-export mock URL in case load-dotenv.sh clobbered it export VITE_BACKEND_URL="http://127.0.0.1:${E2E_MOCK_PORT:-18473}" -# Build .app only (skip DMG to avoid bundle_dmg.sh failures) -tauri build --bundles app --debug +# Use npx so CI does not require a global Tauri CLI +npx tauri build --bundles app --debug echo "E2E build complete." diff --git a/scripts/e2e-crypto-payment.sh b/scripts/e2e-crypto-payment.sh index 42303ba94..f51a508ef 100755 --- a/scripts/e2e-crypto-payment.sh +++ b/scripts/e2e-crypto-payment.sh @@ -1,90 +1,4 @@ #!/usr/bin/env bash -# -# Run E2E cryptocurrency payment flow tests only. -# -# Starts Appium, cleans app caches, runs the crypto-payment-flow spec, -# then tears everything down. Each flow script is self-contained so -# specs don't pollute each other's Redux Persist state. -# -# Usage: -# ./scripts/e2e-crypto-payment.sh -# APPIUM_PORT=4723 ./scripts/e2e-crypto-payment.sh -# +# Run E2E crypto payment flow tests only. See scripts/e2e-run-spec.sh. set -euo pipefail - -APPIUM_PORT="${APPIUM_PORT:-4723}" -E2E_MOCK_PORT="${E2E_MOCK_PORT:-18473}" -SPEC="test/e2e/specs/crypto-payment-flow.spec.ts" - -export VITE_BACKEND_URL="http://127.0.0.1:${E2E_MOCK_PORT}" -export BACKEND_URL="http://127.0.0.1:${E2E_MOCK_PORT}" - -# Clean cached app data for a fresh state — Redux Persist would otherwise -# remember the JWT from a previous run and skip the login flow. -echo "Cleaning cached app data..." -rm -rf ~/Library/WebKit/com.openhuman.app -rm -rf ~/Library/Caches/com.openhuman.app -rm -rf "$HOME/Library/Application Support/com.openhuman.app" - -# Verify the frontend dist has the mock server URL baked in. -DIST_JS="$(ls dist/assets/index-*.js 2>/dev/null | head -1)" -if [ -z "$DIST_JS" ]; then - echo "ERROR: No frontend bundle found at dist/assets/index-*.js." >&2 - echo " Run 'yarn test:e2e:build' to build the app before running E2E tests." >&2 - exit 1 -fi -if ! grep -q "127.0.0.1:${E2E_MOCK_PORT}" "$DIST_JS"; then - echo "ERROR: frontend bundle does NOT contain mock server URL (127.0.0.1:${E2E_MOCK_PORT})." >&2 - echo " Run 'yarn test:e2e:build' to rebuild with the mock URL." >&2 - exit 1 -fi -echo "Verified: frontend bundle contains mock server URL." - -# --- Resolve Node 24 via nvm --------------------------------------------------- -export NVM_DIR="${NVM_DIR:-$HOME/.nvm}" -# shellcheck source=/dev/null -[ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh" - -NODE24="$(nvm which 24 2>/dev/null || true)" -if [ -z "$NODE24" ] || [ ! -x "$NODE24" ]; then - echo "ERROR: Node 24 is required for appium v3. Install it with: nvm install 24" >&2 - exit 1 -fi - -APPIUM_BIN="$(dirname "$NODE24")/appium" -if [ ! -x "$APPIUM_BIN" ]; then - echo "ERROR: appium not found at $APPIUM_BIN. Install it with: nvm use 24 && npm i -g appium" >&2 - exit 1 -fi - -# --- Start Appium in the background ------------------------------------------- -APPIUM_LOG="/tmp/appium-e2e-crypto-payment.log" -NODE_VER=$("$NODE24" --version) -echo "Starting Appium on port $APPIUM_PORT (Node $NODE_VER)..." -echo " Appium logs: $APPIUM_LOG" -"$NODE24" "$APPIUM_BIN" --port "$APPIUM_PORT" --relaxed-security > "$APPIUM_LOG" 2>&1 & -APPIUM_PID=$! - -cleanup() { - echo "Stopping Appium (pid $APPIUM_PID)..." - kill "$APPIUM_PID" 2>/dev/null || true - wait "$APPIUM_PID" 2>/dev/null || true -} -trap cleanup EXIT - -# Wait for Appium to be ready -for i in $(seq 1 30); do - if curl -sf "http://127.0.0.1:$APPIUM_PORT/status" >/dev/null 2>&1; then - echo "Appium is ready." - break - fi - if [ "$i" -eq 30 ]; then - echo "ERROR: Appium did not start within 30 seconds." >&2 - exit 1 - fi - sleep 1 -done - -# --- Run WebDriverIO ---------------------------------------------------------- -echo "Running E2E crypto payment flow tests ($SPEC)..." -npx wdio run test/wdio.conf.ts --spec "$SPEC" +exec "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/e2e-run-spec.sh" "test/e2e/specs/crypto-payment-flow.spec.ts" "crypto-payment" diff --git a/scripts/e2e-gmail.sh b/scripts/e2e-gmail.sh index b4a772d5a..6ee647778 100755 --- a/scripts/e2e-gmail.sh +++ b/scripts/e2e-gmail.sh @@ -1,90 +1,4 @@ #!/usr/bin/env bash -# -# Run E2E Gmail integration flow tests only. -# -# Starts Appium, cleans app caches, runs the gmail-flow spec, -# then tears everything down. Each flow script is self-contained so -# specs don't pollute each other's Redux Persist state. -# -# Usage: -# ./scripts/e2e-gmail.sh -# APPIUM_PORT=4723 ./scripts/e2e-gmail.sh -# +# Run E2E Gmail integration flow tests only. See scripts/e2e-run-spec.sh. set -euo pipefail - -APPIUM_PORT="${APPIUM_PORT:-4723}" -E2E_MOCK_PORT="${E2E_MOCK_PORT:-18473}" -SPEC="test/e2e/specs/gmail-flow.spec.ts" - -export VITE_BACKEND_URL="http://127.0.0.1:${E2E_MOCK_PORT}" -export BACKEND_URL="http://127.0.0.1:${E2E_MOCK_PORT}" - -# Clean cached app data for a fresh state — Redux Persist would otherwise -# remember the JWT from a previous run and skip the login flow. -echo "Cleaning cached app data..." -rm -rf ~/Library/WebKit/com.openhuman.app -rm -rf ~/Library/Caches/com.openhuman.app -rm -rf "$HOME/Library/Application Support/com.openhuman.app" - -# Verify the frontend dist has the mock server URL baked in. -DIST_JS="$(ls dist/assets/index-*.js 2>/dev/null | head -1)" -if [ -z "$DIST_JS" ]; then - echo "ERROR: No frontend bundle found at dist/assets/index-*.js." >&2 - echo " Run 'yarn test:e2e:build' to build the app before running E2E tests." >&2 - exit 1 -fi -if ! grep -q "127.0.0.1:${E2E_MOCK_PORT}" "$DIST_JS"; then - echo "ERROR: frontend bundle does NOT contain mock server URL (127.0.0.1:${E2E_MOCK_PORT})." >&2 - echo " Run 'yarn test:e2e:build' to rebuild with the mock URL." >&2 - exit 1 -fi -echo "Verified: frontend bundle contains mock server URL." - -# --- Resolve Node 24 via nvm --------------------------------------------------- -export NVM_DIR="${NVM_DIR:-$HOME/.nvm}" -# shellcheck source=/dev/null -[ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh" - -NODE24="$(nvm which 24 2>/dev/null || true)" -if [ -z "$NODE24" ] || [ ! -x "$NODE24" ]; then - echo "ERROR: Node 24 is required for appium v3. Install it with: nvm install 24" >&2 - exit 1 -fi - -APPIUM_BIN="$(dirname "$NODE24")/appium" -if [ ! -x "$APPIUM_BIN" ]; then - echo "ERROR: appium not found at $APPIUM_BIN. Install it with: nvm use 24 && npm i -g appium" >&2 - exit 1 -fi - -# --- Start Appium in the background ------------------------------------------- -APPIUM_LOG="/tmp/appium-e2e-gmail.log" -NODE_VER=$("$NODE24" --version) -echo "Starting Appium on port $APPIUM_PORT (Node $NODE_VER)..." -echo " Appium logs: $APPIUM_LOG" -"$NODE24" "$APPIUM_BIN" --port "$APPIUM_PORT" --relaxed-security > "$APPIUM_LOG" 2>&1 & -APPIUM_PID=$! - -cleanup() { - echo "Stopping Appium (pid $APPIUM_PID)..." - kill "$APPIUM_PID" 2>/dev/null || true - wait "$APPIUM_PID" 2>/dev/null || true -} -trap cleanup EXIT - -# Wait for Appium to be ready -for i in $(seq 1 30); do - if curl -sf "http://127.0.0.1:$APPIUM_PORT/status" >/dev/null 2>&1; then - echo "Appium is ready." - break - fi - if [ "$i" -eq 30 ]; then - echo "ERROR: Appium did not start within 30 seconds." >&2 - exit 1 - fi - sleep 1 -done - -# --- Run WebDriverIO ---------------------------------------------------------- -echo "Running E2E Gmail integration flow tests ($SPEC)..." -npx wdio run test/wdio.conf.ts --spec "$SPEC" +exec "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/e2e-run-spec.sh" "test/e2e/specs/gmail-flow.spec.ts" "gmail" diff --git a/scripts/e2e-login.sh b/scripts/e2e-login.sh index 1de5ac050..0596b4822 100755 --- a/scripts/e2e-login.sh +++ b/scripts/e2e-login.sh @@ -1,93 +1,4 @@ #!/usr/bin/env bash -# -# Run E2E login flow tests only. -# -# Starts Appium, cleans app caches, runs the login-flow spec, -# then tears everything down. Each flow script is self-contained so -# specs don't pollute each other's Redux Persist state. -# -# Usage: -# ./scripts/e2e-login.sh -# APPIUM_PORT=4723 ./scripts/e2e-login.sh -# +# Run E2E login flow tests only. See scripts/e2e-run-spec.sh. set -euo pipefail - -APPIUM_PORT="${APPIUM_PORT:-4723}" -E2E_MOCK_PORT="${E2E_MOCK_PORT:-18473}" -SPEC="test/e2e/specs/login-flow.spec.ts" - -# Point the app at the local mock server (baked into the build already, but -# also exported here so the Rust backend / any env-based config picks it up). -export VITE_BACKEND_URL="http://127.0.0.1:${E2E_MOCK_PORT}" -export BACKEND_URL="http://127.0.0.1:${E2E_MOCK_PORT}" - -# Clean cached app data for a fresh state — Redux Persist would otherwise -# remember the JWT from a previous run and skip the login flow. -echo "Cleaning cached app data..." -rm -rf ~/Library/WebKit/com.openhuman.app -rm -rf ~/Library/Caches/com.openhuman.app -rm -rf "$HOME/Library/Application Support/com.openhuman.app" - -# Verify the frontend dist has the mock server URL baked in (not production). -# Tauri compresses assets when embedding, so we check dist/ not the binary. -DIST_JS="$(ls dist/assets/index-*.js 2>/dev/null | head -1)" -if [ -z "$DIST_JS" ]; then - echo "ERROR: No frontend bundle found at dist/assets/index-*.js." >&2 - echo " Run 'yarn test:e2e:build' to build the app before running E2E tests." >&2 - exit 1 -fi -if ! grep -q "127.0.0.1:${E2E_MOCK_PORT}" "$DIST_JS"; then - echo "ERROR: frontend bundle does NOT contain mock server URL (127.0.0.1:${E2E_MOCK_PORT})." >&2 - echo " Run 'yarn test:e2e:build' to rebuild with the mock URL." >&2 - exit 1 -fi -echo "Verified: frontend bundle contains mock server URL." - -# --- Resolve Node 24 via nvm --------------------------------------------------- -export NVM_DIR="${NVM_DIR:-$HOME/.nvm}" -# shellcheck source=/dev/null -[ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh" - -NODE24="$(nvm which 24 2>/dev/null || true)" -if [ -z "$NODE24" ] || [ ! -x "$NODE24" ]; then - echo "ERROR: Node 24 is required for appium v3. Install it with: nvm install 24" >&2 - exit 1 -fi - -APPIUM_BIN="$(dirname "$NODE24")/appium" -if [ ! -x "$APPIUM_BIN" ]; then - echo "ERROR: appium not found at $APPIUM_BIN. Install it with: nvm use 24 && npm i -g appium" >&2 - exit 1 -fi - -# --- Start Appium in the background ------------------------------------------- -APPIUM_LOG="/tmp/appium-e2e-login.log" -NODE_VER=$("$NODE24" --version) -echo "Starting Appium on port $APPIUM_PORT (Node $NODE_VER)..." -echo " Appium logs: $APPIUM_LOG" -"$NODE24" "$APPIUM_BIN" --port "$APPIUM_PORT" --relaxed-security > "$APPIUM_LOG" 2>&1 & -APPIUM_PID=$! - -cleanup() { - echo "Stopping Appium (pid $APPIUM_PID)..." - kill "$APPIUM_PID" 2>/dev/null || true - wait "$APPIUM_PID" 2>/dev/null || true -} -trap cleanup EXIT - -# Wait for Appium to be ready -for i in $(seq 1 30); do - if curl -sf "http://127.0.0.1:$APPIUM_PORT/status" >/dev/null 2>&1; then - echo "Appium is ready." - break - fi - if [ "$i" -eq 30 ]; then - echo "ERROR: Appium did not start within 30 seconds." >&2 - exit 1 - fi - sleep 1 -done - -# --- Run WebDriverIO ---------------------------------------------------------- -echo "Running E2E login flow tests ($SPEC)..." -npx wdio run test/wdio.conf.ts --spec "$SPEC" +exec "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/e2e-run-spec.sh" "test/e2e/specs/login-flow.spec.ts" "login" diff --git a/scripts/e2e-notion.sh b/scripts/e2e-notion.sh index dd920b843..e87c8e761 100755 --- a/scripts/e2e-notion.sh +++ b/scripts/e2e-notion.sh @@ -1,90 +1,4 @@ #!/usr/bin/env bash -# -# Run E2E Notion integration flow tests only. -# -# Starts Appium, cleans app caches, runs the notion-flow spec, -# then tears everything down. Each flow script is self-contained so -# specs don't pollute each other's Redux Persist state. -# -# Usage: -# ./scripts/e2e-notion.sh -# APPIUM_PORT=4723 ./scripts/e2e-notion.sh -# +# Run E2E Notion integration flow tests only. See scripts/e2e-run-spec.sh. set -euo pipefail - -APPIUM_PORT="${APPIUM_PORT:-4723}" -E2E_MOCK_PORT="${E2E_MOCK_PORT:-18473}" -SPEC="test/e2e/specs/notion-flow.spec.ts" - -export VITE_BACKEND_URL="http://127.0.0.1:${E2E_MOCK_PORT}" -export BACKEND_URL="http://127.0.0.1:${E2E_MOCK_PORT}" - -# Clean cached app data for a fresh state — Redux Persist would otherwise -# remember the JWT from a previous run and skip the login flow. -echo "Cleaning cached app data..." -rm -rf ~/Library/WebKit/com.openhuman.app -rm -rf ~/Library/Caches/com.openhuman.app -rm -rf "$HOME/Library/Application Support/com.openhuman.app" - -# Verify the frontend dist has the mock server URL baked in. -DIST_JS="$(ls dist/assets/index-*.js 2>/dev/null | head -1)" -if [ -z "$DIST_JS" ]; then - echo "ERROR: No frontend bundle found at dist/assets/index-*.js." >&2 - echo " Run 'yarn test:e2e:build' to build the app before running E2E tests." >&2 - exit 1 -fi -if ! grep -q "127.0.0.1:${E2E_MOCK_PORT}" "$DIST_JS"; then - echo "ERROR: frontend bundle does NOT contain mock server URL (127.0.0.1:${E2E_MOCK_PORT})." >&2 - echo " Run 'yarn test:e2e:build' to rebuild with the mock URL." >&2 - exit 1 -fi -echo "Verified: frontend bundle contains mock server URL." - -# --- Resolve Node 24 via nvm --------------------------------------------------- -export NVM_DIR="${NVM_DIR:-$HOME/.nvm}" -# shellcheck source=/dev/null -[ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh" - -NODE24="$(nvm which 24 2>/dev/null || true)" -if [ -z "$NODE24" ] || [ ! -x "$NODE24" ]; then - echo "ERROR: Node 24 is required for appium v3. Install it with: nvm install 24" >&2 - exit 1 -fi - -APPIUM_BIN="$(dirname "$NODE24")/appium" -if [ ! -x "$APPIUM_BIN" ]; then - echo "ERROR: appium not found at $APPIUM_BIN. Install it with: nvm use 24 && npm i -g appium" >&2 - exit 1 -fi - -# --- Start Appium in the background ------------------------------------------- -APPIUM_LOG="/tmp/appium-e2e-notion.log" -NODE_VER=$("$NODE24" --version) -echo "Starting Appium on port $APPIUM_PORT (Node $NODE_VER)..." -echo " Appium logs: $APPIUM_LOG" -"$NODE24" "$APPIUM_BIN" --port "$APPIUM_PORT" --relaxed-security > "$APPIUM_LOG" 2>&1 & -APPIUM_PID=$! - -cleanup() { - echo "Stopping Appium (pid $APPIUM_PID)..." - kill "$APPIUM_PID" 2>/dev/null || true - wait "$APPIUM_PID" 2>/dev/null || true -} -trap cleanup EXIT - -# Wait for Appium to be ready -for i in $(seq 1 30); do - if curl -sf "http://127.0.0.1:$APPIUM_PORT/status" >/dev/null 2>&1; then - echo "Appium is ready." - break - fi - if [ "$i" -eq 30 ]; then - echo "ERROR: Appium did not start within 30 seconds." >&2 - exit 1 - fi - sleep 1 -done - -# --- Run WebDriverIO ---------------------------------------------------------- -echo "Running E2E Notion integration flow tests ($SPEC)..." -npx wdio run test/wdio.conf.ts --spec "$SPEC" +exec "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/e2e-run-spec.sh" "test/e2e/specs/notion-flow.spec.ts" "notion" diff --git a/scripts/e2e-payment.sh b/scripts/e2e-payment.sh index 084e9e13e..5899d4e89 100755 --- a/scripts/e2e-payment.sh +++ b/scripts/e2e-payment.sh @@ -1,90 +1,4 @@ #!/usr/bin/env bash -# -# Run E2E card payment flow tests only. -# -# Starts Appium, cleans app caches, runs the card-payment-flow spec, -# then tears everything down. Each flow script is self-contained so -# specs don't pollute each other's Redux Persist state. -# -# Usage: -# ./scripts/e2e-payment.sh -# APPIUM_PORT=4723 ./scripts/e2e-payment.sh -# +# Run E2E card payment flow tests only. See scripts/e2e-run-spec.sh. set -euo pipefail - -APPIUM_PORT="${APPIUM_PORT:-4723}" -E2E_MOCK_PORT="${E2E_MOCK_PORT:-18473}" -SPEC="test/e2e/specs/card-payment-flow.spec.ts" - -export VITE_BACKEND_URL="http://127.0.0.1:${E2E_MOCK_PORT}" -export BACKEND_URL="http://127.0.0.1:${E2E_MOCK_PORT}" - -# Clean cached app data for a fresh state — Redux Persist would otherwise -# remember the JWT from a previous run and skip the login flow. -echo "Cleaning cached app data..." -rm -rf ~/Library/WebKit/com.openhuman.app -rm -rf ~/Library/Caches/com.openhuman.app -rm -rf "$HOME/Library/Application Support/com.openhuman.app" - -# Verify the frontend dist has the mock server URL baked in. -DIST_JS="$(ls dist/assets/index-*.js 2>/dev/null | head -1)" -if [ -z "$DIST_JS" ]; then - echo "ERROR: No frontend bundle found at dist/assets/index-*.js." >&2 - echo " Run 'yarn test:e2e:build' to build the app before running E2E tests." >&2 - exit 1 -fi -if ! grep -q "127.0.0.1:${E2E_MOCK_PORT}" "$DIST_JS"; then - echo "ERROR: frontend bundle does NOT contain mock server URL (127.0.0.1:${E2E_MOCK_PORT})." >&2 - echo " Run 'yarn test:e2e:build' to rebuild with the mock URL." >&2 - exit 1 -fi -echo "Verified: frontend bundle contains mock server URL." - -# --- Resolve Node 24 via nvm --------------------------------------------------- -export NVM_DIR="${NVM_DIR:-$HOME/.nvm}" -# shellcheck source=/dev/null -[ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh" - -NODE24="$(nvm which 24 2>/dev/null || true)" -if [ -z "$NODE24" ] || [ ! -x "$NODE24" ]; then - echo "ERROR: Node 24 is required for appium v3. Install it with: nvm install 24" >&2 - exit 1 -fi - -APPIUM_BIN="$(dirname "$NODE24")/appium" -if [ ! -x "$APPIUM_BIN" ]; then - echo "ERROR: appium not found at $APPIUM_BIN. Install it with: nvm use 24 && npm i -g appium" >&2 - exit 1 -fi - -# --- Start Appium in the background ------------------------------------------- -APPIUM_LOG="/tmp/appium-e2e-payment.log" -NODE_VER=$("$NODE24" --version) -echo "Starting Appium on port $APPIUM_PORT (Node $NODE_VER)..." -echo " Appium logs: $APPIUM_LOG" -"$NODE24" "$APPIUM_BIN" --port "$APPIUM_PORT" --relaxed-security > "$APPIUM_LOG" 2>&1 & -APPIUM_PID=$! - -cleanup() { - echo "Stopping Appium (pid $APPIUM_PID)..." - kill "$APPIUM_PID" 2>/dev/null || true - wait "$APPIUM_PID" 2>/dev/null || true -} -trap cleanup EXIT - -# Wait for Appium to be ready -for i in $(seq 1 30); do - if curl -sf "http://127.0.0.1:$APPIUM_PORT/status" >/dev/null 2>&1; then - echo "Appium is ready." - break - fi - if [ "$i" -eq 30 ]; then - echo "ERROR: Appium did not start within 30 seconds." >&2 - exit 1 - fi - sleep 1 -done - -# --- Run WebDriverIO ---------------------------------------------------------- -echo "Running E2E card payment flow tests ($SPEC)..." -npx wdio run test/wdio.conf.ts --spec "$SPEC" +exec "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/e2e-run-spec.sh" "test/e2e/specs/card-payment-flow.spec.ts" "card-payment" diff --git a/scripts/e2e-resolve-node-appium.sh b/scripts/e2e-resolve-node-appium.sh new file mode 100755 index 000000000..def5d110f --- /dev/null +++ b/scripts/e2e-resolve-node-appium.sh @@ -0,0 +1,38 @@ +#!/usr/bin/env bash +# Resolve Node 24+ and Appium for E2E scripts (local nvm or CI PATH). +# shellcheck disable=SC2034 +# Outputs: NODE24, APPIUM_BIN (export for callers) + +NODE24="$(command -v node 2>/dev/null || true)" +export NVM_DIR="${NVM_DIR:-$HOME/.nvm}" +if [ -s "$NVM_DIR/nvm.sh" ]; then + # shellcheck source=/dev/null + . "$NVM_DIR/nvm.sh" + NVM_NODE="$(nvm which 24 2>/dev/null || true)" + if [ -n "${NVM_NODE:-}" ] && [ -x "$NVM_NODE" ]; then + NODE24="$NVM_NODE" + fi +fi + +if [ -z "${NODE24:-}" ] || [ ! -x "$NODE24" ]; then + echo "ERROR: Node.js is required (Node 24+ for Appium v3)." >&2 + exit 1 +fi + +NODE_MAJOR="$("$NODE24" --version | sed 's/^v//' | cut -d. -f1)" +if [ "${NODE_MAJOR:-0}" -lt 24 ]; then + echo "ERROR: Node 24+ is required for Appium v3 (found $($NODE24 --version))." >&2 + exit 1 +fi + +APPIUM_BIN="$(command -v appium 2>/dev/null || true)" +if [ -z "${APPIUM_BIN:-}" ] || [ ! -x "$APPIUM_BIN" ]; then + APPIUM_BIN="$(dirname "$NODE24")/appium" +fi +if [ ! -x "$APPIUM_BIN" ]; then + echo "ERROR: appium not found. Install with: npm install -g appium" >&2 + exit 1 +fi + +export NODE24 +export APPIUM_BIN diff --git a/scripts/e2e-run-all-flows.sh b/scripts/e2e-run-all-flows.sh new file mode 100755 index 000000000..b9465ac87 --- /dev/null +++ b/scripts/e2e-run-all-flows.sh @@ -0,0 +1,26 @@ +#!/usr/bin/env bash +# +# Run all E2E WDIO specs sequentially (Appium restarted per spec). +# Requires a prior E2E app build: yarn test:e2e:build +# +set -euo pipefail + +ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +cd "$ROOT" + +run() { + "$ROOT/scripts/e2e-run-spec.sh" "$1" "$2" +} + +run "test/e2e/specs/login-flow.spec.ts" "login" +run "test/e2e/specs/auth-access-control.spec.ts" "auth" +run "test/e2e/specs/telegram-flow.spec.ts" "telegram" +run "test/e2e/specs/gmail-flow.spec.ts" "gmail" +run "test/e2e/specs/notion-flow.spec.ts" "notion" +run "test/e2e/specs/card-payment-flow.spec.ts" "card-payment" +run "test/e2e/specs/crypto-payment-flow.spec.ts" "crypto-payment" +run "test/e2e/specs/navigation.spec.ts" "navigation" +run "test/e2e/specs/smoke.spec.ts" "smoke" +run "test/e2e/specs/tauri-commands.spec.ts" "tauri-commands" + +echo "All E2E flows completed." diff --git a/scripts/e2e-run-spec.sh b/scripts/e2e-run-spec.sh new file mode 100755 index 000000000..04fb9b9e4 --- /dev/null +++ b/scripts/e2e-run-spec.sh @@ -0,0 +1,68 @@ +#!/usr/bin/env bash +# +# Run a single WebDriverIO E2E spec (Appium mac2 + mock server in spec). +# +# Usage: +# ./scripts/e2e-run-spec.sh test/e2e/specs/login-flow.spec.ts [log-suffix] +# +set -euo pipefail + +SPEC="${1:?spec path required}" +LOG_SUFFIX="${2:-$(basename "$SPEC" .spec.ts)}" + +APPIUM_PORT="${APPIUM_PORT:-4723}" +E2E_MOCK_PORT="${E2E_MOCK_PORT:-18473}" + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +# shellcheck source=/dev/null +source "$SCRIPT_DIR/e2e-resolve-node-appium.sh" + +export VITE_BACKEND_URL="http://127.0.0.1:${E2E_MOCK_PORT}" +export BACKEND_URL="http://127.0.0.1:${E2E_MOCK_PORT}" + +echo "Cleaning cached app data..." +rm -rf ~/Library/WebKit/com.openhuman.app +rm -rf ~/Library/Caches/com.openhuman.app +rm -rf "$HOME/Library/Application Support/com.openhuman.app" + +DIST_JS="$(ls dist/assets/index-*.js 2>/dev/null | head -1)" +if [ -z "$DIST_JS" ]; then + echo "ERROR: No frontend bundle found at dist/assets/index-*.js." >&2 + echo " Run 'yarn test:e2e:build' to build the app before running E2E tests." >&2 + exit 1 +fi +if ! grep -q "127.0.0.1:${E2E_MOCK_PORT}" "$DIST_JS"; then + echo "ERROR: frontend bundle does NOT contain mock server URL (127.0.0.1:${E2E_MOCK_PORT})." >&2 + echo " Run 'yarn test:e2e:build' to rebuild with the mock URL." >&2 + exit 1 +fi +echo "Verified: frontend bundle contains mock server URL." + +APPIUM_LOG="/tmp/appium-e2e-${LOG_SUFFIX}.log" +NODE_VER=$("$NODE24" --version) +echo "Starting Appium on port $APPIUM_PORT (Node $NODE_VER)..." +echo " Appium logs: $APPIUM_LOG" +"$APPIUM_BIN" --port "$APPIUM_PORT" --relaxed-security > "$APPIUM_LOG" 2>&1 & +APPIUM_PID=$! + +cleanup() { + echo "Stopping Appium (pid $APPIUM_PID)..." + kill "$APPIUM_PID" 2>/dev/null || true + wait "$APPIUM_PID" 2>/dev/null || true +} +trap cleanup EXIT + +for i in $(seq 1 30); do + if curl -sf "http://127.0.0.1:$APPIUM_PORT/status" >/dev/null 2>&1; then + echo "Appium is ready." + break + fi + if [ "$i" -eq 30 ]; then + echo "ERROR: Appium did not start within 30 seconds." >&2 + exit 1 + fi + sleep 1 +done + +echo "Running E2E spec ($SPEC)..." +npx wdio run test/wdio.conf.ts --spec "$SPEC" diff --git a/scripts/e2e-telegram.sh b/scripts/e2e-telegram.sh index 9c4aa2469..0b30bf356 100755 --- a/scripts/e2e-telegram.sh +++ b/scripts/e2e-telegram.sh @@ -1,90 +1,4 @@ #!/usr/bin/env bash -# -# Run E2E Telegram integration flow tests only. -# -# Starts Appium, cleans app caches, runs the telegram-flow spec, -# then tears everything down. Each flow script is self-contained so -# specs don't pollute each other's Redux Persist state. -# -# Usage: -# ./scripts/e2e-telegram.sh -# APPIUM_PORT=4723 ./scripts/e2e-telegram.sh -# +# Run E2E Telegram integration flow tests only. See scripts/e2e-run-spec.sh. set -euo pipefail - -APPIUM_PORT="${APPIUM_PORT:-4723}" -E2E_MOCK_PORT="${E2E_MOCK_PORT:-18473}" -SPEC="test/e2e/specs/telegram-flow.spec.ts" - -export VITE_BACKEND_URL="http://127.0.0.1:${E2E_MOCK_PORT}" -export BACKEND_URL="http://127.0.0.1:${E2E_MOCK_PORT}" - -# Clean cached app data for a fresh state — Redux Persist would otherwise -# remember the JWT from a previous run and skip the login flow. -echo "Cleaning cached app data..." -rm -rf ~/Library/WebKit/com.openhuman.app -rm -rf ~/Library/Caches/com.openhuman.app -rm -rf "$HOME/Library/Application Support/com.openhuman.app" - -# Verify the frontend dist has the mock server URL baked in. -DIST_JS="$(ls dist/assets/index-*.js 2>/dev/null | head -1)" -if [ -z "$DIST_JS" ]; then - echo "ERROR: No frontend bundle found at dist/assets/index-*.js." >&2 - echo " Run 'yarn test:e2e:build' to build the app before running E2E tests." >&2 - exit 1 -fi -if ! grep -q "127.0.0.1:${E2E_MOCK_PORT}" "$DIST_JS"; then - echo "ERROR: frontend bundle does NOT contain mock server URL (127.0.0.1:${E2E_MOCK_PORT})." >&2 - echo " Run 'yarn test:e2e:build' to rebuild with the mock URL." >&2 - exit 1 -fi -echo "Verified: frontend bundle contains mock server URL." - -# --- Resolve Node 24 via nvm --------------------------------------------------- -export NVM_DIR="${NVM_DIR:-$HOME/.nvm}" -# shellcheck source=/dev/null -[ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh" - -NODE24="$(nvm which 24 2>/dev/null || true)" -if [ -z "$NODE24" ] || [ ! -x "$NODE24" ]; then - echo "ERROR: Node 24 is required for appium v3. Install it with: nvm install 24" >&2 - exit 1 -fi - -APPIUM_BIN="$(dirname "$NODE24")/appium" -if [ ! -x "$APPIUM_BIN" ]; then - echo "ERROR: appium not found at $APPIUM_BIN. Install it with: nvm use 24 && npm i -g appium" >&2 - exit 1 -fi - -# --- Start Appium in the background ------------------------------------------- -APPIUM_LOG="/tmp/appium-e2e-telegram.log" -NODE_VER=$("$NODE24" --version) -echo "Starting Appium on port $APPIUM_PORT (Node $NODE_VER)..." -echo " Appium logs: $APPIUM_LOG" -"$NODE24" "$APPIUM_BIN" --port "$APPIUM_PORT" --relaxed-security > "$APPIUM_LOG" 2>&1 & -APPIUM_PID=$! - -cleanup() { - echo "Stopping Appium (pid $APPIUM_PID)..." - kill "$APPIUM_PID" 2>/dev/null || true - wait "$APPIUM_PID" 2>/dev/null || true -} -trap cleanup EXIT - -# Wait for Appium to be ready -for i in $(seq 1 30); do - if curl -sf "http://127.0.0.1:$APPIUM_PORT/status" >/dev/null 2>&1; then - echo "Appium is ready." - break - fi - if [ "$i" -eq 30 ]; then - echo "ERROR: Appium did not start within 30 seconds." >&2 - exit 1 - fi - sleep 1 -done - -# --- Run WebDriverIO ---------------------------------------------------------- -echo "Running E2E Telegram integration flow tests ($SPEC)..." -npx wdio run test/wdio.conf.ts --spec "$SPEC" +exec "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/e2e-run-spec.sh" "test/e2e/specs/telegram-flow.spec.ts" "telegram" diff --git a/skills b/skills index 4ef9e9b36..e856b8e75 160000 --- a/skills +++ b/skills @@ -1 +1 @@ -Subproject commit 4ef9e9b3688e483ab3e088525063e71eb9cb24d5 +Subproject commit e856b8e756ad7ea86f36dbd1697542e76a8d3571 diff --git a/src-tauri/src/core_process.rs b/src-tauri/src/core_process.rs index f2851f5ea..997586e25 100644 --- a/src-tauri/src/core_process.rs +++ b/src-tauri/src/core_process.rs @@ -117,16 +117,25 @@ pub fn default_core_bin() -> Option { let exe_dir = exe.parent()?; #[cfg(windows)] - let standalone = exe_dir.join("openhuman-core.exe"); + let standalone = exe_dir.join("openhuman.exe"); #[cfg(not(windows))] - let standalone = exe_dir.join("openhuman-core"); + let standalone = exe_dir.join("openhuman"); if standalone.exists() { return Some(standalone); } - // Sidecar layout: bundle.externalBin("binaries/openhuman-core") is emitted as - // openhuman-core-(.exe) under app resources. + #[cfg(windows)] + let legacy_standalone = exe_dir.join("openhuman-core.exe"); + #[cfg(not(windows))] + let legacy_standalone = exe_dir.join("openhuman-core"); + + if legacy_standalone.exists() { + return Some(legacy_standalone); + } + + // Sidecar layout: bundle.externalBin("binaries/openhuman") is emitted as + // openhuman-(.exe) under app resources. let mut search_dirs = vec![exe_dir.to_path_buf()]; #[cfg(target_os = "macos")] { @@ -149,9 +158,11 @@ pub fn default_core_bin() -> Option { }; #[cfg(windows)] - let matches = file_name.starts_with("openhuman-core-") && file_name.ends_with(".exe"); + let matches = (file_name.starts_with("openhuman-") && file_name.ends_with(".exe")) + || (file_name.starts_with("openhuman-core-") && file_name.ends_with(".exe")); #[cfg(not(windows))] - let matches = file_name.starts_with("openhuman-core-"); + let matches = + file_name.starts_with("openhuman-") || file_name.starts_with("openhuman-core-"); if matches { return Some(path); diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index 395288640..112ab137c 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -42,7 +42,7 @@ "../rust-core/ai" ], "externalBin": [ - "binaries/openhuman-core" + "binaries/openhuman" ], "createUpdaterArtifacts": true, "macOS": { diff --git a/index.html b/src/index.html similarity index 84% rename from index.html rename to src/index.html index 5905037e2..01a066746 100644 --- a/index.html +++ b/src/index.html @@ -9,6 +9,6 @@
- + diff --git a/src/polyfills.ts b/src/polyfills.ts index baa4b6ce3..5c349f69e 100644 --- a/src/polyfills.ts +++ b/src/polyfills.ts @@ -1,5 +1,5 @@ /* eslint-disable @typescript-eslint/no-explicit-any -- intentional global polyfill assignments */ -// Polyfill Node.js globals for browser (required by telegram package) +// Polyfill Node.js globals for browser dependencies // This must be imported FIRST before any other imports that use Node.js APIs import * as util from 'util'; import { Buffer } from 'buffer'; diff --git a/src/test/setup.ts b/src/test/setup.ts index 92ce06c22..7ab99ffcb 100644 --- a/src/test/setup.ts +++ b/src/test/setup.ts @@ -30,8 +30,6 @@ vi.mock('@tauri-apps/plugin-opener', () => ({ open: vi.fn() })); vi.mock('@tauri-apps/plugin-os', () => ({ platform: vi.fn().mockResolvedValue('macos') })); -vi.mock('@tauri-apps/plugin-shell', () => ({ Command: vi.fn(), open: vi.fn() })); - // Mock tauriCommands to prevent Tauri API calls in tests vi.mock('../utils/tauriCommands', () => ({ isTauri: () => false, diff --git a/tailwind.config.js b/tailwind.config.js index da0ac6a8b..687d08133 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -2,7 +2,7 @@ module.exports = { darkMode: 'class', content: [ - "./index.html", + "./src/index.html", "./src/**/*.{js,ts,jsx,tsx}", ], theme: { diff --git a/vite.config.ts b/vite.config.ts index 791c89bc3..6ac4bdb10 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -7,6 +7,12 @@ const host = process.env.TAURI_DEV_HOST; // https://vite.dev/config/ export default defineConfig(async () => ({ + root: "src", + publicDir: "../public", + build: { + outDir: "../dist", + emptyOutDir: true, + }, plugins: [ nodePolyfills({ include: ["buffer", "process", "util", "os", "crypto", "stream"], @@ -52,6 +58,6 @@ export default defineConfig(async () => ({ }, }, optimizeDeps: { - include: ["buffer", "process", "util", "os-browserify", "telegram"], + include: ["buffer", "process", "util", "os-browserify"], }, })); diff --git a/yarn.lock b/yarn.lock index 291af035e..1825ba4d7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -281,11 +281,6 @@ resolved "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-1.0.2.tgz" integrity sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA== -"@cryptography/aes@^0.1.1": - version "0.1.1" - resolved "https://registry.npmjs.org/@cryptography/aes/-/aes-0.1.1.tgz" - integrity sha512-PcYz4FDGblO6tM2kSC+VzhhK62vml6k6/YAkiWtyPvrgJVfnDRoHGDtKn5UiaRRUrvUTTocBpvc2rRgTCqxjsg== - "@csstools/color-helpers@^6.0.1": version "6.0.1" resolved "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-6.0.1.tgz" @@ -1402,13 +1397,6 @@ dependencies: "@tauri-apps/api" "^2.8.0" -"@tauri-apps/plugin-shell@~2": - version "2.3.4" - resolved "https://registry.npmjs.org/@tauri-apps/plugin-shell/-/plugin-shell-2.3.4.tgz" - integrity sha512-ktsRWf8wHLD17aZEyqE8c5x98eNAuTizR1FSX475zQ4TxaiJnhwksLygQz+AGwckJL5bfEP13nWrlTNQJUpKpA== - dependencies: - "@tauri-apps/api" "^2.8.0" - "@testing-library/dom@^10.4.1": version "10.4.1" resolved "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.1.tgz" @@ -1442,11 +1430,6 @@ dependencies: "@babel/runtime" "^7.12.5" -"@testing-library/user-event@^14.6.1": - version "14.6.1" - resolved "https://registry.npmjs.org/@testing-library/user-event/-/user-event-14.6.1.tgz" - integrity sha512-vq7fv0rnt+QTXgPxr5Hjc210p6YKq2kmdziLgnsZGgLJ9e6VAShx1pACLuRjd/AS/sr7phAR58OIIpf0LlmQNw== - "@tootallnate/quickjs-emscripten@^0.23.0": version "0.23.0" resolved "https://registry.npmjs.org/@tootallnate/quickjs-emscripten/-/quickjs-emscripten-0.23.0.tgz" @@ -1548,11 +1531,6 @@ dependencies: "@types/unist" "*" -"@types/history@^4.7.11": - version "4.7.11" - resolved "https://registry.npmjs.org/@types/history/-/history-4.7.11.tgz" - integrity sha512-qjDJRrmvBMiTx+jyLxvLfJU7UznFuokDv4f3WRuriHKERccVpFU+8XMQUAbDzoiJCsmexxRExQeMwwCdamSKDA== - "@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.6": version "2.0.6" resolved "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz" @@ -1630,24 +1608,7 @@ resolved "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz" integrity sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ== -"@types/react-router-dom@^5.3.3": - version "5.3.3" - resolved "https://registry.npmjs.org/@types/react-router-dom/-/react-router-dom-5.3.3.tgz" - integrity sha512-kpqnYK4wcdm5UaWI3fLcELopqLrHgLqNsdpHauzlQktfkHL3npOSwtj1Uz9oKBAzs7lFtVkV8j83voAz2D8fhw== - dependencies: - "@types/history" "^4.7.11" - "@types/react" "*" - "@types/react-router" "*" - -"@types/react-router@*": - version "5.1.20" - resolved "https://registry.npmjs.org/@types/react-router/-/react-router-5.1.20.tgz" - integrity sha512-jGjmu/ZqS7FjSH6owMcD5qpq19+1RS9DeVRqfl1FeBMxTDQAGwlMWOcs52NDoXaNKyG3d1cYQFMs9rCrb88o9Q== - dependencies: - "@types/history" "^4.7.11" - "@types/react" "*" - -"@types/react@*", "@types/react@^19.1.8": +"@types/react@^19.1.8": version "19.2.9" resolved "https://registry.npmjs.org/@types/react/-/react-19.2.9.tgz" integrity sha512-Lpo8kgb/igvMIPeNV2rsYKTgaORYdO1XGVZ4Qz3akwOj0ySGYMPlQWa8BaLn0G63D1aSaAQ5ldR06wCpChQCjA== @@ -2424,13 +2385,6 @@ async-function@^1.0.0: resolved "https://registry.npmjs.org/async-function/-/async-function-1.0.0.tgz" integrity sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA== -async-mutex@^0.3.0: - version "0.3.2" - resolved "https://registry.npmjs.org/async-mutex/-/async-mutex-0.3.2.tgz" - integrity sha512-HuTK7E7MT7jZEh1P9GtRW9+aTWiDWWi9InbZ5hjxrnRa39KS4BW04+xLBhYNS2aXhHUIKZSw3gj4Pn1pj+qGAA== - dependencies: - tslib "^2.3.1" - async@^3.2.4, async@^3.2.6: version "3.2.6" resolved "https://registry.npmjs.org/async/-/async-3.2.6.tgz" @@ -2533,11 +2487,6 @@ bidi-js@^1.0.3: dependencies: require-from-string "^2.0.2" -big-integer@^1.6.48: - version "1.6.52" - resolved "https://registry.npmjs.org/big-integer/-/big-integer-1.6.52.tgz" - integrity sha512-QxD8cf2eVqJOOz63z6JIN9BzvVs/dlySa5HGSBH5xtR8dPteIRQnBxxKqkNTiT6jbDTF6jAfrd4oMcND9RGbQg== - binary-extensions@^2.0.0: version "2.3.0" resolved "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz" @@ -2701,13 +2650,6 @@ buffer@^6.0.3: base64-js "^1.3.1" ieee754 "^1.2.1" -bufferutil@^4.0.1, bufferutil@^4.0.3: - version "4.1.0" - resolved "https://registry.npmjs.org/bufferutil/-/bufferutil-4.1.0.tgz" - integrity sha512-ZMANVnAixE6AWWnPzlW2KpUrxhm9woycYvPOo67jWHyFowASTEd9s+QN1EIMsSDtwhIxN4sWE1jotpuDUIgyIw== - dependencies: - node-gyp-build "^4.3.0" - builtin-status-codes@^3.0.0: version "3.0.0" resolved "https://registry.npmjs.org/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz" @@ -3132,14 +3074,6 @@ csstype@^3.2.2: resolved "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz" integrity sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ== -d@1, d@^1.0.1, d@^1.0.2: - version "1.0.2" - resolved "https://registry.npmjs.org/d/-/d-1.0.2.tgz" - integrity sha512-MOqHvMWF9/9MX6nza0KgvFH4HpMU0EF5uUDXqX/BtxtU8NfB0QzRtJ8Oe/6SuS4kbhyzVJwjd97EA4PKrzJ8bw== - dependencies: - es5-ext "^0.10.64" - type "^2.7.2" - data-uri-to-buffer@^6.0.2: version "6.0.2" resolved "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-6.0.2.tgz" @@ -3187,13 +3121,6 @@ debug@4, debug@^4.0.0, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, d dependencies: ms "^2.1.3" -debug@^2.2.0: - version "2.6.9" - resolved "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz" - integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== - dependencies: - ms "2.0.0" - debug@^3.2.7: version "3.2.7" resolved "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz" @@ -3343,15 +3270,6 @@ dom-accessibility-api@^0.6.3: resolved "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.6.3.tgz" integrity sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w== -dom-serializer@^1.0.1: - version "1.4.1" - resolved "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz" - integrity sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag== - dependencies: - domelementtype "^2.0.1" - domhandler "^4.2.0" - entities "^2.0.0" - dom-serializer@^2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz" @@ -3366,18 +3284,11 @@ domain-browser@4.22.0: resolved "https://registry.npmjs.org/domain-browser/-/domain-browser-4.22.0.tgz" integrity sha512-IGBwjF7tNk3cwypFNH/7bfzBcgSCbaMOD3GsaY1AU/JRrnHnYgEM0+9kQt52iZxjNsjBtJYtao146V+f8jFZNw== -domelementtype@^2.0.1, domelementtype@^2.2.0, domelementtype@^2.3.0: +domelementtype@^2.3.0: version "2.3.0" resolved "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz" integrity sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw== -domhandler@^4.0.0, domhandler@^4.2.0: - version "4.3.1" - resolved "https://registry.npmjs.org/domhandler/-/domhandler-4.3.1.tgz" - integrity sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ== - dependencies: - domelementtype "^2.2.0" - domhandler@^5.0.2, domhandler@^5.0.3: version "5.0.3" resolved "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz" @@ -3385,15 +3296,6 @@ domhandler@^5.0.2, domhandler@^5.0.3: dependencies: domelementtype "^2.3.0" -domutils@^2.5.2: - version "2.8.0" - resolved "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz" - integrity sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A== - dependencies: - dom-serializer "^1.0.1" - domelementtype "^2.2.0" - domhandler "^4.2.0" - domutils@^3.0.1, domutils@^3.2.2: version "3.2.2" resolved "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz" @@ -3519,11 +3421,6 @@ engine.io-parser@~5.2.1: resolved "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.3.tgz" integrity sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q== -entities@^2.0.0: - version "2.2.0" - resolved "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz" - integrity sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A== - entities@^4.2.0: version "4.5.0" resolved "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz" @@ -3676,33 +3573,6 @@ es-to-primitive@^1.3.0: is-date-object "^1.0.5" is-symbol "^1.0.4" -es5-ext@^0.10.35, es5-ext@^0.10.62, es5-ext@^0.10.63, es5-ext@^0.10.64, es5-ext@~0.10.14: - version "0.10.64" - resolved "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.64.tgz" - integrity sha512-p2snDhiLaXe6dahss1LddxqEm+SkuDvV8dnIQG0MWjyHpcMNfXKPE+/Cc0y+PhxJX3A4xGNeFCj5oc0BUh6deg== - dependencies: - es6-iterator "^2.0.3" - es6-symbol "^3.1.3" - esniff "^2.0.1" - next-tick "^1.1.0" - -es6-iterator@^2.0.3: - version "2.0.3" - resolved "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz" - integrity sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g== - dependencies: - d "1" - es5-ext "^0.10.35" - es6-symbol "^3.1.1" - -es6-symbol@^3.1.1, es6-symbol@^3.1.3: - version "3.1.4" - resolved "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.4.tgz" - integrity sha512-U9bFFjX8tFiATgtkJ1zg25+KviIXpgRvRHS8sau3GfhVzThRQrOeksPeT0BWW2MNZs1OEWJ1DPXOQMn0KKRkvg== - dependencies: - d "^1.0.2" - ext "^1.7.0" - esbuild@^0.27.0: version "0.27.2" resolved "https://registry.npmjs.org/esbuild/-/esbuild-0.27.2.tgz" @@ -3932,16 +3802,6 @@ eslint@^9.39.2: natural-compare "^1.4.0" optionator "^0.9.3" -esniff@^2.0.1: - version "2.0.1" - resolved "https://registry.npmjs.org/esniff/-/esniff-2.0.1.tgz" - integrity sha512-kTUIGKQ/mDPFoJ0oVfcmyJn4iBDRptjNVIzwIFR7tqWXdVI9xfA2RMwY/gbSpJG3lkdWNEjLap/NqVHZiJsdfg== - dependencies: - d "^1.0.1" - es5-ext "^0.10.62" - event-emitter "^0.3.5" - type "^2.7.2" - espree@^10.0.1, espree@^10.4.0: version "10.4.0" resolved "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz" @@ -3997,14 +3857,6 @@ esutils@^2.0.2: resolved "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz" integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== -event-emitter@^0.3.5: - version "0.3.5" - resolved "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz" - integrity sha512-D9rRn9y7kLPnJ+hMq7S/nhvoKwwvVJahBi2BPmx3bvbsEdK3W9ii8cBSGjP+72/LnM4n6fo3+dkCX5FeTQruXA== - dependencies: - d "1" - es5-ext "~0.10.14" - event-target-shim@^5.0.0: version "5.0.1" resolved "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz" @@ -4080,13 +3932,6 @@ expect@^30.2.0: jest-mock "30.2.0" jest-util "30.2.0" -ext@^1.7.0: - version "1.7.0" - resolved "https://registry.npmjs.org/ext/-/ext-1.7.0.tgz" - integrity sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw== - dependencies: - type "^2.7.2" - extend@^3.0.0: version "3.0.2" resolved "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz" @@ -4435,7 +4280,7 @@ gopd@^1.0.1, gopd@^1.2.0: resolved "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz" integrity sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg== -graceful-fs@^4.1.11, graceful-fs@^4.2.0, graceful-fs@^4.2.11: +graceful-fs@^4.2.0, graceful-fs@^4.2.11: version "4.2.11" resolved "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz" integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== @@ -4624,16 +4469,6 @@ htmlparser2@^10.1.0: domutils "^3.2.2" entities "^7.0.1" -htmlparser2@^6.1.0: - version "6.1.0" - resolved "https://registry.npmjs.org/htmlparser2/-/htmlparser2-6.1.0.tgz" - integrity sha512-gyyPk6rgonLFEDGoeRgQNaEUvdJ4ktTmmUh/h2t7s+M8oPpIPxgNACWa+6ESR57kXstwqPiCut0V8NRpcwgU7A== - dependencies: - domelementtype "^2.0.1" - domhandler "^4.0.0" - domutils "^2.5.2" - entities "^2.0.0" - http-proxy-agent@^7.0.0, http-proxy-agent@^7.0.1, http-proxy-agent@^7.0.2: version "7.0.2" resolved "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz" @@ -4699,7 +4534,7 @@ immediate@~3.0.5: resolved "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz" integrity sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ== -immer@^11.0.0, immer@^11.1.3: +immer@^11.0.0: version "11.1.3" resolved "https://registry.npmjs.org/immer/-/immer-11.1.3.tgz" integrity sha512-6jQTc5z0KJFtr1UgFpIL3N9XSC3saRaI9PwWtzM2pSqkNGtiNkYY2OSwkOGDK2XcTRcLb1pi/aNkKZz0nxVH4Q== @@ -5021,11 +4856,6 @@ is-typed-array@^1.1.13, is-typed-array@^1.1.14, is-typed-array@^1.1.15, is-typed dependencies: which-typed-array "^1.1.16" -is-typedarray@^1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz" - integrity sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA== - is-unicode-supported@^0.1.0: version "0.1.0" resolved "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz" @@ -5852,11 +5682,6 @@ miller-rabin@^4.0.0: bn.js "^4.0.0" brorand "^1.0.1" -mime@^3.0.0: - version "3.0.0" - resolved "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz" - integrity sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A== - min-indent@^1.0.0: version "1.0.1" resolved "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz" @@ -5944,11 +5769,6 @@ modern-tar@^0.7.2: resolved "https://registry.npmjs.org/modern-tar/-/modern-tar-0.7.3.tgz" integrity sha512-4W79zekKGyYU4JXVmB78DOscMFaJth2gGhgfTl2alWE4rNe3nf4N2pqenQ0rEtIewrnD79M687Ouba3YGTLOvg== -ms@2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz" - integrity sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A== - ms@^2.1.1, ms@^2.1.3: version "2.1.3" resolved "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz" @@ -6007,23 +5827,6 @@ netmask@^2.0.2: resolved "https://registry.npmjs.org/netmask/-/netmask-2.0.2.tgz" integrity sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg== -next-tick@^1.1.0: - version "1.1.0" - resolved "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz" - integrity sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ== - -node-gyp-build@^4.3.0: - version "4.8.4" - resolved "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.4.tgz" - integrity sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ== - -node-localstorage@^2.2.1: - version "2.2.1" - resolved "https://registry.npmjs.org/node-localstorage/-/node-localstorage-2.2.1.tgz" - integrity sha512-vv8fJuOUCCvSPjDjBLlMqYMHob4aGjkmrkaE42/mZr0VT+ZAU10jRF8oTnX9+pgU9/vYJ8P7YT3Vd6ajkmzSCw== - dependencies: - write-file-atomic "^1.1.4" - node-releases@^2.0.27: version "2.0.27" resolved "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz" @@ -6277,11 +6080,6 @@ package-json-from-dist@^1.0.0: resolved "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz" integrity sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw== -pako@^2.0.3: - version "2.1.0" - resolved "https://registry.npmjs.org/pako/-/pako-2.1.0.tgz" - integrity sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug== - pako@~1.0.2, pako@~1.0.5: version "1.0.11" resolved "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz" @@ -6655,11 +6453,6 @@ punycode@^2.1.0, punycode@^2.3.1: resolved "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz" integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg== -qrcode.react@^4.2.0: - version "4.2.0" - resolved "https://registry.npmjs.org/qrcode.react/-/qrcode.react-4.2.0.tgz" - integrity sha512-QpgqWi8rD9DsS9EP3z7BT+5lY5SFhsqGjpgW5DY/i3mK4M9DTBNz3ErMi8BWYEfI3L0d8GIbGmcdFAS1uIRGjA== - qs@^6.12.3: version "6.14.1" resolved "https://registry.npmjs.org/qs/-/qs-6.14.1.tgz" @@ -6847,11 +6640,6 @@ readdirp@~3.6.0: dependencies: picomatch "^2.2.1" -real-cancellable-promise@^1.1.1: - version "1.2.3" - resolved "https://registry.npmjs.org/real-cancellable-promise/-/real-cancellable-promise-1.2.3.tgz" - integrity sha512-hBI5Gy/55VEeeMtImMgEirD7eq5UmqJf1J8dFZtbJZA/3rB0pYFZ7PayMGueb6v4UtUtpKpP+05L0VwyE1hI9Q== - recursive-readdir@^2.2.3: version "2.2.3" resolved "https://registry.npmjs.org/recursive-readdir/-/recursive-readdir-2.2.3.tgz" @@ -7280,11 +7068,6 @@ slash@^3.0.0: resolved "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz" integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== -slide@^1.1.5: - version "1.1.6" - resolved "https://registry.npmjs.org/slide/-/slide-1.1.6.tgz" - integrity sha512-NwrtjCg+lZoqhFU8fOwl4ay2ei8PaqCBOUV3/ektPY9trO1yQ1oXEfmHAhKArUVUr/hOHvy5f6AdP17dCM0zMw== - smart-buffer@^4.2.0: version "4.2.0" resolved "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz" @@ -7317,7 +7100,7 @@ socks-proxy-agent@^8.0.5: debug "^4.3.4" socks "^2.8.3" -socks@^2.6.2, socks@^2.8.3: +socks@^2.8.3: version "2.8.7" resolved "https://registry.npmjs.org/socks/-/socks-2.8.7.tgz" integrity sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A== @@ -7406,11 +7189,6 @@ stop-iteration-iterator@^1.1.0: es-errors "^1.3.0" internal-slot "^1.1.0" -store2@^2.13.0: - version "2.14.4" - resolved "https://registry.npmjs.org/store2/-/store2-2.14.4.tgz" - integrity sha512-srTItn1GOvyvOycgxjAnPA63FZNwy0PTyUBFMHRM+hVFltAeoh0LmNBz9SZqUS9mMqGk8rfyWyXn3GH5ReJ8Zw== - stream-browserify@^3.0.0: version "3.0.0" resolved "https://registry.npmjs.org/stream-browserify/-/stream-browserify-3.0.0.tgz" @@ -7708,29 +7486,6 @@ tar-stream@^3.0.0, tar-stream@^3.1.5: fast-fifo "^1.2.0" streamx "^2.15.0" -telegram@^2.26.22: - version "2.26.22" - resolved "https://registry.npmjs.org/telegram/-/telegram-2.26.22.tgz" - integrity sha512-EIj7Yrjiu0Yosa3FZ/7EyPg9s6UiTi/zDQrFmR/2Mg7pIUU+XjAit1n1u9OU9h2oRnRM5M+67/fxzQluZpaJJg== - dependencies: - "@cryptography/aes" "^0.1.1" - async-mutex "^0.3.0" - big-integer "^1.6.48" - buffer "^6.0.3" - htmlparser2 "^6.1.0" - mime "^3.0.0" - node-localstorage "^2.2.1" - pako "^2.0.3" - path-browserify "^1.0.1" - real-cancellable-promise "^1.1.1" - socks "^2.6.2" - store2 "^2.13.0" - ts-custom-error "^3.2.0" - websocket "^1.0.34" - optionalDependencies: - bufferutil "^4.0.3" - utf-8-validate "^5.0.5" - text-decoder@^1.1.0: version "1.2.3" resolved "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.3.tgz" @@ -7854,11 +7609,6 @@ ts-api-utils@^2.4.0: resolved "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.4.0.tgz" integrity sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA== -ts-custom-error@^3.2.0: - version "3.3.1" - resolved "https://registry.npmjs.org/ts-custom-error/-/ts-custom-error-3.3.1.tgz" - integrity sha512-5OX1tzOjxWEgsr/YEUWSuPrQ00deKLh6D7OTWcvNHm12/7QPyRh8SYpyWvA4IZv8H/+GQWQEh/kwo95Q9OVW1A== - ts-interface-checker@^0.1.9: version "0.1.13" resolved "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz" @@ -7874,7 +7624,7 @@ tsconfig-paths@^3.15.0: minimist "^1.2.6" strip-bom "^3.0.0" -tslib@^2.0.1, tslib@^2.1.0, tslib@^2.3.1: +tslib@^2.0.1, tslib@^2.1.0: version "2.8.1" resolved "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz" integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w== @@ -7923,11 +7673,6 @@ type-fest@^5.2.0: dependencies: tagged-tag "^1.0.0" -type@^2.7.2: - version "2.7.3" - resolved "https://registry.npmjs.org/type/-/type-2.7.3.tgz" - integrity sha512-8j+1QmAbPvLZow5Qpi6NCaN8FB60p/6x8/vfNqOk/hC+HuvFZhL4+WfekuhQLiqFZXOgQdrs3B+XxEmCc6b3FQ== - typed-array-buffer@^1.0.3: version "1.0.3" resolved "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz" @@ -7973,13 +7718,6 @@ typed-array-length@^1.0.7: possible-typed-array-names "^1.0.0" reflect.getprototypeof "^1.0.6" -typedarray-to-buffer@^3.1.5: - version "3.1.5" - resolved "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz" - integrity sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q== - dependencies: - is-typedarray "^1.0.0" - typescript@~5.8.3: version "5.8.3" resolved "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz" @@ -8114,13 +7852,6 @@ userhome@1.0.1: resolved "https://registry.npmjs.org/userhome/-/userhome-1.0.1.tgz" integrity sha512-5cnLm4gseXjAclKowC4IjByaGsjtAoV6PrOQOljplNB54ReUYJP8HdAFq2muHinSDAh09PPX/uXDPfdxRHvuSA== -utf-8-validate@^5.0.2, utf-8-validate@^5.0.5: - version "5.0.10" - resolved "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-5.0.10.tgz" - integrity sha512-Z6czzLq4u8fPOyx7TU6X3dvUZVvoJmxSQ+IcrlmagKhilxlhZgxPK6C5Jqbkw1IDUmFTM+cz9QDnnLTwDz/2gQ== - dependencies: - node-gyp-build "^4.3.0" - util-deprecate@^1.0.1, util-deprecate@^1.0.2, util-deprecate@~1.0.1: version "1.0.2" resolved "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz" @@ -8290,18 +8021,6 @@ webidl-conversions@^8.0.1: resolved "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-8.0.1.tgz" integrity sha512-BMhLD/Sw+GbJC21C/UgyaZX41nPt8bUTg+jWyDeg7e7YN4xOM05YPSIXceACnXVtqyEw/LMClUQMtMZ+PGGpqQ== -websocket@^1.0.34: - version "1.0.35" - resolved "https://registry.npmjs.org/websocket/-/websocket-1.0.35.tgz" - integrity sha512-/REy6amwPZl44DDzvRCkaI1q1bIiQB0mEFQLUrhz3z2EK91cp3n72rAjUlrTP0zV22HJIUOVHQGPxhFRjxjt+Q== - dependencies: - bufferutil "^4.0.1" - debug "^2.2.0" - es5-ext "^0.10.63" - typedarray-to-buffer "^3.1.5" - utf-8-validate "^5.0.2" - yaeti "^0.0.6" - whatwg-encoding@^3.1.1: version "3.1.1" resolved "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz" @@ -8454,15 +8173,6 @@ wrappy@1: resolved "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz" integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== -write-file-atomic@^1.1.4: - version "1.3.4" - resolved "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-1.3.4.tgz" - integrity sha512-SdrHoC/yVBPpV0Xq/mUZQIpW2sWXAShb/V4pomcJXh92RuaO+f3UTWItiR3Px+pLnV2PvC2/bfn5cwr5X6Vfxw== - dependencies: - graceful-fs "^4.1.11" - imurmurhash "^0.1.4" - slide "^1.1.5" - ws@^8.8.0: version "8.19.0" resolved "https://registry.npmjs.org/ws/-/ws-8.19.0.tgz" @@ -8503,11 +8213,6 @@ y18n@^5.0.5: resolved "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz" integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== -yaeti@^0.0.6: - version "0.0.6" - resolved "https://registry.npmjs.org/yaeti/-/yaeti-0.0.6.tgz" - integrity sha512-MvQa//+KcZCUkBTIC9blM+CU9J2GzuTytsOUwf2lidtvkx/6gnEp1QvJv34t9vdjhFmha/mUiNDbN0D0mJWdug== - yallist@^3.0.2: version "3.1.1" resolved "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz" @@ -8606,11 +8311,6 @@ zip-stream@^6.0.1: resolved "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz" integrity sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg== -zustand@^5.0.10: - version "5.0.10" - resolved "https://registry.npmjs.org/zustand/-/zustand-5.0.10.tgz" - integrity sha512-U1AiltS1O9hSy3rul+Ub82ut2fqIAefiSuwECWt6jlMVUGejvf+5omLcRBSzqbRagSM3hQZbtzdeRc6QVScXTg== - zwitch@^2.0.0: version "2.0.4" resolved "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz"