@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,92 @@
1
+ 'use strict';
2
+
3
+ const fs = require('node:fs');
4
+ const { createRequire } = require('node:module');
5
+ const path = require('node:path');
6
+ const { pathToFileURL } = require('node:url');
7
+
8
+ function escapeRegExp(value) {
9
+ return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
10
+ }
11
+
12
+ function appendBlockList(blockList, pattern) {
13
+ if (Array.isArray(blockList)) return [...blockList, pattern];
14
+ return blockList instanceof RegExp ? [blockList, pattern] : [pattern];
15
+ }
16
+
17
+ function blockListFlags(blockList) {
18
+ const first = Array.isArray(blockList) ? blockList[0] : blockList;
19
+ return first instanceof RegExp && first.ignoreCase ? 'i' : '';
20
+ }
21
+
22
+ function resolveExpoConfigLoader(projectRoot) {
23
+ const requireFromProject = createRequire(path.join(projectRoot, 'package.json'));
24
+ let requireUtilsPath;
25
+ try {
26
+ requireUtilsPath = requireFromProject.resolve('@expo/require-utils');
27
+ } catch (error) {
28
+ if (error?.code !== 'MODULE_NOT_FOUND') throw error;
29
+ return null;
30
+ }
31
+ const { loadModuleSync } = requireFromProject(requireUtilsPath);
32
+ return typeof loadModuleSync === 'function' ? loadModuleSync : null;
33
+ }
34
+
35
+ async function loadProductConfig(configPath, baseConfig, projectRoot) {
36
+ let loaded;
37
+ const expoConfigLoader = resolveExpoConfigLoader(projectRoot);
38
+ if (expoConfigLoader) {
39
+ loaded = expoConfigLoader(configPath);
40
+ } else {
41
+ try {
42
+ loaded = require(configPath);
43
+ } catch (error) {
44
+ if (error?.code !== 'ERR_REQUIRE_ESM') throw error;
45
+ loaded = await import(pathToFileURL(configPath).href);
46
+ }
47
+ }
48
+
49
+ const moduleValue = await loaded;
50
+ const exported =
51
+ moduleValue?.__esModule || moduleValue?.[Symbol.toStringTag] === 'Module'
52
+ ? moduleValue.default
53
+ : moduleValue;
54
+ const config = await exported;
55
+ return typeof config === 'function' ? config(baseConfig) : config;
56
+ }
57
+
58
+ module.exports = async function harnessMetroConfig(baseConfig) {
59
+ const projectRoot = path.resolve(process.env.RECIPE_RN_METRO_PROJECT_ROOT || process.cwd());
60
+ const productConfigPath = path.resolve(
61
+ process.env.RECIPE_RN_METRO_BASE_CONFIG || path.join(projectRoot, 'metro.config.js'),
62
+ );
63
+ if (productConfigPath === __filename) {
64
+ throw new Error(
65
+ 'RECIPE_RN_METRO_BASE_CONFIG must reference the product Metro config, not the harness wrapper.',
66
+ );
67
+ }
68
+ let productConfig = baseConfig;
69
+ if (fs.existsSync(productConfigPath)) {
70
+ productConfig = await loadProductConfig(productConfigPath, baseConfig, projectRoot);
71
+ }
72
+
73
+ const tempRoot = path.join(projectRoot, 'temp');
74
+ const existingBlockList = productConfig.resolver?.blockList ?? baseConfig.resolver?.blockList;
75
+ const tempPattern = new RegExp(
76
+ `^${escapeRegExp(tempRoot)}(?:${escapeRegExp(path.sep)}|$)`,
77
+ blockListFlags(existingBlockList),
78
+ );
79
+ // Inline environment values affect transformed JavaScript. Keep separate
80
+ // cache generations for those inputs instead of deleting the shared cache.
81
+ const envFingerprint = process.env.RECIPE_RN_METRO_ENV_FINGERPRINT;
82
+ return {
83
+ ...productConfig,
84
+ ...(envFingerprint
85
+ ? { cacheVersion: `${productConfig.cacheVersion ?? ''}:mm-env:${envFingerprint}` }
86
+ : {}),
87
+ resolver: {
88
+ ...productConfig.resolver,
89
+ blockList: appendBlockList(existingBlockList, tempPattern),
90
+ },
91
+ };
92
+ };
@@ -0,0 +1,116 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ const crypto = require('node:crypto');
5
+ const fs = require('node:fs');
6
+ const path = require('node:path');
7
+
8
+ const generationIdPattern = /^[A-Za-z0-9-]+$/u;
9
+
10
+ function archiveGenerationId(name) {
11
+ if (!name.startsWith('metro.') || !name.endsWith('.log')) return null;
12
+ const generationId = name.slice('metro.'.length, -'.log'.length);
13
+ return generationIdPattern.test(generationId) ? generationId : null;
14
+ }
15
+
16
+ function positiveInteger(name, fallback) {
17
+ const raw = process.env[name];
18
+ if (raw === undefined || raw === '') return fallback;
19
+ if (!/^[1-9][0-9]*$/u.test(raw)) throw new Error(`${name} must be a positive integer`);
20
+ return Number(raw);
21
+ }
22
+
23
+ function main() {
24
+ const runtimeArg = process.argv[2];
25
+ const port = process.argv[3];
26
+ const reason = process.argv[4];
27
+ if (!runtimeArg || !/^[1-9][0-9]*$/u.test(port ?? '') || !reason) {
28
+ throw new Error('usage: metro-log-generation.cjs <runtime-dir> <port> <reason>');
29
+ }
30
+ const runtimeDir = fs.realpathSync(runtimeArg);
31
+ const activeLog = path.join(runtimeDir, 'metro.log');
32
+ const evidenceFile = path.join(runtimeDir, 'metro-generation.json');
33
+ const maxArchives = positiveInteger('RECIPE_RN_METRO_LOG_ARCHIVE_COUNT', 4);
34
+ const maxArchiveBytes = positiveInteger('RECIPE_RN_METRO_LOG_ARCHIVE_BYTES', 8 * 1024 * 1024);
35
+ const generationId = `${new Date().toISOString().replace(/[-:.]/gu, '')}-${port}-${process.pid}-${crypto.randomBytes(4).toString('hex')}`;
36
+ if (!generationIdPattern.test(generationId))
37
+ throw new Error('generated Metro log identity is invalid');
38
+ let rotatedLog = null;
39
+
40
+ rotatedLog = path.join(runtimeDir, `metro.${generationId}.log`);
41
+ try {
42
+ fs.renameSync(activeLog, rotatedLog);
43
+ const stat = fs.lstatSync(rotatedLog);
44
+ if (!stat.isFile()) {
45
+ fs.renameSync(rotatedLog, activeLog);
46
+ throw new Error('metro.log must be a regular file');
47
+ }
48
+ } catch (error) {
49
+ if (error && error.code === 'ENOENT') rotatedLog = null;
50
+ else throw error;
51
+ }
52
+ try {
53
+ fs.closeSync(fs.openSync(activeLog, 'wx', 0o600));
54
+ } catch (error) {
55
+ if (error && error.code === 'EEXIST')
56
+ throw new Error('another Metro generation recreated metro.log; refusing to truncate it');
57
+ throw error;
58
+ }
59
+
60
+ const archives = fs
61
+ .readdirSync(runtimeDir)
62
+ .filter((name) => archiveGenerationId(name) !== null)
63
+ .map((name) => {
64
+ const file = path.join(runtimeDir, name);
65
+ const stat = fs.lstatSync(file);
66
+ if (!stat.isFile()) return null;
67
+ return { file, size: stat.size, mtimeMs: stat.mtimeMs };
68
+ })
69
+ .filter(Boolean)
70
+ .sort((left, right) => right.mtimeMs - left.mtimeMs || right.file.localeCompare(left.file));
71
+
72
+ let retainedBytes = 0;
73
+ let retainedCount = 0;
74
+ const retained = [];
75
+ const removed = [];
76
+ for (const archive of archives) {
77
+ if (retainedCount < maxArchives && retainedBytes + archive.size <= maxArchiveBytes) {
78
+ retained.push(archive.file);
79
+ retainedCount += 1;
80
+ retainedBytes += archive.size;
81
+ continue;
82
+ }
83
+ fs.unlinkSync(archive.file);
84
+ removed.push({
85
+ path: archive.file,
86
+ size: archive.size,
87
+ reason: retainedCount >= maxArchives ? 'count-limit' : 'size-limit',
88
+ });
89
+ }
90
+
91
+ const evidence = {
92
+ schemaVersion: 1,
93
+ generationId,
94
+ port: Number(port),
95
+ startedAt: new Date().toISOString(),
96
+ currentLog: activeLog,
97
+ rotatedLog: rotatedLog && fs.existsSync(rotatedLog) ? rotatedLog : null,
98
+ archivedLogs: retained,
99
+ rotationReason: reason,
100
+ retention: { maxArchives, maxArchiveBytes, retainedBytes, removed },
101
+ };
102
+ const temporary = `${evidenceFile}.${process.pid}.tmp`;
103
+ fs.writeFileSync(temporary, `${JSON.stringify(evidence, null, 2)}\n`, {
104
+ mode: 0o600,
105
+ flag: 'wx',
106
+ });
107
+ fs.renameSync(temporary, evidenceFile);
108
+ process.stdout.write(`${JSON.stringify(evidence)}\n`);
109
+ }
110
+
111
+ try {
112
+ main();
113
+ } catch (error) {
114
+ console.error(`metro-log-generation: ${error instanceof Error ? error.message : String(error)}`);
115
+ process.exit(1);
116
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@farmslot/adapter-rn",
3
- "version": "0.15.0",
3
+ "version": "0.16.0",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -12,23 +12,39 @@
12
12
  "types": "./dist/index.d.ts",
13
13
  "import": "./dist/index.js",
14
14
  "default": "./dist/index.js"
15
- }
15
+ },
16
+ "./bridge-runtime/lib/bridge-errors.cjs": "./bridge-runtime/lib/bridge-errors.cjs",
17
+ "./bridge-runtime/lib/cdp-eval.cjs": "./bridge-runtime/lib/cdp-eval.cjs",
18
+ "./bridge-runtime/lib/config.cjs": "./bridge-runtime/lib/config.cjs",
19
+ "./bridge-runtime/lib/console-format.cjs": "./bridge-runtime/lib/console-format.cjs",
20
+ "./bridge-runtime/lib/devtools-proxy.cjs": "./bridge-runtime/lib/devtools-proxy.cjs",
21
+ "./bridge-runtime/lib/issue-capture.cjs": "./bridge-runtime/lib/issue-capture.cjs",
22
+ "./bridge-runtime/lib/match-bridge-target.cjs": "./bridge-runtime/lib/match-bridge-target.cjs",
23
+ "./bridge-runtime/lib/target-discovery.cjs": "./bridge-runtime/lib/target-discovery.cjs",
24
+ "./bridge-runtime/lib/ws-client.cjs": "./bridge-runtime/lib/ws-client.cjs",
25
+ "./bridge-runtime/console-forwarder.cjs": "./bridge-runtime/console-forwarder.cjs",
26
+ "./metro/coalesce-metro-log.cjs": "./metro/coalesce-metro-log.cjs",
27
+ "./metro/launch-metro.cjs": "./metro/launch-metro.cjs",
28
+ "./metro/metro-config.cjs": "./metro/metro-config.cjs",
29
+ "./metro/metro-log-generation.cjs": "./metro/metro-log-generation.cjs",
30
+ "./package.json": "./package.json"
16
31
  },
17
32
  "scripts": {
18
33
  "typecheck": "tsc --noEmit --project tsconfig.json",
19
34
  "build": "yarn workspace @farmslot/protocol build && yarn workspace @farmslot/recipe-runner build && rm -rf dist && tsc --project tsconfig.build.json",
20
- "test": "tsx --tsconfig tsconfig.json --test src/agent-device-ui-transport.test.ts src/adapter-rn.test.ts src/metro-recipe-bridge-transport.test.ts",
35
+ "test": "node ../../scripts/quality/run-tsx-tests.mjs --cwd . --tsconfig tsconfig.json src/agent-device-ui-transport.test.ts src/adapter-rn.test.ts src/metro-recipe-bridge-transport.test.ts test",
21
36
  "format": "prettier --write --ignore-path ../../.prettierignore .",
22
37
  "format:check": "prettier --check --ignore-path ../../.prettierignore .",
23
38
  "lint": "eslint \"src/**/*.ts\" \"templates/src/farmslot/**/*.{ts,tsx}\"",
24
- "quality": "prettier --check --ignore-path ../../.prettierignore . && eslint \"src/**/*.ts\" \"templates/src/farmslot/**/*.{ts,tsx}\" && tsc --noEmit --project tsconfig.json && yarn build && yarn test",
39
+ "quality": "prettier --check --ignore-path ../../.prettierignore . && eslint \"src/**/*.ts\" \"test/**/*.ts\" \"templates/src/farmslot/**/*.{ts,tsx}\" && tsc --noEmit --project tsconfig.json && yarn build && yarn test",
25
40
  "prepack": "yarn build",
26
41
  "prepublishOnly": "node ../../scripts/quality/check-farmslot-package-readiness.mjs --publish --packages @farmslot/adapter-rn"
27
42
  },
28
43
  "dependencies": {
29
44
  "@farmslot/protocol": "0.34.0",
30
- "@farmslot/recipe-runner": "0.23.0",
31
- "pngjs": "^7.0.0"
45
+ "@farmslot/recipe-runner": "0.24.0",
46
+ "pngjs": "^7.0.0",
47
+ "ws": "^8.18.0"
32
48
  },
33
49
  "peerDependencies": {
34
50
  "agent-device": ">=0.19.3"
@@ -41,6 +57,7 @@
41
57
  "devDependencies": {
42
58
  "@types/node": "^22.0.0",
43
59
  "@types/pngjs": "^6.0.5",
60
+ "@types/ws": "^8.5.12",
44
61
  "agent-device": "0.19.3",
45
62
  "eslint": "^10.3.0",
46
63
  "prettier": "^3.8.3",
@@ -73,7 +90,9 @@
73
90
  "dist/**/*.d.ts",
74
91
  "dist/**/*.d.ts.map",
75
92
  "dist/**/*.js.map",
76
- "templates/**/*"
93
+ "templates/**/*",
94
+ "bridge-runtime/**/*.cjs",
95
+ "metro/*.cjs"
77
96
  ],
78
97
  "publishConfig": {
79
98
  "access": "public"