diff --git a/.github/actions/run-browserstack-maestro/action.yml b/.github/actions/run-browserstack-maestro/action.yml new file mode 100644 index 0000000..b317c2c --- /dev/null +++ b/.github/actions/run-browserstack-maestro/action.yml @@ -0,0 +1,130 @@ +name: Run BrowserStack Maestro Tests + +description: Triggers a BrowserStack Maestro build and polls until completion + +inputs: + platform: + description: Platform to run tests on (android or ios) + required: true + app_url: + description: BrowserStack app URL (bs://...) + required: true + test_suite_url: + description: BrowserStack test suite URL (bs://...) + required: true + devices: + description: JSON array of device strings + required: true + browserstack_project: + description: BrowserStack project + required: true + browserstack_username: + description: BrowserStack username + required: true + browserstack_access_key: + description: BrowserStack access key + required: true + timeout: + description: Test run timeout in seconds + required: false + default: "600" + +runs: + using: composite + steps: + - name: Validate inputs + env: + PLATFORM: ${{ inputs.platform }} + TIMEOUT: ${{ fromJson(inputs.timeout) }} + shell: bash + run: | + if [[ "$PLATFORM" != "android" && "$PLATFORM" != "ios" ]]; then + echo "platform must be android or ios" + exit 1 + fi + + if [[ "$TIMEOUT" -lt 30 ]]; then + echo "timeout must be at least 30 seconds" + exit 1 + fi + + - name: Trigger build + id: trigger + env: + APP_URL: ${{ inputs.app_url }} + TEST_SUITE_URL: ${{ inputs.test_suite_url }} + DEVICES: ${{ inputs.devices }} + BROWSERSTACK_PROJECT: ${{ inputs.browserstack_project }} + BROWSERSTACK_USERNAME: ${{ inputs.browserstack_username }} + BROWSERSTACK_ACCESS_KEY: ${{ inputs.browserstack_access_key }} + PLATFORM: ${{ inputs.platform }} + shell: bash + run: | + PAYLOAD=$(jq -n \ + --arg app "$APP_URL" \ + --arg suite "$TEST_SUITE_URL" \ + --argjson devices "$DEVICES" \ + --arg project "$BROWSERSTACK_PROJECT" \ + '{ + app: $app, + testSuite: $suite, + devices: $devices, + project: $project, + deviceLogs: true, + maestroVersion: "latest" + }' + ) + + BUILD_ID=$(curl --fail --show-error -s -u "$BROWSERSTACK_USERNAME:$BROWSERSTACK_ACCESS_KEY" \ + -X POST "https://api-cloud.browserstack.com/app-automate/maestro/v2/$PLATFORM/build" \ + -H "Content-Type: application/json" \ + -d "$PAYLOAD" \ + | jq -r '.build_id') + + echo "Triggered build: $BUILD_ID" + echo "build_id=$BUILD_ID" >> $GITHUB_OUTPUT + + - name: Poll for result + env: + BUILD_ID: ${{ steps.trigger.outputs.build_id }} + BROWSERSTACK_USERNAME: ${{ inputs.browserstack_username }} + BROWSERSTACK_ACCESS_KEY: ${{ inputs.browserstack_access_key }} + TIMEOUT: ${{ fromJson(inputs.timeout) }} + shell: bash + run: | + MAX_WAIT=$TIMEOUT + POLL_INTERVAL=30 + ELAPSED=0 + + while [ "$ELAPSED" -lt "$MAX_WAIT" ]; do + RESPONSE=$(curl --fail --show-error -s -u "$BROWSERSTACK_USERNAME:$BROWSERSTACK_ACCESS_KEY" \ + "https://api-cloud.browserstack.com/app-automate/maestro/v2/builds/$BUILD_ID") + + STATUS=$(echo "$RESPONSE" | jq -r '.status') + echo "[${ELAPSED}s] Build $BUILD_ID status: $STATUS" + + case "$STATUS" in + passed|completed) echo "Tests passed"; exit 0 ;; + failed) echo "Tests failed"; exit 1 ;; + esac + + sleep "$POLL_INTERVAL" + ELAPSED=$((ELAPSED + POLL_INTERVAL)) + done + + echo "Timed out after ${MAX_WAIT}s waiting for build $BUILD_ID" + exit 1 + + - name: Stop BrowserStack build on cancel + if: cancelled() && steps.trigger.outputs.build_id != '' + env: + BUILD_ID: ${{ steps.trigger.outputs.build_id }} + BROWSERSTACK_USERNAME: ${{ inputs.browserstack_username }} + BROWSERSTACK_ACCESS_KEY: ${{ inputs.browserstack_access_key }} + shell: bash + run: | + [[ "$BUILD_ID" == "null" ]] && exit 0 + echo "Stopping BrowserStack build $BUILD_ID" + curl --show-error -s -u "$BROWSERSTACK_USERNAME:$BROWSERSTACK_ACCESS_KEY" \ + -X POST "https://api-cloud.browserstack.com/app-automate/maestro/builds/$BUILD_ID/stop" \ + -H "Content-Type: application/json" || true diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml new file mode 100644 index 0000000..f49335f --- /dev/null +++ b/.github/workflows/e2e-tests.yml @@ -0,0 +1,271 @@ +name: E2E Tests + +on: + push: + branches: [main] + pull_request: + branches: [main] + types: [opened, synchronize, reopened, ready_for_review] + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +env: + NODEJS_MOBILE_VERSION: v18.20.4 + +jobs: + build-android: + if: ${{ github.event.pull_request.draft == false }} + name: Build (Android) + runs-on: ubuntu-latest + timeout-minutes: 30 + permissions: + contents: read + outputs: + app_url: ${{ steps.upload.outputs.app_url }} + steps: + - uses: actions/checkout@v4 + with: + persist-credentials: false + + - uses: actions/setup-node@v6 + with: + node-version-file: package.json + + - uses: actions/setup-java@v4 + with: + distribution: temurin + java-version: 17 + + - name: Setup Android SDK + uses: android-actions/setup-android@v3 + + - name: Cache nodejs-mobile binaries + id: cache-libnode + uses: actions/cache@v4 + with: + path: android/libnode + # hashFiles(...) folds the BASE_URL (defined inside the + # download script) into the cache key, so any swap of the + # source repo automatically invalidates stale caches. + key: nodejs-mobile-${{ env.NODEJS_MOBILE_VERSION }}-android-${{ hashFiles('scripts/download-nodejs-mobile.sh') }} + + - name: Download nodejs-mobile binaries + if: steps.cache-libnode.outputs.cache-hit != 'true' + run: ./scripts/download-nodejs-mobile.sh + + - name: Set up Gradle + uses: gradle/actions/setup-gradle@v4 + + - name: Install npm dependencies + run: npm install + + - name: Build backend bundle + run: npm run backend:build + + - name: Set up E2E app + working-directory: apps/e2e + run: | + npm install + npx expo prebuild --platform android --no-install + + - name: Build APK + working-directory: apps/e2e/android + run: | + ./gradlew assembleRelease --no-daemon + + - name: Upload APK + id: upload + env: + BROWSERSTACK_ACCESS_KEY: ${{ secrets.BROWSERSTACK_ACCESS_KEY_TESTS }} + BROWSERSTACK_USERNAME: ${{ secrets.BROWSERSTACK_USERNAME_TESTS }} + run: | + APK_RELATIVE_PATH="./apps/e2e/android/app/build/outputs/apk/release/app-release.apk" + + APP_URL=$(curl --fail --show-error -s -u "$BROWSERSTACK_USERNAME:$BROWSERSTACK_ACCESS_KEY" \ + -X POST "https://api-cloud.browserstack.com/app-automate/upload" \ + -F "file=@$APK_RELATIVE_PATH" | jq -r '.app_url') + + echo "app_url=$APP_URL" >> $GITHUB_OUTPUT + + build-ios: + if: ${{ github.event.pull_request.draft == false }} + name: Build (iOS) + runs-on: macos-15 + timeout-minutes: 15 + permissions: + contents: read + outputs: + app_url: ${{ steps.upload.outputs.app_url }} + steps: + - uses: actions/checkout@v4 + with: + persist-credentials: false + + - name: Select Xcode + run: sudo xcode-select -s /Applications/Xcode_26.3.app/Contents/Developer + + - uses: actions/setup-node@v6 + with: + node-version-file: package.json + + - name: Cache nodejs-mobile binaries + id: cache-ios-framework + uses: actions/cache@v4 + with: + # android/libnode is needed even on iOS builds: + # `readNodeJsMobileVersions()` in scripts/lib/node-versions.ts + # reads the Node ABI from the android libnode headers. The + # download script fetches both platforms, but a cache hit used + # to restore only the xcframework, breaking rerun builds. + path: | + ios/NodeMobile.xcframework + android/libnode + # hashFiles(...) folds the BASE_URL (defined inside the + # download script) into the cache key, so any swap of the + # source repo automatically invalidates stale caches. The -v2 + # suffix invalidates older caches that lack android/libnode. + key: nodejs-mobile-${{ env.NODEJS_MOBILE_VERSION }}-ios-v2-${{ hashFiles('scripts/download-nodejs-mobile.sh') }} + + - name: Download nodejs-mobile binaries + if: steps.cache-ios-framework.outputs.cache-hit != 'true' + run: ./scripts/download-nodejs-mobile.sh + + - name: Install npm dependencies + run: npm install + + - name: Build backend bundle + run: npm run backend:build + + - name: Set up E2E app + working-directory: apps/e2e + run: | + npm install + npx expo prebuild --platform ios + + - name: Build .ipa + working-directory: apps/e2e/ios + run: | + xcodebuild archive \ + -workspace corereactnativee2e.xcworkspace \ + -scheme corereactnativee2e \ + -sdk iphoneos \ + -destination 'generic/platform=iOS' \ + -archivePath ./build/corereactnativee2e.xcarchive \ + CODE_SIGNING_ALLOWED='NO' + + mkdir -p Payload + cp -r ./build/corereactnativee2e.xcarchive/Products/Applications/corereactnativee2e.app Payload/ + zip -r corereactnativee2e.ipa Payload/ + + - name: Upload + id: upload + env: + BROWSERSTACK_ACCESS_KEY: ${{ secrets.BROWSERSTACK_ACCESS_KEY_TESTS }} + BROWSERSTACK_USERNAME: ${{ secrets.BROWSERSTACK_USERNAME_TESTS }} + run: | + IPA_RELATIVE_PATH="./apps/e2e/ios/corereactnativee2e.ipa" + + APP_URL=$(curl --fail --show-error -s -u "$BROWSERSTACK_USERNAME:$BROWSERSTACK_ACCESS_KEY" \ + -X POST "https://api-cloud.browserstack.com/app-automate/upload" \ + -F "file=@$IPA_RELATIVE_PATH" | jq -r '.app_url') + + echo "app_url=$APP_URL" >> $GITHUB_OUTPUT + + upload-test-suite: + if: ${{ github.event.pull_request.draft == false }} + name: Upload test suite + # TODO: Enable when we know this job works + # needs: [build-android, build-ios] + runs-on: ubuntu-latest + permissions: + contents: read + outputs: + test_suite_url: ${{ steps.upload.outputs.test_suite_url }} + steps: + - uses: actions/checkout@v4 + with: + persist-credentials: false + sparse-checkout: maestro + + - name: Upload test suite to Browserstack + id: upload + env: + BROWSERSTACK_ACCESS_KEY: ${{ secrets.BROWSERSTACK_ACCESS_KEY_TESTS }} + BROWSERSTACK_USERNAME: ${{ secrets.BROWSERSTACK_USERNAME_TESTS }} + run: | + ZIP_NAME="flows.zip" + + zip -r "$ZIP_NAME" maestro/e2e.yaml + + TEST_SUITE_URL=$(curl --fail --show-error -s -u "$BROWSERSTACK_USERNAME:$BROWSERSTACK_ACCESS_KEY" \ + -X POST "https://api-cloud.browserstack.com/app-automate/maestro/v2/test-suite" \ + -F "file=@$ZIP_NAME" | jq -r '.test_suite_url') + + echo "test_suite_url=$TEST_SUITE_URL" >> $GITHUB_OUTPUT + + test-android: + name: Run tests (Android) + needs: [build-android, upload-test-suite] + runs-on: ubuntu-latest + # TODO: Adjust based on actual timing + timeout-minutes: 60 + permissions: + contents: read + steps: + - uses: actions/checkout@v4 + with: + persist-credentials: false + sparse-checkout: .github/actions + + - uses: ./.github/actions/run-browserstack-maestro + with: + platform: android + app_url: ${{ needs.build-android.outputs.app_url }} + test_suite_url: ${{ needs.upload-test-suite.outputs.test_suite_url }} + browserstack_project: ${{ vars.BROWSERSTACK_PROJECT_TESTS }} + browserstack_username: ${{ secrets.BROWSERSTACK_USERNAME_TESTS }} + browserstack_access_key: ${{ secrets.BROWSERSTACK_ACCESS_KEY_TESTS }} + # TODO: Adjust based on actual timing + timeout: 1800 + devices: >- + [ + "Google Pixel 9-16.0", + "Huawei Nova 11 SE-12.0", + "Motorola Moto G71 5G-11.0", + "OnePlus 13R-15.0", + "Samsung Galaxy Note 9-8.1", + "Vivo Y21-11.0", + "Xiaomi Redmi Note 11-11.0" + ] + + test-ios: + name: Run tests (iOS) + needs: [build-ios, upload-test-suite] + runs-on: ubuntu-latest + # TODO: Adjust based on actual timing + timeout-minutes: 60 + permissions: + contents: read + steps: + - uses: actions/checkout@v4 + with: + persist-credentials: false + sparse-checkout: .github/actions + + - uses: ./.github/actions/run-browserstack-maestro + with: + platform: ios + app_url: ${{ needs.build-ios.outputs.app_url }} + test_suite_url: ${{ needs.upload-test-suite.outputs.test_suite_url }} + browserstack_project: ${{ vars.BROWSERSTACK_PROJECT_TESTS }} + browserstack_username: ${{ secrets.BROWSERSTACK_USERNAME_TESTS }} + browserstack_access_key: ${{ secrets.BROWSERSTACK_ACCESS_KEY_TESTS }} + # TODO: Adjust based on actual timing + timeout: 1800 + devices: >- + [ + "iPhone 15-17", + "iPhone 17-26" + ] diff --git a/app.plugin.js b/app.plugin.js index dbd5203..21353b9 100644 --- a/app.plugin.js +++ b/app.plugin.js @@ -27,7 +27,11 @@ import configPlugins from "@expo/config-plugins"; import { createRequire } from "node:module"; const { withAndroidManifest } = configPlugins; const { withInfoPlist } = configPlugins; +const { withPodfile } = configPlugins; const require = createRequire(import.meta.url); +const { + mergeContents, +} = require("@expo/config-plugins/build/utils/generateCode"); // Manifest meta-data on the main `` tag is shared // across processes within the package. @@ -78,9 +82,39 @@ function withComapeoCore(config, props) { const moduleIdent = sentry ? readModuleIdentification() : null; config = withSentryAndroid(config, sentry, moduleIdent); config = withSentryIos(config, sentry); + config = withSentryLibraryEvolution(config); return config; } +// getsentry/sentry-cocoa#7950: Xcode 26's Swift compiler drops +// `SentrySDK.startTransaction` (and other Swift-only APIs) from the +// Sentry module unless the pod builds with library evolution. +// `SentryNativeBridge.swift` calls that API, so every consumer needs +// this. Inserted INSIDE the existing `post_install` block because +// CocoaPods allows only one `post_install` hook per Podfile. +const SENTRY_LIBRARY_EVOLUTION_HOOK = `\ + installer.pods_project.targets.each do |target| + if target.name.start_with?('Sentry') + target.build_configurations.each do |build_configuration| + build_configuration.build_settings['BUILD_LIBRARY_FOR_DISTRIBUTION'] = 'YES' + end + end + end`; + +function withSentryLibraryEvolution(config) { + return withPodfile(config, (cfg) => { + cfg.modResults.contents = mergeContents({ + tag: "comapeo-core-sentry-library-evolution", + src: cfg.modResults.contents, + newSrc: SENTRY_LIBRARY_EVOLUTION_HOOK, + anchor: /post_install do \|installer\|/, + offset: 1, + comment: "#", + }).contents; + return cfg; + }); +} + /** * Module version label + bundled-backend dep map — the same values * `src/version.ts` exposes to the RN-side `initSentry`. Used only on diff --git a/apps/e2e/app.json b/apps/e2e/app.json index fdd77ad..da60a31 100644 --- a/apps/e2e/app.json +++ b/apps/e2e/app.json @@ -22,6 +22,9 @@ }, "package": "com.comapeo.core.e2e" }, - "plugins": [["expo-dev-client", { "launchMode": "most-recent" }]] + "plugins": [ + ["expo-dev-client", { "launchMode": "most-recent" }], + "../../app.plugin.js" + ] } } diff --git a/apps/e2e/package-lock.json b/apps/e2e/package-lock.json index 22791ea..780f859 100644 --- a/apps/e2e/package-lock.json +++ b/apps/e2e/package-lock.json @@ -9,6 +9,7 @@ "version": "1.0.0", "hasInstallScript": true, "dependencies": { + "@sentry/react-native": "^7.13.0", "expo": "55.0.19", "expo-crypto": "55.0.14", "react": "19.2.5", @@ -4195,6 +4196,335 @@ "integrity": "sha512-bTM24b5v4qN3h52oflnv+OujFORn/kVi06WaWhnQQw14/ycilPqIsqsa+DpIBqdBrXxvLa9fXtCRrQtGATZCEw==", "license": "MIT" }, + "node_modules/@sentry-internal/browser-utils": { + "version": "10.38.0", + "resolved": "https://registry.npmjs.org/@sentry-internal/browser-utils/-/browser-utils-10.38.0.tgz", + "integrity": "sha512-UOJtYmdcxHCcV0NPfXFff/a95iXl/E0EhuQ1y0uE0BuZDMupWSF5t2BgC4HaE5Aw3RTjDF3XkSHWoIF6ohy7eA==", + "license": "MIT", + "dependencies": { + "@sentry/core": "10.38.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@sentry-internal/feedback": { + "version": "10.38.0", + "resolved": "https://registry.npmjs.org/@sentry-internal/feedback/-/feedback-10.38.0.tgz", + "integrity": "sha512-JXneg9zRftyfy1Fyfc39bBlF/Qd8g4UDublFFkVvdc1S6JQPlK+P6q22DKz3Pc8w3ySby+xlIq/eTu9Pzqi4KA==", + "license": "MIT", + "dependencies": { + "@sentry/core": "10.38.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@sentry-internal/replay": { + "version": "10.38.0", + "resolved": "https://registry.npmjs.org/@sentry-internal/replay/-/replay-10.38.0.tgz", + "integrity": "sha512-YWIkL6/dnaiQyFiZXJ/nN+NXGv/15z45ia86bE/TMq01CubX/DUOilgsFz0pk2v/pg3tp/U2MskLO9Hz0cnqeg==", + "license": "MIT", + "dependencies": { + "@sentry-internal/browser-utils": "10.38.0", + "@sentry/core": "10.38.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@sentry-internal/replay-canvas": { + "version": "10.38.0", + "resolved": "https://registry.npmjs.org/@sentry-internal/replay-canvas/-/replay-canvas-10.38.0.tgz", + "integrity": "sha512-OXWM9jEqNYh4VTvrMu7v+z1anz+QKQ/fZXIZdsO7JTT2lGNZe58UUMeoq386M+Saxen8F9SUH7yTORy/8KI5qw==", + "license": "MIT", + "dependencies": { + "@sentry-internal/replay": "10.38.0", + "@sentry/core": "10.38.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@sentry/babel-plugin-component-annotate": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@sentry/babel-plugin-component-annotate/-/babel-plugin-component-annotate-4.9.1.tgz", + "integrity": "sha512-0gEoi2Lb54MFYPOmdTfxlNKxI7kCOvNV7gP8lxMXJ7nCazF5OqOOZIVshfWjDLrc0QrSV6XdVvwPV9GDn4wBMg==", + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/@sentry/browser": { + "version": "10.38.0", + "resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-10.38.0.tgz", + "integrity": "sha512-3phzp1YX4wcQr9mocGWKbjv0jwtuoDBv7+Y6Yfrys/kwyaL84mDLjjQhRf4gL5SX7JdYkhBp4WaiNlR0UC4kTA==", + "license": "MIT", + "dependencies": { + "@sentry-internal/browser-utils": "10.38.0", + "@sentry-internal/feedback": "10.38.0", + "@sentry-internal/replay": "10.38.0", + "@sentry-internal/replay-canvas": "10.38.0", + "@sentry/core": "10.38.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@sentry/cli": { + "version": "2.58.4", + "resolved": "https://registry.npmjs.org/@sentry/cli/-/cli-2.58.4.tgz", + "integrity": "sha512-ArDrpuS8JtDYEvwGleVE+FgR+qHaOp77IgdGSacz6SZy6Lv90uX0Nu4UrHCQJz8/xwIcNxSqnN22lq0dH4IqTg==", + "hasInstallScript": true, + "license": "FSL-1.1-MIT", + "dependencies": { + "https-proxy-agent": "^5.0.0", + "node-fetch": "^2.6.7", + "progress": "^2.0.3", + "proxy-from-env": "^1.1.0", + "which": "^2.0.2" + }, + "bin": { + "sentry-cli": "bin/sentry-cli" + }, + "engines": { + "node": ">= 10" + }, + "optionalDependencies": { + "@sentry/cli-darwin": "2.58.4", + "@sentry/cli-linux-arm": "2.58.4", + "@sentry/cli-linux-arm64": "2.58.4", + "@sentry/cli-linux-i686": "2.58.4", + "@sentry/cli-linux-x64": "2.58.4", + "@sentry/cli-win32-arm64": "2.58.4", + "@sentry/cli-win32-i686": "2.58.4", + "@sentry/cli-win32-x64": "2.58.4" + } + }, + "node_modules/@sentry/cli-darwin": { + "version": "2.58.4", + "resolved": "https://registry.npmjs.org/@sentry/cli-darwin/-/cli-darwin-2.58.4.tgz", + "integrity": "sha512-kbTD+P4X8O+nsNwPxCywtj3q22ecyRHWff98rdcmtRrvwz8CKi/T4Jxn/fnn2i4VEchy08OWBuZAqaA5Kh2hRQ==", + "license": "FSL-1.1-MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@sentry/cli-linux-arm": { + "version": "2.58.4", + "resolved": "https://registry.npmjs.org/@sentry/cli-linux-arm/-/cli-linux-arm-2.58.4.tgz", + "integrity": "sha512-rdQ8beTwnN48hv7iV7e7ZKucPec5NJkRdrrycMJMZlzGBPi56LqnclgsHySJ6Kfq506A2MNuQnKGaf/sBC9REA==", + "cpu": [ + "arm" + ], + "license": "FSL-1.1-MIT", + "optional": true, + "os": [ + "linux", + "freebsd", + "android" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@sentry/cli-linux-arm64": { + "version": "2.58.4", + "resolved": "https://registry.npmjs.org/@sentry/cli-linux-arm64/-/cli-linux-arm64-2.58.4.tgz", + "integrity": "sha512-0g0KwsOozkLtzN8/0+oMZoOuQ0o7W6O+hx+ydVU1bktaMGKEJLMAWxOQNjsh1TcBbNIXVOKM/I8l0ROhaAb8Ig==", + "cpu": [ + "arm64" + ], + "license": "FSL-1.1-MIT", + "optional": true, + "os": [ + "linux", + "freebsd", + "android" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@sentry/cli-linux-i686": { + "version": "2.58.4", + "resolved": "https://registry.npmjs.org/@sentry/cli-linux-i686/-/cli-linux-i686-2.58.4.tgz", + "integrity": "sha512-NseoIQAFtkziHyjZNPTu1Gm1opeQHt7Wm1LbLrGWVIRvUOzlslO9/8i6wETUZ6TjlQxBVRgd3Q0lRBG2A8rFYA==", + "cpu": [ + "x86", + "ia32" + ], + "license": "FSL-1.1-MIT", + "optional": true, + "os": [ + "linux", + "freebsd", + "android" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@sentry/cli-linux-x64": { + "version": "2.58.4", + "resolved": "https://registry.npmjs.org/@sentry/cli-linux-x64/-/cli-linux-x64-2.58.4.tgz", + "integrity": "sha512-d3Arz+OO/wJYTqCYlSN3Ktm+W8rynQ/IMtSZLK8nu0ryh5mJOh+9XlXY6oDXw4YlsM8qCRrNquR8iEI1Y/IH+Q==", + "cpu": [ + "x64" + ], + "license": "FSL-1.1-MIT", + "optional": true, + "os": [ + "linux", + "freebsd", + "android" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@sentry/cli-win32-arm64": { + "version": "2.58.4", + "resolved": "https://registry.npmjs.org/@sentry/cli-win32-arm64/-/cli-win32-arm64-2.58.4.tgz", + "integrity": "sha512-bqYrF43+jXdDBh0f8HIJU3tbvlOFtGyRjHB8AoRuMQv9TEDUfENZyCelhdjA+KwDKYl48R1Yasb4EHNzsoO83w==", + "cpu": [ + "arm64" + ], + "license": "FSL-1.1-MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@sentry/cli-win32-i686": { + "version": "2.58.4", + "resolved": "https://registry.npmjs.org/@sentry/cli-win32-i686/-/cli-win32-i686-2.58.4.tgz", + "integrity": "sha512-3triFD6jyvhVcXOmGyttf+deKZcC1tURdhnmDUIBkiDPJKGT/N5xa4qAtHJlAB/h8L9jgYih9bvJnvvFVM7yug==", + "cpu": [ + "x86", + "ia32" + ], + "license": "FSL-1.1-MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@sentry/cli-win32-x64": { + "version": "2.58.4", + "resolved": "https://registry.npmjs.org/@sentry/cli-win32-x64/-/cli-win32-x64-2.58.4.tgz", + "integrity": "sha512-cSzN4PjM1RsCZ4pxMjI0VI7yNCkxiJ5jmWncyiwHXGiXrV1eXYdQ3n1LhUYLZ91CafyprR0OhDcE+RVZ26Qb5w==", + "cpu": [ + "x64" + ], + "license": "FSL-1.1-MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@sentry/cli/node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "license": "MIT", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/@sentry/cli/node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "license": "MIT", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/@sentry/core": { + "version": "10.38.0", + "resolved": "https://registry.npmjs.org/@sentry/core/-/core-10.38.0.tgz", + "integrity": "sha512-1pubWDZE5y5HZEPMAZERP4fVl2NH3Ihp1A+vMoVkb3Qc66Diqj1WierAnStlZP7tCx0TBa0dK85GTW/ZFYyB9g==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@sentry/react": { + "version": "10.38.0", + "resolved": "https://registry.npmjs.org/@sentry/react/-/react-10.38.0.tgz", + "integrity": "sha512-3UiKo6QsqTyPGUt0XWRY9KLaxc/cs6Kz4vlldBSOXEL6qPDL/EfpwNJT61osRo81VFWu8pKu7ZY2bvLPryrnBQ==", + "license": "MIT", + "dependencies": { + "@sentry/browser": "10.38.0", + "@sentry/core": "10.38.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "react": "^16.14.0 || 17.x || 18.x || 19.x" + } + }, + "node_modules/@sentry/react-native": { + "version": "7.13.0", + "resolved": "https://registry.npmjs.org/@sentry/react-native/-/react-native-7.13.0.tgz", + "integrity": "sha512-pKY+rln3CEnhnG21eUXcl/yeG5fkNruqXXjEikE5FjsyLmJi2POAGwT4tlupyHtG3fQT5mp06QI7bQdpAuH4Xw==", + "license": "MIT", + "dependencies": { + "@sentry/babel-plugin-component-annotate": "4.9.1", + "@sentry/browser": "10.38.0", + "@sentry/cli": "2.58.4", + "@sentry/core": "10.38.0", + "@sentry/react": "10.38.0", + "@sentry/types": "10.38.0" + }, + "bin": { + "sentry-expo-upload-sourcemaps": "scripts/expo-upload-sourcemaps.js" + }, + "peerDependencies": { + "expo": ">=49.0.0", + "react": ">=17.0.0", + "react-native": ">=0.65.0" + }, + "peerDependenciesMeta": { + "expo": { + "optional": true + } + } + }, + "node_modules/@sentry/types": { + "version": "10.38.0", + "resolved": "https://registry.npmjs.org/@sentry/types/-/types-10.38.0.tgz", + "integrity": "sha512-DoeyTv/TvnoVDhHgdyv/wehieAKdyjLjEMtPOqqq/AjkP02BxeC0JYUrrWKOjV0wdLq5ZP8jKcCX8GN7awZonQ==", + "license": "MIT", + "dependencies": { + "@sentry/core": "10.38.0" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/@sinclair/typebox": { "version": "0.27.8", "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", @@ -10203,6 +10533,26 @@ "node": ">=10" } }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, "node_modules/node-forge": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.4.0.tgz", @@ -11198,6 +11548,12 @@ "node": ">= 0.10" } }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "license": "MIT" + }, "node_modules/pump": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.4.tgz", @@ -13465,6 +13821,12 @@ "integrity": "sha512-FWAPzCIHZHnrE/5/w9MPk0kK25hSQSH2IKhYh9PyjS3SG/+IEMvlwIHbhz+oF7xl54I+ueZlVnMjyzdSwLmAwA==", "license": "MIT" }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "license": "MIT" + }, "node_modules/tslib": { "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", @@ -13751,12 +14113,28 @@ "defaults": "^1.0.3" } }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "license": "BSD-2-Clause" + }, "node_modules/whatwg-fetch": { "version": "3.6.20", "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.6.20.tgz", "integrity": "sha512-EqhiFU6daOA8kpjOWTL0olhVOF3i7OrFzSYiGsEMB8GcXS+RrzauAERX65xMeNWVqxA6HXH2m69Z9LaKKdisfg==", "license": "MIT" }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "license": "MIT", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, "node_modules/whatwg-url-minimum": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/whatwg-url-minimum/-/whatwg-url-minimum-0.1.1.tgz", diff --git a/apps/e2e/src/TestRunner.tsx b/apps/e2e/src/TestRunner.tsx index 7f706a0..a6d87ca 100644 --- a/apps/e2e/src/TestRunner.tsx +++ b/apps/e2e/src/TestRunner.tsx @@ -18,6 +18,9 @@ type TestState = | { status: 'idle' | 'pending'; results: Array } | { status: 'done'; info: JasmineDoneInfo; results: Array } +// Default of 5s is too short for IPC-heavy tests on slow CI devices. +const DEFAULT_TIMEOUT_INTERVAL_MS = 60_000 + export function TestRunner() { const [testState, setTestState] = useState({ status: 'idle', @@ -34,9 +37,11 @@ export function TestRunner() { jasmineEnv.addReporter({ jasmineStarted: () => { + console.log('[e2e] jasmine started') setTestState({ status: 'pending', results: [] }) }, jasmineDone: (info) => { + console.log(`[e2e] jasmine done: ${info.overallStatus}`) setTestState((prev) => { if (prev.status === 'done') { throw new Error( @@ -51,9 +56,25 @@ export function TestRunner() { } }) }, + specStarted: (result) => { + console.log(`[e2e] spec started: ${result.fullName}`) + }, specDone: (result) => { const describeText = result.fullName.replaceAll(result.description, '') + if (result.status === 'passed') { + console.log(`[e2e] PASS: ${result.fullName}`) + } else { + console.log( + `[e2e] FAIL: ${result.fullName} — ${result.failedExpectations + .map((e) => e.message) + .join(' | ')}`, + ) + for (const err of result.failedExpectations) { + if (err.stack) console.log(`[e2e] stack: ${err.stack}`) + } + } + setTestState((prev) => { if (prev.status === 'done') { throw new Error( @@ -82,12 +103,24 @@ export function TestRunner() { }, }) - const { describe, it, expect, expectAsync, jasmine } = + const { describe, it, expect, expectAsync, jasmine, beforeEach, afterEach } = jasmineRequire.interface(jasmineCore, jasmineEnv) + jasmine.DEFAULT_TIMEOUT_INTERVAL = DEFAULT_TIMEOUT_INTERVAL_MS + + const ctx = { + describe, + it, + expect, + expectAsync, + jasmine, + beforeEach, + afterEach, + } + // 👇 Register tests here! - basicTest({ describe, it, expect, expectAsync, jasmine }) - projectCrudTest({ describe, it, expect, expectAsync, jasmine }) + basicTest(ctx) + projectCrudTest(ctx) await jasmineEnv.execute() } @@ -106,10 +139,21 @@ export function TestRunner() { {`${testState.status === 'pending' ? 'Pending' : 'Done'}: ${testState.results.filter((r) => r.passed).length} out of ${testState.results.length} tests passed`} + {testState.status === 'done' ? ( + Done. + ) : null} + {testState.status === 'done' && testState.info.overallStatus === 'passed' ? ( All tests passed! ) : null} + + {testState.status === 'done' && + testState.info.overallStatus !== 'passed' ? ( + + {`Tests failed (${testState.info.overallStatus}).`} + + ) : null} ) : null} diff --git a/apps/e2e/src/tests/project-crud.ts b/apps/e2e/src/tests/project-crud.ts index fe61ec1..6fa0a9a 100644 --- a/apps/e2e/src/tests/project-crud.ts +++ b/apps/e2e/src/tests/project-crud.ts @@ -38,9 +38,28 @@ export function test({ expectAsync, it, jasmine, + afterEach, }: TestContext) { const CREATE_COUNT = 100 + const openProjects = new Set() + + async function openProject(projectId: string): Promise { + const project = await comapeo.getProject(projectId) + openProjects.add(project) + return project + } + + afterEach(async () => { + const projects = [...openProjects] + openProjects.clear() + // Close in afterEach to avoid leaking listeners across tests (otherwise + // EventEmitter MaxListenersExceeded fires and later tests slow down). + await Promise.all( + projects.map((p) => p.close().catch(() => undefined)), + ) + }) + const FIXTURES: Array< | FieldValue | ObservationValue @@ -105,7 +124,7 @@ export function test({ it(`create and read (${schemaName})`, async () => { const projectId = await comapeo.createProject() - const project = await comapeo.getProject(projectId) + const project = await openProject(projectId) const updates: Array = [] project[schemaName].on('updated-docs', (docs) => updates.push(...docs)) const written = await createWithMockData( @@ -132,7 +151,7 @@ export function test({ it(`update (${schemaName})`, async () => { const projectId = await comapeo.createProject() - const project = await comapeo.getProject(projectId) + const project = await openProject(projectId) const written = await create(project, value) const updateValue = getUpdateFixture(value) @@ -163,7 +182,7 @@ export function test({ it(`getMany (${schemaName})`, async () => { const projectId = await comapeo.createProject() - const project = await comapeo.getProject(projectId) + const project = await openProject(projectId) const written = await createWithMockData( project, schemaName, @@ -198,7 +217,7 @@ export function test({ it(`create, close and then create, update (${schemaName})`, async () => { const projectId = await comapeo.createProject() - const project = await comapeo.getProject(projectId) + const project = await openProject(projectId) const values = new Array(5).fill(null).map(() => { return getUpdateFixture(value) }) @@ -237,7 +256,7 @@ export function test({ it(`create, read, close, re-open, read (${schemaName})`, async () => { const projectId = await comapeo.createProject() - let project = await comapeo.getProject(projectId) + let project = await openProject(projectId) const values = new Array(5).fill(null).map(() => { return getUpdateFixture(value) @@ -254,7 +273,7 @@ export function test({ await project.close() // re-open project - project = await comapeo.getProject(projectId) + project = await openProject(projectId) const many2 = await project[schemaName].getMany() const manyValues2 = many2.map((doc) => valueOf(doc)) @@ -267,7 +286,7 @@ export function test({ it(`create and delete (${schemaName})`, async () => { const projectId = await comapeo.createProject() - const project = await comapeo.getProject(projectId) + const project = await openProject(projectId) const written = await createWithMockData( project, schemaName, @@ -289,7 +308,7 @@ export function test({ it(`delete forks ${schemaName}`, async () => { const projectId = await comapeo.createProject() - const project = await comapeo.getProject(projectId) + const project = await openProject(projectId) const written = await create(project, value) const updateValue = getUpdateFixture(value) const updatedFork1 = await update( diff --git a/apps/e2e/src/tests/utils.ts b/apps/e2e/src/tests/utils.ts index e1dd86a..cbd01f4 100644 --- a/apps/e2e/src/tests/utils.ts +++ b/apps/e2e/src/tests/utils.ts @@ -3,7 +3,13 @@ import { JasmineInterface } from 'jasmine-core/lib/jasmine-core/jasmine' export type TestContext = Pick< JasmineInterface, - 'describe' | 'it' | 'expect' | 'expectAsync' | 'jasmine' + | 'describe' + | 'it' + | 'expect' + | 'expectAsync' + | 'jasmine' + | 'beforeEach' + | 'afterEach' > export function sortBy(arr: Array, key: keyof T) { diff --git a/backend/package-lock.json b/backend/package-lock.json index b154cb9..d67f200 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -1313,37 +1313,6 @@ "unslab": "^1.3.0" } }, - "node_modules/@hyperswarm/secret-stream/node_modules/sodium-native": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/sodium-native/-/sodium-native-5.1.0.tgz", - "integrity": "sha512-3RxgyWyJlhTsABPnJVpCI5CoTDANZTqqFrEPqr+kjfnRaBihpVtMUE3yTF40ukdoB1APXeoBNKF3MzZAIHg39g==", - "license": "MIT", - "dependencies": { - "bare-assert": "^1.2.0", - "require-addon": "^1.1.0", - "which-runtime": "^1.2.1" - }, - "engines": { - "bare": ">=1.16.0" - } - }, - "node_modules/@hyperswarm/secret-stream/node_modules/sodium-universal": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/sodium-universal/-/sodium-universal-5.0.1.tgz", - "integrity": "sha512-rv+aH+tnKB5H0MAc2UadHShLMslpJsc4wjdnHRtiSIEYpOetCgu8MS4ExQRia+GL/MK3uuCyZPeEsi+J3h+Q+Q==", - "license": "MIT", - "dependencies": { - "sodium-native": "^5.0.1" - }, - "peerDependencies": { - "sodium-javascript": "~0.8.0" - }, - "peerDependenciesMeta": { - "sodium-javascript": { - "optional": true - } - } - }, "node_modules/@inquirer/checkbox": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/@inquirer/checkbox/-/checkbox-3.0.1.tgz", @@ -3365,19 +3334,6 @@ "undici-types": "~5.26.4" } }, - "node_modules/@types/pg": { - "version": "8.6.1", - "resolved": "https://registry.npmjs.org/@types/pg/-/pg-8.6.1.tgz", - "integrity": "sha512-1Kc4oAGzAl7uqUStZCDvaLFqZrW9qWSjXOmBfdgyBP5La7Us6Mg4GBvRlSoaZMhQF/zSj1C8CtKMBkoiT8eL8w==", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "@types/node": "*", - "pg-protocol": "*", - "pg-types": "^2.2.0" - } - }, "node_modules/@types/readable-stream": { "version": "4.0.23", "resolved": "https://registry.npmjs.org/@types/readable-stream/-/readable-stream-4.0.23.tgz", @@ -6754,37 +6710,6 @@ "sodium-universal": "^5.0.0" } }, - "node_modules/noise-curve-ed/node_modules/sodium-native": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/sodium-native/-/sodium-native-5.1.0.tgz", - "integrity": "sha512-3RxgyWyJlhTsABPnJVpCI5CoTDANZTqqFrEPqr+kjfnRaBihpVtMUE3yTF40ukdoB1APXeoBNKF3MzZAIHg39g==", - "license": "MIT", - "dependencies": { - "bare-assert": "^1.2.0", - "require-addon": "^1.1.0", - "which-runtime": "^1.2.1" - }, - "engines": { - "bare": ">=1.16.0" - } - }, - "node_modules/noise-curve-ed/node_modules/sodium-universal": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/sodium-universal/-/sodium-universal-5.0.1.tgz", - "integrity": "sha512-rv+aH+tnKB5H0MAc2UadHShLMslpJsc4wjdnHRtiSIEYpOetCgu8MS4ExQRia+GL/MK3uuCyZPeEsi+J3h+Q+Q==", - "license": "MIT", - "dependencies": { - "sodium-native": "^5.0.1" - }, - "peerDependencies": { - "sodium-javascript": "~0.8.0" - }, - "peerDependenciesMeta": { - "sodium-javascript": { - "optional": true - } - } - }, "node_modules/noise-handshake": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/noise-handshake/-/noise-handshake-4.2.0.tgz", @@ -6796,37 +6721,6 @@ "sodium-universal": "^5.0.0" } }, - "node_modules/noise-handshake/node_modules/sodium-native": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/sodium-native/-/sodium-native-5.1.0.tgz", - "integrity": "sha512-3RxgyWyJlhTsABPnJVpCI5CoTDANZTqqFrEPqr+kjfnRaBihpVtMUE3yTF40ukdoB1APXeoBNKF3MzZAIHg39g==", - "license": "MIT", - "dependencies": { - "bare-assert": "^1.2.0", - "require-addon": "^1.1.0", - "which-runtime": "^1.2.1" - }, - "engines": { - "bare": ">=1.16.0" - } - }, - "node_modules/noise-handshake/node_modules/sodium-universal": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/sodium-universal/-/sodium-universal-5.0.1.tgz", - "integrity": "sha512-rv+aH+tnKB5H0MAc2UadHShLMslpJsc4wjdnHRtiSIEYpOetCgu8MS4ExQRia+GL/MK3uuCyZPeEsi+J3h+Q+Q==", - "license": "MIT", - "dependencies": { - "sodium-native": "^5.0.1" - }, - "peerDependencies": { - "sodium-javascript": "~0.8.0" - }, - "peerDependenciesMeta": { - "sodium-javascript": { - "optional": true - } - } - }, "node_modules/normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", @@ -7336,43 +7230,6 @@ "dev": true, "license": "MIT" }, - "node_modules/pg-int8": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz", - "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==", - "license": "ISC", - "optional": true, - "peer": true, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/pg-protocol": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.13.0.tgz", - "integrity": "sha512-zzdvXfS6v89r6v7OcFCHfHlyG/wvry1ALxZo4LqgUoy7W9xhBDMaqOuMiF3qEV45VqsN6rdlcehHrfDtlCPc8w==", - "license": "MIT", - "optional": true, - "peer": true - }, - "node_modules/pg-types": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz", - "integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "pg-int8": "1.0.1", - "postgres-array": "~2.0.0", - "postgres-bytea": "~1.0.0", - "postgres-date": "~1.0.4", - "postgres-interval": "^1.1.0" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -7463,53 +7320,6 @@ "integrity": "sha512-13Lg5S4fkF6onHsIeITWBnrPYhPe6iqdZCGJ8K+RAlL3qvGptPuWR4aQtULmbkGW2/XzXxWoHKlWsYRAvfcLnA==", "license": "MIT" }, - "node_modules/postgres-array": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz", - "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==", - "license": "MIT", - "optional": true, - "peer": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/postgres-bytea": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.1.tgz", - "integrity": "sha512-5+5HqXnsZPE65IJZSMkZtURARZelel2oXUEO8rH83VS/hxH5vv1uHquPg5wZs8yMAfdv971IU+kcPUczi7NVBQ==", - "license": "MIT", - "optional": true, - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/postgres-date": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz", - "integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==", - "license": "MIT", - "optional": true, - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/postgres-interval": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz", - "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "xtend": "^4.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/prebuild-install": { "version": "7.1.3", "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz", @@ -8433,25 +8243,6 @@ "license": "MIT" }, "node_modules/sodium-native": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/sodium-native/-/sodium-native-4.3.3.tgz", - "integrity": "sha512-OnxSlN3uyY8D0EsLHpmm2HOFmKddQVvEMmsakCrXUzSd8kjjbzL413t4ZNF3n0UxSwNgwTyUvkmZHTfuCeiYSw==", - "license": "MIT", - "dependencies": { - "require-addon": "^1.1.0" - } - }, - "node_modules/sodium-secretstream": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/sodium-secretstream/-/sodium-secretstream-1.2.0.tgz", - "integrity": "sha512-q/DbraNFXm1KfCiiZvapmz5UC3OlpirYFIvBK2MhGaOFSb3gRyk8OXTi17UI9SGfshQNCpsVvlopogbzZNyW6Q==", - "license": "MIT", - "dependencies": { - "b4a": "^1.1.1", - "sodium-universal": "^5.0.0" - } - }, - "node_modules/sodium-secretstream/node_modules/sodium-native": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/sodium-native/-/sodium-native-5.1.0.tgz", "integrity": "sha512-3RxgyWyJlhTsABPnJVpCI5CoTDANZTqqFrEPqr+kjfnRaBihpVtMUE3yTF40ukdoB1APXeoBNKF3MzZAIHg39g==", @@ -8465,30 +8256,23 @@ "bare": ">=1.16.0" } }, - "node_modules/sodium-secretstream/node_modules/sodium-universal": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/sodium-universal/-/sodium-universal-5.0.1.tgz", - "integrity": "sha512-rv+aH+tnKB5H0MAc2UadHShLMslpJsc4wjdnHRtiSIEYpOetCgu8MS4ExQRia+GL/MK3uuCyZPeEsi+J3h+Q+Q==", + "node_modules/sodium-secretstream": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/sodium-secretstream/-/sodium-secretstream-1.2.0.tgz", + "integrity": "sha512-q/DbraNFXm1KfCiiZvapmz5UC3OlpirYFIvBK2MhGaOFSb3gRyk8OXTi17UI9SGfshQNCpsVvlopogbzZNyW6Q==", "license": "MIT", "dependencies": { - "sodium-native": "^5.0.1" - }, - "peerDependencies": { - "sodium-javascript": "~0.8.0" - }, - "peerDependenciesMeta": { - "sodium-javascript": { - "optional": true - } + "b4a": "^1.1.1", + "sodium-universal": "^5.0.0" } }, "node_modules/sodium-universal": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/sodium-universal/-/sodium-universal-4.0.1.tgz", - "integrity": "sha512-sNp13PrxYLaUFHTGoDKkSDFvoEu51bfzE12RwGlqU1fcrkpAOK0NvizaJzOWV0Omtk9me2+Pnbjcf/l0efxuGQ==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/sodium-universal/-/sodium-universal-5.0.1.tgz", + "integrity": "sha512-rv+aH+tnKB5H0MAc2UadHShLMslpJsc4wjdnHRtiSIEYpOetCgu8MS4ExQRia+GL/MK3uuCyZPeEsi+J3h+Q+Q==", "license": "MIT", "dependencies": { - "sodium-native": "^4.0.0" + "sodium-native": "^5.0.1" }, "peerDependencies": { "sodium-javascript": "~0.8.0" @@ -9470,17 +9254,6 @@ "url": "https://opencollective.com/xstate" } }, - "node_modules/xtend": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", - "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", - "license": "MIT", - "optional": true, - "peer": true, - "engines": { - "node": ">=0.4" - } - }, "node_modules/yallist": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", diff --git a/backend/package.json b/backend/package.json index 1089695..1174d58 100644 --- a/backend/package.json +++ b/backend/package.json @@ -54,6 +54,8 @@ "typescript": "5.9.3" }, "overrides": { - "require-addon": "1.1.0" + "require-addon": "1.1.0", + "sodium-native": "5.1.0", + "sodium-universal": "5.0.1" } } diff --git a/maestro/e2e.yaml b/maestro/e2e.yaml new file mode 100644 index 0000000..dc50024 --- /dev/null +++ b/maestro/e2e.yaml @@ -0,0 +1,14 @@ +appId: com.comapeo.core.e2e +env: + TIMEOUT: 300000 +--- +- launchApp: + clearState: true +- tapOn: "Run tests" +- extendedWaitUntil: + visible: + id: "all-tests-done" + timeout: ${TIMEOUT} +- takeScreenshot: results +- assertVisible: + id: "all-tests-passed" diff --git a/scripts/lib/native-modules.ts b/scripts/lib/native-modules.ts index 09d6489..fec7545 100644 --- a/scripts/lib/native-modules.ts +++ b/scripts/lib/native-modules.ts @@ -47,7 +47,12 @@ export async function collectNativePairs( })`npm list --all --parseable --long --production`; for (const line of npmListResult.stdout) { - const moduleInfo = line.split(":").at(-1); + // `--parseable --long` lines are `:@` plus + // optional trailing flag fields (e.g. `:OVERRIDDEN` for packages + // resolved via package.json `overrides`, `:INVALID`, ...). Take + // the field after the path — `.at(-1)` reads a flag when present, + // which silently dropped sodium-native from the shipped addons. + const moduleInfo = line.split(":")[1]; if (!moduleInfo) continue; deps.add(moduleInfo); }