Skip to content

Commit 405a77a

Browse files
committed
Merge remote-tracking branch 'origin/main' into feat/bridge-port
# Conflicts: # packages/bridge/src/server.ts # packages/bundler-metro/src/factory.ts # packages/bundler-metro/src/index.ts # packages/bundler-metro/src/types.ts # packages/jest/src/__tests__/harness.test.ts # packages/jest/src/harness.ts # packages/jest/src/setup.ts
2 parents 9364c8f + 4844dc3 commit 405a77a

34 files changed

Lines changed: 1881 additions & 647 deletions

actions/shared/index.cjs

Lines changed: 91 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -158,20 +158,6 @@ var require_src = __commonJS({
158158
}
159159
});
160160

161-
// ../../node_modules/is-unicode-supported/index.js
162-
var require_is_unicode_supported = __commonJS({
163-
"../../node_modules/is-unicode-supported/index.js"(exports2, module2) {
164-
"use strict";
165-
module2.exports = () => {
166-
if (process.platform !== "win32") {
167-
return true;
168-
}
169-
return Boolean(process.env.CI) || Boolean(process.env.WT_SESSION) || // Windows Terminal
170-
process.env.TERM_PROGRAM === "vscode" || process.env.TERM === "xterm-256color" || process.env.TERM === "alacritty";
171-
};
172-
}
173-
});
174-
175161
// ../../node_modules/zod/dist/esm/v3/external.js
176162
var external_exports = {};
177163
__export(external_exports, {
@@ -4208,45 +4194,56 @@ var coerce = {
42084194
};
42094195
var NEVER = INVALID;
42104196

4211-
// ../plugins/dist/utils.js
4212-
var isHookTree = (value) => {
4213-
if (value == null || typeof value !== "object" || Array.isArray(value)) {
4214-
return false;
4215-
}
4216-
for (const child of Object.values(value)) {
4217-
if (child === void 0) {
4218-
continue;
4219-
}
4220-
if (typeof child === "function") {
4221-
continue;
4222-
}
4223-
if (child == null || typeof child !== "object" || Array.isArray(child) || !isHookTree(child)) {
4224-
return false;
4225-
}
4226-
}
4227-
return true;
4197+
// ../tools/dist/logger.js
4198+
var import_node_util = __toESM(require("util"), 1);
4199+
var verbose = !!process.env.HARNESS_DEBUG;
4200+
var BASE_TAG = "[harness]";
4201+
var getTimestamp = () => (/* @__PURE__ */ new Date()).toISOString();
4202+
var normalizeScope = (scope) => scope.trim().replace(/^\[+|\]+$/g, "").replace(/\]\[/g, "][");
4203+
var formatPrefix = (scopes) => {
4204+
const suffix = scopes.map((scope) => `[${normalizeScope(scope)}]`).join("");
4205+
return `${BASE_TAG}${suffix}`;
42284206
};
4229-
4230-
// ../plugins/dist/plugin.js
4231-
var isHarnessPlugin = (value) => {
4232-
if (value == null || typeof value !== "object" || Array.isArray(value)) {
4233-
return false;
4234-
}
4235-
const candidate = value;
4236-
if (typeof candidate.name !== "string" || candidate.name.length === 0) {
4237-
return false;
4238-
}
4239-
if (candidate.createState != null && typeof candidate.createState !== "function") {
4240-
return false;
4241-
}
4242-
if (candidate.hooks != null && !isHookTree(candidate.hooks)) {
4243-
return false;
4244-
}
4245-
return true;
4207+
var mapLines = (text, prefix) => text.split("\n").map((line) => `${prefix} ${line}`).join("\n");
4208+
var writeLog = (level, scopes, messages) => {
4209+
const method = level === "warn" ? console.warn : level === "error" ? console.error : level === "debug" ? console.debug : console.info;
4210+
const output = import_node_util.default.format(...messages);
4211+
const prefix = `${getTimestamp()} ${formatPrefix(scopes)}`;
4212+
method(mapLines(output, prefix));
42464213
};
4247-
4248-
// ../tools/dist/logger.js
4249-
var import_node_util2 = __toESM(require("util"), 1);
4214+
var setVerbose = (level) => {
4215+
verbose = level;
4216+
};
4217+
var isVerbose = () => {
4218+
return verbose;
4219+
};
4220+
var createScopedLogger = (scopes = []) => ({
4221+
debug: (...messages) => {
4222+
if (!verbose) {
4223+
return;
4224+
}
4225+
writeLog("debug", scopes, messages);
4226+
},
4227+
info: (...messages) => {
4228+
writeLog("info", scopes, messages);
4229+
},
4230+
warn: (...messages) => {
4231+
writeLog("warn", scopes, messages);
4232+
},
4233+
error: (...messages) => {
4234+
writeLog("error", scopes, messages);
4235+
},
4236+
log: (...messages) => {
4237+
writeLog("log", scopes, messages);
4238+
},
4239+
success: (...messages) => {
4240+
writeLog("success", scopes, messages);
4241+
},
4242+
child: (scope) => createScopedLogger([...scopes, scope]),
4243+
setVerbose,
4244+
isVerbose
4245+
});
4246+
var logger = createScopedLogger();
42504247

42514248
// ../../node_modules/@clack/core/dist/index.mjs
42524249
var import_node_process = require("process");
@@ -4271,7 +4268,7 @@ var import_node_process2 = __toESM(require("process"), 1);
42714268
var import_node_fs = require("fs");
42724269
var import_node_path = require("path");
42734270
var import_sisteransi2 = __toESM(require_src(), 1);
4274-
var import_node_util = require("util");
4271+
var import_node_util2 = require("util");
42754272
function ht() {
42764273
return import_node_process2.default.platform !== "win32" ? import_node_process2.default.env.TERM !== "linux" : !!import_node_process2.default.env.CI || !!import_node_process2.default.env.WT_SESSION || !!import_node_process2.default.env.TERMINUS_SUBLIME || import_node_process2.default.env.ConEmuTask === "{cmd::Cmder}" || import_node_process2.default.env.TERM_PROGRAM === "Terminus-Sublime" || import_node_process2.default.env.TERM_PROGRAM === "vscode" || import_node_process2.default.env.TERM === "xterm-256color" || import_node_process2.default.env.TERM === "alacritty" || import_node_process2.default.env.TERMINAL_EMULATOR === "JetBrains-JediTerm";
42774274
}
@@ -4313,12 +4310,8 @@ var Ut = import_picocolors.default.magenta;
43134310
var Ye = { light: w("\u2500", "-"), heavy: w("\u2501", "="), block: w("\u2588", "#") };
43144311
var ze = `${import_picocolors.default.gray(h)} `;
43154312

4316-
// ../tools/dist/logger.js
4317-
var import_is_unicode_supported = __toESM(require_is_unicode_supported(), 1);
4318-
var unicode = (0, import_is_unicode_supported.default)();
4319-
var unicodeWithFallback = (c, fallback) => unicode ? c : fallback;
4320-
var SYMBOL_DEBUG = unicodeWithFallback("\u25CF", "\u2022");
4321-
var verbose = !!process.env.HARNESS_DEBUG;
4313+
// ../tools/dist/spawn.js
4314+
var spawnLogger = logger.child("spawn");
43224315

43234316
// ../tools/dist/react-native.js
43244317
var import_node_module = require("module");
@@ -4338,6 +4331,46 @@ var import_node_fs4 = __toESM(require("fs"), 1);
43384331
var import_node_path4 = __toESM(require("path"), 1);
43394332
var DEFAULT_ARTIFACT_ROOT = import_node_path4.default.join(process.cwd(), ".harness", "crash-reports");
43404333

4334+
// ../plugins/dist/utils.js
4335+
var isHookTree = (value) => {
4336+
if (value == null || typeof value !== "object" || Array.isArray(value)) {
4337+
return false;
4338+
}
4339+
for (const child of Object.values(value)) {
4340+
if (child === void 0) {
4341+
continue;
4342+
}
4343+
if (typeof child === "function") {
4344+
continue;
4345+
}
4346+
if (child == null || typeof child !== "object" || Array.isArray(child) || !isHookTree(child)) {
4347+
return false;
4348+
}
4349+
}
4350+
return true;
4351+
};
4352+
4353+
// ../plugins/dist/plugin.js
4354+
var isHarnessPlugin = (value) => {
4355+
if (value == null || typeof value !== "object" || Array.isArray(value)) {
4356+
return false;
4357+
}
4358+
const candidate = value;
4359+
if (typeof candidate.name !== "string" || candidate.name.length === 0) {
4360+
return false;
4361+
}
4362+
if (candidate.createState != null && typeof candidate.createState !== "function") {
4363+
return false;
4364+
}
4365+
if (candidate.hooks != null && !isHookTree(candidate.hooks)) {
4366+
return false;
4367+
}
4368+
return true;
4369+
};
4370+
4371+
// ../plugins/dist/manager.js
4372+
var pluginsLogger = logger.child("plugins");
4373+
43414374
// ../config/dist/types.js
43424375
var DEFAULT_METRO_PORT = 8081;
43434376
var RunnerSchema = external_exports.object({

packages/bridge/src/server.ts

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import { DeviceNotRespondingError } from './errors.js';
2424
import { matchImageSnapshot } from './image-snapshot.js';
2525

2626
export { DeviceNotRespondingError } from './errors.js';
27+
const bridgeLogger = logger.child('bridge');
2728

2829
type BridgeServerStandaloneOptions = {
2930
port: number;
@@ -100,6 +101,16 @@ export const getBridgeServer = async ({
100101
noServer: true,
101102
}
102103
);
104+
if ('port' in transport) {
105+
bridgeLogger.debug('bridge server listening on port %d', transport.port);
106+
} else if ('server' in transport) {
107+
bridgeLogger.debug(
108+
'bridge server attached to existing HTTP server at path %s',
109+
transport.path ?? '/'
110+
);
111+
} else {
112+
bridgeLogger.debug('bridge server created in noServer mode');
113+
}
103114
const emitter = new EventEmitter();
104115
const clients = new Set<WebSocket>();
105116
const binaryStore = new BinaryStore();
@@ -158,7 +169,8 @@ export const getBridgeServer = async ({
158169
{
159170
timeout,
160171
onFunctionError: (error, functionName, args) => {
161-
console.error('Function error', error, functionName, args);
172+
bridgeLogger.error('rpc function failed: %s args=%o', functionName, args);
173+
bridgeLogger.error(error);
162174
throw error;
163175
},
164176
onTimeoutError(functionName, args) {
@@ -168,9 +180,9 @@ export const getBridgeServer = async ({
168180
);
169181

170182
wss.on('connection', (ws: WebSocket) => {
171-
logger.debug('Client connected to the bridge');
183+
bridgeLogger.debug('client connected');
172184
ws.on('close', () => {
173-
logger.debug('Client disconnected from the bridge');
185+
bridgeLogger.debug('client disconnected');
174186

175187
// TODO: Remove channel when connection is closed.
176188
clients.delete(ws);
@@ -191,7 +203,7 @@ export const getBridgeServer = async ({
191203
binaryStore.add(transferId, data);
192204
return;
193205
} catch (error) {
194-
logger.warn('Failed to parse binary frame', error);
206+
bridgeLogger.warn('failed to parse binary frame', error);
195207
}
196208
}
197209
const message = event.toString();
@@ -206,6 +218,7 @@ export const getBridgeServer = async ({
206218
});
207219

208220
const dispose = () => {
221+
bridgeLogger.debug('disposing bridge server');
209222
for (const client of wss.clients) {
210223
client.terminate();
211224
}
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
import fs from 'node:fs';
2+
import os from 'node:os';
3+
import path from 'node:path';
4+
import { afterEach, describe, expect, it, vi } from 'vitest';
5+
import type { Config as HarnessConfig } from '@react-native-harness/config';
6+
import type { Reporter, ReportableEvent } from '../reporter.js';
7+
import { getBundleRequestObserverMiddleware } from '../middlewares/bundle-request-middleware.js';
8+
import { HARNESS_REQUEST_KIND_HEADER } from '../request-kind.js';
9+
10+
const createReporter = () => {
11+
const events: ReportableEvent[] = [];
12+
13+
const reporter: Reporter = {
14+
addListener: vi.fn(),
15+
removeListener: vi.fn(),
16+
clearAllListeners: vi.fn(),
17+
emit: (event) => {
18+
events.push(event);
19+
},
20+
};
21+
22+
return { events, reporter };
23+
};
24+
25+
const createProjectRoot = () => {
26+
const projectRoot = fs.mkdtempSync(
27+
path.join(os.tmpdir(), 'rn-harness-bundle-request-')
28+
);
29+
tempDirs.push(projectRoot);
30+
fs.writeFileSync(path.join(projectRoot, 'index.js'), 'module.exports = {};');
31+
return projectRoot;
32+
};
33+
34+
const tempDirs: string[] = [];
35+
36+
afterEach(() => {
37+
for (const tempDir of tempDirs.splice(0)) {
38+
fs.rmSync(tempDir, { recursive: true, force: true });
39+
}
40+
});
41+
42+
const createHarnessConfig = (): HarnessConfig =>
43+
({
44+
entryPoint: './index.js',
45+
}) as HarnessConfig;
46+
47+
describe('bundle request observer middleware', () => {
48+
it('emits app-originated entry bundle requests', () => {
49+
const { events, reporter } = createReporter();
50+
const middleware = getBundleRequestObserverMiddleware(
51+
createProjectRoot(),
52+
createHarnessConfig(),
53+
reporter
54+
);
55+
const next = vi.fn();
56+
57+
middleware(
58+
{
59+
headers: {},
60+
url: '/index.bundle?platform=ios&dev=true',
61+
} as never,
62+
{} as never,
63+
next
64+
);
65+
66+
expect(events).toEqual([
67+
expect.objectContaining({
68+
type: 'bundle_request_observed',
69+
platform: 'ios',
70+
requestKind: 'app',
71+
url: '/index.bundle?platform=ios&dev=true',
72+
}),
73+
]);
74+
expect(next).toHaveBeenCalledTimes(1);
75+
});
76+
77+
it('tags prewarm requests using the Harness header', () => {
78+
const { events, reporter } = createReporter();
79+
const middleware = getBundleRequestObserverMiddleware(
80+
createProjectRoot(),
81+
createHarnessConfig(),
82+
reporter
83+
);
84+
85+
middleware(
86+
{
87+
headers: {
88+
[HARNESS_REQUEST_KIND_HEADER]: 'prewarm',
89+
},
90+
url: '/index.bundle?platform=android',
91+
} as never,
92+
{} as never,
93+
vi.fn()
94+
);
95+
96+
expect(events).toEqual([
97+
expect.objectContaining({
98+
platform: 'android',
99+
requestKind: 'prewarm',
100+
}),
101+
]);
102+
});
103+
104+
it('ignores non-entry bundle requests', () => {
105+
const { events, reporter } = createReporter();
106+
const middleware = getBundleRequestObserverMiddleware(
107+
createProjectRoot(),
108+
createHarnessConfig(),
109+
reporter
110+
);
111+
112+
middleware(
113+
{
114+
headers: {},
115+
url: '/other.bundle?platform=ios',
116+
} as never,
117+
{} as never,
118+
vi.fn()
119+
);
120+
121+
expect(events).toEqual([]);
122+
});
123+
});

0 commit comments

Comments
 (0)