diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 60cbbe6c..2b59dedf 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -9,6 +9,7 @@ on: branches: - main pull_request: + types: [labeled, opened, synchronize, reopened] concurrency: group: ${{ github.workflow }}-${{ github.ref }} @@ -25,7 +26,7 @@ jobs: node-version: lts/jod - run: npm ci - run: npm run lint - test: + unit-tests: strategy: fail-fast: false matrix: @@ -33,26 +34,30 @@ jobs: - ubuntu-latest - windows-latest - macos-latest - variant: - - unit-tests - - android-tests - - ios-tests - exclude: - # iOS tests are only supported on macOS runners - - runner: ubuntu-latest - variant: ios-tests - - runner: windows-latest - variant: ios-tests - # The reactivecircus/android-emulator-runner action does not support Windows runners - # See https://github.com/marketplace/actions/android-emulator-runner#github-action---android-emulator-runner - - runner: windows-latest - variant: android-tests - # MacOS is slow and expensive at running Android emulators - - runner: macos-latest - variant: android-tests - - name: Test (${{ matrix.variant }} on ${{ matrix.runner }}) runs-on: ${{ matrix.runner }} + name: Unit tests (${{ matrix.runner }}) + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: lts/jod + - name: Set up JDK 17 + uses: actions/setup-java@v3 + with: + java-version: "17" + distribution: "temurin" + - name: Setup Android SDK + uses: android-actions/setup-android@v3 + with: + packages: tools platform-tools ndk;${{ env.NDK_VERSION }} + - run: rustup target add x86_64-linux-android aarch64-linux-android armv7-linux-androideabi i686-linux-android aarch64-apple-ios-sim + - run: npm ci + - run: npm run bootstrap + - run: npm test + test-ios: + if: github.ref == 'refs/heads/main' || contains(github.event.pull_request.labels.*.name, 'Apple 🍎') + name: Test app (iOS) + runs-on: macos-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 @@ -70,28 +75,39 @@ jobs: - run: rustup target add x86_64-linux-android aarch64-linux-android armv7-linux-androideabi i686-linux-android aarch64-apple-ios-sim - run: npm ci - run: npm run bootstrap - # Unit tests - - if: matrix.variant == 'unit-tests' - name: Run tests (Unit) - run: npm test - # Integration tests (iOS) - - if: matrix.variant == 'ios-tests' - run: npm run pod-install + - run: npm run pod-install working-directory: apps/test-app - - if: matrix.variant == 'ios-tests' - name: Run tests (iOS) + - name: Run tests (iOS) run: npm run test:ios working-directory: apps/test-app - # Integration tests (Android) + test-android: + if: github.ref == 'refs/heads/main' || contains(github.event.pull_request.labels.*.name, 'Android 🤖') + name: Test app (Android) + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: lts/jod + - name: Set up JDK 17 + uses: actions/setup-java@v3 + with: + java-version: "17" + distribution: "temurin" + - name: Setup Android SDK + uses: android-actions/setup-android@v3 + with: + packages: tools platform-tools ndk;${{ env.NDK_VERSION }} + - run: rustup target add x86_64-linux-android aarch64-linux-android armv7-linux-androideabi i686-linux-android aarch64-apple-ios-sim + - run: npm ci + - run: npm run bootstrap - name: Clone patched Hermes version - if: matrix.variant == 'android-tests' shell: bash run: | REACT_NATIVE_OVERRIDE_HERMES_DIR=$(npx react-native-node-api vendor-hermes --silent) echo "REACT_NATIVE_OVERRIDE_HERMES_DIR=$REACT_NATIVE_OVERRIDE_HERMES_DIR" >> $GITHUB_ENV working-directory: apps/test-app - name: Setup Android Emulator cache - if: matrix.variant == 'android-tests' uses: actions/cache@v4 id: avd-cache with: @@ -101,21 +117,17 @@ jobs: key: ${{ runner.os }}-avd-29 # See https://github.com/marketplace/actions/android-emulator-runner#running-hardware-accelerated-emulators-on-linux-runners - name: Enable KVM group perms - if: matrix.runner == 'ubuntu-latest' && matrix.variant == 'android-tests' run: | echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules sudo udevadm control --reload-rules sudo udevadm trigger --name-match=kvm - name: Build weak-node-api for all architectures - if: matrix.variant == 'android-tests' run: npm run build-weak-node-api -- --android working-directory: packages/host - name: Build ferric-example for all architectures - if: matrix.variant == 'android-tests' run: npm run build -- --android working-directory: packages/ferric-example - name: Run tests (Android) - if: matrix.variant == 'android-tests' timeout-minutes: 75 uses: reactivecircus/android-emulator-runner@v2 with: @@ -123,7 +135,7 @@ jobs: force-avd-creation: false emulator-options: -no-snapshot-save -no-metrics -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none disable-animations: true - arch: ${{ runner.os == 'macOS' && 'arm64-v8a' || 'x86' }} + arch: x86 ndk: ${{ env.NDK_VERSION }} cmake: 3.22.1 working-directory: apps/test-app @@ -134,5 +146,3 @@ jobs: adb uninstall com.microsoft.reacttestapp || true # Build, install and run the app npm run test:android - # Wait a bit for the sub-process to terminate, before terminating the emulator - sleep 5 diff --git a/apps/test-app/package.json b/apps/test-app/package.json index 1a741be5..e82ed052 100644 --- a/apps/test-app/package.json +++ b/apps/test-app/package.json @@ -1,5 +1,6 @@ { "name": "react-native-node-api-test-app", + "type": "commonjs", "private": true, "version": "0.1.0", "scripts": { @@ -8,8 +9,8 @@ "build:android": "react-native bundle --entry-file index.js --platform android --dev true --bundle-output dist/main.android.jsbundle --assets-dest dist/res", "ios": "react-native run-ios --no-packager", "pod-install": "cd ios && pod install", - "test:android": "mocha-remote --exit-on-error -- concurrently --kill-timeout 1000 npm:start npm:android", - "test:ios": "mocha-remote --exit-on-error -- concurrently --kill-timeout 1000 npm:start npm:ios" + "test:android": "mocha-remote --exit-on-error -- tsx ./scripts/run-tests.ts android", + "test:ios": "mocha-remote --exit-on-error -- tsx ./scripts/run-tests.ts ios" }, "dependencies": { "@babel/core": "^7.26.10", @@ -23,11 +24,11 @@ "@react-native/typescript-config": "0.79.0", "@rnx-kit/metro-config": "^2.0.1", "@types/react": "^19.0.0", - "concurrently": "^9.1.2", + "bufout": "^0.3.4", "ferric-example": "^0.1.0", "mocha": "^11.6.0", - "mocha-remote-cli": "^1.13.1", - "mocha-remote-react-native": "^1.13.1", + "mocha-remote-cli": "^1.13.2", + "mocha-remote-react-native": "^1.13.2", "react": "19.0.0", "react-native": "0.79.1", "react-native-node-addon-examples": "*", diff --git a/apps/test-app/scripts/run-tests.ts b/apps/test-app/scripts/run-tests.ts new file mode 100644 index 00000000..d0968de5 --- /dev/null +++ b/apps/test-app/scripts/run-tests.ts @@ -0,0 +1,48 @@ +import assert from "node:assert/strict"; +import path from "node:path"; + +import { spawn, SpawnFailure } from "bufout"; + +// Ideally, we would just use "concurrently" or "npm-run-all" to run these in parallel but: +// - "concurrently" hangs the emulator action on Ubuntu +// - "npm-run-all" shows symptoms of not closing metro when Mocha Remote sends a SIGTERM + +const platform = process.argv[2]; +assert( + platform === "android" || platform === "ios", + "Platform must be 'android' or 'ios'" +); + +const cwd = path.resolve(__dirname, ".."); +const env = { + ...process.env, + FORCE_COLOR: "1", +}; + +const metro = spawn("react-native", ["start", "--no-interactive"], { + cwd, + stdio: "inherit", + outputPrefix: "[metro] ", + env, +}); + +const build = spawn( + "react-native", + [ + `run-${platform}`, + "--no-packager", + ...(platform === "android" ? ["--active-arch-only"] : []), + ], + { + cwd, + stdio: "inherit", + outputPrefix: `[${platform}] `, + env, + } +); + +Promise.all([metro, build]).catch((err) => { + if (!(err instanceof SpawnFailure)) { + throw err; + } +}); diff --git a/apps/test-app/tsconfig.json b/apps/test-app/tsconfig.json index 304ab4e2..425dd3e4 100644 --- a/apps/test-app/tsconfig.json +++ b/apps/test-app/tsconfig.json @@ -1,3 +1,8 @@ { - "extends": "@react-native/typescript-config/tsconfig.json" + "extends": "@react-native/typescript-config/tsconfig.json", + "compilerOptions": { + "types": ["react-native"] + }, + "files": ["App.tsx", "index.ts"], + "references": [{ "path": "./tsconfig.node-scripts.json" }] } diff --git a/apps/test-app/tsconfig.node-scripts.json b/apps/test-app/tsconfig.node-scripts.json new file mode 100644 index 00000000..5e2d2fb3 --- /dev/null +++ b/apps/test-app/tsconfig.node-scripts.json @@ -0,0 +1,12 @@ +{ + "extends": "@tsconfig/node22/tsconfig.json", + "compilerOptions": { + "composite": true, + "emitDeclarationOnly": true, + "outDir": "dist", + "rootDir": "scripts", + "types": ["node", "bufout"] + }, + "include": ["scripts/**/*.ts"], + "exclude": [] +} diff --git a/package-lock.json b/package-lock.json index 6dad1269..24768040 100644 --- a/package-lock.json +++ b/package-lock.json @@ -44,11 +44,11 @@ "@react-native/typescript-config": "0.79.0", "@rnx-kit/metro-config": "^2.0.1", "@types/react": "^19.0.0", - "concurrently": "^9.1.2", + "bufout": "^0.3.4", "ferric-example": "^0.1.0", "mocha": "^11.6.0", - "mocha-remote-cli": "^1.13.1", - "mocha-remote-react-native": "^1.13.1", + "mocha-remote-cli": "^1.13.2", + "mocha-remote-react-native": "^1.13.2", "react": "19.0.0", "react-native": "0.79.1", "react-native-node-addon-examples": "*", @@ -6288,9 +6288,9 @@ "license": "MIT" }, "node_modules/bufout": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/bufout/-/bufout-0.3.2.tgz", - "integrity": "sha512-8C3TSxBG6jbr0L/PvDo90z5lsc+mpebbV2ZeRlg7f/4Lda/88mb8A4sOngfBj5y1V3I7yRrCuMBPpg5T5tGkcg==", + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/bufout/-/bufout-0.3.4.tgz", + "integrity": "sha512-m8iGxYUvWLdQ9CQ9Sjnmr8hJHlpXfRQn2CV3eI5b107MWQqAe/K/pqsCGmczkSy3r7E1HW5u5z86z2aBYbwwxQ==", "license": "ISC" }, "node_modules/bytes": { @@ -6768,46 +6768,6 @@ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", "license": "MIT" }, - "node_modules/concurrently": { - "version": "9.1.2", - "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-9.1.2.tgz", - "integrity": "sha512-H9MWcoPsYddwbOGM6difjVwVZHl63nwMEwDJG/L7VGtuaJhb12h2caPG2tVPWs7emuYix252iGfqOyrz1GczTQ==", - "license": "MIT", - "dependencies": { - "chalk": "^4.1.2", - "lodash": "^4.17.21", - "rxjs": "^7.8.1", - "shell-quote": "^1.8.1", - "supports-color": "^8.1.1", - "tree-kill": "^1.2.2", - "yargs": "^17.7.2" - }, - "bin": { - "conc": "dist/bin/concurrently.js", - "concurrently": "dist/bin/concurrently.js" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/open-cli-tools/concurrently?sponsor=1" - } - }, - "node_modules/concurrently/node_modules/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" - } - }, "node_modules/connect": { "version": "3.7.0", "resolved": "https://registry.npmjs.org/connect/-/connect-3.7.0.tgz", @@ -8948,12 +8908,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "license": "MIT" - }, "node_modules/lodash-es": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", @@ -9682,14 +9636,14 @@ } }, "node_modules/mocha-remote-cli": { - "version": "1.13.1", - "resolved": "https://registry.npmjs.org/mocha-remote-cli/-/mocha-remote-cli-1.13.1.tgz", - "integrity": "sha512-hT4jJHZL27LAB2VPCzbWayJ5n2kz/eUK2pH6PyyJnvyAl/8tj6t0lM65TR/3x7XHg3EBHI3gbr0eQpNUBdXdmQ==", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/mocha-remote-cli/-/mocha-remote-cli-1.13.2.tgz", + "integrity": "sha512-Jly/TCM1BAhk3isQ4VzvHEfR5raRacjA9dqfvijN9X3/Gx+bJxQN+96AS41HiEi10PShrg6hWO2KvR49BlO68Q==", "license": "ISC", "dependencies": { "chalk": "^5.3.0", "debug": "^4.3.1", - "mocha-remote-server": "1.13.1", + "mocha-remote-server": "1.13.2", "yargs": "^17.7.2" }, "bin": { @@ -9712,14 +9666,14 @@ } }, "node_modules/mocha-remote-client": { - "version": "1.13.1", - "resolved": "https://registry.npmjs.org/mocha-remote-client/-/mocha-remote-client-1.13.1.tgz", - "integrity": "sha512-0/5kezWmZ6nrKTc+PGCzoqJye42kOX662UZzGM738eMTo54bQ4taMkHmJgTHX7W3lseGPnBw1QfdJB1PWqPJxg==", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/mocha-remote-client/-/mocha-remote-client-1.13.2.tgz", + "integrity": "sha512-XDzWQrjA1/CmrNg0TipFOL4xK5xkjWjpwv8INKwA2pQ0QUckRWcnhEQZO78EFI5+pLu4fTAl79at6Z0Cks7orA==", "license": "ISC", "dependencies": { "debug": "^4.3.4", "fast-equals": "^5.0.1", - "mocha-remote-common": "1.13.1", + "mocha-remote-common": "1.13.2", "ws": "^8.17.1" } }, @@ -9745,20 +9699,20 @@ } }, "node_modules/mocha-remote-common": { - "version": "1.13.1", - "resolved": "https://registry.npmjs.org/mocha-remote-common/-/mocha-remote-common-1.13.1.tgz", - "integrity": "sha512-rrF7896CSxpXUYiLvhHhXpxNHNmLT97RwTYhGUe4hM29FF0J0XRBWOOo4JuFHm930+m2Ak48FLp44zcii8kR1A==", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/mocha-remote-common/-/mocha-remote-common-1.13.2.tgz", + "integrity": "sha512-TThArVI17eH7a5zcTxdquOqpn1fkHBR+diI/zlOckLPkRFl5YL185bMJUFET2RkD2Im79aSaip4c+wH9ebJDXw==", "license": "ISC", "dependencies": { "debug": "^4.3.1" } }, "node_modules/mocha-remote-react-native": { - "version": "1.13.1", - "resolved": "https://registry.npmjs.org/mocha-remote-react-native/-/mocha-remote-react-native-1.13.1.tgz", - "integrity": "sha512-vEpQk2Jmx0C1cBVObR3GxgVfpHKhY0r+4upldOBzoKLUte4w5rWgmcXXX2TdpATiTBNz6JLG5KWw2iuUi6cMMA==", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/mocha-remote-react-native/-/mocha-remote-react-native-1.13.2.tgz", + "integrity": "sha512-6ipwmyNzDPt1Pj7iKpNWdgDYel912q4Vd41vUA/l9JTDOe3FD1DJjULmJ67htkJgUoYZPm/xmElZp+sBZ5Bi5w==", "dependencies": { - "mocha-remote-client": "1.13.1" + "mocha-remote-client": "1.13.2" }, "peerDependencies": { "react": "^18||^19", @@ -9766,14 +9720,14 @@ } }, "node_modules/mocha-remote-server": { - "version": "1.13.1", - "resolved": "https://registry.npmjs.org/mocha-remote-server/-/mocha-remote-server-1.13.1.tgz", - "integrity": "sha512-hCUgwKQawdjpikOlOzgDxDXTLdygWOL7kREQH+mv9Siw5t/x0EeTIIpAlNv1uM9fvfffeiWPf4BOKhDedKFOJA==", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/mocha-remote-server/-/mocha-remote-server-1.13.2.tgz", + "integrity": "sha512-U85Eh1YMFOPioc6/XBNbCPCNTih5sJeqCjk44R6X4PF6DL+p58+T8vuz4Dc8KhUMoP3AccUVvCru1Ez+qzvyjg==", "license": "ISC", "dependencies": { "debug": "^4.3.4", "flatted": "^3.3.1", - "mocha-remote-common": "1.13.1", + "mocha-remote-common": "1.13.2", "ws": "^8.17.1" } }, @@ -10962,15 +10916,6 @@ "queue-microtask": "^1.2.2" } }, - "node_modules/rxjs": { - "version": "7.8.2", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", - "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.1.0" - } - }, "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -11672,15 +11617,6 @@ "integrity": "sha512-y/mWCZinnvxjTKYhJ+pYxwD0mRLVvOtdS2Awbgxln6iEnt4rk0yBxeSBHkGJcPucRiG0e55mwWp+g/05rsrd6w==", "license": "MIT" }, - "node_modules/tree-kill": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", - "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", - "license": "MIT", - "bin": { - "tree-kill": "cli.js" - } - }, "node_modules/ts-api-utils": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz", @@ -11698,7 +11634,8 @@ "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "license": "0BSD" + "license": "0BSD", + "optional": true }, "node_modules/tsx": { "version": "4.19.3", @@ -12284,7 +12221,7 @@ "version": "0.1.0", "dependencies": { "@commander-js/extra-typings": "^13.1.0", - "bufout": "^0.3.2", + "bufout": "^0.3.4", "chalk": "^5.4.1", "cmake-js": "^7.3.1", "commander": "^13.1.0", @@ -12517,7 +12454,7 @@ "dependencies": { "@commander-js/extra-typings": "^13.1.0", "@napi-rs/cli": "3.0.0-alpha.89", - "bufout": "^0.3.2", + "bufout": "^0.3.4", "chalk": "^5.4.1", "commander": "^13.1.0", "ora": "^8.2.0" @@ -12780,7 +12717,7 @@ "license": "MIT", "dependencies": { "@commander-js/extra-typings": "^13.1.0", - "bufout": "^0.3.2", + "bufout": "^0.3.4", "chalk": "^5.4.1", "commander": "^13.1.0", "ora": "^8.2.0", diff --git a/packages/cmake-rn/package.json b/packages/cmake-rn/package.json index 6f684371..44d61906 100644 --- a/packages/cmake-rn/package.json +++ b/packages/cmake-rn/package.json @@ -23,7 +23,7 @@ }, "dependencies": { "@commander-js/extra-typings": "^13.1.0", - "bufout": "^0.3.2", + "bufout": "^0.3.4", "chalk": "^5.4.1", "cmake-js": "^7.3.1", "commander": "^13.1.0", diff --git a/packages/ferric/package.json b/packages/ferric/package.json index 59919d5e..c6fc9a13 100644 --- a/packages/ferric/package.json +++ b/packages/ferric/package.json @@ -18,7 +18,7 @@ "dependencies": { "@napi-rs/cli": "3.0.0-alpha.89", "@commander-js/extra-typings": "^13.1.0", - "bufout": "^0.3.2", + "bufout": "^0.3.4", "chalk": "^5.4.1", "commander": "^13.1.0", "ora": "^8.2.0" diff --git a/packages/host/package.json b/packages/host/package.json index 81831acb..3b5f6a5f 100644 --- a/packages/host/package.json +++ b/packages/host/package.json @@ -77,7 +77,7 @@ "license": "MIT", "dependencies": { "@commander-js/extra-typings": "^13.1.0", - "bufout": "^0.3.2", + "bufout": "^0.3.4", "chalk": "^5.4.1", "commander": "^13.1.0", "ora": "^8.2.0",