@react-native-harness/cli 1.0.0-alpha.9 → 1.0.0-canary.1761046904277
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +23 -4
- package/dist/bundlers/metro.d.ts.map +1 -1
- package/dist/bundlers/metro.js +15 -5
- package/dist/commands/test.d.ts +2 -1
- package/dist/commands/test.d.ts.map +1 -1
- package/dist/commands/test.js +23 -19
- package/dist/discovery/index.d.ts +3 -0
- package/dist/discovery/index.d.ts.map +1 -0
- package/dist/discovery/index.js +1 -0
- package/dist/discovery/testDiscovery.d.ts +11 -0
- package/dist/discovery/testDiscovery.d.ts.map +1 -0
- package/dist/discovery/testDiscovery.js +29 -0
- package/dist/errors/errorHandler.d.ts.map +1 -1
- package/dist/errors/errorHandler.js +27 -3
- package/dist/errors/errors.d.ts +6 -2
- package/dist/errors/errors.d.ts.map +1 -1
- package/dist/errors/errors.js +14 -1
- package/dist/external.d.ts +9 -0
- package/dist/external.d.ts.map +1 -0
- package/dist/external.js +25 -0
- package/dist/index.js +20 -4
- package/dist/platforms/android/emulator.d.ts +0 -1
- package/dist/platforms/android/emulator.d.ts.map +1 -1
- package/dist/platforms/android/emulator.js +26 -20
- package/dist/platforms/android/index.js +1 -1
- package/dist/platforms/ios/build.d.ts.map +1 -1
- package/dist/platforms/ios/build.js +5 -1
- package/dist/platforms/ios/device.d.ts +6 -2
- package/dist/platforms/ios/device.d.ts.map +1 -1
- package/dist/platforms/ios/simulator.d.ts.map +1 -1
- package/dist/platforms/ios/simulator.js +8 -3
- package/dist/platforms/platform-registry.d.ts.map +1 -1
- package/dist/platforms/platform-registry.js +3 -1
- package/dist/platforms/vega/build.d.ts +23 -0
- package/dist/platforms/vega/build.d.ts.map +1 -0
- package/dist/platforms/vega/build.js +55 -0
- package/dist/platforms/vega/device.d.ts +57 -0
- package/dist/platforms/vega/device.d.ts.map +1 -0
- package/dist/platforms/vega/device.js +206 -0
- package/dist/platforms/vega/index.d.ts +4 -0
- package/dist/platforms/vega/index.d.ts.map +1 -0
- package/dist/platforms/vega/index.js +75 -0
- package/dist/process.js +1 -1
- package/dist/tsconfig.lib.tsbuildinfo +1 -1
- package/dist/utils/status-formatter.d.ts +27 -0
- package/dist/utils/status-formatter.d.ts.map +1 -0
- package/dist/utils/status-formatter.js +54 -0
- package/dist/utils.d.ts +1 -0
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +15 -0
- package/eslint.config.mjs +1 -0
- package/package.json +21 -6
- package/src/bundlers/metro.ts +17 -4
- package/src/commands/test.ts +39 -25
- package/src/discovery/index.ts +2 -0
- package/src/discovery/testDiscovery.ts +50 -0
- package/src/errors/errorHandler.ts +34 -4
- package/src/errors/errors.ts +16 -4
- package/src/external.ts +44 -0
- package/src/index.ts +33 -5
- package/src/platforms/android/emulator.ts +26 -28
- package/src/platforms/android/index.ts +1 -1
- package/src/platforms/ios/build.ts +3 -0
- package/src/platforms/ios/device.ts +5 -2
- package/src/platforms/ios/simulator.ts +8 -3
- package/src/platforms/platform-registry.ts +3 -1
- package/src/platforms/vega/build.ts +85 -0
- package/src/platforms/vega/device.ts +258 -0
- package/src/platforms/vega/index.ts +107 -0
- package/src/process.ts +1 -1
- package/src/utils.ts +17 -0
- package/tsconfig.json +3 -0
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
AppNotInstalledError,
|
|
14
14
|
BridgeTimeoutError,
|
|
15
15
|
BundlingFailedError,
|
|
16
|
+
MetroPortUnavailableError,
|
|
16
17
|
} from './errors.js';
|
|
17
18
|
|
|
18
19
|
export const handleError = (error: unknown): void => {
|
|
@@ -128,10 +129,14 @@ export const handleError = (error: unknown): void => {
|
|
|
128
129
|
console.error(`\nPlease check your bridge connection and try again.`);
|
|
129
130
|
} else if (error instanceof AppNotInstalledError) {
|
|
130
131
|
console.error(`\n❌ App Not Installed`);
|
|
132
|
+
const deviceType =
|
|
133
|
+
error.platform === 'ios'
|
|
134
|
+
? 'simulator'
|
|
135
|
+
: error.platform === 'android'
|
|
136
|
+
? 'emulator'
|
|
137
|
+
: 'virtual device';
|
|
131
138
|
console.error(
|
|
132
|
-
`\nThe app "${error.bundleId}" is not installed on ${
|
|
133
|
-
error.platform === 'ios' ? 'simulator' : 'emulator'
|
|
134
|
-
} "${error.deviceName}".`
|
|
139
|
+
`\nThe app "${error.bundleId}" is not installed on ${deviceType} "${error.deviceName}".`
|
|
135
140
|
);
|
|
136
141
|
console.error(`\nTo resolve this issue:`);
|
|
137
142
|
if (error.platform === 'ios') {
|
|
@@ -141,13 +146,21 @@ export const handleError = (error: unknown): void => {
|
|
|
141
146
|
console.error(
|
|
142
147
|
` • Or install from Xcode: Open ios/*.xcworkspace and run the project`
|
|
143
148
|
);
|
|
144
|
-
} else {
|
|
149
|
+
} else if (error.platform === 'android') {
|
|
145
150
|
console.error(
|
|
146
151
|
` • Build and install the app: npx react-native run-android`
|
|
147
152
|
);
|
|
148
153
|
console.error(
|
|
149
154
|
` • Or build manually: ./gradlew assembleDebug && adb install android/app/build/outputs/apk/debug/app-debug.apk`
|
|
150
155
|
);
|
|
156
|
+
} else if (error.platform === 'vega') {
|
|
157
|
+
console.error(` • Build the Vega app: npm run build:app`);
|
|
158
|
+
console.error(
|
|
159
|
+
` • Install the app: kepler device install-app -p <path-to-vpkg> --device "${error.deviceName}"`
|
|
160
|
+
);
|
|
161
|
+
console.error(
|
|
162
|
+
` • Or use the combined command: kepler run-kepler <path-to-vpkg> "${error.bundleId}" -d "${error.deviceName}"`
|
|
163
|
+
);
|
|
151
164
|
}
|
|
152
165
|
console.error(`\nPlease install the app and try running the tests again.`);
|
|
153
166
|
} else if (error instanceof BundlingFailedError) {
|
|
@@ -189,6 +202,23 @@ export const handleError = (error: unknown): void => {
|
|
|
189
202
|
console.error(
|
|
190
203
|
`\nIf the app needs more time to start, consider increasing the timeout in the configuration.`
|
|
191
204
|
);
|
|
205
|
+
} else if (error instanceof MetroPortUnavailableError) {
|
|
206
|
+
console.error(`\n❌ Metro Port Unavailable`);
|
|
207
|
+
console.error(`\nPort ${error.port} is already in use or unavailable.`);
|
|
208
|
+
console.error(`\nThis usually indicates that:`);
|
|
209
|
+
console.error(` • Another Metro bundler instance is already running`);
|
|
210
|
+
console.error(` • Another application is using port ${error.port}`);
|
|
211
|
+
console.error(` • The port is blocked by a firewall or security software`);
|
|
212
|
+
console.error(`\nTo resolve this issue:`);
|
|
213
|
+
console.error(` • Stop any running Metro bundler instances`);
|
|
214
|
+
console.error(
|
|
215
|
+
` • Check for other applications using port ${error.port}: lsof -i :${error.port}`
|
|
216
|
+
);
|
|
217
|
+
console.error(` • Kill the process using the port: kill -9 <PID>`);
|
|
218
|
+
console.error(
|
|
219
|
+
` • Or use a different port by updating your Metro configuration`
|
|
220
|
+
);
|
|
221
|
+
console.error(`\nPlease free up the port and try again.`);
|
|
192
222
|
} else {
|
|
193
223
|
console.error(`\n❌ Unexpected Error`);
|
|
194
224
|
console.error(error);
|
package/src/errors/errors.ts
CHANGED
|
@@ -97,12 +97,17 @@ export class AppNotInstalledError extends Error {
|
|
|
97
97
|
constructor(
|
|
98
98
|
public readonly deviceName: string,
|
|
99
99
|
public readonly bundleId: string,
|
|
100
|
-
public readonly platform: 'ios' | 'android'
|
|
100
|
+
public readonly platform: 'ios' | 'android' | 'vega'
|
|
101
101
|
) {
|
|
102
|
+
const deviceType =
|
|
103
|
+
platform === 'ios'
|
|
104
|
+
? 'simulator'
|
|
105
|
+
: platform === 'android'
|
|
106
|
+
? 'emulator'
|
|
107
|
+
: 'virtual device';
|
|
108
|
+
|
|
102
109
|
super(
|
|
103
|
-
`App "${bundleId}" is not installed on ${
|
|
104
|
-
platform === 'ios' ? 'simulator' : 'emulator'
|
|
105
|
-
} "${deviceName}"`
|
|
110
|
+
`App "${bundleId}" is not installed on ${deviceType} "${deviceName}"`
|
|
106
111
|
);
|
|
107
112
|
this.name = 'AppNotInstalledError';
|
|
108
113
|
}
|
|
@@ -117,3 +122,10 @@ export class BundlingFailedError extends Error {
|
|
|
117
122
|
this.name = 'BundlingFailedError';
|
|
118
123
|
}
|
|
119
124
|
}
|
|
125
|
+
|
|
126
|
+
export class MetroPortUnavailableError extends Error {
|
|
127
|
+
constructor(public readonly port: number) {
|
|
128
|
+
super(`Metro port ${port} is not available`);
|
|
129
|
+
this.name = 'MetroPortUnavailableError';
|
|
130
|
+
}
|
|
131
|
+
}
|
package/src/external.ts
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { TestRunnerConfig } from '@react-native-harness/config';
|
|
2
|
+
import { Environment } from './platforms/platform-adapter.js';
|
|
3
|
+
import {
|
|
4
|
+
BridgeServer,
|
|
5
|
+
getBridgeServer,
|
|
6
|
+
} from '@react-native-harness/bridge/server';
|
|
7
|
+
import { BridgeTimeoutError } from './errors/errors.js';
|
|
8
|
+
import { getPlatformAdapter } from './platforms/platform-registry.js';
|
|
9
|
+
|
|
10
|
+
export type Harness = {
|
|
11
|
+
environment: Environment;
|
|
12
|
+
bridge: BridgeServer;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export const getHarness = async (
|
|
16
|
+
runner: TestRunnerConfig
|
|
17
|
+
): Promise<Harness> => {
|
|
18
|
+
const bridgeTimeout = 60000;
|
|
19
|
+
const platformAdapter = await getPlatformAdapter(runner.platform);
|
|
20
|
+
const serverBridge = await getBridgeServer({
|
|
21
|
+
port: 3001,
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
const readyPromise = new Promise<void>((resolve, reject) => {
|
|
25
|
+
const timeout = setTimeout(() => {
|
|
26
|
+
reject(
|
|
27
|
+
new BridgeTimeoutError(bridgeTimeout, runner.name, runner.platform)
|
|
28
|
+
);
|
|
29
|
+
}, bridgeTimeout);
|
|
30
|
+
|
|
31
|
+
serverBridge.once('ready', () => {
|
|
32
|
+
clearTimeout(timeout);
|
|
33
|
+
resolve();
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
const environment = await platformAdapter.getEnvironment(runner);
|
|
38
|
+
await readyPromise;
|
|
39
|
+
|
|
40
|
+
return {
|
|
41
|
+
environment,
|
|
42
|
+
bridge: serverBridge,
|
|
43
|
+
};
|
|
44
|
+
};
|
package/src/index.ts
CHANGED
|
@@ -13,14 +13,20 @@ const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
|
|
|
13
13
|
|
|
14
14
|
const program = new Command();
|
|
15
15
|
|
|
16
|
-
logger.setVerbose(true);
|
|
17
|
-
|
|
18
16
|
program
|
|
19
17
|
.name('react-native-harness')
|
|
20
18
|
.description(
|
|
21
19
|
'React Native Test Harness - A comprehensive testing framework for React Native applications'
|
|
22
20
|
)
|
|
23
|
-
.version(packageJson.version)
|
|
21
|
+
.version(packageJson.version)
|
|
22
|
+
.option('-v, --verbose', 'Enable verbose logging')
|
|
23
|
+
.hook('preAction', (thisCommand) => {
|
|
24
|
+
// Handle global verbose option
|
|
25
|
+
const opts = thisCommand.optsWithGlobals();
|
|
26
|
+
if (opts.verbose) {
|
|
27
|
+
logger.setVerbose(true);
|
|
28
|
+
}
|
|
29
|
+
});
|
|
24
30
|
|
|
25
31
|
program
|
|
26
32
|
.command('test')
|
|
@@ -33,9 +39,31 @@ program
|
|
|
33
39
|
'[pattern]',
|
|
34
40
|
'glob pattern to match test files (uses config.include if not specified)'
|
|
35
41
|
)
|
|
36
|
-
.
|
|
42
|
+
.option(
|
|
43
|
+
'-t, --testNamePattern <pattern>',
|
|
44
|
+
'Run only tests with names matching regex pattern'
|
|
45
|
+
)
|
|
46
|
+
.option(
|
|
47
|
+
'--testPathPattern <pattern>',
|
|
48
|
+
'Run only test files with paths matching regex pattern'
|
|
49
|
+
)
|
|
50
|
+
.option(
|
|
51
|
+
'--testPathIgnorePatterns <patterns...>',
|
|
52
|
+
'Ignore test files matching these patterns'
|
|
53
|
+
)
|
|
54
|
+
.option(
|
|
55
|
+
'--testMatch <patterns...>',
|
|
56
|
+
'Override config.include with these glob patterns'
|
|
57
|
+
)
|
|
58
|
+
.action(async (runner, pattern, options) => {
|
|
37
59
|
try {
|
|
38
|
-
|
|
60
|
+
// Convert CLI pattern argument to testMatch option
|
|
61
|
+
const mergedOptions = {
|
|
62
|
+
...options,
|
|
63
|
+
testMatch: pattern ? [pattern] : options.testMatch,
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
await testCommand(runner, mergedOptions);
|
|
39
67
|
} catch (error) {
|
|
40
68
|
handleError(error);
|
|
41
69
|
process.exit(1);
|
|
@@ -7,7 +7,13 @@ export const getEmulatorNameFromId = async (
|
|
|
7
7
|
emulatorId: string
|
|
8
8
|
): Promise<string | null> => {
|
|
9
9
|
try {
|
|
10
|
-
const { stdout } = await spawn('adb', [
|
|
10
|
+
const { stdout } = await spawn('adb', [
|
|
11
|
+
'-s',
|
|
12
|
+
emulatorId,
|
|
13
|
+
'emu',
|
|
14
|
+
'avd',
|
|
15
|
+
'name',
|
|
16
|
+
]);
|
|
11
17
|
const avdName = stdout.split('\n')[0].trim();
|
|
12
18
|
return avdName || null;
|
|
13
19
|
} catch {
|
|
@@ -48,7 +54,13 @@ export const getEmulatorStatus = async (
|
|
|
48
54
|
|
|
49
55
|
try {
|
|
50
56
|
// Check if device is fully booted by checking boot completion
|
|
51
|
-
const { stdout } = await spawn('adb', [
|
|
57
|
+
const { stdout } = await spawn('adb', [
|
|
58
|
+
'-s',
|
|
59
|
+
emulatorId,
|
|
60
|
+
'shell',
|
|
61
|
+
'getprop',
|
|
62
|
+
'sys.boot_completed',
|
|
63
|
+
]);
|
|
52
64
|
const bootCompleted = stdout.trim() === '1';
|
|
53
65
|
return bootCompleted ? 'running' : 'loading';
|
|
54
66
|
} catch {
|
|
@@ -69,17 +81,17 @@ export const runEmulator = async (name: string): Promise<ChildProcess> => {
|
|
|
69
81
|
return;
|
|
70
82
|
} else if (status === 'loading') {
|
|
71
83
|
// Check again in 2 seconds
|
|
72
|
-
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
84
|
+
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
73
85
|
await checkStatus();
|
|
74
86
|
} else {
|
|
75
87
|
// Still stopped, check again in 1 second
|
|
76
|
-
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
88
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
77
89
|
await checkStatus();
|
|
78
90
|
}
|
|
79
91
|
};
|
|
80
92
|
|
|
81
93
|
// Start checking status after a brief delay to allow emulator to start
|
|
82
|
-
await new Promise(resolve => setTimeout(resolve, 3000));
|
|
94
|
+
await new Promise((resolve) => setTimeout(resolve, 3000));
|
|
83
95
|
await checkStatus();
|
|
84
96
|
|
|
85
97
|
return nodeChildProcess;
|
|
@@ -105,7 +117,15 @@ export const isAppInstalled = async (
|
|
|
105
117
|
bundleId: string
|
|
106
118
|
): Promise<boolean> => {
|
|
107
119
|
try {
|
|
108
|
-
const { stdout } = await spawn('adb', [
|
|
120
|
+
const { stdout } = await spawn('adb', [
|
|
121
|
+
'-s',
|
|
122
|
+
emulatorId,
|
|
123
|
+
'shell',
|
|
124
|
+
'pm',
|
|
125
|
+
'list',
|
|
126
|
+
'packages',
|
|
127
|
+
bundleId,
|
|
128
|
+
]);
|
|
109
129
|
return stdout.trim() !== '';
|
|
110
130
|
} catch {
|
|
111
131
|
return false;
|
|
@@ -115,25 +135,3 @@ export const isAppInstalled = async (
|
|
|
115
135
|
export const reversePort = async (port: number): Promise<void> => {
|
|
116
136
|
await spawn('adb', ['reverse', `tcp:${port}`, `tcp:${port}`]);
|
|
117
137
|
};
|
|
118
|
-
|
|
119
|
-
export const getEmulatorScreenshot = async (
|
|
120
|
-
emulatorId: string,
|
|
121
|
-
name: string = `${emulatorId}-${new Date()
|
|
122
|
-
.toISOString()
|
|
123
|
-
.replace(/:/g, '-')
|
|
124
|
-
.replace(/\//g, '-')}.png`
|
|
125
|
-
): Promise<string> => {
|
|
126
|
-
// Use screencap to save directly to device, then pull the file
|
|
127
|
-
const devicePath = '/sdcard/screenshot.png';
|
|
128
|
-
|
|
129
|
-
// Take screenshot and save to device
|
|
130
|
-
await spawn('adb', ['-s', emulatorId, 'shell', 'screencap', '-p', devicePath]);
|
|
131
|
-
|
|
132
|
-
// Pull the file from device to local
|
|
133
|
-
await spawn('adb', ['-s', emulatorId, 'pull', devicePath, name]);
|
|
134
|
-
|
|
135
|
-
// Clean up the file on device
|
|
136
|
-
await spawn('adb', ['-s', emulatorId, 'shell', 'rm', devicePath]);
|
|
137
|
-
|
|
138
|
-
return name;
|
|
139
|
-
};
|
|
@@ -69,7 +69,7 @@ const androidPlatformAdapter: PlatformAdapter = {
|
|
|
69
69
|
|
|
70
70
|
return {
|
|
71
71
|
restart: async () => {
|
|
72
|
-
await runApp(
|
|
72
|
+
await runApp(deviceId, runner.bundleId, runner.activityName);
|
|
73
73
|
},
|
|
74
74
|
dispose: async () => {
|
|
75
75
|
await killApp(deviceId, runner.bundleId);
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { spawn, spawnAndForget } from '@react-native-harness/tools';
|
|
2
2
|
|
|
3
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
3
4
|
export const listDevices = async (): Promise<any> => {
|
|
4
5
|
const { stdout } = await spawn('xcrun', [
|
|
5
6
|
'simctl',
|
|
@@ -13,6 +14,7 @@ export const listDevices = async (): Promise<any> => {
|
|
|
13
14
|
export const getDeviceByName = async (
|
|
14
15
|
simulatorName: string,
|
|
15
16
|
systemVersion: string
|
|
17
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
16
18
|
): Promise<any | null> => {
|
|
17
19
|
const devices = await listDevices();
|
|
18
20
|
const expectedRuntimeId = `com.apple.CoreSimulator.SimRuntime.iOS-${systemVersion.replace(
|
|
@@ -27,6 +29,7 @@ export const getDeviceByName = async (
|
|
|
27
29
|
}
|
|
28
30
|
|
|
29
31
|
const runtimeDevices = devices.devices[runtime];
|
|
32
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
30
33
|
const device = runtimeDevices.find((d: any) => d.name === simulatorName);
|
|
31
34
|
|
|
32
35
|
if (device) {
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { spawn, spawnAndForget } from '@react-native-harness/tools';
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
4
|
+
type Device = any;
|
|
5
|
+
|
|
6
|
+
export const listDevices = async (): Promise<{ devices: Device[] }> => {
|
|
4
7
|
const { stdout } = await spawn('xcrun', [
|
|
5
8
|
'simctl',
|
|
6
9
|
'list',
|
|
@@ -12,7 +15,7 @@ export const listDevices = async (): Promise<any> => {
|
|
|
12
15
|
|
|
13
16
|
export const getDeviceByName = async (
|
|
14
17
|
simulatorName: string
|
|
15
|
-
): Promise<
|
|
18
|
+
): Promise<Device | null> => {
|
|
16
19
|
const devices = await listDevices();
|
|
17
20
|
|
|
18
21
|
for (const runtime in devices.devices) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { spawn } from '@react-native-harness/tools';
|
|
1
|
+
import { spawn, SubprocessError } from '@react-native-harness/tools';
|
|
2
2
|
|
|
3
3
|
export type IOSSimulatorStatus = 'stopped' | 'loading' | 'running';
|
|
4
4
|
|
|
@@ -25,6 +25,7 @@ export const getSimulatorDeviceId = async (
|
|
|
25
25
|
return null;
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
28
29
|
const device = runtime.find((d: any) => d.name === simulatorName);
|
|
29
30
|
|
|
30
31
|
if (device) {
|
|
@@ -57,6 +58,7 @@ export const getAvailableSimulators = async (): Promise<
|
|
|
57
58
|
for (const runtime in devices.devices) {
|
|
58
59
|
if (runtime.includes('iOS')) {
|
|
59
60
|
const runtimeDevices = devices.devices[runtime];
|
|
61
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
60
62
|
runtimeDevices.forEach((device: any) => {
|
|
61
63
|
if (device.isAvailable) {
|
|
62
64
|
simulators.push({
|
|
@@ -90,6 +92,7 @@ export const getSimulatorStatus = async (
|
|
|
90
92
|
for (const runtime in devices.devices) {
|
|
91
93
|
if (runtime.includes('iOS')) {
|
|
92
94
|
const runtimeDevices = devices.devices[runtime];
|
|
95
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
93
96
|
const device = runtimeDevices.find((d: any) => d.udid === udid);
|
|
94
97
|
|
|
95
98
|
if (device) {
|
|
@@ -114,9 +117,10 @@ export const getSimulatorStatus = async (
|
|
|
114
117
|
export const runSimulator = async (udid: string): Promise<void> => {
|
|
115
118
|
try {
|
|
116
119
|
await spawn('xcrun', ['simctl', 'boot', udid]);
|
|
117
|
-
} catch (bootError
|
|
120
|
+
} catch (bootError) {
|
|
118
121
|
// Ignore if simulator is already booted
|
|
119
122
|
if (
|
|
123
|
+
bootError instanceof SubprocessError &&
|
|
120
124
|
!bootError.stderr?.includes(
|
|
121
125
|
'Unable to boot device in current state: Booted'
|
|
122
126
|
)
|
|
@@ -153,9 +157,10 @@ export const stopSimulator = async (udid: string): Promise<void> => {
|
|
|
153
157
|
const stopSimulatorById = async (udid: string): Promise<void> => {
|
|
154
158
|
try {
|
|
155
159
|
await spawn('xcrun', ['simctl', 'shutdown', udid]);
|
|
156
|
-
} catch (shutdownError
|
|
160
|
+
} catch (shutdownError) {
|
|
157
161
|
// Ignore if simulator is already shut down
|
|
158
162
|
if (
|
|
163
|
+
shutdownError instanceof SubprocessError &&
|
|
159
164
|
!shutdownError.stderr?.includes(
|
|
160
165
|
'Unable to shutdown device in current state: Shutdown'
|
|
161
166
|
)
|
|
@@ -2,11 +2,13 @@ import { PlatformAdapter } from './platform-adapter.js';
|
|
|
2
2
|
import androidPlatformAdapter from './android/index.js';
|
|
3
3
|
import iosPlatformAdapter from './ios/index.js';
|
|
4
4
|
import webPlatformAdapter from './web/index.js';
|
|
5
|
+
import vegaPlatformAdapter from './vega/index.js';
|
|
5
6
|
|
|
6
7
|
const platformAdapters = {
|
|
7
8
|
android: androidPlatformAdapter,
|
|
8
9
|
ios: iosPlatformAdapter,
|
|
9
10
|
web: webPlatformAdapter,
|
|
11
|
+
vega: vegaPlatformAdapter,
|
|
10
12
|
};
|
|
11
13
|
|
|
12
14
|
export const getPlatformAdapter = async (
|
|
@@ -18,7 +20,7 @@ export const getPlatformAdapter = async (
|
|
|
18
20
|
|
|
19
21
|
try {
|
|
20
22
|
return platformAdapters[platformName as keyof typeof platformAdapters];
|
|
21
|
-
} catch
|
|
23
|
+
} catch {
|
|
22
24
|
throw new Error(`Platform adapter for ${platformName} not found`);
|
|
23
25
|
}
|
|
24
26
|
};
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { spawn } from '@react-native-harness/tools';
|
|
3
|
+
|
|
4
|
+
export type VegaBuildTarget = 'sim_tv_x86_64' | 'sim_tv_aarch64';
|
|
5
|
+
export type VegaBuildType = 'Debug' | 'Release';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Build Vega app and produce .vpkg file
|
|
9
|
+
*/
|
|
10
|
+
export const buildVegaApp = async (
|
|
11
|
+
buildType: VegaBuildType = 'Release',
|
|
12
|
+
target?: VegaBuildTarget
|
|
13
|
+
): Promise<void> => {
|
|
14
|
+
const args = ['run', 'build:app'];
|
|
15
|
+
|
|
16
|
+
if (buildType) {
|
|
17
|
+
args.push('-b', buildType);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
if (target) {
|
|
21
|
+
args.push('-t', target);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
await spawn('npm', args);
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Clean build artifacts
|
|
29
|
+
*/
|
|
30
|
+
export const cleanBuild = async (): Promise<void> => {
|
|
31
|
+
await spawn('kepler', ['clean']);
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Get the expected .vpkg file path based on build configuration
|
|
36
|
+
*/
|
|
37
|
+
export const getVpkgPath = (
|
|
38
|
+
appName: string,
|
|
39
|
+
buildType: VegaBuildType = 'Release',
|
|
40
|
+
target: VegaBuildTarget = 'sim_tv_x86_64'
|
|
41
|
+
): string => {
|
|
42
|
+
const buildTypeStr = buildType.toLowerCase();
|
|
43
|
+
const vpkgFileName = `${appName}_${target}.vpkg`;
|
|
44
|
+
|
|
45
|
+
return path.join(
|
|
46
|
+
process.cwd(),
|
|
47
|
+
'build',
|
|
48
|
+
`${target}-${buildTypeStr}`,
|
|
49
|
+
vpkgFileName
|
|
50
|
+
);
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Launch an already installed app on specified Vega virtual device
|
|
55
|
+
*/
|
|
56
|
+
export const runApp = async (
|
|
57
|
+
deviceId: string,
|
|
58
|
+
bundleId: string
|
|
59
|
+
): Promise<void> => {
|
|
60
|
+
await spawn('kepler', [
|
|
61
|
+
'device',
|
|
62
|
+
'launch-app',
|
|
63
|
+
'--device',
|
|
64
|
+
deviceId,
|
|
65
|
+
'--appName',
|
|
66
|
+
bundleId,
|
|
67
|
+
]);
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Kill/terminate app on specified Vega virtual device
|
|
72
|
+
*/
|
|
73
|
+
export const killApp = async (
|
|
74
|
+
deviceId: string,
|
|
75
|
+
bundleId: string
|
|
76
|
+
): Promise<void> => {
|
|
77
|
+
await spawn('kepler', [
|
|
78
|
+
'device',
|
|
79
|
+
'terminate-app',
|
|
80
|
+
'--device',
|
|
81
|
+
deviceId,
|
|
82
|
+
'--appName',
|
|
83
|
+
bundleId,
|
|
84
|
+
]);
|
|
85
|
+
};
|