@react-native-harness/cli 1.0.0-alpha.10 → 1.0.0-alpha.12

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.
Files changed (47) hide show
  1. package/README.md +23 -4
  2. package/dist/bundlers/metro.d.ts.map +1 -1
  3. package/dist/bundlers/metro.js +8 -4
  4. package/dist/commands/test.d.ts +2 -1
  5. package/dist/commands/test.d.ts.map +1 -1
  6. package/dist/commands/test.js +19 -17
  7. package/dist/discovery/index.d.ts +3 -0
  8. package/dist/discovery/index.d.ts.map +1 -0
  9. package/dist/discovery/index.js +1 -0
  10. package/dist/discovery/testDiscovery.d.ts +11 -0
  11. package/dist/discovery/testDiscovery.d.ts.map +1 -0
  12. package/dist/discovery/testDiscovery.js +29 -0
  13. package/dist/errors/errorHandler.d.ts.map +1 -1
  14. package/dist/errors/errorHandler.js +12 -2
  15. package/dist/errors/errors.d.ts +2 -2
  16. package/dist/errors/errors.d.ts.map +1 -1
  17. package/dist/errors/errors.js +6 -1
  18. package/dist/index.js +20 -4
  19. package/dist/platforms/android/index.js +1 -1
  20. package/dist/platforms/platform-registry.d.ts.map +1 -1
  21. package/dist/platforms/platform-registry.js +2 -0
  22. package/dist/platforms/vega/build.d.ts +23 -0
  23. package/dist/platforms/vega/build.d.ts.map +1 -0
  24. package/dist/platforms/vega/build.js +55 -0
  25. package/dist/platforms/vega/device.d.ts +57 -0
  26. package/dist/platforms/vega/device.d.ts.map +1 -0
  27. package/dist/platforms/vega/device.js +206 -0
  28. package/dist/platforms/vega/index.d.ts +4 -0
  29. package/dist/platforms/vega/index.d.ts.map +1 -0
  30. package/dist/platforms/vega/index.js +75 -0
  31. package/dist/tsconfig.lib.tsbuildinfo +1 -1
  32. package/dist/utils/status-formatter.d.ts +27 -0
  33. package/dist/utils/status-formatter.d.ts.map +1 -0
  34. package/dist/utils/status-formatter.js +54 -0
  35. package/package.json +4 -4
  36. package/src/bundlers/metro.ts +8 -3
  37. package/src/commands/test.ts +33 -23
  38. package/src/discovery/index.ts +2 -0
  39. package/src/discovery/testDiscovery.ts +50 -0
  40. package/src/errors/errorHandler.ts +16 -4
  41. package/src/errors/errors.ts +9 -4
  42. package/src/index.ts +33 -5
  43. package/src/platforms/android/index.ts +1 -1
  44. package/src/platforms/platform-registry.ts +2 -0
  45. package/src/platforms/vega/build.ts +85 -0
  46. package/src/platforms/vega/device.ts +258 -0
  47. package/src/platforms/vega/index.ts +107 -0
package/README.md CHANGED
@@ -1,7 +1,26 @@
1
- # cli
1
+ ![harness-banner](https://react-native-harness.dev/harness-banner.jpg)
2
2
 
3
- This library was generated with [Nx](https://nx.dev).
3
+ ### Command Line Interface for React Native Harness
4
4
 
5
- ## Building
5
+ [![mit licence][license-badge]][license]
6
+ [![npm downloads][npm-downloads-badge]][npm-downloads]
7
+ [![Chat][chat-badge]][chat]
8
+ [![PRs Welcome][prs-welcome-badge]][prs-welcome]
6
9
 
7
- Run `nx build cli` to build the library.
10
+ Command-line interface that orchestrates test execution across iOS simulators and Android emulators, managing the entire testing workflow from bundling to result reporting.
11
+
12
+ ## Made with ❤️ at Callstack
13
+
14
+ `@react-native-harness/cli` is an open source project and will always remain free to use. If you think it's cool, please star it 🌟. [Callstack][callstack-readme-with-love] is a group of React and React Native geeks, contact us at [hello@callstack.com](mailto:hello@callstack.com) if you need any help with these or just want to say hi!
15
+
16
+ Like the project? ⚛️ [Join the team](https://callstack.com/careers/?utm_campaign=Senior_RN&utm_source=github&utm_medium=readme) who does amazing stuff for clients and drives React Native Open Source! 🔥
17
+
18
+ [callstack-readme-with-love]: https://callstack.com/?utm_source=github.com&utm_medium=referral&utm_campaign=react-native-harness&utm_term=readme-with-love
19
+ [license-badge]: https://img.shields.io/npm/l/@react-native-harness/cli?style=for-the-badge
20
+ [license]: https://github.com/callstackincubator/react-native-harness/blob/main/LICENSE
21
+ [npm-downloads-badge]: https://img.shields.io/npm/dm/@react-native-harness/cli?style=for-the-badge
22
+ [npm-downloads]: https://www.npmjs.com/package/@react-native-harness/cli
23
+ [prs-welcome-badge]: https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=for-the-badge
24
+ [prs-welcome]: ../../CONTRIBUTING.md
25
+ [chat-badge]: https://img.shields.io/discord/426714625279524876.svg?style=for-the-badge
26
+ [chat]: https://discord.gg/xgGt7KAjxv
@@ -1 +1 @@
1
- {"version":3,"file":"metro.d.ts","sourceRoot":"","sources":["../../src/bundlers/metro.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAWvD,eAAO,MAAM,QAAQ,GAAU,gBAAc,KAAG,OAAO,CAAC,YAAY,CAuCnE,CAAC;AAEF,eAAO,MAAM,YAAY,GACvB,aAAW,EACX,mBAAe,EACf,mBAAiB,KAChB,OAAO,CAAC,IAAI,CA4Bd,CAAC;AAEF,eAAO,MAAM,SAAS,QAAa,OAAO,CAAC,IAAI,CAE9C,CAAC"}
1
+ {"version":3,"file":"metro.d.ts","sourceRoot":"","sources":["../../src/bundlers/metro.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAYvD,eAAO,MAAM,QAAQ,GAAU,gBAAc,KAAG,OAAO,CAAC,YAAY,CA2CnE,CAAC;AAEF,eAAO,MAAM,YAAY,GACvB,aAAW,EACX,mBAAe,EACf,mBAAiB,KAChB,OAAO,CAAC,IAAI,CA4Bd,CAAC;AAEF,eAAO,MAAM,SAAS,QAAa,OAAO,CAAC,IAAI,CAE9C,CAAC"}
@@ -1,4 +1,4 @@
1
- import { getReactNativeCliPath, getExpoCliPath, getTimeoutSignal, spawn, SubprocessError, } from '@react-native-harness/tools';
1
+ import { getReactNativeCliPath, getExpoCliPath, getTimeoutSignal, spawn, SubprocessError, logger, } from '@react-native-harness/tools';
2
2
  const METRO_PORT = 8081;
3
3
  export const runMetro = async (isExpo = false) => {
4
4
  const metro = spawn('node', [
@@ -13,13 +13,17 @@ export const runMetro = async (isExpo = false) => {
13
13
  ...(isExpo && { EXPO_NO_METRO_WORKSPACE_ROOT: 'true' }),
14
14
  },
15
15
  });
16
- // Forward metro output to CLI output
16
+ // Forward metro output to logger
17
17
  metro.nodeChildProcess.then((childProcess) => {
18
18
  if (childProcess.stdout) {
19
- childProcess.stdout.pipe(process.stdout);
19
+ childProcess.stdout.on('data', (data) => {
20
+ logger.debug(data.toString().trim());
21
+ });
20
22
  }
21
23
  if (childProcess.stderr) {
22
- childProcess.stderr.pipe(process.stderr);
24
+ childProcess.stderr.on('data', (data) => {
25
+ logger.debug(data.toString().trim());
26
+ });
23
27
  }
24
28
  });
25
29
  metro.catch((error) => {
@@ -1,2 +1,3 @@
1
- export declare const testCommand: (runnerName?: string, pattern?: string) => Promise<void>;
1
+ import { type TestFilterOptions } from '../discovery/index.js';
2
+ export declare const testCommand: (runnerName?: string, options?: TestFilterOptions) => Promise<void>;
2
3
  //# sourceMappingURL=test.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"test.d.ts","sourceRoot":"","sources":["../../src/commands/test.ts"],"names":[],"mappings":"AA4KA,eAAO,MAAM,WAAW,GACtB,aAAa,MAAM,EACnB,UAAU,MAAM,KACf,OAAO,CAAC,IAAI,CA0Cd,CAAC"}
1
+ {"version":3,"file":"test.d.ts","sourceRoot":"","sources":["../../src/commands/test.ts"],"names":[],"mappings":"AAsBA,OAAO,EAEL,KAAK,iBAAiB,EACvB,MAAM,uBAAuB,CAAC;AA6J/B,eAAO,MAAM,WAAW,GACtB,aAAa,MAAM,EACnB,UAAS,iBAAsB,KAC9B,OAAO,CAAC,IAAI,CA0Cd,CAAC"}
@@ -1,11 +1,11 @@
1
1
  import { getBridgeServer, } from '@react-native-harness/bridge/server';
2
2
  import { getConfig, } from '@react-native-harness/config';
3
3
  import { getPlatformAdapter } from '../platforms/platform-registry.js';
4
- import { Glob } from 'glob';
5
- import { intro, logger, outro, spinner, progress, } from '@react-native-harness/tools';
4
+ import { intro, logger, outro, spinner } from '@react-native-harness/tools';
6
5
  import { BridgeTimeoutError } from '../errors/errors.js';
7
6
  import { assert } from '../utils.js';
8
7
  import { EnvironmentInitializationError, NoRunnerSpecifiedError, RpcClientError, RunnerNotFoundError, } from '../errors/errors.js';
8
+ import { discoverTestFiles, } from '../discovery/index.js';
9
9
  const setupEnvironment = async (context) => {
10
10
  const startSpinner = spinner();
11
11
  const platform = context.runner.platform;
@@ -33,28 +33,24 @@ const setupEnvironment = async (context) => {
33
33
  }
34
34
  startSpinner.stop(`"${context.runner.name}" (${platform}) runner started`);
35
35
  };
36
- const findTestFiles = async (context, pattern) => {
36
+ const findTestFiles = async (context, options = {}) => {
37
37
  const discoverSpinner = spinner();
38
38
  discoverSpinner.start('Discovering tests');
39
- const globPattern = pattern || context.config.include;
40
- const glob = new Glob(globPattern, {
41
- cwd: context.projectRoot,
42
- });
43
- context.testFiles = await glob.walk();
39
+ context.testFiles = await discoverTestFiles(context.projectRoot, context.config.include, options);
44
40
  discoverSpinner.stop(`Found ${context.testFiles.length} test files`);
45
41
  };
46
- const runTests = async (context) => {
42
+ const runTests = async (context, options = {}) => {
47
43
  const { bridge, environment, testFiles } = context;
48
44
  assert(bridge != null, 'Bridge not initialized');
49
45
  assert(environment != null, 'Environment not initialized');
50
46
  assert(testFiles != null, 'Test files not initialized');
51
- let runSpinner = progress({ style: 'block' });
47
+ let runSpinner = spinner();
52
48
  runSpinner.start('Running tests');
53
49
  let shouldRestart = false;
54
50
  for (const testFile of testFiles) {
55
51
  if (shouldRestart) {
56
- runSpinner = progress({ style: 'block' });
57
- runSpinner.message(`Restarting environment for next test file`);
52
+ runSpinner = spinner();
53
+ runSpinner.start(`Restarting environment for next test file`);
58
54
  await new Promise((resolve) => {
59
55
  bridge.once('ready', resolve);
60
56
  environment.restart();
@@ -65,14 +61,20 @@ const runTests = async (context) => {
65
61
  if (!client) {
66
62
  throw new RpcClientError('No RPC client available', 3001, 'No clients connected');
67
63
  }
68
- const result = await client.runTests(testFile);
64
+ // Pass only testNamePattern to runtime (file filtering already done)
65
+ const executionOptions = {
66
+ testNamePattern: options.testNamePattern,
67
+ };
68
+ const result = await client.runTests(testFile, executionOptions);
69
69
  context.results = [...(context.results ?? []), ...result.suites];
70
70
  shouldRestart = true;
71
+ runSpinner.stop(`Test file ${testFile} completed`);
71
72
  }
73
+ runSpinner.stop('Tests completed');
72
74
  };
73
75
  const cleanUp = async (context) => {
74
76
  if (context.bridge) {
75
- context.bridge.ws.close();
77
+ context.bridge.dispose();
76
78
  }
77
79
  if (context.environment) {
78
80
  await context.environment.dispose();
@@ -97,7 +99,7 @@ const hasFailedTests = (results) => {
97
99
  }
98
100
  return false;
99
101
  };
100
- export const testCommand = async (runnerName, pattern) => {
102
+ export const testCommand = async (runnerName, options = {}) => {
101
103
  intro('React Native Test Harness');
102
104
  const { config, projectRoot } = await getConfig(process.cwd());
103
105
  const selectedRunnerName = runnerName ?? config.defaultRunner;
@@ -117,8 +119,8 @@ export const testCommand = async (runnerName, pattern) => {
117
119
  };
118
120
  try {
119
121
  await setupEnvironment(context);
120
- await findTestFiles(context, pattern);
121
- await runTests(context);
122
+ await findTestFiles(context, options);
123
+ await runTests(context, options);
122
124
  assert(context.results != null, 'Results not initialized');
123
125
  config.reporter?.report(context.results);
124
126
  }
@@ -0,0 +1,3 @@
1
+ export { discoverTestFiles } from './testDiscovery.js';
2
+ export type { TestFilterOptions } from './testDiscovery.js';
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/discovery/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AACvD,YAAY,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC"}
@@ -0,0 +1 @@
1
+ export { discoverTestFiles } from './testDiscovery.js';
@@ -0,0 +1,11 @@
1
+ export type TestFilterOptions = {
2
+ testNamePattern?: string;
3
+ testPathPattern?: string;
4
+ testPathIgnorePatterns?: string[];
5
+ testMatch?: string[];
6
+ };
7
+ /**
8
+ * Discovers test files based on patterns and filtering options
9
+ */
10
+ export declare const discoverTestFiles: (projectRoot: string, configInclude: string | string[], options?: TestFilterOptions) => Promise<string[]>;
11
+ //# sourceMappingURL=testDiscovery.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"testDiscovery.d.ts","sourceRoot":"","sources":["../../src/discovery/testDiscovery.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,iBAAiB,GAAG;IAC9B,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,sBAAsB,CAAC,EAAE,MAAM,EAAE,CAAC;IAClC,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;CACtB,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,iBAAiB,GAC5B,aAAa,MAAM,EACnB,eAAe,MAAM,GAAG,MAAM,EAAE,EAChC,UAAS,iBAAsB,KAC9B,OAAO,CAAC,MAAM,EAAE,CAiClB,CAAC"}
@@ -0,0 +1,29 @@
1
+ import { Glob } from 'glob';
2
+ /**
3
+ * Discovers test files based on patterns and filtering options
4
+ */
5
+ export const discoverTestFiles = async (projectRoot, configInclude, options = {}) => {
6
+ // Priority: testMatch > configInclude
7
+ const patterns = options.testMatch || configInclude;
8
+ const patternArray = Array.isArray(patterns) ? patterns : [patterns];
9
+ // Glob discovery
10
+ const allFiles = [];
11
+ for (const pattern of patternArray) {
12
+ const glob = new Glob(pattern, { cwd: projectRoot, nodir: true });
13
+ const files = await glob.walk();
14
+ allFiles.push(...files);
15
+ }
16
+ // Remove duplicates
17
+ let uniqueFiles = [...new Set(allFiles)];
18
+ // Apply testPathPattern filtering
19
+ if (options.testPathPattern) {
20
+ const regex = new RegExp(options.testPathPattern);
21
+ uniqueFiles = uniqueFiles.filter((file) => regex.test(file));
22
+ }
23
+ // Apply testPathIgnorePatterns filtering
24
+ if (options.testPathIgnorePatterns?.length) {
25
+ const ignoreRegexes = options.testPathIgnorePatterns.map((p) => new RegExp(p));
26
+ uniqueFiles = uniqueFiles.filter((file) => !ignoreRegexes.some((regex) => regex.test(file)));
27
+ }
28
+ return uniqueFiles;
29
+ };
@@ -1 +1 @@
1
- {"version":3,"file":"errorHandler.d.ts","sourceRoot":"","sources":["../../src/errors/errorHandler.ts"],"names":[],"mappings":"AAiBA,eAAO,MAAM,WAAW,GAAI,OAAO,OAAO,KAAG,IAkL5C,CAAC"}
1
+ {"version":3,"file":"errorHandler.d.ts","sourceRoot":"","sources":["../../src/errors/errorHandler.ts"],"names":[],"mappings":"AAiBA,eAAO,MAAM,WAAW,GAAI,OAAO,OAAO,KAAG,IA8L5C,CAAC"}
@@ -104,16 +104,26 @@ export const handleError = (error) => {
104
104
  }
105
105
  else if (error instanceof AppNotInstalledError) {
106
106
  console.error(`\n❌ App Not Installed`);
107
- console.error(`\nThe app "${error.bundleId}" is not installed on ${error.platform === 'ios' ? 'simulator' : 'emulator'} "${error.deviceName}".`);
107
+ const deviceType = error.platform === 'ios'
108
+ ? 'simulator'
109
+ : error.platform === 'android'
110
+ ? 'emulator'
111
+ : 'virtual device';
112
+ console.error(`\nThe app "${error.bundleId}" is not installed on ${deviceType} "${error.deviceName}".`);
108
113
  console.error(`\nTo resolve this issue:`);
109
114
  if (error.platform === 'ios') {
110
115
  console.error(` • Build and install the app: npx react-native run-ios --simulator="${error.deviceName}"`);
111
116
  console.error(` • Or install from Xcode: Open ios/*.xcworkspace and run the project`);
112
117
  }
113
- else {
118
+ else if (error.platform === 'android') {
114
119
  console.error(` • Build and install the app: npx react-native run-android`);
115
120
  console.error(` • Or build manually: ./gradlew assembleDebug && adb install android/app/build/outputs/apk/debug/app-debug.apk`);
116
121
  }
122
+ else if (error.platform === 'vega') {
123
+ console.error(` • Build the Vega app: npm run build:app`);
124
+ console.error(` • Install the app: kepler device install-app -p <path-to-vpkg> --device "${error.deviceName}"`);
125
+ console.error(` • Or use the combined command: kepler run-kepler <path-to-vpkg> "${error.bundleId}" -d "${error.deviceName}"`);
126
+ }
117
127
  console.error(`\nPlease install the app and try running the tests again.`);
118
128
  }
119
129
  else if (error instanceof BundlingFailedError) {
@@ -35,8 +35,8 @@ export declare class BridgeTimeoutError extends Error {
35
35
  export declare class AppNotInstalledError extends Error {
36
36
  readonly deviceName: string;
37
37
  readonly bundleId: string;
38
- readonly platform: 'ios' | 'android';
39
- constructor(deviceName: string, bundleId: string, platform: 'ios' | 'android');
38
+ readonly platform: 'ios' | 'android' | 'vega';
39
+ constructor(deviceName: string, bundleId: string, platform: 'ios' | 'android' | 'vega');
40
40
  }
41
41
  export declare class BundlingFailedError extends Error {
42
42
  readonly modulePath: string;
@@ -1 +1 @@
1
- {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/errors/errors.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAC;AAEhE,qBAAa,sBAAuB,SAAQ,KAAK;gBACnC,gBAAgB,EAAE,gBAAgB,EAAE;IAKhD,gBAAgB,EAAE,gBAAgB,EAAE,CAAC;CACtC;AAED,qBAAa,mBAAoB,SAAQ,KAAK;gBAChC,UAAU,EAAE,MAAM,EAAE,gBAAgB,EAAE,gBAAgB,EAAE;IAMpE,UAAU,EAAE,MAAM,CAAC;IACnB,gBAAgB,EAAE,gBAAgB,EAAE,CAAC;CACtC;AAED,qBAAa,8BAA+B,SAAQ,KAAK;gBAErD,OAAO,EAAE,MAAM,EACf,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,MAAM,EAChB,OAAO,CAAC,EAAE,MAAM;IAUlB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,qBAAa,kBAAmB,SAAQ,KAAK;gBAEzC,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,OAAO,EACd,SAAS,CAAC,EAAE,MAAM,EAClB,QAAQ,CAAC,EAAE,MAAM;IAenB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,aAAa,EAAE,OAAO,CAAC;CACxB;AAED,qBAAa,cAAe,SAAQ,KAAK;gBAC3B,OAAO,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,EAAE,gBAAgB,CAAC,EAAE,MAAM;IAS3E,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED,qBAAa,kBAAmB,SAAQ,KAAK;aAEzB,OAAO,EAAE,MAAM;aACf,UAAU,EAAE,MAAM;aAClB,QAAQ,EAAE,MAAM;gBAFhB,OAAO,EAAE,MAAM,EACf,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,MAAM;CAOnC;AAED,qBAAa,oBAAqB,SAAQ,KAAK;aAE3B,UAAU,EAAE,MAAM;aAClB,QAAQ,EAAE,MAAM;aAChB,QAAQ,EAAE,KAAK,GAAG,SAAS;gBAF3B,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,KAAK,GAAG,SAAS;CAS9C;AAED,qBAAa,mBAAoB,SAAQ,KAAK;aAE1B,UAAU,EAAE,MAAM;aAClB,MAAM,EAAE,MAAM;gBADd,UAAU,EAAE,MAAM,EAClB,MAAM,EAAE,MAAM;CAKjC"}
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/errors/errors.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAC;AAEhE,qBAAa,sBAAuB,SAAQ,KAAK;gBACnC,gBAAgB,EAAE,gBAAgB,EAAE;IAKhD,gBAAgB,EAAE,gBAAgB,EAAE,CAAC;CACtC;AAED,qBAAa,mBAAoB,SAAQ,KAAK;gBAChC,UAAU,EAAE,MAAM,EAAE,gBAAgB,EAAE,gBAAgB,EAAE;IAMpE,UAAU,EAAE,MAAM,CAAC;IACnB,gBAAgB,EAAE,gBAAgB,EAAE,CAAC;CACtC;AAED,qBAAa,8BAA+B,SAAQ,KAAK;gBAErD,OAAO,EAAE,MAAM,EACf,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,MAAM,EAChB,OAAO,CAAC,EAAE,MAAM;IAUlB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,qBAAa,kBAAmB,SAAQ,KAAK;gBAEzC,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,OAAO,EACd,SAAS,CAAC,EAAE,MAAM,EAClB,QAAQ,CAAC,EAAE,MAAM;IAenB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,aAAa,EAAE,OAAO,CAAC;CACxB;AAED,qBAAa,cAAe,SAAQ,KAAK;gBAC3B,OAAO,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,EAAE,gBAAgB,CAAC,EAAE,MAAM;IAS3E,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED,qBAAa,kBAAmB,SAAQ,KAAK;aAEzB,OAAO,EAAE,MAAM;aACf,UAAU,EAAE,MAAM;aAClB,QAAQ,EAAE,MAAM;gBAFhB,OAAO,EAAE,MAAM,EACf,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,MAAM;CAOnC;AAED,qBAAa,oBAAqB,SAAQ,KAAK;aAE3B,UAAU,EAAE,MAAM;aAClB,QAAQ,EAAE,MAAM;aAChB,QAAQ,EAAE,KAAK,GAAG,SAAS,GAAG,MAAM;gBAFpC,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,KAAK,GAAG,SAAS,GAAG,MAAM;CAcvD;AAED,qBAAa,mBAAoB,SAAQ,KAAK;aAE1B,UAAU,EAAE,MAAM;aAClB,MAAM,EAAE,MAAM;gBADd,UAAU,EAAE,MAAM,EAClB,MAAM,EAAE,MAAM;CAKjC"}
@@ -74,7 +74,12 @@ export class AppNotInstalledError extends Error {
74
74
  bundleId;
75
75
  platform;
76
76
  constructor(deviceName, bundleId, platform) {
77
- super(`App "${bundleId}" is not installed on ${platform === 'ios' ? 'simulator' : 'emulator'} "${deviceName}"`);
77
+ const deviceType = platform === 'ios'
78
+ ? 'simulator'
79
+ : platform === 'android'
80
+ ? 'emulator'
81
+ : 'virtual device';
82
+ super(`App "${bundleId}" is not installed on ${deviceType} "${deviceName}"`);
78
83
  this.deviceName = deviceName;
79
84
  this.bundleId = bundleId;
80
85
  this.platform = platform;
package/dist/index.js CHANGED
@@ -10,19 +10,35 @@ const __dirname = dirname(__filename);
10
10
  const packageJsonPath = join(__dirname, '../package.json');
11
11
  const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
12
12
  const program = new Command();
13
- logger.setVerbose(true);
14
13
  program
15
14
  .name('react-native-harness')
16
15
  .description('React Native Test Harness - A comprehensive testing framework for React Native applications')
17
- .version(packageJson.version);
16
+ .version(packageJson.version)
17
+ .option('-v, --verbose', 'Enable verbose logging')
18
+ .hook('preAction', (thisCommand) => {
19
+ // Handle global verbose option
20
+ const opts = thisCommand.optsWithGlobals();
21
+ if (opts.verbose) {
22
+ logger.setVerbose(true);
23
+ }
24
+ });
18
25
  program
19
26
  .command('test')
20
27
  .description('Run tests using the specified runner')
21
28
  .argument('[runner]', 'test runner name (uses defaultRunner from config if not specified)')
22
29
  .argument('[pattern]', 'glob pattern to match test files (uses config.include if not specified)')
23
- .action(async (runner, pattern) => {
30
+ .option('-t, --testNamePattern <pattern>', 'Run only tests with names matching regex pattern')
31
+ .option('--testPathPattern <pattern>', 'Run only test files with paths matching regex pattern')
32
+ .option('--testPathIgnorePatterns <patterns...>', 'Ignore test files matching these patterns')
33
+ .option('--testMatch <patterns...>', 'Override config.include with these glob patterns')
34
+ .action(async (runner, pattern, options) => {
24
35
  try {
25
- await testCommand(runner, pattern);
36
+ // Convert CLI pattern argument to testMatch option
37
+ const mergedOptions = {
38
+ ...options,
39
+ testMatch: pattern ? [pattern] : options.testMatch,
40
+ };
41
+ await testCommand(runner, mergedOptions);
26
42
  }
27
43
  catch (error) {
28
44
  handleError(error);
@@ -41,7 +41,7 @@ const androidPlatformAdapter = {
41
41
  logger.debug('App running');
42
42
  return {
43
43
  restart: async () => {
44
- await runApp(runner.deviceId, runner.bundleId, runner.activityName);
44
+ await runApp(deviceId, runner.bundleId, runner.activityName);
45
45
  },
46
46
  dispose: async () => {
47
47
  await killApp(deviceId, runner.bundleId);
@@ -1 +1 @@
1
- {"version":3,"file":"platform-registry.d.ts","sourceRoot":"","sources":["../../src/platforms/platform-registry.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAWxD,eAAO,MAAM,kBAAkB,GAC7B,cAAc,MAAM,KACnB,OAAO,CAAC,eAAe,CAUzB,CAAC"}
1
+ {"version":3,"file":"platform-registry.d.ts","sourceRoot":"","sources":["../../src/platforms/platform-registry.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAaxD,eAAO,MAAM,kBAAkB,GAC7B,cAAc,MAAM,KACnB,OAAO,CAAC,eAAe,CAUzB,CAAC"}
@@ -1,10 +1,12 @@
1
1
  import androidPlatformAdapter from './android/index.js';
2
2
  import iosPlatformAdapter from './ios/index.js';
3
3
  import webPlatformAdapter from './web/index.js';
4
+ import vegaPlatformAdapter from './vega/index.js';
4
5
  const platformAdapters = {
5
6
  android: androidPlatformAdapter,
6
7
  ios: iosPlatformAdapter,
7
8
  web: webPlatformAdapter,
9
+ vega: vegaPlatformAdapter,
8
10
  };
9
11
  export const getPlatformAdapter = async (platformName) => {
10
12
  if (!(platformName in platformAdapters)) {
@@ -0,0 +1,23 @@
1
+ export type VegaBuildTarget = 'sim_tv_x86_64' | 'sim_tv_aarch64';
2
+ export type VegaBuildType = 'Debug' | 'Release';
3
+ /**
4
+ * Build Vega app and produce .vpkg file
5
+ */
6
+ export declare const buildVegaApp: (buildType?: VegaBuildType, target?: VegaBuildTarget) => Promise<void>;
7
+ /**
8
+ * Clean build artifacts
9
+ */
10
+ export declare const cleanBuild: () => Promise<void>;
11
+ /**
12
+ * Get the expected .vpkg file path based on build configuration
13
+ */
14
+ export declare const getVpkgPath: (appName: string, buildType?: VegaBuildType, target?: VegaBuildTarget) => string;
15
+ /**
16
+ * Launch an already installed app on specified Vega virtual device
17
+ */
18
+ export declare const runApp: (deviceId: string, bundleId: string) => Promise<void>;
19
+ /**
20
+ * Kill/terminate app on specified Vega virtual device
21
+ */
22
+ export declare const killApp: (deviceId: string, bundleId: string) => Promise<void>;
23
+ //# sourceMappingURL=build.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"build.d.ts","sourceRoot":"","sources":["../../../src/platforms/vega/build.ts"],"names":[],"mappings":"AAGA,MAAM,MAAM,eAAe,GAAG,eAAe,GAAG,gBAAgB,CAAC;AACjE,MAAM,MAAM,aAAa,GAAG,OAAO,GAAG,SAAS,CAAC;AAEhD;;GAEG;AACH,eAAO,MAAM,YAAY,GACvB,YAAW,aAAyB,EACpC,SAAS,eAAe,KACvB,OAAO,CAAC,IAAI,CAYd,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,UAAU,QAAa,OAAO,CAAC,IAAI,CAE/C,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,WAAW,GACtB,SAAS,MAAM,EACf,YAAW,aAAyB,EACpC,SAAQ,eAAiC,KACxC,MAUF,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,MAAM,GACjB,UAAU,MAAM,EAChB,UAAU,MAAM,KACf,OAAO,CAAC,IAAI,CASd,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,OAAO,GAClB,UAAU,MAAM,EAChB,UAAU,MAAM,KACf,OAAO,CAAC,IAAI,CASd,CAAC"}
@@ -0,0 +1,55 @@
1
+ import path from 'node:path';
2
+ import { spawn } from '@react-native-harness/tools';
3
+ /**
4
+ * Build Vega app and produce .vpkg file
5
+ */
6
+ export const buildVegaApp = async (buildType = 'Release', target) => {
7
+ const args = ['run', 'build:app'];
8
+ if (buildType) {
9
+ args.push('-b', buildType);
10
+ }
11
+ if (target) {
12
+ args.push('-t', target);
13
+ }
14
+ await spawn('npm', args);
15
+ };
16
+ /**
17
+ * Clean build artifacts
18
+ */
19
+ export const cleanBuild = async () => {
20
+ await spawn('kepler', ['clean']);
21
+ };
22
+ /**
23
+ * Get the expected .vpkg file path based on build configuration
24
+ */
25
+ export const getVpkgPath = (appName, buildType = 'Release', target = 'sim_tv_x86_64') => {
26
+ const buildTypeStr = buildType.toLowerCase();
27
+ const vpkgFileName = `${appName}_${target}.vpkg`;
28
+ return path.join(process.cwd(), 'build', `${target}-${buildTypeStr}`, vpkgFileName);
29
+ };
30
+ /**
31
+ * Launch an already installed app on specified Vega virtual device
32
+ */
33
+ export const runApp = async (deviceId, bundleId) => {
34
+ await spawn('kepler', [
35
+ 'device',
36
+ 'launch-app',
37
+ '--device',
38
+ deviceId,
39
+ '--appName',
40
+ bundleId,
41
+ ]);
42
+ };
43
+ /**
44
+ * Kill/terminate app on specified Vega virtual device
45
+ */
46
+ export const killApp = async (deviceId, bundleId) => {
47
+ await spawn('kepler', [
48
+ 'device',
49
+ 'terminate-app',
50
+ '--device',
51
+ deviceId,
52
+ '--appName',
53
+ bundleId,
54
+ ]);
55
+ };
@@ -0,0 +1,57 @@
1
+ export type VegaVirtualDeviceStatus = 'running' | 'stopped';
2
+ /**
3
+ * List all available Vega virtual devices
4
+ * Returns array of device identifiers that can be used with kepler commands
5
+ */
6
+ export declare const listVegaDevices: () => Promise<string[]>;
7
+ /**
8
+ * Check if a specific Vega virtual device is connected/available
9
+ */
10
+ export declare const isVegaDeviceConnected: (deviceId: string) => Promise<boolean>;
11
+ /**
12
+ * Check if an app is installed on the specified Vega virtual device
13
+ */
14
+ export declare const isAppInstalled: (deviceId: string, bundleId: string) => Promise<boolean>;
15
+ /**
16
+ * Check if an app is currently running on the specified Vega virtual device
17
+ */
18
+ export declare const isAppRunning: (deviceId: string, bundleId: string) => Promise<boolean>;
19
+ /**
20
+ * Install app on specified Vega virtual device using .vpkg file
21
+ */
22
+ export declare const installApp: (deviceId: string, vpkgPath: string) => Promise<void>;
23
+ /**
24
+ * Terminate app on specified Vega virtual device
25
+ */
26
+ export declare const terminateApp: (deviceId: string, bundleId: string) => Promise<void>;
27
+ /**
28
+ * Uninstall app from specified Vega virtual device
29
+ */
30
+ export declare const uninstallApp: (deviceId: string, bundleId: string) => Promise<void>;
31
+ /**
32
+ * Start port forwarding for debugging on specified Vega virtual device
33
+ */
34
+ export declare const startPortForwarding: (deviceId: string, port: number, forward?: boolean) => Promise<void>;
35
+ /**
36
+ * Stop port forwarding on specified Vega virtual device
37
+ */
38
+ export declare const stopPortForwarding: (deviceId: string, port: number, forward?: boolean) => Promise<void>;
39
+ /**
40
+ * Get status of a specific Vega virtual device
41
+ * Note: Vega CLI might manage virtual devices globally, so this checks if the device is available
42
+ */
43
+ export declare const getVegaDeviceStatus: (deviceId: string) => Promise<VegaVirtualDeviceStatus>;
44
+ /**
45
+ * Start Vega Virtual Device
46
+ * Note: Vega might manage virtual devices globally, this starts the virtual device system
47
+ */
48
+ export declare const startVirtualDevice: () => Promise<void>;
49
+ /**
50
+ * Stop Vega Virtual Device
51
+ */
52
+ export declare const stopVirtualDevice: () => Promise<void>;
53
+ /**
54
+ * Combined install and run command for specified Vega virtual device (Vega-specific convenience method)
55
+ */
56
+ export declare const runKepler: (deviceId: string, vpkgPath: string, bundleId: string) => Promise<void>;
57
+ //# sourceMappingURL=device.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"device.d.ts","sourceRoot":"","sources":["../../../src/platforms/vega/device.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,uBAAuB,GAAG,SAAS,GAAG,SAAS,CAAC;AAE5D;;;GAGG;AACH,eAAO,MAAM,eAAe,QAAa,OAAO,CAAC,MAAM,EAAE,CAwBxD,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,qBAAqB,GAChC,UAAU,MAAM,KACf,OAAO,CAAC,OAAO,CAYjB,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,cAAc,GACzB,UAAU,MAAM,EAChB,UAAU,MAAM,KACf,OAAO,CAAC,OAAO,CAcjB,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,YAAY,GACvB,UAAU,MAAM,EAChB,UAAU,MAAM,KACf,OAAO,CAAC,OAAO,CAcjB,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,UAAU,GACrB,UAAU,MAAM,EAChB,UAAU,MAAM,KACf,OAAO,CAAC,IAAI,CASd,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,YAAY,GACvB,UAAU,MAAM,EAChB,UAAU,MAAM,KACf,OAAO,CAAC,IAAI,CASd,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,YAAY,GACvB,UAAU,MAAM,EAChB,UAAU,MAAM,KACf,OAAO,CAAC,IAAI,CASd,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,mBAAmB,GAC9B,UAAU,MAAM,EAChB,MAAM,MAAM,EACZ,UAAS,OAAc,KACtB,OAAO,CAAC,IAAI,CAWd,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,kBAAkB,GAC7B,UAAU,MAAM,EAChB,MAAM,MAAM,EACZ,UAAS,OAAc,KACtB,OAAO,CAAC,IAAI,CAWd,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,mBAAmB,GAC9B,UAAU,MAAM,KACf,OAAO,CAAC,uBAAuB,CAkBjC,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,kBAAkB,QAAa,OAAO,CAAC,IAAI,CAqBvD,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,iBAAiB,QAAa,OAAO,CAAC,IAAI,CAEtD,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,SAAS,GACpB,UAAU,MAAM,EAChB,UAAU,MAAM,EAChB,UAAU,MAAM,KACf,OAAO,CAAC,IAAI,CAEd,CAAC"}