From 4de3c1952777026d1516dc406befb31047a4b6e9 Mon Sep 17 00:00:00 2001 From: gmegidish Date: Wed, 13 May 2026 15:22:33 +0200 Subject: [PATCH 1/4] test: simplifying tests --- test/avdctl.ts | 211 ----------- test/emulator.ts | 193 ++++------ test/package-lock.json | 811 ++++++++++++++++++++++++++--------------- test/package.json | 12 +- test/server.ts | 149 ++++---- test/simctl.ts | 113 +----- test/simulator.ts | 20 +- 7 files changed, 676 insertions(+), 833 deletions(-) delete mode 100644 test/avdctl.ts diff --git a/test/avdctl.ts b/test/avdctl.ts deleted file mode 100644 index 9467afb..0000000 --- a/test/avdctl.ts +++ /dev/null @@ -1,211 +0,0 @@ -import {execSync} from 'child_process'; -import {readdirSync} from 'fs'; - -const ANDROID_HOME = process.env.ANDROID_HOME; -const EMULATOR_PATH = `${ANDROID_HOME}/emulator/emulator`; -const ADB_PATH = `${ANDROID_HOME}/platform-tools/adb`; - -let createdEmulators: string[] = []; // Track created emulators for cleanup - -// Emulator management functions -export function findAndroidSystemImage(apiLevel: string): string { - try { - const deviceTypes = ['google_apis_playstore', 'google_apis']; - - for (const type of deviceTypes) { - try { - const systemImagePath = `${ANDROID_HOME}/system-images/android-${apiLevel}/${type}`; - const files = readdirSync(systemImagePath); - - const hasX86_64 = files.includes('x86_64'); - const hasX86 = files.includes('x86'); - const hasArm64 = files.includes('arm64-v8a'); - - if (!hasX86_64 && !hasX86 && !hasArm64) { - continue; - } - - let arch = 'x86_64'; - if (!hasX86_64) { - if (hasX86) { - arch = 'x86'; - } else if (hasArm64) { - arch = 'arm64-v8a'; - } - } - - return `system-images;android-${apiLevel};${type};${arch}`; - } catch (e) { - continue; - } - } - - throw new Error(`No compatible system image found for API ${apiLevel}`); - } catch (error) { - throw new Error(`Failed to find Android API ${apiLevel} system image: ${error}`); - } -} - -function listAvds(): Array { - return execSync(`${EMULATOR_PATH} -list-avds`, {encoding: 'utf8'}) - .toString() - .split("\n"); -} - -export function createEmulator(name: string, systemImage: string, deviceProfile: string = 'pixel'): string { - try { - if (listAvds().includes(name)) { - throw new Error(`AVD ${name} already exists`); - } - - execSync(`echo "no" | ${ANDROID_HOME}/cmdline-tools/latest/bin/avdmanager create avd -n "${name}" -k "${systemImage}" -d "${deviceProfile}"`, {encoding: 'utf8'}); - - createdEmulators.push(name); - return name; - } catch (error) { - throw new Error(`Failed to create emulator: ${error}`); - } -} - -export function launchEmulator(emulatorName: string): void { - try { - execSync(`${EMULATOR_PATH} -avd "${emulatorName}" -no-snapshot-save -wipe-data 2>&1 >/dev/null &`, - {encoding: 'utf8'}); - - execSync('sleep 60'); - } catch (error) { - throw new Error(`Failed to launch emulator: ${error}`); - } -} - -export function waitForEmulatorReady(emulatorName: string, timeout: number = 180000): string { - const startTime = Date.now(); - let deviceId = ''; - - console.log(`Waiting for emulator ${emulatorName} to be ready...`); - - while (Date.now() - startTime < timeout) { - try { - // Check if emulator is listed in adb devices - const devices = execSync(`${ADB_PATH} devices`, {encoding: 'utf8'}); - const deviceLines = devices - .split('\n') - .filter(line => line.includes('device') && !line.includes('List')); - - for (const line of deviceLines) { - const parts = line.split('\t'); - if (parts.length >= 2 && parts[1].trim() === 'device') { - deviceId = parts[0].trim(); - - try { - const bootCompleted = execSync(`${ADB_PATH} -s ${deviceId} shell getprop sys.boot_completed`, { - encoding: 'utf8', - timeout: 5000 - }).trim(); - const bootAnim = execSync(`${ADB_PATH} -s ${deviceId} shell getprop init.svc.bootanim`, { - encoding: 'utf8', - timeout: 5000 - }).trim(); - - if (bootCompleted === '1' && bootAnim === 'stopped') { - console.log(`Emulator ${emulatorName} is ready with device ID: ${deviceId}`); - return deviceId; - } - } catch (propError) { - } - } - } - } catch (error) { - // Continue waiting - } - - // Wait 2 seconds before next attempt - execSync('sleep 2'); - } - - throw new Error(`Emulator did not become ready within ${timeout}ms`); -} - -export function shutdownEmulator(deviceId: string): void { - try { - execSync(`${ADB_PATH} -s ${deviceId} emu kill`, {encoding: 'utf8'}); - } catch (error) { - try { - // Force kill if graceful shutdown fails - const processes = execSync(`ps aux | grep "${deviceId}" | grep -v grep`, {encoding: 'utf8'}); - if (processes.trim()) { - execSync(`pkill -f "${deviceId}"`); - } - } catch (killError) { - console.warn(`Warning: Failed to shutdown emulator ${deviceId}: ${error}`); - } - } -} - -export function deleteEmulator(emulatorName: string): void { - try { - const homeDir = process.env.HOME; - const avdDir = `${homeDir}/.android/avd`; - - // Remove AVD directory and ini file - execSync(`rm -rf "${avdDir}/${emulatorName}.avd"`); - execSync(`rm -f "${avdDir}/${emulatorName}.ini"`); - - removeFromTracking(emulatorName); - } catch (error) { - console.warn(`Warning: Failed to delete emulator ${emulatorName}: ${error}`); - } -} - -export function createAndLaunchEmulator(apiLevel: string = '36', deviceProfile: string = 'pixel'): { name: string, deviceId: string } { - const systemImage = findAndroidSystemImage(apiLevel); - const emulatorName = `Test-Android-${apiLevel}-${Date.now()}`; - - console.log(`Creating Android API ${apiLevel} emulator ${emulatorName}...`); - createEmulator(emulatorName, systemImage, deviceProfile); - - launchEmulator(emulatorName); - const deviceId = waitForEmulatorReady(emulatorName); - - console.log(`Emulator ${emulatorName} is ready with device ID ${deviceId}!`); - return {name: emulatorName, deviceId}; -} - -export function cleanupEmulators(): void { - // First try to shutdown all running emulators - try { - const devices = execSync(`${ADB_PATH} devices`, {encoding: 'utf8'}); - const deviceLines = devices.split('\n').filter(line => line.includes('device') && !line.includes('List')); - - for (const line of deviceLines) { - const parts = line.split('\t'); - if (parts.length >= 2 && parts[1].trim() === 'device') { - shutdownEmulator(parts[0].trim()); - } - } - } catch (error) { - console.warn('Warning: Failed to shutdown running emulators during cleanup'); - } - - // Delete tracked emulators - for (const emulatorName of createdEmulators) { - deleteEmulator(emulatorName); - } - createdEmulators = []; -} - -export function removeFromTracking(emulatorName: string): void { - const index = createdEmulators.indexOf(emulatorName); - if (index > -1) { - createdEmulators.splice(index, 1); - } -} - -export function getAvailableEmulators(): string[] { - try { - const stdout = execSync(`${EMULATOR_PATH} -list-avds`, {encoding: 'utf8'}); - return stdout.trim().split('\n').filter(line => line.trim().length > 0); - } catch (error) { - throw new Error(`Failed to list available emulators: ${error}`); - } -} diff --git a/test/emulator.ts b/test/emulator.ts index 6cb1679..b93c2ab 100644 --- a/test/emulator.ts +++ b/test/emulator.ts @@ -1,148 +1,93 @@ -import { expect } from 'chai'; -import { execSync } from 'child_process'; +import {expect} from 'chai'; +import {execSync} from 'child_process'; import * as path from 'path'; import * as fs from 'fs'; -import { - createAndLaunchEmulator, - shutdownEmulator, - deleteEmulator, - cleanupEmulators, - findAndroidSystemImage, - getAvailableEmulators -} from './avdctl'; -const TEST_SERVER_URL = 'http://localhost:12001'; +const mobilecliBinary = path.join(__dirname, '..', 'mobilecli'); -const SUPPORTED_VERSIONS = ['31', '36']; +type Device = { + id: string; + name: string; + platform: string; + type: string; + version: string; + state: string; +}; + +function getFirstAndroidDevice(): Device | null { + try { + const output = execSync(`${mobilecliBinary} devices`, {encoding: 'utf8'}); + const result = JSON.parse(output); + return result?.data?.devices?.find((d: Device) => d.platform === 'android') ?? null; + } catch (error) { + return null; + } +} describe('Android Emulator Tests', () => { - after(() => { - cleanupEmulators(); + let device: Device | null; + + before(function () { + device = getFirstAndroidDevice(); + if (!device) { + console.log('No Android device found. See test/README.md for setup instructions.'); + } }); - SUPPORTED_VERSIONS.forEach((apiLevel) => { - describe(`Android API ${apiLevel}`, () => { - let emulatorName: string; - let deviceId: string; - let systemImageAvailable: boolean = false; - - before(function () { - this.timeout(300000); // 5 minutes for emulator startup - - try { - findAndroidSystemImage(apiLevel); - systemImageAvailable = true; - - console.log(`Creating and launching Android API ${apiLevel} emulator...`); - const result = createAndLaunchEmulator(apiLevel, 'pixel'); - emulatorName = result.name; - deviceId = result.deviceId; - console.log(`Emulator ready: ${emulatorName} (${deviceId})`); - } catch (error) { - console.log(`Android API ${apiLevel} system image not available, skipping tests: ${error}`); - systemImageAvailable = false; - } - }); - - after(() => { - if (deviceId && emulatorName) { - console.log(`Cleaning up emulator ${emulatorName} (${deviceId})`); - shutdownEmulator(deviceId); - deleteEmulator(emulatorName); - } - }); - - it('should take screenshot', async function () { - if (!systemImageAvailable) { - this.skip(); - return; - } - - this.timeout(180000); - - const screenshotPath = `/tmp/screenshot-android${apiLevel}-${Date.now()}.png`; - - takeScreenshot(deviceId, screenshotPath); - verifyScreenshotFileWasCreated(screenshotPath); - verifyScreenshotFileHasValidContent(screenshotPath); - - // console.log(`Screenshot saved at: ${screenshotPath}`); - }); - - it('should open URL https://example.com', async function () { - if (!systemImageAvailable) { - this.skip(); - return; - } - - this.timeout(180000); - - openUrl(deviceId, 'https://example.com'); - }); - - it('should get device info', async function () { - if (!systemImageAvailable) { - this.skip(); - return; - } - - this.timeout(60000); - - getDeviceInfo(deviceId); - }); - }); + it('should take screenshot', function () { + if (!device) { + this.skip(); + return; + } + + this.timeout(30000); + + const screenshotPath = `/tmp/screenshot-android-${Date.now()}.png`; + mobilecli(`screenshot --device ${device.id} --format png --output ${screenshotPath}`); + + const fileExists = fs.existsSync(screenshotPath); + expect(fileExists).to.be.true; + + const stats = fs.statSync(screenshotPath); + expect(stats.size).to.be.greaterThan(64 * 1024); + }); + + it('should open URL https://example.com', function () { + if (!device) { + this.skip(); + return; + } + + this.timeout(30000); + + mobilecli(`url "https://example.com" --device ${device.id}`); + }); + + it('should get device info', function () { + if (!device) { + this.skip(); + return; + } + + this.timeout(30000); + + mobilecli(`device info --device ${device.id}`); }); }); function mobilecli(args: string): void { - const mobilecliBinary = path.join(__dirname, '..', 'mobilecli'); const command = `${mobilecliBinary} ${args}`; try { - const result = execSync(command, { + execSync(command, { encoding: 'utf8', timeout: 180000, stdio: ['pipe', 'pipe', 'pipe'], - env: { - ANDROID_HOME: process.env.ANDROID_HOME || "", - } }); } catch (error: any) { console.log(`Command failed: ${command}`); - if (error.stderr) { - console.log(`Error stderr: ${error.stderr}`); - } - if (error.stdout) { - console.log(`Error stdout: ${error.stdout}`); - } - if (error.message && !error.stderr && !error.stdout) { - console.log(`Error message: ${error.message}`); - } + if (error.stderr) console.log(`stderr: ${error.stderr}`); + if (error.stdout) console.log(`stdout: ${error.stdout}`); throw error; } } - -function takeScreenshot(deviceId: string, screenshotPath: string): void { - execSync('../mobilecli devices', { stdio: 'inherit' }); - mobilecli(`screenshot --device ${deviceId} --format png --output ${screenshotPath}`); -} - -function verifyScreenshotFileWasCreated(screenshotPath: string): void { - const fileExists = fs.existsSync(screenshotPath); - expect(fileExists).to.be.true; - // console.log(`✓ Screenshot file was created: ${screenshotPath}`); -} - -function verifyScreenshotFileHasValidContent(screenshotPath: string): void { - const stats = fs.statSync(screenshotPath); - const fileSizeInBytes = stats.size; - expect(fileSizeInBytes).to.be.greaterThan(64 * 1024); -} - -function openUrl(deviceId: string, url: string): void { - mobilecli(`url "${url}" --device ${deviceId}`); -} - -function getDeviceInfo(deviceId: string): void { - mobilecli(`device info --device ${deviceId}`); -} diff --git a/test/package-lock.json b/test/package-lock.json index 77d52e6..552ee28 100644 --- a/test/package-lock.json +++ b/test/package-lock.json @@ -12,10 +12,10 @@ "@types/chai": "^4.3.0", "@types/mocha": "^10.0.10", "@types/node": "^24.3.0", - "axios": "^1.12.0", "chai": "^4.3.0", "mocha": "^11.7.2", "ts-node": "^10.9.2", + "tsx": "^4.21.0", "typescript": "^5.9.2" } }, @@ -32,6 +32,448 @@ "node": ">=12" } }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.7.tgz", + "integrity": "sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.7.tgz", + "integrity": "sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.7.tgz", + "integrity": "sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.7.tgz", + "integrity": "sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.7.tgz", + "integrity": "sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.7.tgz", + "integrity": "sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.7.tgz", + "integrity": "sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.7.tgz", + "integrity": "sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.7.tgz", + "integrity": "sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.7.tgz", + "integrity": "sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.7.tgz", + "integrity": "sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.7.tgz", + "integrity": "sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.7.tgz", + "integrity": "sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.7.tgz", + "integrity": "sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.7.tgz", + "integrity": "sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.7.tgz", + "integrity": "sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.7.tgz", + "integrity": "sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.7.tgz", + "integrity": "sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.7.tgz", + "integrity": "sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.7.tgz", + "integrity": "sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.7.tgz", + "integrity": "sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.7.tgz", + "integrity": "sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.7.tgz", + "integrity": "sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.7.tgz", + "integrity": "sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.7.tgz", + "integrity": "sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.7.tgz", + "integrity": "sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, "node_modules/@isaacs/cliui": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", @@ -220,25 +662,6 @@ "node": "*" } }, - "node_modules/asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", - "dev": true, - "license": "MIT" - }, - "node_modules/axios": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.15.0.tgz", - "integrity": "sha512-wWyJDlAatxk30ZJer+GeCWS209sA42X+N5jU2jy6oHTp7ufw8uzUTVFBX9+wTfAlhiJXGS0Bq7X6efruWjuK9Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "follow-redirects": "^1.15.11", - "form-data": "^4.0.5", - "proxy-from-env": "^2.1.0" - } - }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -263,20 +686,6 @@ "dev": true, "license": "ISC" }, - "node_modules/call-bind-apply-helpers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", - "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, "node_modules/camelcase": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", @@ -466,19 +875,6 @@ "dev": true, "license": "MIT" }, - "node_modules/combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dev": true, - "license": "MIT", - "dependencies": { - "delayed-stream": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, "node_modules/create-require": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", @@ -545,16 +941,6 @@ "node": ">=6" } }, - "node_modules/delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.4.0" - } - }, "node_modules/diff": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/diff/-/diff-7.0.0.tgz", @@ -565,21 +951,6 @@ "node": ">=0.3.1" } }, - "node_modules/dunder-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", - "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.1", - "es-errors": "^1.3.0", - "gopd": "^1.2.0" - }, - "engines": { - "node": ">= 0.4" - } - }, "node_modules/eastasianwidth": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", @@ -594,53 +965,46 @@ "dev": true, "license": "MIT" }, - "node_modules/es-define-property": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", - "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-errors": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", - "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-object-atoms": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", - "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "node_modules/esbuild": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.7.tgz", + "integrity": "sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==", "dev": true, + "hasInstallScript": true, "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0" + "bin": { + "esbuild": "bin/esbuild" }, "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-set-tostringtag": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", - "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.6", - "has-tostringtag": "^1.0.2", - "hasown": "^2.0.2" + "node": ">=18" }, - "engines": { - "node": ">= 0.4" + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.7", + "@esbuild/android-arm": "0.27.7", + "@esbuild/android-arm64": "0.27.7", + "@esbuild/android-x64": "0.27.7", + "@esbuild/darwin-arm64": "0.27.7", + "@esbuild/darwin-x64": "0.27.7", + "@esbuild/freebsd-arm64": "0.27.7", + "@esbuild/freebsd-x64": "0.27.7", + "@esbuild/linux-arm": "0.27.7", + "@esbuild/linux-arm64": "0.27.7", + "@esbuild/linux-ia32": "0.27.7", + "@esbuild/linux-loong64": "0.27.7", + "@esbuild/linux-mips64el": "0.27.7", + "@esbuild/linux-ppc64": "0.27.7", + "@esbuild/linux-riscv64": "0.27.7", + "@esbuild/linux-s390x": "0.27.7", + "@esbuild/linux-x64": "0.27.7", + "@esbuild/netbsd-arm64": "0.27.7", + "@esbuild/netbsd-x64": "0.27.7", + "@esbuild/openbsd-arm64": "0.27.7", + "@esbuild/openbsd-x64": "0.27.7", + "@esbuild/openharmony-arm64": "0.27.7", + "@esbuild/sunos-x64": "0.27.7", + "@esbuild/win32-arm64": "0.27.7", + "@esbuild/win32-ia32": "0.27.7", + "@esbuild/win32-x64": "0.27.7" } }, "node_modules/escalade": { @@ -693,27 +1057,6 @@ "flat": "cli.js" } }, - "node_modules/follow-redirects": { - "version": "1.16.0", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz", - "integrity": "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/RubenVerborgh" - } - ], - "license": "MIT", - "engines": { - "node": ">=4.0" - }, - "peerDependenciesMeta": { - "debug": { - "optional": true - } - } - }, "node_modules/foreground-child": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", @@ -731,31 +1074,19 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/form-data": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", - "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", "dev": true, + "hasInstallScript": true, "license": "MIT", - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "es-set-tostringtag": "^2.1.0", - "hasown": "^2.0.2", - "mime-types": "^2.1.12" - }, + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": ">= 6" - } - }, - "node_modules/function-bind": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, "node_modules/get-caller-file": { @@ -778,43 +1109,17 @@ "node": "*" } }, - "node_modules/get-intrinsic": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", - "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "node_modules/get-tsconfig": { + "version": "4.14.0", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.14.0.tgz", + "integrity": "sha512-yTb+8DXzDREzgvYmh6s9vHsSVCHeC0G3PI5bEXNBHtmshPnO+S5O7qgLEOn0I5QvMy6kpZN8K1NKGyilLb93wA==", "dev": true, "license": "MIT", "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "es-define-property": "^1.0.1", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.1.1", - "function-bind": "^1.1.2", - "get-proto": "^1.0.1", - "gopd": "^1.2.0", - "has-symbols": "^1.1.0", - "hasown": "^2.0.2", - "math-intrinsics": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" + "resolve-pkg-maps": "^1.0.0" }, "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", - "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", - "dev": true, - "license": "MIT", - "dependencies": { - "dunder-proto": "^1.0.1", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" } }, "node_modules/glob": { @@ -838,19 +1143,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/gopd": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", - "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -861,48 +1153,6 @@ "node": ">=8" } }, - "node_modules/has-symbols": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", - "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-tostringtag": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", - "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-symbols": "^1.0.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, "node_modules/he": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", @@ -1049,39 +1299,6 @@ "dev": true, "license": "ISC" }, - "node_modules/math-intrinsics": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", - "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "dev": true, - "license": "MIT", - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } - }, "node_modules/minimatch": { "version": "9.0.9", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", @@ -1245,16 +1462,6 @@ "dev": true, "license": "ISC" }, - "node_modules/proxy-from-env": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz", - "integrity": "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - } - }, "node_modules/readdirp": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", @@ -1279,6 +1486,16 @@ "node": ">=0.10.0" } }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, "node_modules/serialize-javascript": { "version": "7.0.5", "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-7.0.5.tgz", @@ -1512,6 +1729,26 @@ "node": ">=0.3.1" } }, + "node_modules/tsx": { + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.21.0.tgz", + "integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "~0.27.0", + "get-tsconfig": "^4.7.5" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, "node_modules/type-detect": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.1.0.tgz", diff --git a/test/package.json b/test/package.json index ff9783c..11e0482 100644 --- a/test/package.json +++ b/test/package.json @@ -4,14 +4,10 @@ "description": "E2E tests for MobileCLI server", "main": "index.ts", "scripts": { - "test": "mocha --require ts-node/register --timeout 180000 '*.ts'", - "test-simulator": "mocha --require ts-node/register --timeout 180000 'simulator.ts'" + "test:server": "./node_modules/.bin/tsx ./node_modules/.bin/mocha --timeout 180000 'server.ts'", + "test:android": "./node_modules/.bin/tsx ./node_modules/.bin/mocha --timeout 180000 'emulator.ts'", + "test:simulator": "./node_modules/.bin/tsx ./node_modules/.bin/mocha --timeout 180000 'simulator.ts'" }, - "keywords": [ - "mobilecli", - "testing", - "e2e" - ], "author": "", "license": "ISC", "type": "commonjs", @@ -19,10 +15,10 @@ "@types/chai": "^4.3.0", "@types/mocha": "^10.0.10", "@types/node": "^24.3.0", - "axios": "^1.12.0", "chai": "^4.3.0", "mocha": "^11.7.2", "ts-node": "^10.9.2", + "tsx": "^4.21.0", "typescript": "^5.9.2" }, "overrides": { diff --git a/test/server.ts b/test/server.ts index 43ba3e7..cea6ad3 100644 --- a/test/server.ts +++ b/test/server.ts @@ -1,13 +1,9 @@ import {expect} from 'chai'; -import {spawn, ChildProcess} from 'child_process'; -import axios from 'axios'; +import {spawn} from 'child_process'; +import type {ChildProcess} from 'child_process'; import * as path from 'path'; +import type {JSONRPCRequest, JSONRPCResponse} from './jsonrpc'; import { - cleanupSimulators -} from './simctl'; -import { - JSONRPCRequest, - JSONRPCResponse, ErrCodeParseError, ErrCodeInvalidRequest, ErrCodeMethodNotFound, @@ -40,37 +36,30 @@ describe('server jsonrpc', () => { // Stop server after all tests after(() => { stopTestServer(); - cleanupSimulators(); }); it('should return status "ok" for root endpoint', async () => { - const response = await axios.get(TEST_SERVER_URL); + const response = await fetch(TEST_SERVER_URL); expect(response.status).to.equal(200); - expect(response.data).to.have.property('status', 'ok'); + expect(await response.json()).to.have.property('status', 'ok'); }); it('GET should return 405 Method Not Allowed for /rpc endpoint', async () => { - try { - await axios.get(`${TEST_SERVER_URL}/rpc`); - throw new Error('Expected request to fail'); - } catch (error: any) { - expect(error.response.status).to.equal(405); - } + const response = await fetch(`${TEST_SERVER_URL}/rpc`); + expect(response.status).to.equal(405); }); it('Empty POST body should return parse error', async () => { - const response = await axios.post( - `${TEST_SERVER_URL}/rpc`, - '', - { - headers: {'Content-Type': 'application/json'} - } - ); + const response = await fetch(`${TEST_SERVER_URL}/rpc`, { + method: 'POST', + headers: {'Content-Type': 'application/json'}, + body: '' + }); expect(response.status).to.equal(200); - const jsonResp: JSONRPCResponse = response.data; + const jsonResp: JSONRPCResponse = await response.json(); expect(jsonResp.jsonrpc).to.equal('2.0'); expect(jsonResp.error).to.not.be.null; expect(jsonResp.error).to.not.be.undefined; @@ -88,20 +77,19 @@ describe('server jsonrpc', () => { id: 1, }; - const response = await axios.post( - `${TEST_SERVER_URL}/rpc`, - JSON.stringify(payload), - { - headers: {'Content-Type': 'application/json'} - } - ); + const response = await fetch(`${TEST_SERVER_URL}/rpc`, { + method: 'POST', + headers: {'Content-Type': 'application/json'}, + body: JSON.stringify(payload) + }); + const data: JSONRPCResponse = await response.json(); expect(response.status).to.equal(200); - expect(response.data.jsonrpc).to.equal('2.0'); - expect(response.data.error).to.not.be.null; - expect(response.data.error).to.not.be.undefined; - expect(response.data.error!.code).to.equal(ErrCodeInvalidRequest); - expect(response.data.error!.data).to.equal("'jsonrpc' must be '2.0'"); + expect(data.jsonrpc).to.equal('2.0'); + expect(data.error).to.not.be.null; + expect(data.error).to.not.be.undefined; + expect(data.error!.code).to.equal(ErrCodeInvalidRequest); + expect(data.error!.data).to.equal("'jsonrpc' must be '2.0'"); }); it('Missing id field should return error', async () => { @@ -111,17 +99,15 @@ describe('server jsonrpc', () => { params: {} }; - const response = await axios.post( - `${TEST_SERVER_URL}/rpc`, - JSON.stringify(payload), - { - headers: {'Content-Type': 'application/json'} - } - ); + const response = await fetch(`${TEST_SERVER_URL}/rpc`, { + method: 'POST', + headers: {'Content-Type': 'application/json'}, + body: JSON.stringify(payload) + }); expect(response.status).to.equal(200); - const jsonResp: JSONRPCResponse = response.data; + const jsonResp: JSONRPCResponse = await response.json(); expect(jsonResp.jsonrpc).to.equal('2.0'); expect(jsonResp.error).to.not.be.null; expect(jsonResp.error).to.not.be.undefined; @@ -139,17 +125,15 @@ describe('server jsonrpc', () => { id: 1 }; - const response = await axios.post( - `${TEST_SERVER_URL}/rpc`, - JSON.stringify(payload), - { - headers: {'Content-Type': 'application/json'} - } - ); + const response = await fetch(`${TEST_SERVER_URL}/rpc`, { + method: 'POST', + headers: {'Content-Type': 'application/json'}, + body: JSON.stringify(payload) + }); expect(response.status).to.equal(200); - const jsonResp: JSONRPCResponse = response.data; + const jsonResp: JSONRPCResponse = await response.json(); expect(jsonResp.jsonrpc).to.equal('2.0'); expect(jsonResp.id).to.equal(1); expect(jsonResp.error).to.not.be.null; @@ -167,17 +151,15 @@ describe('server jsonrpc', () => { id: 1 }; - const response = await axios.post( - `${TEST_SERVER_URL}/rpc`, - JSON.stringify(payload), - { - headers: {'Content-Type': 'application/json'} - } - ); + const response = await fetch(`${TEST_SERVER_URL}/rpc`, { + method: 'POST', + headers: {'Content-Type': 'application/json'}, + body: JSON.stringify(payload) + }); expect(response.status).to.equal(200); - const jsonResp: JSONRPCResponse = response.data; + const jsonResp: JSONRPCResponse = await response.json(); expect(jsonResp.error).to.not.be.null; if (jsonResp.error) { @@ -191,15 +173,13 @@ describe('server jsonrpc', () => { id: 1 }; - const response = await axios.post( - `${TEST_SERVER_URL}/rpc`, - JSON.stringify(payload), - { - headers: {'Content-Type': 'application/json'} - } - ); + const response = await fetch(`${TEST_SERVER_URL}/rpc`, { + method: 'POST', + headers: {'Content-Type': 'application/json'}, + body: JSON.stringify(payload) + }); - const jsonResp: JSONRPCResponse = response.data; + const jsonResp: JSONRPCResponse = await response.json(); expect(jsonResp.error).to.not.be.null; if (jsonResp.error) { @@ -211,17 +191,15 @@ describe('server jsonrpc', () => { it('should handle invalid JSON gracefully', async () => { const invalidJson = '{invalid json}'; - const response = await axios.post( - `${TEST_SERVER_URL}/rpc`, - invalidJson, - { - headers: {'Content-Type': 'application/json'} - } - ); + const response = await fetch(`${TEST_SERVER_URL}/rpc`, { + method: 'POST', + headers: {'Content-Type': 'application/json'}, + body: invalidJson + }); expect(response.status).to.equal(200); - const jsonResp: JSONRPCResponse = response.data; + const jsonResp: JSONRPCResponse = await response.json(); expect(jsonResp.error).to.not.be.null; if (jsonResp.error) { @@ -236,17 +214,15 @@ describe('server jsonrpc', () => { id: 1 }; - const response = await axios.post( - `${TEST_SERVER_URL}/rpc`, - JSON.stringify(payload), - { - headers: {'Content-Type': 'application/json'} - } - ); + const response = await fetch(`${TEST_SERVER_URL}/rpc`, { + method: 'POST', + headers: {'Content-Type': 'application/json'}, + body: JSON.stringify(payload) + }); expect(response.status).to.equal(200); - const jsonResp: JSONRPCResponse = response.data; + const jsonResp: JSONRPCResponse = await response.json(); expect(jsonResp.error).to.not.be.null; }); }); @@ -303,7 +279,10 @@ async function waitForServer(url: string, timeout: number): Promise { while (Date.now() - startTime < timeout) { try { - const response = await axios.get(url, {timeout: 1000}); + const controller = new AbortController(); + const timer = setTimeout(() => controller.abort(), 1000); + const response = await fetch(url, {signal: controller.signal}); + clearTimeout(timer); if (response.status === 200) { return; } diff --git a/test/simctl.ts b/test/simctl.ts index d4400cf..7ff3611 100644 --- a/test/simctl.ts +++ b/test/simctl.ts @@ -1,69 +1,18 @@ import {execSync} from 'child_process'; -// track created simulators for cleanup -const createdSimulators: string[] = []; - -export function findIOSRuntime(majorVersion: string): string { - try { - const matchingLines = execSync('xcrun simctl list runtimes', {encoding: 'utf8'}) - .toString() - .split('\n') - .filter(line => line.includes(`iOS ${majorVersion}.`) && line.includes('com.apple.CoreSimulator.SimRuntime.')); - - const runtime = matchingLines[0]?.split(' ').pop(); - - if (!runtime) { - throw new Error(`No iOS ${majorVersion} runtime found`); +export function findSimulatorByName(name: string): string { + const output = execSync('xcrun simctl list devices --json', {encoding: 'utf8'}); + const data = JSON.parse(output); + + for (const runtime of Object.values(data.devices) as any[]) { + for (const device of runtime as any[]) { + if (device.name === name) { + return device.udid; + } } - - return runtime; - } catch (error) { - throw new Error(`Failed to find iOS ${majorVersion} runtime: ${error}`); } -} -export function createSimulator(name: string, deviceType: string, runtime: string): string { - try { - const stdout = execSync(`xcrun simctl create "${name}" "${deviceType}" "${runtime}"`, {encoding: 'utf8'}).toString(); - const simulatorId = stdout.trim(); - createdSimulators.push(simulatorId); - return simulatorId; - } catch (error) { - throw new Error(`Failed to create simulator: ${error}`); - } -} - -export function bootSimulator(simulatorId: string): void { - try { - execSync(`xcrun simctl boot "${simulatorId}"`, {stdio: 'inherit',}); - execSync(`xcrun simctl bootstatus "${simulatorId}"`, {stdio: 'ignore',}); - } catch (error) { - // Simulator might already be booted, check if it's actually an error - const errorMessage = (error as Error).message; - if (!errorMessage.includes('current state: Booted')) { - throw new Error(`Failed to boot simulator: ${error}`); - } - } -} - -export function waitForSimulatorReady(simulatorId: string, timeout: number = 30000): void { - const startTime = Date.now(); - - while (Date.now() - startTime < timeout) { - const stdout = execSync(`xcrun simctl list devices`, {encoding: 'utf8'}) - .split('\n') - .filter(line => line.includes(simulatorId)) - .filter(line => line.includes('(Booted)')); - if (stdout.length > 0) { - // verify simulator is actually ready by taking a screenshot - execSync(`xcrun simctl io "${simulatorId}" screenshot /dev/null 2>/dev/null`); - return; - } - - execSync('sleep 1'); - } - - throw new Error(`Simulator did not boot within ${timeout}ms`); + throw new Error(`Simulator "${name}" not found. Please create and boot it before running tests.`); } export function printAllLogsFromSimulator(simulatorId: string): void { @@ -84,45 +33,3 @@ export function shutdownSimulator(simulatorId: string): void { console.warn(`Warning: Failed to shutdown simulator ${simulatorId}: ${error}`); } } - -export function deleteSimulator(simulatorId: string): void { - try { - execSync(`xcrun simctl delete "${simulatorId}"`, {encoding: 'utf8'}); - removeFromTracking(simulatorId); - } catch (error) { - console.warn(`Warning: Failed to delete simulator ${simulatorId}: ${error}`); - } -} - -export function createAndLaunchSimulator(iosVersion: string, deviceType: string = 'iPhone 14'): string { - const runtime = findIOSRuntime(iosVersion); - const simulatorName = `Test-iOS-${iosVersion}`; - - // console.log(`Creating iOS ${iosVersion} simulator with runtime ${runtime}...`); - const simulatorId = createSimulator(simulatorName, deviceType, runtime); - - // console.log(`Booting simulator ${simulatorId}...`); - bootSimulator(simulatorId); - - // console.log('Waiting for simulator to be ready...'); - waitForSimulatorReady(simulatorId); - - // console.log(`Simulator ${simulatorId} is ready!`); - return simulatorId; -} - -export function cleanupSimulators(): void { - for (const simulatorId of [...createdSimulators]) { - shutdownSimulator(simulatorId); - deleteSimulator(simulatorId); - } - - createdSimulators.length = 0; -} - -export function removeFromTracking(simulatorId: string): void { - const index = createdSimulators.indexOf(simulatorId); - if (index > -1) { - createdSimulators.splice(index, 1); - } -} diff --git a/test/simulator.ts b/test/simulator.ts index 05ef36e..7348f47 100644 --- a/test/simulator.ts +++ b/test/simulator.ts @@ -3,24 +3,17 @@ import {execFileSync} from 'child_process'; import * as path from 'path'; import * as fs from 'fs'; import { - createAndLaunchSimulator, + findSimulatorByName, printAllLogsFromSimulator, shutdownSimulator, - deleteSimulator, - cleanupSimulators, - findIOSRuntime } from './simctl'; import {randomUUID} from "node:crypto"; import {mkdirSync} from "fs"; -import {UIElement, UIDumpResponse, DeviceInfoResponse, ForegroundAppResponse} from './types'; +import type {UIElement, UIDumpResponse, DeviceInfoResponse, ForegroundAppResponse} from './types'; const TEST_SERVER_URL = 'http://localhost:12001'; describe('iOS Simulator Tests', () => { - after(() => { - cleanupSimulators(); - }); - [/*'16',*/ /*'17', '18',*/ '26'].forEach((iosVersion) => { describe(`iOS ${iosVersion}`, () => { let simulatorId: string; @@ -28,13 +21,12 @@ describe('iOS Simulator Tests', () => { before(function () { this.timeout(180000); - // Check if runtime is available + const simulatorName = `Test-iOS-${iosVersion}`; try { - findIOSRuntime(iosVersion); - simulatorId = createAndLaunchSimulator(iosVersion); + simulatorId = findSimulatorByName(simulatorName); installDeviceKitAgent(simulatorId); } catch (error) { - console.log(`iOS ${iosVersion} runtime not available, skipping tests: ${error}`); + console.log(`Simulator "${simulatorName}" not found, skipping tests: ${error}`); this.skip(); } }); @@ -42,8 +34,6 @@ describe('iOS Simulator Tests', () => { after(() => { if (simulatorId) { printAllLogsFromSimulator(simulatorId); - shutdownSimulator(simulatorId); - deleteSimulator(simulatorId); } }); From 7fa59fa7851f34509393650b0d79b123ce34a24d Mon Sep 17 00:00:00 2001 From: gmegidish Date: Wed, 13 May 2026 15:23:02 +0200 Subject: [PATCH 2/4] test: simplifying tests --- test/README.md | 79 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 test/README.md diff --git a/test/README.md b/test/README.md new file mode 100644 index 0000000..6f115cb --- /dev/null +++ b/test/README.md @@ -0,0 +1,79 @@ +# Test Setup + +## iOS + +The iOS simulator tests look up an existing simulator by name — no simulator is created automatically. You must create and boot the simulator before running the tests. + +The expected simulator name for iOS 26 is `Test-iOS-26`. To create it: + +**1. Find the iOS 26 runtime identifier** + +```sh +xcrun simctl list runtimes +``` + +Look for a line containing `iOS 26.` and note its identifier (e.g. `com.apple.CoreSimulator.SimRuntime.iOS-26-0`). + +**2. Create the simulator** + +```sh +xcrun simctl create "Test-iOS-26" "iPhone 14" com.apple.CoreSimulator.SimRuntime.iOS-26-0 +``` + +**3. Boot it** + +```sh +xcrun simctl boot "Test-iOS-26" +``` + +**4. Verify it is visible to mobilecli** + +```sh +mobilecli devices +``` + +You should see the simulator listed with `"platform": "ios"` and `"type": "simulator"`. Once it's there, run the tests. + +--- + +## Android + +The Android tests pick the first available device reported by `mobilecli devices`. No emulator is created automatically — you need one already running before executing the tests. + +### Setting up an Android emulator + +**Prerequisites** + +- Android SDK installed and `ANDROID_HOME` set (e.g. `~/Library/Android/sdk`) +- `cmdline-tools` installed via Android Studio SDK Manager or `sdkmanager` + +**1. Download a system image** + +```sh +$ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager "system-images;android-36;google_apis_playstore;x86_64" +``` + +Replace `android-36` and `x86_64` with your target API level and architecture. On Apple Silicon use `arm64-v8a`. + +**2. Create an AVD** + +```sh +echo "no" | $ANDROID_HOME/cmdline-tools/latest/bin/avdmanager create avd \ + -n "test-android-36" \ + -k "system-images;android-36;google_apis_playstore;x86_64" \ + -d "pixel" +``` + +**3. Launch the emulator** + +```sh +$ANDROID_HOME/emulator/emulator -avd test-android-36 -no-snapshot-save & +``` + +**4. Verify it's visible to mobilecli** + +```sh +mobilecli devices +``` + +You should see the emulator listed with `"platform": "android"`. Once it's there, run the tests. From d83744af0111a59881dac525dadfca36375668e6 Mon Sep 17 00:00:00 2001 From: gmegidish Date: Sat, 16 May 2026 20:09:55 +0200 Subject: [PATCH 3/4] fixed commands --- .github/workflows/build.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index cf3ec82..38ee38d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -232,7 +232,7 @@ jobs: run: | cd test npm install --ignore-scripts - npm run test -- --grep "iOS ${{ matrix.ios_version }}" + npm run test:ios -- --grep "iOS ${{ matrix.ios_version }}" server_test: runs-on: ubuntu-latest @@ -256,7 +256,7 @@ jobs: run: | cd test npm install --ignore-scripts - npm run test -- --grep "server" + npm run test:server android_emulator_test: if: false @@ -325,7 +325,7 @@ jobs: disable-animations: true target: google_apis working-directory: test - script: npm run test -- --grep "Android API ${{ matrix.api_level }}" + script: npm run test:android -- --grep "Android API ${{ matrix.api_level }}" # - name: Run tests for Android API ${{ matrix.api_level }} # run: | From cf2ab3b93fb304dcf5110e4880b171aa2260d36b Mon Sep 17 00:00:00 2001 From: gmegidish Date: Sat, 16 May 2026 20:12:35 +0200 Subject: [PATCH 4/4] fixed commands --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 38ee38d..e8e20fb 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -325,7 +325,7 @@ jobs: disable-animations: true target: google_apis working-directory: test - script: npm run test:android -- --grep "Android API ${{ matrix.api_level }}" + script: npm run test:android -- --grep "Android API ${{ matrix.api_level }}" # - name: Run tests for Android API ${{ matrix.api_level }} # run: |