@deeeed/metamask-harness 0.17.4 → 0.17.5
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 +6 -103
- package/README.md +136 -96
- package/adapters/extension/console-tail.mjs +24 -55
- package/adapters/extension/ensure-browser.sh +7 -1
- package/adapters/extension/inject.mjs +15 -5
- package/adapters/extension/launch-browser.cjs +4 -3
- package/adapters/extension/launch.sh +11 -8
- package/adapters/extension/lib/chrome-args.cjs +24 -0
- package/adapters/extension/live.sh +1 -2
- package/adapters/extension/reattach.sh +0 -1
- package/adapters/extension/seed-fixture.sh +12 -4
- package/adapters/extension/sidepanel-toggle.sh +1 -4
- package/adapters/extension/start-watch.sh +4 -17
- package/adapters/extension/stop-viewers.sh +1 -1
- package/adapters/extension/verify.sh +1 -1
- package/adapters/manifest.json +45 -53
- package/adapters/mobile/bridge-runtime/cdp-bridge.cjs +0 -16
- package/adapters/mobile/bridge-runtime/console-forwarder.cjs +1 -1
- package/adapters/mobile/bridge-runtime/lib/match-bridge-target.cjs +1 -3
- package/adapters/mobile/bridge-runtime/lib/target-discovery.cjs +3 -2
- package/adapters/mobile/cleanup.sh +176 -16
- package/adapters/mobile/inject.sh +671 -90
- package/adapters/mobile/overlay/app/dev-tools/AgenticService/AgentStepHud.tsx.patch +185 -0
- package/adapters/mobile/overlay/app/dev-tools/AgenticService/AgenticService.ts.patch +1662 -0
- package/adapters/mobile/overlay/compat/README.md +28 -0
- package/adapters/mobile/overlay/compat/rn81-message-event-source.patch +42 -0
- package/adapters/mobile/start-metro.sh +41 -0
- package/adapters/mobile/verify.sh +61 -4
- package/adapters/mobile/wait-for-bridge.sh +8 -22
- package/adapters/shared/harness-source-fingerprint.mjs +49 -0
- package/adapters/shared/install-repo-deps.sh +1 -5
- package/adapters/shared/open-debug.mjs +184 -103
- package/adapters/shared/resolve-farmslot-ports-core.mjs +3 -0
- package/adapters/shared/resolve-farmslot-ports.mjs +5 -0
- package/adapters/shared/resolve-farmslot-ports.sh +7 -0
- package/adapters/shared/resolve-slot-ports-core.mjs +6 -0
- package/adapters/shared/resolve-slot-ports.sh +7 -2
- package/bin/mm-harness +0 -7
- package/dist/adapters/extension/harness-freshness.js +39 -0
- package/dist/adapters/extension/runtime-decision.js +4 -73
- package/dist/adapters/extension/surface.js +2 -3
- package/dist/adapters/mobile/perps-env.js +3 -43
- package/dist/adapters/mobile/prepare.js +7 -22
- package/dist/adapters/mobile/source-freshness.js +26 -41
- package/dist/adapters/mobile/surface.js +1 -1
- package/dist/adapters/resolve-slot-ports.js +2 -0
- package/dist/adapters/slot-ports.js +0 -16
- package/dist/adapters.js +5 -12
- package/dist/cli-commands.js +1 -1
- package/dist/cli.js +12 -6
- package/dist/commands/call.js +22 -56
- package/dist/commands/check.js +3 -9
- package/dist/commands/checklist.js +4 -1
- package/dist/commands/debug.js +31 -38
- package/dist/commands/device-target.js +12 -27
- package/dist/commands/doctor.js +23 -77
- package/dist/commands/fixtures.js +17 -27
- package/dist/commands/launch/extension.js +2 -0
- package/dist/commands/launch/index.js +59 -167
- package/dist/commands/logs.js +6 -8
- package/dist/commands/manifest.js +25 -151
- package/dist/commands/parse-args.js +0 -2
- package/dist/commands/provision.js +3 -10
- package/dist/commands/run-engine.js +69 -71
- package/dist/commands/run-report.js +3 -12
- package/dist/commands/run.js +35 -245
- package/dist/commands/shared.js +3 -55
- package/dist/commands/status-probe.js +1 -1
- package/dist/commands/status.js +1 -1
- package/dist/commands/stop.js +2 -7
- package/dist/commands/update.js +28 -16
- package/dist/doctor.js +4 -1
- package/dist/harness.js +134 -43
- package/dist/heal-bounds.js +1 -1
- package/dist/live-adapter-contract.js +20 -6
- package/dist/mm-harness-cli.js +48 -123
- package/dist/paths.js +5 -2
- package/dist/runner.js +2 -37
- package/dist/runtime-context.js +8 -26
- package/docs/ADAPTER-SURFACE.md +112 -19
- package/docs/CHEATSHEET.md +52 -39
- package/docs/CLI-SPEC.md +1098 -0
- package/docs/CODE-MAP.md +1 -1
- package/docs/DEBUG-HANDOVER.md +36 -0
- package/docs/MENTAL-MODEL.md +295 -0
- package/docs/UX-PRINCIPLES.md +1 -3
- package/docs/architecture.md +375 -65
- package/docs/live-adapter-contract.md +2 -4
- package/docs/recipe-libraries.md +275 -45
- package/library/README.md +0 -4
- package/library/actions/core/perps/_controller.mjs +55 -10
- package/library/actions/mobile/platform/bridge.mjs +1 -5
- package/library/manifests/core.action-manifest.json +7 -7
- package/library/manifests/mobile.action-manifest.json +3 -28
- package/package.json +1 -4
- package/adapters/extension/launch-webpack.cjs +0 -83
- package/adapters/extension/stamp-runtime-title.cjs +0 -58
- package/adapters/extension/sync-webpack-dist.cjs +0 -107
- package/adapters/mobile/launch-console-forwarder.cjs +0 -39
- package/adapters/mobile/start-console-forwarder.sh +0 -70
- package/adapters/shared/update-check-worker.mjs +0 -43
- package/dist/adapters/extension/console-capture.js +0 -93
- package/dist/command-contract.js +0 -441
- package/dist/command-journal.js +0 -225
- package/dist/commands/last.js +0 -52
- package/dist/json-stream.js +0 -57
- package/dist/run-diagnostics.js +0 -261
- package/docs/CLI-ERGONOMICS-AUDIT.md +0 -32
- package/docs/CLI-ERGONOMICS-HUMAN-QA.md +0 -64
- package/docs/VIDEO-DEMO-VALIDATION.md +0 -74
- package/library/recipes/runner/smoke.core.recipe.json +0 -27
- package/library/recipes/wallet/smoke.extension.recipe.json +0 -42
- package/library/recipes/wallet/smoke.mobile.recipe.json +0 -42
- package/scripts/validate-human-outcomes.mjs +0 -169
|
@@ -1,83 +0,0 @@
|
|
|
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
|
-
const args = {};
|
|
9
|
-
for (let index = 2; index < process.argv.length; index += 1) {
|
|
10
|
-
const key = process.argv[index];
|
|
11
|
-
if (key === '--help' || key === '-h') {
|
|
12
|
-
process.stdout.write('Usage: launch-webpack.cjs --target <path> --log <path> --pid-file <path> --sync-script <path> --sync-pid-file <path> --sync-log <path> --dist <path> --runtime-dist <path> [--port <port>] [--path-prefix <path>]\n');
|
|
13
|
-
process.exit(0);
|
|
14
|
-
}
|
|
15
|
-
if (!key.startsWith('--') || index + 1 >= process.argv.length) throw new Error(`invalid argument: ${key}`);
|
|
16
|
-
args[key.slice(2)] = process.argv[++index];
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
async function main() {
|
|
20
|
-
for (const required of ['target', 'log', 'pid-file', 'sync-script', 'sync-pid-file', 'sync-log', 'dist', 'runtime-dist']) {
|
|
21
|
-
if (!args[required]) throw new Error(`--${required} is required`);
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
const target = path.resolve(args.target);
|
|
25
|
-
const log = path.resolve(args.log);
|
|
26
|
-
const pidFile = path.resolve(args['pid-file']);
|
|
27
|
-
const syncScript = path.resolve(args['sync-script']);
|
|
28
|
-
const syncPidFile = path.resolve(args['sync-pid-file']);
|
|
29
|
-
const syncLog = path.resolve(args['sync-log']);
|
|
30
|
-
fs.mkdirSync(path.dirname(log), { recursive: true });
|
|
31
|
-
fs.mkdirSync(path.dirname(pidFile), { recursive: true });
|
|
32
|
-
fs.mkdirSync(path.dirname(syncLog), { recursive: true });
|
|
33
|
-
if (!fs.existsSync(syncScript)) throw new Error(`sync script not found: ${syncScript}`);
|
|
34
|
-
const logFd = fs.openSync(log, 'a');
|
|
35
|
-
const syncLogFd = fs.openSync(syncLog, 'a');
|
|
36
|
-
const env = { ...process.env };
|
|
37
|
-
if (args.port) env.PORT = String(args.port);
|
|
38
|
-
if (args['path-prefix']) env.PATH = `${args['path-prefix']}${env.PATH || ''}`;
|
|
39
|
-
|
|
40
|
-
try {
|
|
41
|
-
const child = spawn('yarn', ['start'], {
|
|
42
|
-
cwd: target,
|
|
43
|
-
detached: true,
|
|
44
|
-
env,
|
|
45
|
-
stdio: ['ignore', logFd, logFd],
|
|
46
|
-
});
|
|
47
|
-
await new Promise((resolve, reject) => {
|
|
48
|
-
child.once('spawn', resolve);
|
|
49
|
-
child.once('error', reject);
|
|
50
|
-
});
|
|
51
|
-
fs.writeFileSync(pidFile, `${child.pid}\n`);
|
|
52
|
-
const syncChild = spawn(process.execPath, [
|
|
53
|
-
syncScript,
|
|
54
|
-
'--watcher-pid', String(child.pid),
|
|
55
|
-
'--watch-log', log,
|
|
56
|
-
'--target', target,
|
|
57
|
-
'--dist', path.resolve(args.dist),
|
|
58
|
-
'--runtime-dist', path.resolve(args['runtime-dist']),
|
|
59
|
-
'--pid-file', syncPidFile,
|
|
60
|
-
], {
|
|
61
|
-
cwd: target,
|
|
62
|
-
detached: true,
|
|
63
|
-
env,
|
|
64
|
-
stdio: ['ignore', syncLogFd, syncLogFd],
|
|
65
|
-
});
|
|
66
|
-
await new Promise((resolve, reject) => {
|
|
67
|
-
syncChild.once('spawn', resolve);
|
|
68
|
-
syncChild.once('error', reject);
|
|
69
|
-
});
|
|
70
|
-
fs.writeFileSync(syncPidFile, `${syncChild.pid}\n`);
|
|
71
|
-
process.stdout.write(`${child.pid}\n`);
|
|
72
|
-
syncChild.unref();
|
|
73
|
-
child.unref();
|
|
74
|
-
} finally {
|
|
75
|
-
fs.closeSync(logFd);
|
|
76
|
-
fs.closeSync(syncLogFd);
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
main().catch((error) => {
|
|
81
|
-
process.stderr.write(`launch-webpack: ${error.message}\n`);
|
|
82
|
-
process.exit(1);
|
|
83
|
-
});
|
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
'use strict';
|
|
3
|
-
|
|
4
|
-
const fs = require('node:fs');
|
|
5
|
-
const path = require('node:path');
|
|
6
|
-
|
|
7
|
-
const { readSlotId } = require('./lib/slot-title.cjs');
|
|
8
|
-
|
|
9
|
-
function stampRuntimeTitles({ target, runtimeDist, runtimeDir = process.env.RECIPE_RUNTIME_DIR || 'temp/recipe/runtime' }) {
|
|
10
|
-
const slotId = readSlotId(target, runtimeDir);
|
|
11
|
-
if (!slotId) return { slotId: '', stamped: [] };
|
|
12
|
-
const stamped = [];
|
|
13
|
-
for (const name of ['home.html', 'sidepanel.html']) {
|
|
14
|
-
const file = path.join(runtimeDist, name);
|
|
15
|
-
let fd;
|
|
16
|
-
try {
|
|
17
|
-
fd = fs.openSync(file, 'r+');
|
|
18
|
-
const source = fs.readFileSync(fd, 'utf8');
|
|
19
|
-
const next = source.replace(/<title>([^<]*)<\/title>/iu, (_match, title) => {
|
|
20
|
-
const base = String(title || 'MetaMask').replace(/^.+?\s+[—–-]\s+/u, '') || 'MetaMask';
|
|
21
|
-
return `<title>${slotId} — ${base}</title>`;
|
|
22
|
-
});
|
|
23
|
-
if (next === source) continue;
|
|
24
|
-
const bytes = Buffer.from(next, 'utf8');
|
|
25
|
-
fs.writeSync(fd, bytes, 0, bytes.length, 0);
|
|
26
|
-
fs.ftruncateSync(fd, bytes.length);
|
|
27
|
-
stamped.push(name);
|
|
28
|
-
} catch (error) {
|
|
29
|
-
if (error?.code !== 'ENOENT') throw error;
|
|
30
|
-
} finally {
|
|
31
|
-
if (fd !== undefined) fs.closeSync(fd);
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
return { slotId, stamped };
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
if (require.main === module) {
|
|
38
|
-
const args = {};
|
|
39
|
-
for (let index = 2; index < process.argv.length; index += 1) {
|
|
40
|
-
const key = process.argv[index];
|
|
41
|
-
if (key === '--help' || key === '-h') {
|
|
42
|
-
process.stdout.write('Usage: stamp-runtime-title.cjs --target <path> --runtime-dist <path> [--runtime-dir <path>]\n');
|
|
43
|
-
process.exit(0);
|
|
44
|
-
}
|
|
45
|
-
if (!key.startsWith('--') || index + 1 >= process.argv.length) throw new Error(`invalid argument: ${key}`);
|
|
46
|
-
args[key.slice(2)] = process.argv[++index];
|
|
47
|
-
}
|
|
48
|
-
if (!args.target) throw new Error('--target is required');
|
|
49
|
-
if (!args['runtime-dist']) throw new Error('--runtime-dist is required');
|
|
50
|
-
const result = stampRuntimeTitles({
|
|
51
|
-
target: path.resolve(args.target),
|
|
52
|
-
runtimeDist: path.resolve(args['runtime-dist']),
|
|
53
|
-
runtimeDir: args['runtime-dir'],
|
|
54
|
-
});
|
|
55
|
-
if (result.stamped.length > 0) process.stdout.write(`slot title ${result.slotId}: ${result.stamped.join(', ')}\n`);
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
module.exports = { stampRuntimeTitles };
|
|
@@ -1,107 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
'use strict';
|
|
3
|
-
|
|
4
|
-
const { spawnSync } = require('node:child_process');
|
|
5
|
-
const fs = require('node:fs');
|
|
6
|
-
const path = require('node:path');
|
|
7
|
-
const { stampRuntimeTitles } = require('./stamp-runtime-title.cjs');
|
|
8
|
-
|
|
9
|
-
const args = {};
|
|
10
|
-
for (let index = 2; index < process.argv.length; index += 1) {
|
|
11
|
-
const key = process.argv[index];
|
|
12
|
-
if (key === '--help' || key === '-h') {
|
|
13
|
-
process.stdout.write('Usage: sync-webpack-dist.cjs --watcher-pid <pid> --watch-log <path> --target <path> --dist <path> --runtime-dist <path> --pid-file <path>\n');
|
|
14
|
-
process.exit(0);
|
|
15
|
-
}
|
|
16
|
-
if (!key.startsWith('--') || index + 1 >= process.argv.length) throw new Error(`invalid argument: ${key}`);
|
|
17
|
-
args[key.slice(2)] = process.argv[++index];
|
|
18
|
-
}
|
|
19
|
-
for (const required of ['watcher-pid', 'watch-log', 'target', 'dist', 'runtime-dist', 'pid-file']) {
|
|
20
|
-
if (!args[required]) throw new Error(`--${required} is required`);
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
const watcherPid = Number(args['watcher-pid']);
|
|
24
|
-
if (!Number.isInteger(watcherPid) || watcherPid <= 0) throw new Error('--watcher-pid must be a positive integer');
|
|
25
|
-
|
|
26
|
-
const watchLog = path.resolve(args['watch-log']);
|
|
27
|
-
const dist = path.resolve(args.dist);
|
|
28
|
-
const runtimeDist = path.resolve(args['runtime-dist']);
|
|
29
|
-
const pidFile = path.resolve(args['pid-file']);
|
|
30
|
-
const compilePattern = /compiled successfully|compiled with \d+ warning|MetaMask .* compiled|Bundle end: service worker|Bundle end:.*app-init/u;
|
|
31
|
-
let offset = 0;
|
|
32
|
-
let carry = '';
|
|
33
|
-
let timer;
|
|
34
|
-
|
|
35
|
-
function processAlive(pid) {
|
|
36
|
-
try {
|
|
37
|
-
process.kill(pid, 0);
|
|
38
|
-
return true;
|
|
39
|
-
} catch {
|
|
40
|
-
return false;
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
function removeOwnPidFile() {
|
|
45
|
-
try {
|
|
46
|
-
if (fs.readFileSync(pidFile, 'utf8').trim() === String(process.pid)) fs.rmSync(pidFile, { force: true });
|
|
47
|
-
} catch {
|
|
48
|
-
// The launcher may have already removed or replaced it.
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
function syncRuntimeDist() {
|
|
53
|
-
if (!fs.existsSync(path.join(dist, 'manifest.json')) || !fs.existsSync(runtimeDist)) return;
|
|
54
|
-
const result = spawnSync('rsync', ['-a', '--delete', '--delay-updates', '--exclude', '_metadata', `${dist}${path.sep}`, `${runtimeDist}${path.sep}`], {
|
|
55
|
-
encoding: 'utf8',
|
|
56
|
-
});
|
|
57
|
-
if (result.status !== 0) {
|
|
58
|
-
process.stderr.write(`runtime-dist sync failed: ${(result.stderr || result.stdout || `rsync exited ${result.status}`).trim()}\n`);
|
|
59
|
-
return;
|
|
60
|
-
}
|
|
61
|
-
stampRuntimeTitles({ target: path.resolve(args.target), runtimeDist });
|
|
62
|
-
process.stdout.write(`runtime-dist updated after webpack compile (${new Date().toISOString()})\n`);
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
function poll() {
|
|
66
|
-
if (!processAlive(watcherPid)) {
|
|
67
|
-
removeOwnPidFile();
|
|
68
|
-
process.exit(0);
|
|
69
|
-
}
|
|
70
|
-
let fd;
|
|
71
|
-
try {
|
|
72
|
-
fd = fs.openSync(watchLog, 'r');
|
|
73
|
-
const size = fs.fstatSync(fd).size;
|
|
74
|
-
if (size < offset) {
|
|
75
|
-
offset = 0;
|
|
76
|
-
carry = '';
|
|
77
|
-
}
|
|
78
|
-
if (size > offset) {
|
|
79
|
-
const length = size - offset;
|
|
80
|
-
const chunk = Buffer.alloc(length);
|
|
81
|
-
const bytesRead = fs.readSync(fd, chunk, 0, length, offset);
|
|
82
|
-
offset += bytesRead;
|
|
83
|
-
const text = carry + chunk.subarray(0, bytesRead).toString('utf8');
|
|
84
|
-
const lines = text.split(/\r?\n/u);
|
|
85
|
-
carry = lines.pop() || '';
|
|
86
|
-
if (lines.some((line) => compilePattern.test(line))) syncRuntimeDist();
|
|
87
|
-
}
|
|
88
|
-
} catch (error) {
|
|
89
|
-
if (error?.code !== 'ENOENT') process.stderr.write(`runtime-dist sync monitor: ${error.message}\n`);
|
|
90
|
-
} finally {
|
|
91
|
-
if (fd !== undefined) fs.closeSync(fd);
|
|
92
|
-
}
|
|
93
|
-
timer = setTimeout(poll, 250);
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
fs.mkdirSync(path.dirname(pidFile), { recursive: true });
|
|
97
|
-
process.on('SIGTERM', () => {
|
|
98
|
-
clearTimeout(timer);
|
|
99
|
-
removeOwnPidFile();
|
|
100
|
-
process.exit(0);
|
|
101
|
-
});
|
|
102
|
-
process.on('SIGINT', () => {
|
|
103
|
-
clearTimeout(timer);
|
|
104
|
-
removeOwnPidFile();
|
|
105
|
-
process.exit(0);
|
|
106
|
-
});
|
|
107
|
-
poll();
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
'use strict';
|
|
3
|
-
|
|
4
|
-
const fs = require('node:fs');
|
|
5
|
-
const path = require('node:path');
|
|
6
|
-
const { spawn } = require('node:child_process');
|
|
7
|
-
|
|
8
|
-
const args = { port: '', out: '', err: '' };
|
|
9
|
-
for (let i = 2; i < process.argv.length; i += 1) {
|
|
10
|
-
const key = process.argv[i];
|
|
11
|
-
if (key === '--port') args.port = process.argv[++i] || '';
|
|
12
|
-
else if (key === '--out') args.out = process.argv[++i] || '';
|
|
13
|
-
else if (key === '--err') args.err = process.argv[++i] || '';
|
|
14
|
-
else if (key === '--help' || key === '-h') {
|
|
15
|
-
process.stdout.write('Usage: launch-console-forwarder.cjs --port <port> --out <app-log> --err <error-log>\n');
|
|
16
|
-
process.exit(0);
|
|
17
|
-
} else {
|
|
18
|
-
process.stderr.write(`launch-console-forwarder: unknown arg: ${key}\n`);
|
|
19
|
-
process.exit(2);
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
if (!args.port || !args.out || !args.err) {
|
|
24
|
-
process.stderr.write('launch-console-forwarder: --port, --out, and --err are required\n');
|
|
25
|
-
process.exit(2);
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
const forwarder = path.join(__dirname, 'bridge-runtime', 'console-forwarder.cjs');
|
|
29
|
-
const errFd = fs.openSync(args.err, 'a');
|
|
30
|
-
try {
|
|
31
|
-
const child = spawn(process.execPath, [forwarder, '--port', args.port, '--out', args.out], {
|
|
32
|
-
detached: true,
|
|
33
|
-
stdio: ['ignore', errFd, errFd],
|
|
34
|
-
});
|
|
35
|
-
child.unref();
|
|
36
|
-
process.stdout.write(`${child.pid}\n`);
|
|
37
|
-
} finally {
|
|
38
|
-
fs.closeSync(errFd);
|
|
39
|
-
}
|
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
# Ensure one runner-owned CDP console forwarder is alive for this Mobile slot.
|
|
3
|
-
set -uo pipefail
|
|
4
|
-
|
|
5
|
-
TARGET="$PWD"
|
|
6
|
-
PORT="${WATCHER_PORT:-${METRO_PORT:-8081}}"
|
|
7
|
-
QUIET=false
|
|
8
|
-
|
|
9
|
-
while [ "$#" -gt 0 ]; do
|
|
10
|
-
case "$1" in
|
|
11
|
-
--target) [ "$#" -ge 2 ] || { echo "Missing value for --target" >&2; exit 2; }; TARGET="$2"; shift 2 ;;
|
|
12
|
-
--port) [ "$#" -ge 2 ] || { echo "Missing value for --port" >&2; exit 2; }; PORT="$2"; shift 2 ;;
|
|
13
|
-
--quiet) QUIET=true; shift ;;
|
|
14
|
-
-h|--help)
|
|
15
|
-
printf 'Usage: start-console-forwarder.sh [--target <dir>] [--port <port>] [--quiet]\n'
|
|
16
|
-
exit 0
|
|
17
|
-
;;
|
|
18
|
-
*) printf 'start-console-forwarder: unknown arg: %s\n' "$1" >&2; exit 2 ;;
|
|
19
|
-
esac
|
|
20
|
-
done
|
|
21
|
-
|
|
22
|
-
[ "${METAMASK_RECIPE_CONSOLE_FORWARD:-1}" != "0" ] || exit 0
|
|
23
|
-
TARGET="$(cd "$TARGET" && pwd -P)"
|
|
24
|
-
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
25
|
-
# shellcheck disable=SC1091
|
|
26
|
-
. "$SCRIPT_DIR/../shared/harness-path.sh"
|
|
27
|
-
LOG_DIR="$TARGET/$(recipe_runtime_dir)" || exit 1
|
|
28
|
-
FORWARDER="$SCRIPT_DIR/bridge-runtime/console-forwarder.cjs"
|
|
29
|
-
PID_FILE="$LOG_DIR/console-forwarder.pid"
|
|
30
|
-
APP_LOG_FILE="$LOG_DIR/app-console.log"
|
|
31
|
-
|
|
32
|
-
{ [ -f "$FORWARDER" ] && command -v node >/dev/null 2>&1; } || exit 0
|
|
33
|
-
mkdir -p "$LOG_DIR"
|
|
34
|
-
|
|
35
|
-
# One debugger owns a React Native page. Reap any collector for this Metro port,
|
|
36
|
-
# including one left by an older globally installed harness.
|
|
37
|
-
while read -r pid cmd; do
|
|
38
|
-
case "$cmd" in
|
|
39
|
-
*console-forwarder.cjs*" --port $PORT "*|*console-forwarder.cjs*" --port=$PORT "*)
|
|
40
|
-
kill "$pid" 2>/dev/null || true
|
|
41
|
-
;;
|
|
42
|
-
esac
|
|
43
|
-
done < <(ps -axo pid=,command= 2>/dev/null || true)
|
|
44
|
-
|
|
45
|
-
if [ -f "$PID_FILE" ]; then
|
|
46
|
-
old_pid="$(cat "$PID_FILE" 2>/dev/null || true)"
|
|
47
|
-
if [ -n "$old_pid" ]; then
|
|
48
|
-
case "$(ps -p "$old_pid" -o command= 2>/dev/null)" in
|
|
49
|
-
*console-forwarder.cjs*) kill "$old_pid" 2>/dev/null || true ;;
|
|
50
|
-
esac
|
|
51
|
-
fi
|
|
52
|
-
rm -f "$PID_FILE"
|
|
53
|
-
fi
|
|
54
|
-
|
|
55
|
-
: > "$APP_LOG_FILE"
|
|
56
|
-
pid="$(node "$SCRIPT_DIR/launch-console-forwarder.cjs" \
|
|
57
|
-
--port "$PORT" \
|
|
58
|
-
--out "$APP_LOG_FILE" \
|
|
59
|
-
--err "$LOG_DIR/console-forwarder.err")" || exit 1
|
|
60
|
-
printf '%s\n' "$pid" > "$PID_FILE"
|
|
61
|
-
|
|
62
|
-
# A successful launch must never claim capture when the collector died at boot.
|
|
63
|
-
sleep 0.1
|
|
64
|
-
if ! kill -0 "$pid" 2>/dev/null; then
|
|
65
|
-
rm -f "$PID_FILE"
|
|
66
|
-
printf 'start-console-forwarder: collector exited during startup; inspect %s\n' \
|
|
67
|
-
"$LOG_DIR/console-forwarder.err" >&2
|
|
68
|
-
exit 1
|
|
69
|
-
fi
|
|
70
|
-
[ "$QUIET" = true ] || printf 'Device console → app-console.log (CDP forwarder pid %s)\n' "$pid" >&2
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import fs from 'node:fs';
|
|
3
|
-
import path from 'node:path';
|
|
4
|
-
|
|
5
|
-
const [cacheFile, timeoutText, endpoint] = process.argv.slice(2);
|
|
6
|
-
const timeoutMs = Number.parseInt(timeoutText ?? '', 10);
|
|
7
|
-
|
|
8
|
-
function stableVersion(value) {
|
|
9
|
-
const match = /^(\d{1,6})\.(\d{1,6})\.(\d{1,6})$/u.exec(value);
|
|
10
|
-
if (!match) return null;
|
|
11
|
-
return `${Number(match[1])}.${Number(match[2])}.${Number(match[3])}`;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
async function main() {
|
|
15
|
-
if (!cacheFile || !endpoint || !Number.isInteger(timeoutMs) || timeoutMs < 1) return;
|
|
16
|
-
const controller = new AbortController();
|
|
17
|
-
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
18
|
-
try {
|
|
19
|
-
const response = await fetch(endpoint, { signal: controller.signal });
|
|
20
|
-
if (!response.ok) return;
|
|
21
|
-
const data = await response.json();
|
|
22
|
-
if (!data || typeof data.latest !== 'string' || data.latest.length === 0) return;
|
|
23
|
-
const latest = stableVersion(data.latest);
|
|
24
|
-
if (!latest) return;
|
|
25
|
-
let lastCheck = Date.now();
|
|
26
|
-
try {
|
|
27
|
-
const cached = JSON.parse(fs.readFileSync(cacheFile, 'utf8'));
|
|
28
|
-
if (typeof cached.lastCheck === 'number') lastCheck = cached.lastCheck;
|
|
29
|
-
} catch {
|
|
30
|
-
// The parent may have exited before creating the cache; recreate it below.
|
|
31
|
-
}
|
|
32
|
-
fs.mkdirSync(path.dirname(cacheFile), { recursive: true });
|
|
33
|
-
const temporary = `${cacheFile}.${process.pid}.tmp`;
|
|
34
|
-
fs.writeFileSync(temporary, JSON.stringify({ lastCheck, latest }));
|
|
35
|
-
fs.renameSync(temporary, cacheFile);
|
|
36
|
-
} catch {
|
|
37
|
-
// Passive checks are intentionally silent; the explicit update command teaches failures.
|
|
38
|
-
} finally {
|
|
39
|
-
clearTimeout(timer);
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
await main();
|
|
@@ -1,93 +0,0 @@
|
|
|
1
|
-
import { spawn, spawnSync } from "node:child_process";
|
|
2
|
-
import fs from "node:fs";
|
|
3
|
-
import path from "node:path";
|
|
4
|
-
import { recipeRuntimePath, runnerDir } from "../../paths.js";
|
|
5
|
-
async function ensureExtensionConsoleCapture(target) {
|
|
6
|
-
const cdpPort = process.env.CDP_PORT ?? process.env.RECIPE_CDP_PORT;
|
|
7
|
-
if (!cdpPort) throw new Error("Extension console capture requires a resolved CDP port.");
|
|
8
|
-
const response = await fetch(`http://127.0.0.1:${cdpPort}/json/version`);
|
|
9
|
-
if (!response.ok) throw new Error(`Extension CDP ${cdpPort} is not ready for console capture.`);
|
|
10
|
-
const extensionLog = recipeRuntimePath(target, "extension-console.log");
|
|
11
|
-
const dappLog = recipeRuntimePath(target, "dapp-console.log");
|
|
12
|
-
const collectorLog = recipeRuntimePath(target, "console-tail.log");
|
|
13
|
-
const pidFile = recipeRuntimePath(target, "console-tail.pid");
|
|
14
|
-
fs.mkdirSync(path.dirname(extensionLog), { recursive: true });
|
|
15
|
-
const existingPid = readPid(pidFile);
|
|
16
|
-
if (existingPid && collectorOwnsLog(existingPid, extensionLog)) return;
|
|
17
|
-
if (existingPid) {
|
|
18
|
-
try {
|
|
19
|
-
process.kill(existingPid, "SIGTERM");
|
|
20
|
-
} catch {
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
fs.rmSync(pidFile, { force: true });
|
|
24
|
-
fs.writeFileSync(extensionLog, "");
|
|
25
|
-
fs.writeFileSync(dappLog, "");
|
|
26
|
-
const script = path.join(runnerDir, "adapters", "extension", "console-tail.mjs");
|
|
27
|
-
const output = fs.openSync(collectorLog, "a");
|
|
28
|
-
let child;
|
|
29
|
-
try {
|
|
30
|
-
child = spawn(process.execPath, [
|
|
31
|
-
script,
|
|
32
|
-
"--cdp-port",
|
|
33
|
-
cdpPort,
|
|
34
|
-
"--extension-log",
|
|
35
|
-
extensionLog,
|
|
36
|
-
"--dapp-log",
|
|
37
|
-
dappLog
|
|
38
|
-
], {
|
|
39
|
-
cwd: target,
|
|
40
|
-
detached: true,
|
|
41
|
-
stdio: ["ignore", output, output]
|
|
42
|
-
});
|
|
43
|
-
} finally {
|
|
44
|
-
fs.closeSync(output);
|
|
45
|
-
}
|
|
46
|
-
if (!child.pid) throw new Error("Extension console collector did not start.");
|
|
47
|
-
child.unref();
|
|
48
|
-
fs.writeFileSync(pidFile, `${child.pid}
|
|
49
|
-
`);
|
|
50
|
-
for (let attempt = 0; attempt < 100; attempt += 1) {
|
|
51
|
-
if (!processAlive(child.pid)) break;
|
|
52
|
-
if (collectorOwnsLog(child.pid, extensionLog) && containsAttachment(extensionLog, dappLog)) return;
|
|
53
|
-
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
54
|
-
}
|
|
55
|
-
throw new Error(`Extension console collector ${child.pid} did not attach to CDP ${cdpPort}.`);
|
|
56
|
-
}
|
|
57
|
-
function processAlive(pid) {
|
|
58
|
-
try {
|
|
59
|
-
process.kill(pid, 0);
|
|
60
|
-
return true;
|
|
61
|
-
} catch {
|
|
62
|
-
return false;
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
function readPid(pidFile) {
|
|
66
|
-
try {
|
|
67
|
-
const pid = Number.parseInt(fs.readFileSync(pidFile, "utf8").trim(), 10);
|
|
68
|
-
return Number.isInteger(pid) && pid > 0 ? pid : null;
|
|
69
|
-
} catch {
|
|
70
|
-
return null;
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
function collectorOwnsLog(pid, extensionLog) {
|
|
74
|
-
try {
|
|
75
|
-
process.kill(pid, 0);
|
|
76
|
-
} catch {
|
|
77
|
-
return false;
|
|
78
|
-
}
|
|
79
|
-
const result = spawnSync("ps", ["-p", String(pid), "-o", "command="], { encoding: "utf8" });
|
|
80
|
-
return result.status === 0 && result.stdout.includes("console-tail.mjs") && result.stdout.includes(extensionLog);
|
|
81
|
-
}
|
|
82
|
-
function containsAttachment(...logs) {
|
|
83
|
-
return logs.some((log) => {
|
|
84
|
-
try {
|
|
85
|
-
return fs.readFileSync(log, "utf8").includes("[attached]");
|
|
86
|
-
} catch {
|
|
87
|
-
return false;
|
|
88
|
-
}
|
|
89
|
-
});
|
|
90
|
-
}
|
|
91
|
-
export {
|
|
92
|
-
ensureExtensionConsoleCapture
|
|
93
|
-
};
|