@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.
- package/CHANGELOG.md +9 -0
- package/README.md +15 -10
- package/bridge-runtime/console-forwarder.cjs +485 -0
- package/bridge-runtime/lib/bridge-errors.cjs +108 -0
- package/bridge-runtime/lib/cdp-eval.cjs +118 -0
- package/bridge-runtime/lib/config.cjs +68 -0
- package/bridge-runtime/lib/console-format.cjs +54 -0
- package/bridge-runtime/lib/devtools-proxy.cjs +169 -0
- package/bridge-runtime/lib/issue-capture.cjs +86 -0
- package/bridge-runtime/lib/match-bridge-target.cjs +89 -0
- package/bridge-runtime/lib/target-discovery.cjs +436 -0
- package/bridge-runtime/lib/ws-client.cjs +119 -0
- package/dist/cli.js +1 -1
- package/dist/cli.js.map +1 -1
- package/dist/devices.d.ts +16 -0
- package/dist/devices.d.ts.map +1 -0
- package/dist/devices.js +131 -0
- package/dist/devices.js.map +1 -0
- package/dist/doctor.js +1 -1
- package/dist/doctor.js.map +1 -1
- package/dist/fingerprint-baseline.d.ts +8 -0
- package/dist/fingerprint-baseline.d.ts.map +1 -0
- package/dist/fingerprint-baseline.js +68 -0
- package/dist/fingerprint-baseline.js.map +1 -0
- package/dist/frame-metrics.d.ts +26 -0
- package/dist/frame-metrics.d.ts.map +1 -0
- package/dist/frame-metrics.js +134 -0
- package/dist/frame-metrics.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -1
- package/dist/metro-env.d.ts +11 -0
- package/dist/metro-env.d.ts.map +1 -0
- package/dist/metro-env.js +29 -0
- package/dist/metro-env.js.map +1 -0
- package/dist/source-freshness.d.ts +11 -0
- package/dist/source-freshness.d.ts.map +1 -0
- package/dist/source-freshness.js +98 -0
- package/dist/source-freshness.js.map +1 -0
- package/dist/tool-paths.d.ts +13 -0
- package/dist/tool-paths.d.ts.map +1 -0
- package/dist/tool-paths.js +125 -0
- package/dist/tool-paths.js.map +1 -0
- package/dist/video-recorder.d.ts +7 -0
- package/dist/video-recorder.d.ts.map +1 -0
- package/dist/video-recorder.js +250 -0
- package/dist/video-recorder.js.map +1 -0
- package/metro/coalesce-metro-log.cjs +28 -0
- package/metro/launch-metro.cjs +84 -0
- package/metro/metro-config.cjs +92 -0
- package/metro/metro-log-generation.cjs +116 -0
- 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.
|
|
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
|
|
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.
|
|
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"
|