@farmslot/adapter-rn 0.15.0 → 0.16.0

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 (53) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/README.md +15 -10
  3. package/bridge-runtime/console-forwarder.cjs +485 -0
  4. package/bridge-runtime/lib/bridge-errors.cjs +108 -0
  5. package/bridge-runtime/lib/cdp-eval.cjs +118 -0
  6. package/bridge-runtime/lib/config.cjs +68 -0
  7. package/bridge-runtime/lib/console-format.cjs +54 -0
  8. package/bridge-runtime/lib/devtools-proxy.cjs +169 -0
  9. package/bridge-runtime/lib/issue-capture.cjs +86 -0
  10. package/bridge-runtime/lib/match-bridge-target.cjs +89 -0
  11. package/bridge-runtime/lib/target-discovery.cjs +436 -0
  12. package/bridge-runtime/lib/ws-client.cjs +119 -0
  13. package/dist/cli.js +1 -1
  14. package/dist/cli.js.map +1 -1
  15. package/dist/devices.d.ts +16 -0
  16. package/dist/devices.d.ts.map +1 -0
  17. package/dist/devices.js +131 -0
  18. package/dist/devices.js.map +1 -0
  19. package/dist/doctor.js +1 -1
  20. package/dist/doctor.js.map +1 -1
  21. package/dist/fingerprint-baseline.d.ts +8 -0
  22. package/dist/fingerprint-baseline.d.ts.map +1 -0
  23. package/dist/fingerprint-baseline.js +68 -0
  24. package/dist/fingerprint-baseline.js.map +1 -0
  25. package/dist/frame-metrics.d.ts +26 -0
  26. package/dist/frame-metrics.d.ts.map +1 -0
  27. package/dist/frame-metrics.js +134 -0
  28. package/dist/frame-metrics.js.map +1 -0
  29. package/dist/index.d.ts +7 -0
  30. package/dist/index.d.ts.map +1 -1
  31. package/dist/index.js +6 -0
  32. package/dist/index.js.map +1 -1
  33. package/dist/metro-env.d.ts +11 -0
  34. package/dist/metro-env.d.ts.map +1 -0
  35. package/dist/metro-env.js +29 -0
  36. package/dist/metro-env.js.map +1 -0
  37. package/dist/source-freshness.d.ts +11 -0
  38. package/dist/source-freshness.d.ts.map +1 -0
  39. package/dist/source-freshness.js +98 -0
  40. package/dist/source-freshness.js.map +1 -0
  41. package/dist/tool-paths.d.ts +13 -0
  42. package/dist/tool-paths.d.ts.map +1 -0
  43. package/dist/tool-paths.js +125 -0
  44. package/dist/tool-paths.js.map +1 -0
  45. package/dist/video-recorder.d.ts +7 -0
  46. package/dist/video-recorder.d.ts.map +1 -0
  47. package/dist/video-recorder.js +250 -0
  48. package/dist/video-recorder.js.map +1 -0
  49. package/metro/coalesce-metro-log.cjs +28 -0
  50. package/metro/launch-metro.cjs +84 -0
  51. package/metro/metro-config.cjs +92 -0
  52. package/metro/metro-log-generation.cjs +116 -0
  53. package/package.json +26 -7
@@ -0,0 +1,98 @@
1
+ // Fingerprint of the app source a running bundle was built from. In a git
2
+ // checkout it is HEAD plus the diff and untracked files under the source paths;
3
+ // outside git it hashes the paths themselves. Env files always count. The
4
+ // project supplies both lists.
5
+ import { execFileSync } from 'node:child_process';
6
+ import { createHash } from 'node:crypto';
7
+ import fs from 'node:fs';
8
+ import path from 'node:path';
9
+ import { checkFingerprintBaseline, recordFingerprintBaseline, } from './fingerprint-baseline.js';
10
+ export function sourceFingerprint(projectRoot, inputs) {
11
+ const hash = createHash('sha256');
12
+ const head = gitHead(projectRoot);
13
+ if (head === null) {
14
+ hash.update('non-git\0');
15
+ for (const relative of inputs.paths) {
16
+ hashPath(hash, projectRoot, relative);
17
+ }
18
+ }
19
+ else {
20
+ hash.update(head);
21
+ hash.update(git(projectRoot, ['diff', '--no-ext-diff', '--binary', 'HEAD', '--', ...inputs.paths]));
22
+ const untracked = git(projectRoot, [
23
+ 'ls-files',
24
+ '--others',
25
+ '--exclude-standard',
26
+ '-z',
27
+ '--',
28
+ ...inputs.paths,
29
+ ])
30
+ .toString('utf8')
31
+ .split('\0')
32
+ .filter(Boolean)
33
+ .sort();
34
+ for (const relative of untracked) {
35
+ hash.update(`untracked\0${relative}\0`);
36
+ hash.update(fs.readFileSync(path.join(projectRoot, relative)));
37
+ hash.update('\0');
38
+ }
39
+ }
40
+ for (const relative of inputs.envFiles) {
41
+ const absolute = path.join(projectRoot, relative);
42
+ hash.update(`env\0${relative}\0`);
43
+ hash.update(fs.existsSync(absolute) ? fs.readFileSync(absolute) : '<absent>');
44
+ hash.update('\0');
45
+ }
46
+ return hash.digest('hex');
47
+ }
48
+ export function sourceCheck(projectRoot, inputs, markerPath) {
49
+ return checkFingerprintBaseline(markerPath, sourceFingerprint(projectRoot, inputs));
50
+ }
51
+ export function recordSourceBaseline(projectRoot, inputs, markerPath, expectedFingerprint) {
52
+ return recordFingerprintBaseline(markerPath, expectedFingerprint, () => sourceFingerprint(projectRoot, inputs));
53
+ }
54
+ function git(cwd, args) {
55
+ return execFileSync('git', args, {
56
+ cwd,
57
+ encoding: 'buffer',
58
+ maxBuffer: 128 * 1024 * 1024,
59
+ stdio: ['ignore', 'pipe', 'ignore'],
60
+ });
61
+ }
62
+ // null when the project is not a git checkout (or git is unavailable): the
63
+ // fingerprint then hashes the source paths directly.
64
+ function gitHead(cwd) {
65
+ try {
66
+ return git(cwd, ['rev-parse', 'HEAD']);
67
+ }
68
+ catch (error) {
69
+ const status = error.status;
70
+ const code = error.code;
71
+ if (typeof status === 'number' || code === 'ENOENT')
72
+ return null;
73
+ throw error;
74
+ }
75
+ }
76
+ function hashPath(hash, root, relative) {
77
+ const absolute = path.join(root, relative);
78
+ if (!fs.existsSync(absolute)) {
79
+ hash.update(`absent\0${relative}\0`);
80
+ return;
81
+ }
82
+ const stat = fs.lstatSync(absolute);
83
+ if (stat.isDirectory()) {
84
+ hash.update(`directory\0${relative}\0`);
85
+ for (const child of fs.readdirSync(absolute).sort()) {
86
+ hashPath(hash, root, path.join(relative, child));
87
+ }
88
+ return;
89
+ }
90
+ if (stat.isSymbolicLink()) {
91
+ hash.update(`symlink\0${relative}\0${fs.readlinkSync(absolute)}\0`);
92
+ return;
93
+ }
94
+ hash.update(`file\0${relative}\0`);
95
+ hash.update(fs.readFileSync(absolute));
96
+ hash.update('\0');
97
+ }
98
+ //# sourceMappingURL=source-freshness.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"source-freshness.js","sourceRoot":"","sources":["../src/source-freshness.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,13 @@
1
+ export type MobileTool = 'adb' | 'idb';
2
+ export interface MobileToolPaths {
3
+ adb: string | null;
4
+ idb: string | null;
5
+ }
6
+ export declare function mobileToolRecovery(tool: MobileTool): string;
7
+ export declare function mobileToolEnvName(tool: MobileTool): string;
8
+ export declare function resolveMobileToolPath(tool: MobileTool, { required }?: {
9
+ required?: boolean;
10
+ }): string | null;
11
+ export declare function resolveAndExportMobileToolPaths(platform?: string): MobileToolPaths;
12
+ export declare function clearMobileToolPathCache(): void;
13
+ //# sourceMappingURL=tool-paths.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tool-paths.d.ts","sourceRoot":"","sources":["../src/tool-paths.ts"],"names":[],"mappings":"AAMA,MAAM,MAAM,UAAU,GAAG,KAAK,GAAG,KAAK,CAAC;AAEvC,MAAM,WAAW,eAAe;IAC9B,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IACnB,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;CACpB;AAcD,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,UAAU,GAAG,MAAM,CAG3D;AAED,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,UAAU,GAAG,MAAM,CAG1D;AAED,wBAAgB,qBAAqB,CACnC,IAAI,EAAE,UAAU,EAChB,EAAE,QAAgB,EAAE,GAAE;IAAE,QAAQ,CAAC,EAAE,OAAO,CAAA;CAAO,GAChD,MAAM,GAAG,IAAI,CAmBf;AAED,wBAAgB,+BAA+B,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,eAAe,CAWlF;AAED,wBAAgB,wBAAwB,IAAI,IAAI,CAE/C"}
@@ -0,0 +1,125 @@
1
+ // Locate executable adb and idb clients, cache their paths, and export them to
2
+ // child processes through RECIPE_RN_ADB_PATH / RECIPE_RN_IDB_PATH.
3
+ import { accessSync, constants as fsConstants, readdirSync, realpathSync, statSync } from 'node:fs';
4
+ import os from 'node:os';
5
+ import path from 'node:path';
6
+ const TOOL_ENV = {
7
+ adb: 'RECIPE_RN_ADB_PATH',
8
+ idb: 'RECIPE_RN_IDB_PATH',
9
+ };
10
+ const TOOL_RECOVERY = {
11
+ adb: 'brew install android-platform-tools',
12
+ idb: 'brew install idb-companion pipx python@3.13 && pipx install fb-idb --python python3.13',
13
+ };
14
+ const resolvedPaths = new Map();
15
+ export function mobileToolRecovery(tool) {
16
+ assertTool(tool);
17
+ return TOOL_RECOVERY[tool];
18
+ }
19
+ export function mobileToolEnvName(tool) {
20
+ assertTool(tool);
21
+ return TOOL_ENV[tool];
22
+ }
23
+ export function resolveMobileToolPath(tool, { required = false } = {}) {
24
+ assertTool(tool);
25
+ const cached = resolvedPaths.get(tool);
26
+ if (cached && executablePath(cached))
27
+ return cached;
28
+ for (const candidate of candidatesFor(tool)) {
29
+ const resolved = executablePath(candidate);
30
+ if (!resolved)
31
+ continue;
32
+ resolvedPaths.set(tool, resolved);
33
+ process.env[TOOL_ENV[tool]] = resolved;
34
+ return resolved;
35
+ }
36
+ resolvedPaths.delete(tool);
37
+ if (!required)
38
+ return null;
39
+ const label = tool === 'adb' ? 'Android SDK Platform-Tools (adb)' : 'Facebook idb client';
40
+ throw new Error(`${label} is required but no executable was found.\n Next: ${TOOL_RECOVERY[tool]}`);
41
+ }
42
+ export function resolveAndExportMobileToolPaths(platform) {
43
+ if (platform === 'android') {
44
+ return { adb: resolveMobileToolPath('adb', { required: true }), idb: null };
45
+ }
46
+ if (platform === 'ios') {
47
+ return { adb: null, idb: resolveMobileToolPath('idb') };
48
+ }
49
+ return {
50
+ adb: resolveMobileToolPath('adb'),
51
+ idb: resolveMobileToolPath('idb'),
52
+ };
53
+ }
54
+ export function clearMobileToolPathCache() {
55
+ resolvedPaths.clear();
56
+ }
57
+ function candidatesFor(tool) {
58
+ const override = process.env[TOOL_ENV[tool]];
59
+ const pathCandidates = String(process.env.PATH ?? '')
60
+ .split(path.delimiter)
61
+ .filter(Boolean)
62
+ .map((directory) => path.join(directory, tool));
63
+ const candidates = override ? [override] : [];
64
+ candidates.push(...pathCandidates);
65
+ if (tool === 'adb') {
66
+ for (const root of [
67
+ process.env.ANDROID_HOME,
68
+ process.env.ANDROID_SDK_ROOT,
69
+ path.join(os.homedir(), 'Library/Android/sdk'),
70
+ path.join(os.homedir(), 'Android/Sdk'),
71
+ ]) {
72
+ if (root)
73
+ candidates.push(path.join(root, 'platform-tools', 'adb'));
74
+ }
75
+ }
76
+ else {
77
+ if (process.env.IDB_PATH)
78
+ candidates.push(process.env.IDB_PATH);
79
+ candidates.push('/opt/homebrew/bin/idb', '/usr/local/bin/idb', path.join(os.homedir(), '.local/bin/idb'));
80
+ candidates.push(...pythonUserCandidates());
81
+ }
82
+ return [...new Set(candidates.map((candidate) => path.resolve(candidate)))];
83
+ }
84
+ // Filesystem errors that only mean "this candidate is not there or not usable".
85
+ const UNUSABLE_CANDIDATE_CODES = new Set(['ENOENT', 'ENOTDIR', 'EACCES', 'EPERM', 'ELOOP']);
86
+ function isUnusableCandidate(error) {
87
+ return UNUSABLE_CANDIDATE_CODES.has(String(error?.code));
88
+ }
89
+ function pythonUserCandidates() {
90
+ const root = path.join(os.homedir(), 'Library/Python');
91
+ try {
92
+ return readdirSync(root, { withFileTypes: true })
93
+ .filter((entry) => entry.isDirectory())
94
+ .map((entry) => path.join(root, entry.name, 'bin/idb'));
95
+ }
96
+ catch (error) {
97
+ // No readable ~/Library/Python (Linux, or no pip --user installs): no candidates here.
98
+ if (isUnusableCandidate(error))
99
+ return [];
100
+ throw error;
101
+ }
102
+ }
103
+ // A candidate that does not exist, dangles, or is not executable is simply not a
104
+ // match; the caller tries the next one.
105
+ function executablePath(candidate) {
106
+ try {
107
+ const resolved = realpathSync(candidate);
108
+ const stat = statSync(resolved);
109
+ if (!stat.isFile())
110
+ return null;
111
+ accessSync(resolved, fsConstants.X_OK);
112
+ return path.resolve(resolved);
113
+ }
114
+ catch (error) {
115
+ if (isUnusableCandidate(error))
116
+ return null;
117
+ throw error;
118
+ }
119
+ }
120
+ function assertTool(tool) {
121
+ if (tool !== 'adb' && tool !== 'idb') {
122
+ throw new Error(`Unsupported mobile device tool: ${String(tool)}.`);
123
+ }
124
+ }
125
+ //# sourceMappingURL=tool-paths.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tool-paths.js","sourceRoot":"","sources":["../src/tool-paths.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ import { type VideoRecorder } from '@farmslot/recipe-runner';
2
+ export declare const ANDROID_RECORDING_TARGET_PREFIX = "android-device:";
3
+ export declare const IOS_SIMULATOR_RECORDING_TARGET_PREFIX = "ios-simulator:";
4
+ export declare function androidScreenrecordArgs(remotePath: string): string[];
5
+ export declare function createAndroidVideoRecorder(serial: string): VideoRecorder;
6
+ export declare function createIosSimulatorVideoRecorder(simulator: string): VideoRecorder;
7
+ //# sourceMappingURL=video-recorder.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"video-recorder.d.ts","sourceRoot":"","sources":["../src/video-recorder.ts"],"names":[],"mappings":"AAIA,OAAO,EAGL,KAAK,aAAa,EAGnB,MAAM,yBAAyB,CAAC;AAGjC,eAAO,MAAM,+BAA+B,oBAAoB,CAAC;AACjE,eAAO,MAAM,qCAAqC,mBAAmB,CAAC;AAMtE,wBAAgB,uBAAuB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,EAAE,CAQpE;AAED,wBAAgB,0BAA0B,CAAC,MAAM,EAAE,MAAM,GAAG,aAAa,CAOxE;AAED,wBAAgB,+BAA+B,CAAC,SAAS,EAAE,MAAM,GAAG,aAAa,CAOhF"}
@@ -0,0 +1,250 @@
1
+ import { spawn, spawnSync } from 'node:child_process';
2
+ import fs from 'node:fs';
3
+ import path from 'node:path';
4
+ import { optionalVideoTiming, } from '@farmslot/recipe-runner';
5
+ // Recording target ids: `android-device:<serial>` and `ios-simulator:<udid>`.
6
+ export const ANDROID_RECORDING_TARGET_PREFIX = 'android-device:';
7
+ export const IOS_SIMULATOR_RECORDING_TARGET_PREFIX = 'ios-simulator:';
8
+ const RECORDING_START_TIMEOUT_MS = 5_000;
9
+ const RECORDING_STOP_TIMEOUT_MS = 10_000;
10
+ const RECORDING_TIME_LIMIT_SECONDS = 0;
11
+ export function androidScreenrecordArgs(remotePath) {
12
+ return [
13
+ 'shell',
14
+ 'screenrecord',
15
+ '--time-limit',
16
+ String(RECORDING_TIME_LIMIT_SECONDS),
17
+ remotePath,
18
+ ];
19
+ }
20
+ export function createAndroidVideoRecorder(serial) {
21
+ return {
22
+ name: 'adb-screenrecord',
23
+ platform: 'android',
24
+ doctor: async () => doctorAndroidRecorder(serial),
25
+ start: (request) => startAndroidRecording(serial, request),
26
+ };
27
+ }
28
+ export function createIosSimulatorVideoRecorder(simulator) {
29
+ return {
30
+ name: 'simctl-recordVideo',
31
+ platform: 'ios',
32
+ doctor: async () => doctorIosSimulatorRecorder(simulator),
33
+ start: (request) => startIosSimulatorRecording(simulator, request),
34
+ };
35
+ }
36
+ function doctorIosSimulatorRecorder(simulator) {
37
+ const state = runSimctl(['getenv', simulator, 'SIMULATOR_UDID']);
38
+ if (state.status !== 0 || !state.stdout.trim()) {
39
+ return {
40
+ ok: false,
41
+ code: 'ios_simulator_unavailable',
42
+ message: `iOS simulator ${simulator} is not booted: ${commandFailure(state)}`,
43
+ suggestedFix: `Boot ${simulator} and run xcrun simctl getenv ${simulator} SIMULATOR_UDID.`,
44
+ };
45
+ }
46
+ return {
47
+ ok: true,
48
+ code: 'ok',
49
+ message: `iOS simulator recording is ready on ${simulator}.`,
50
+ };
51
+ }
52
+ async function startIosSimulatorRecording(simulator, request) {
53
+ const targetSimulator = iosTargetSimulator(request);
54
+ if (targetSimulator !== simulator) {
55
+ throw new Error(`iOS recording target ${targetSimulator ?? '<missing>'} does not match selected simulator ${simulator}.`);
56
+ }
57
+ fs.mkdirSync(path.dirname(request.outputPath), { recursive: true });
58
+ const child = spawn('xcrun', ['simctl', 'io', simulator, 'recordVideo', '--codec=h264', '--force', request.outputPath], { stdio: ['ignore', 'pipe', 'pipe'] });
59
+ const output = [];
60
+ child.stdout?.setEncoding('utf8');
61
+ child.stderr?.setEncoding('utf8');
62
+ child.stdout?.on('data', (chunk) => output.push(String(chunk)));
63
+ child.stderr?.on('data', (chunk) => output.push(String(chunk)));
64
+ await waitForIosRecordingStart(child, output);
65
+ let stopped = false;
66
+ return {
67
+ async stop() {
68
+ if (stopped) {
69
+ throw new Error(`iOS simulator recording on ${simulator} was already stopped.`);
70
+ }
71
+ stopped = true;
72
+ child.kill('SIGINT');
73
+ await waitForChildExit(child, RECORDING_STOP_TIMEOUT_MS, 'iOS simulator recordVideo');
74
+ const recorded = fs.statSync(request.outputPath);
75
+ if (!recorded.isFile() || recorded.size <= 0) {
76
+ throw new Error(`iOS simulator recording output is empty: ${request.outputPath}`);
77
+ }
78
+ return {
79
+ recorder: {
80
+ name: 'simctl-recordVideo',
81
+ platform: 'ios',
82
+ target: { selector: 'simulator', value: simulator },
83
+ },
84
+ };
85
+ },
86
+ };
87
+ }
88
+ function doctorAndroidRecorder(serial) {
89
+ const state = runAdb(serial, ['get-state']);
90
+ if (state.status !== 0 || state.stdout.trim() !== 'device') {
91
+ return {
92
+ ok: false,
93
+ code: 'android_device_unavailable',
94
+ message: `Android device ${serial} is not ready: ${commandFailure(state)}`,
95
+ suggestedFix: `Run adb -s ${serial} get-state and reconnect or unlock the device.`,
96
+ };
97
+ }
98
+ const screenrecord = runAdb(serial, ['shell', 'screenrecord', '--help']);
99
+ if (screenrecord.status !== 0) {
100
+ return {
101
+ ok: false,
102
+ code: 'android_screenrecord_unavailable',
103
+ message: `Android screenrecord is unavailable on ${serial}: ${commandFailure(screenrecord)}`,
104
+ suggestedFix: `Run adb -s ${serial} shell screenrecord --help.`,
105
+ };
106
+ }
107
+ return {
108
+ ok: true,
109
+ code: 'ok',
110
+ message: `Android screen recording is ready on ${serial}.`,
111
+ };
112
+ }
113
+ async function startAndroidRecording(serial, request) {
114
+ const targetSerial = androidTargetSerial(request);
115
+ if (targetSerial !== serial) {
116
+ throw new Error(`Android recording target ${targetSerial ?? '<missing>'} does not match selected device ${serial}.`);
117
+ }
118
+ const existingPid = screenrecordPid(serial);
119
+ if (existingPid) {
120
+ throw new Error(`Android screenrecord is already running on ${serial} (pid ${existingPid}). Stop that recording before retrying.`);
121
+ }
122
+ const remotePath = `/sdcard/Download/recipe-video-${Date.now()}-${process.pid}.mp4`;
123
+ const startedAtUnixMs = Date.now();
124
+ const child = spawn('adb', ['-s', serial, ...androidScreenrecordArgs(remotePath)], {
125
+ stdio: ['ignore', 'pipe', 'pipe'],
126
+ });
127
+ const stderr = [];
128
+ child.stderr?.setEncoding('utf8');
129
+ child.stderr?.on('data', (chunk) => stderr.push(String(chunk)));
130
+ child.stdout?.resume();
131
+ const recorderPid = await waitForScreenrecordStart(serial, child, stderr);
132
+ let stopped = false;
133
+ return {
134
+ async stop() {
135
+ if (stopped) {
136
+ throw new Error(`Android screen recording on ${serial} was already stopped.`);
137
+ }
138
+ stopped = true;
139
+ try {
140
+ stopRemoteScreenrecord(serial, recorderPid);
141
+ await waitForChildExit(child, RECORDING_STOP_TIMEOUT_MS, 'Android screenrecord');
142
+ const stoppedAtUnixMs = Date.now();
143
+ fs.mkdirSync(path.dirname(request.outputPath), { recursive: true });
144
+ const pull = runAdb(serial, ['pull', remotePath, request.outputPath]);
145
+ if (pull.status !== 0) {
146
+ throw new Error(`Could not pull Android recording from ${serial}: ${commandFailure(pull)}`);
147
+ }
148
+ const output = fs.statSync(request.outputPath);
149
+ if (!output.isFile() || output.size <= 0) {
150
+ throw new Error(`Android recording output is empty: ${request.outputPath}`);
151
+ }
152
+ return {
153
+ ...(await optionalVideoTiming(request.outputPath, { startedAtUnixMs, stoppedAtUnixMs })),
154
+ recorder: {
155
+ name: 'adb-screenrecord',
156
+ platform: 'android',
157
+ target: { selector: 'adb-serial', value: serial },
158
+ },
159
+ };
160
+ }
161
+ finally {
162
+ runAdb(serial, ['shell', 'rm', '-f', remotePath]);
163
+ }
164
+ },
165
+ };
166
+ }
167
+ function androidTargetSerial(request) {
168
+ if (request.target.kind === 'android-device')
169
+ return request.target.serial;
170
+ if (request.target.kind !== 'window-id')
171
+ return undefined;
172
+ if (!request.target.windowId.startsWith(ANDROID_RECORDING_TARGET_PREFIX))
173
+ return undefined;
174
+ return request.target.windowId.slice(ANDROID_RECORDING_TARGET_PREFIX.length) || undefined;
175
+ }
176
+ async function waitForScreenrecordStart(serial, child, stderr) {
177
+ const deadline = Date.now() + RECORDING_START_TIMEOUT_MS;
178
+ while (Date.now() < deadline) {
179
+ if (child.exitCode !== null) {
180
+ throw new Error(`Android screenrecord exited before capture started (exit ${child.exitCode}): ${stderr.join('').trim() || 'no recorder output'}`);
181
+ }
182
+ const pid = screenrecordPid(serial);
183
+ if (pid)
184
+ return pid;
185
+ await sleep(100);
186
+ }
187
+ child.kill('SIGTERM');
188
+ throw new Error(`Android screenrecord did not start on ${serial} within ${RECORDING_START_TIMEOUT_MS}ms.`);
189
+ }
190
+ function stopRemoteScreenrecord(serial, pid) {
191
+ const stop = runAdb(serial, ['shell', 'kill', '-2', pid]);
192
+ if (stop.status !== 0 && screenrecordPid(serial) === pid) {
193
+ throw new Error(`Could not stop Android screenrecord pid ${pid}: ${commandFailure(stop)}`);
194
+ }
195
+ }
196
+ async function waitForChildExit(child, timeoutMs, recorderName) {
197
+ if (child.exitCode !== null)
198
+ return;
199
+ await new Promise((resolve, reject) => {
200
+ const timeout = setTimeout(() => {
201
+ child.kill('SIGTERM');
202
+ reject(new Error(`${recorderName} did not stop within ${timeoutMs}ms.`));
203
+ }, timeoutMs);
204
+ child.once('exit', () => {
205
+ clearTimeout(timeout);
206
+ resolve();
207
+ });
208
+ });
209
+ }
210
+ async function waitForIosRecordingStart(child, output) {
211
+ const deadline = Date.now() + RECORDING_START_TIMEOUT_MS;
212
+ while (Date.now() < deadline) {
213
+ if (child.exitCode !== null) {
214
+ throw new Error(`iOS simulator recordVideo exited before capture started (exit ${child.exitCode}): ${output.join('').trim() || 'no recorder output'}`);
215
+ }
216
+ if (output.join('').includes('Recording started'))
217
+ return;
218
+ await sleep(100);
219
+ }
220
+ if (child.exitCode === null)
221
+ return;
222
+ throw new Error(`iOS simulator recordVideo did not start within ${RECORDING_START_TIMEOUT_MS}ms.`);
223
+ }
224
+ function iosTargetSimulator(request) {
225
+ if (request.target.kind !== 'window-id')
226
+ return undefined;
227
+ if (!request.target.windowId.startsWith(IOS_SIMULATOR_RECORDING_TARGET_PREFIX)) {
228
+ return undefined;
229
+ }
230
+ return request.target.windowId.slice(IOS_SIMULATOR_RECORDING_TARGET_PREFIX.length) || undefined;
231
+ }
232
+ function screenrecordPid(serial) {
233
+ const result = runAdb(serial, ['shell', 'pidof', 'screenrecord']);
234
+ if (result.status !== 0)
235
+ return undefined;
236
+ return result.stdout.trim().split(/\s+/u).find(Boolean);
237
+ }
238
+ function runAdb(serial, args) {
239
+ return spawnSync('adb', ['-s', serial, ...args], { encoding: 'utf8' });
240
+ }
241
+ function runSimctl(args) {
242
+ return spawnSync('xcrun', ['simctl', ...args], { encoding: 'utf8' });
243
+ }
244
+ function commandFailure(result) {
245
+ return result.stderr.trim() || result.stdout.trim() || `exit ${result.status ?? 'unknown'}`;
246
+ }
247
+ function sleep(durationMs) {
248
+ return new Promise((resolve) => setTimeout(resolve, durationMs));
249
+ }
250
+ //# sourceMappingURL=video-recorder.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"video-recorder.js","sourceRoot":"","sources":["../src/video-recorder.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,28 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ const readline = require('node:readline');
5
+
6
+ const progressPattern = /^\s*(?:iOS|Android).*?(\d{1,3}(?:\.\d+)?)%/u;
7
+ let lastPercent = null;
8
+ let lastProgressAt = 0;
9
+
10
+ const lines = readline.createInterface({
11
+ input: process.stdin,
12
+ crlfDelay: Infinity,
13
+ terminal: false,
14
+ });
15
+ lines.on('line', (line) => {
16
+ const progress = progressPattern.exec(line);
17
+ if (!progress) {
18
+ process.stdout.write(`${line}\n`);
19
+ return;
20
+ }
21
+ const percent = Math.floor(Number(progress[1]));
22
+ const now = Date.now();
23
+ if (percent !== lastPercent || now - lastProgressAt >= 15_000) {
24
+ process.stdout.write(`${line} [metro-progress ${new Date(now).toISOString()}]\n`);
25
+ lastPercent = percent;
26
+ lastProgressAt = now;
27
+ }
28
+ });
@@ -0,0 +1,84 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ const { spawn } = require('node:child_process');
5
+ const fs = require('node:fs');
6
+ const path = require('node:path');
7
+
8
+ function parseArgs(argv) {
9
+ const values = {};
10
+ for (let index = 0; index < argv.length; index += 1) {
11
+ const arg = argv[index];
12
+ if (arg === '-h' || arg === '--help') {
13
+ values.help = true;
14
+ continue;
15
+ }
16
+ if (arg === '--clear') {
17
+ values.clear = true;
18
+ continue;
19
+ }
20
+ if (!arg.startsWith('--') || index + 1 >= argv.length)
21
+ throw new Error(`invalid argument: ${arg}`);
22
+ values[arg.slice(2)] = argv[++index];
23
+ }
24
+ return values;
25
+ }
26
+
27
+ async function main() {
28
+ const args = parseArgs(process.argv.slice(2));
29
+ if (args.help) {
30
+ console.log(
31
+ 'Usage: launch-metro.cjs --target <path> --port <port> --log <path> --pid-file <path> --build-env <path> --runner <path> [--workers <n>] [--clear]',
32
+ );
33
+ return;
34
+ }
35
+ for (const required of ['target', 'port', 'log', 'pid-file', 'build-env', 'runner']) {
36
+ if (!args[required]) throw new Error(`--${required} is required`);
37
+ }
38
+ const target = path.resolve(args.target);
39
+ const log = path.resolve(args.log);
40
+ const pidFile = path.resolve(args['pid-file']);
41
+ const runner = path.resolve(args.runner);
42
+ fs.mkdirSync(path.dirname(log), { recursive: true });
43
+ fs.mkdirSync(path.dirname(pidFile), { recursive: true });
44
+ const runnerStat = fs.lstatSync(runner);
45
+ if (!runnerStat.isFile()) throw new Error('--runner must be a regular file');
46
+ fs.accessSync(runner, fs.constants.X_OK);
47
+ const env = { ...process.env };
48
+ env.BASH_ENV = args['build-env'];
49
+ if (args.workers) env.METRO_MAX_WORKERS = String(args.workers);
50
+ const child = spawn(runner, [], {
51
+ cwd: target,
52
+ detached: true,
53
+ env,
54
+ stdio: 'ignore',
55
+ });
56
+ await new Promise((resolve, reject) => {
57
+ child.once('spawn', resolve);
58
+ child.once('error', reject);
59
+ });
60
+ fs.writeFileSync(pidFile, `${String(child.pid)}\n`);
61
+ fs.writeFileSync(
62
+ path.join(path.dirname(pidFile), 'metro-launch.json'),
63
+ `${JSON.stringify(
64
+ {
65
+ schemaVersion: 1,
66
+ pid: child.pid,
67
+ pgid: child.pid,
68
+ startedAt: new Date().toISOString(),
69
+ target,
70
+ port: Number(args.port),
71
+ clear: Boolean(args.clear),
72
+ command: runner,
73
+ },
74
+ null,
75
+ 2,
76
+ )}\n`,
77
+ );
78
+ child.unref();
79
+ }
80
+
81
+ main().catch((error) => {
82
+ console.error(`launch-metro: ${error.message}`);
83
+ process.exit(1);
84
+ });