rollipop 1.0.0-alpha.30 → 1.0.0-alpha.31
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/dist/constants.js +1 -1
- package/dist/package.js +1 -1
- package/dist/server/devframe/index.js +1 -1
- package/dist/server/devframe/setup.js +1 -1
- package/dist/storage/file-storage.js +1 -1
- package/package.json +2 -2
- package/src/runtime/__tests__/hmr-runtime.spec.ts +153 -5
- package/src/runtime/hmr-client.ts +14 -5
- package/src/runtime/react-native-core.d.ts +0 -11
package/dist/constants.js
CHANGED
|
@@ -20,7 +20,7 @@ var constants_exports = /* @__PURE__ */ __exportAll({
|
|
|
20
20
|
ROLLIPOP_VIRTUAL_ENTRY_ID: () => ROLLIPOP_VIRTUAL_ENTRY_ID,
|
|
21
21
|
ROLLIPOP_VIRTUAL_PREFIX: () => ROLLIPOP_VIRTUAL_PREFIX
|
|
22
22
|
});
|
|
23
|
-
const ROLLIPOP_VERSION = "1.0.0-alpha.
|
|
23
|
+
const ROLLIPOP_VERSION = "1.0.0-alpha.31";
|
|
24
24
|
const ROLLIPOP_VIRTUAL_PREFIX = "\0rollipop/";
|
|
25
25
|
const ROLLIPOP_VIRTUAL_BOOTSTRAP_ID = `${ROLLIPOP_VIRTUAL_PREFIX}bootstrap`;
|
|
26
26
|
const ROLLIPOP_VIRTUAL_ENTRY_ID = `${ROLLIPOP_VIRTUAL_PREFIX}entry`;
|
package/dist/package.js
CHANGED
|
@@ -24,7 +24,7 @@ var RollipopDevframeController = class {
|
|
|
24
24
|
this.definition = defineDevframe({
|
|
25
25
|
id: "rollipop",
|
|
26
26
|
name: "Rollipop",
|
|
27
|
-
version: "1.0.0-alpha.
|
|
27
|
+
version: "1.0.0-alpha.31",
|
|
28
28
|
packageName: "rollipop",
|
|
29
29
|
homepage: "https://github.com/rollipop-dev/rollipop",
|
|
30
30
|
description: "React Native development dashboard powered by Rollipop.",
|
|
@@ -16,7 +16,7 @@ async function setupDevframe(context, server) {
|
|
|
16
16
|
const controller = new RollipopDevframeController(context, createAgentToolContext(context));
|
|
17
17
|
const devtools = initHub({
|
|
18
18
|
name: DEVFRAME_NAME,
|
|
19
|
-
version: "1.0.0-alpha.
|
|
19
|
+
version: "1.0.0-alpha.31",
|
|
20
20
|
base: ROLLIPOP_DEVFRAME_BASE,
|
|
21
21
|
cwd: context.config.root,
|
|
22
22
|
ui: createUi({
|
|
@@ -28,7 +28,7 @@ var FileStorage = class FileStorage {
|
|
|
28
28
|
this.dataFilePath = path.join(FileStorage.getPath(basePath, { prepare: true }), DATA_FILENAME);
|
|
29
29
|
if (fs.existsSync(this.dataFilePath)) {
|
|
30
30
|
const loadedData = JSON.parse(fs.readFileSync(this.dataFilePath, "utf-8"));
|
|
31
|
-
if ("version" in loadedData && loadedData.version === "1.0.0-alpha.
|
|
31
|
+
if ("version" in loadedData && loadedData.version === "1.0.0-alpha.31") this.data = loadedData;
|
|
32
32
|
else {
|
|
33
33
|
this.data = createDefaultData();
|
|
34
34
|
this.flush();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "rollipop",
|
|
3
|
-
"version": "1.0.0-alpha.
|
|
3
|
+
"version": "1.0.0-alpha.31",
|
|
4
4
|
"homepage": "https://github.com/rollipop-dev/rollipop#readme",
|
|
5
5
|
"bugs": {
|
|
6
6
|
"url": "https://github.com/rollipop-dev/rollipop/issues"
|
|
@@ -67,7 +67,7 @@
|
|
|
67
67
|
"@react-native-community/cli-server-api": "20.2.0",
|
|
68
68
|
"@react-native-community/cli-types": "20.2.0",
|
|
69
69
|
"@react-native/dev-middleware": "0.87.1",
|
|
70
|
-
"@rollipop/dashboard": "1.0.0-alpha.
|
|
70
|
+
"@rollipop/dashboard": "1.0.0-alpha.31",
|
|
71
71
|
"@rollipop/rolldown": "1.0.32",
|
|
72
72
|
"@swc/core": "1.15.43",
|
|
73
73
|
"@swc/helpers": "0.5.23",
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
|
+
import { stripVTControlCharacters } from 'node:util';
|
|
2
3
|
import vm from 'node:vm';
|
|
3
4
|
|
|
4
5
|
import { transformSync } from '@swc/core';
|
|
5
|
-
import prettyFormat from 'pretty-format';
|
|
6
|
+
import * as prettyFormat from 'pretty-format';
|
|
6
7
|
import { afterEach, beforeEach, describe, expect, it, vi } from 'vite-plus/test';
|
|
7
8
|
|
|
8
9
|
import type { HMRContext } from '../../types/hmr';
|
|
@@ -85,6 +86,7 @@ class FakeDevRuntime {
|
|
|
85
86
|
}
|
|
86
87
|
|
|
87
88
|
class FakeWebSocket {
|
|
89
|
+
static readonly CONNECTING = 0;
|
|
88
90
|
static readonly OPEN = 1;
|
|
89
91
|
static instances: FakeWebSocket[] = [];
|
|
90
92
|
readonly sent: string[] = [];
|
|
@@ -141,6 +143,148 @@ describe('HMR runtime', () => {
|
|
|
141
143
|
expect((runtime.createModuleHotContext('entry.js') as any).runtime).toBe(runtime);
|
|
142
144
|
});
|
|
143
145
|
|
|
146
|
+
describe('client logs', () => {
|
|
147
|
+
const formatter = { format: prettyFormat.format, plugins: prettyFormat.plugins };
|
|
148
|
+
|
|
149
|
+
it.each([
|
|
150
|
+
['named-only', { __esModule: true, ...formatter }],
|
|
151
|
+
['default-only', { __esModule: true, default: formatter }],
|
|
152
|
+
['CommonJS', formatter],
|
|
153
|
+
[
|
|
154
|
+
'named and callable default',
|
|
155
|
+
{ __esModule: true, ...formatter, default: prettyFormat.format },
|
|
156
|
+
],
|
|
157
|
+
])('sends formatted values with %s pretty-format exports', async (_, module) => {
|
|
158
|
+
await createRuntime();
|
|
159
|
+
const client = createClient(module);
|
|
160
|
+
client.setup('ios', 'index.bundle', 'localhost', 8081, true);
|
|
161
|
+
const socket = FakeWebSocket.instances[0];
|
|
162
|
+
const circular: { self?: unknown } = {};
|
|
163
|
+
circular.self = circular;
|
|
164
|
+
const element = {
|
|
165
|
+
$$typeof: Symbol.for('react.element'),
|
|
166
|
+
type: 'View',
|
|
167
|
+
key: null,
|
|
168
|
+
props: { testID: 'counter', children: 'hello' },
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
client.log('warn', [
|
|
172
|
+
'object: %s',
|
|
173
|
+
{ answer: 42 },
|
|
174
|
+
[1, 2],
|
|
175
|
+
true,
|
|
176
|
+
null,
|
|
177
|
+
undefined,
|
|
178
|
+
new Error('boom'),
|
|
179
|
+
circular,
|
|
180
|
+
element,
|
|
181
|
+
]);
|
|
182
|
+
|
|
183
|
+
expect(socket.sent).toHaveLength(1);
|
|
184
|
+
const message = JSON.parse(socket.sent[0]);
|
|
185
|
+
expect(message.type).toBe('hmr:log');
|
|
186
|
+
expect(message.level).toBe('warn');
|
|
187
|
+
expect(message.data.map(stripVTControlCharacters)).toEqual([
|
|
188
|
+
'object: %s',
|
|
189
|
+
'{"answer": 42}',
|
|
190
|
+
'[1, 2]',
|
|
191
|
+
'true',
|
|
192
|
+
'null',
|
|
193
|
+
'undefined',
|
|
194
|
+
'[Error: boom]',
|
|
195
|
+
'{"self": [Circular]}',
|
|
196
|
+
'<View testID="counter">hello</View>',
|
|
197
|
+
]);
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
it('flushes logs from before setup and while connecting in order without replaying them', async () => {
|
|
201
|
+
const runtime = await createRuntime();
|
|
202
|
+
const client = createClient();
|
|
203
|
+
const circular: { self?: unknown } = {};
|
|
204
|
+
circular.self = circular;
|
|
205
|
+
client.log('info', ['before setup', circular]);
|
|
206
|
+
|
|
207
|
+
client.setup('ios', 'index.bundle', 'localhost', 8081, true);
|
|
208
|
+
const socket = FakeWebSocket.instances[0];
|
|
209
|
+
socket.readyState = FakeWebSocket.CONNECTING;
|
|
210
|
+
client.log('warn', ['while connecting', { count: 1 }, undefined]);
|
|
211
|
+
expect(socket.sent).toEqual([]);
|
|
212
|
+
|
|
213
|
+
socket.readyState = FakeWebSocket.OPEN;
|
|
214
|
+
socket.emit('open', {});
|
|
215
|
+
client.log('error', ['after opening', [1, 2]]);
|
|
216
|
+
|
|
217
|
+
const expectedLogs = [
|
|
218
|
+
{ type: 'hmr:log', level: 'info', data: ['before setup', '{"self": [Circular]}'] },
|
|
219
|
+
{ type: 'hmr:log', level: 'warn', data: ['while connecting', '{"count": 1}', 'undefined'] },
|
|
220
|
+
{ type: 'hmr:log', level: 'error', data: ['after opening', '[1, 2]'] },
|
|
221
|
+
];
|
|
222
|
+
expect(socket.sent.map((message) => JSON.parse(message))).toEqual([
|
|
223
|
+
{
|
|
224
|
+
type: 'hmr:connected',
|
|
225
|
+
clientId: runtime.clientId,
|
|
226
|
+
bundleEntry: 'index.bundle',
|
|
227
|
+
platform: 'ios',
|
|
228
|
+
},
|
|
229
|
+
...expectedLogs,
|
|
230
|
+
]);
|
|
231
|
+
|
|
232
|
+
socket.emit('open', {});
|
|
233
|
+
expect(
|
|
234
|
+
socket.sent.map((message) => JSON.parse(message)).filter(({ type }) => type === 'hmr:log'),
|
|
235
|
+
).toEqual(expectedLogs);
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
it('keeps only the latest 100 logs while the socket is connecting', async () => {
|
|
239
|
+
const { client, socket } = await setupClient();
|
|
240
|
+
socket.readyState = FakeWebSocket.CONNECTING;
|
|
241
|
+
|
|
242
|
+
for (let index = 0; index < 105; index++) {
|
|
243
|
+
client.log('debug', [index]);
|
|
244
|
+
}
|
|
245
|
+
expect(socket.sent).toEqual([]);
|
|
246
|
+
|
|
247
|
+
socket.readyState = FakeWebSocket.OPEN;
|
|
248
|
+
socket.emit('open', {});
|
|
249
|
+
|
|
250
|
+
expect(socket.sent.slice(1).map((message) => JSON.parse(message))).toEqual(
|
|
251
|
+
Array.from({ length: 100 }, (_, index) => ({
|
|
252
|
+
type: 'hmr:log',
|
|
253
|
+
level: 'debug',
|
|
254
|
+
data: [String(index + 5)],
|
|
255
|
+
})),
|
|
256
|
+
);
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
it('waits for the host socket even when a remote socket opens first', async () => {
|
|
260
|
+
const { runtime, client, socket } = await setupClient();
|
|
261
|
+
socket.readyState = FakeWebSocket.CONNECTING;
|
|
262
|
+
client.log('info', ['host log', { count: 1 }]);
|
|
263
|
+
globalThis.__rollipop_runtime__!.registerGraph({
|
|
264
|
+
id: 'remote',
|
|
265
|
+
origin: 'http://localhost:8082',
|
|
266
|
+
bundleEntry: 'remote.bundle',
|
|
267
|
+
platform: 'ios',
|
|
268
|
+
runtime: { ...runtime, setup: vi.fn(), setEnabled: vi.fn() } as unknown as HMRGraphRuntime,
|
|
269
|
+
});
|
|
270
|
+
const remoteSocket = FakeWebSocket.instances[1];
|
|
271
|
+
remoteSocket.emit('open', {});
|
|
272
|
+
client.log('warn', ['host still connecting']);
|
|
273
|
+
|
|
274
|
+
expect(socket.sent).toEqual([]);
|
|
275
|
+
expect(remoteSocket.sent.map((message) => JSON.parse(message).type)).toEqual([
|
|
276
|
+
'hmr:connected',
|
|
277
|
+
]);
|
|
278
|
+
|
|
279
|
+
socket.readyState = FakeWebSocket.OPEN;
|
|
280
|
+
socket.emit('open', {});
|
|
281
|
+
expect(socket.sent.slice(1).map((message) => JSON.parse(message))).toEqual([
|
|
282
|
+
{ type: 'hmr:log', level: 'info', data: ['host log', '{"count": 1}'] },
|
|
283
|
+
{ type: 'hmr:log', level: 'warn', data: ['host still connecting'] },
|
|
284
|
+
]);
|
|
285
|
+
});
|
|
286
|
+
});
|
|
287
|
+
|
|
144
288
|
it('evaluates a patch, acknowledges delivery, and applies accepted updates', async () => {
|
|
145
289
|
// oxlint-disable-next-line no-eval
|
|
146
290
|
const evaluate = vi.fn((source: string) => (0, eval)(source));
|
|
@@ -506,9 +650,15 @@ async function setupRuntime() {
|
|
|
506
650
|
|
|
507
651
|
async function setupClient(enabled = true) {
|
|
508
652
|
const runtime = await createRuntime();
|
|
653
|
+
const client = createClient();
|
|
654
|
+
client.setup('ios', 'index.bundle', 'localhost', 8081, enabled);
|
|
655
|
+
return { runtime, client, socket: FakeWebSocket.instances[0] };
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
function createClient(prettyFormatExports: unknown = prettyFormat) {
|
|
509
659
|
// HMRClient is injected into React Native, so provide its native imports in a sandbox.
|
|
510
660
|
const nativeModules: Record<string, unknown> = {
|
|
511
|
-
'pretty-format':
|
|
661
|
+
'pretty-format': prettyFormatExports,
|
|
512
662
|
'../LogBox/LogBox': { clearAllLogs: vi.fn() },
|
|
513
663
|
'../NativeModules/specs/NativeRedBox': { dismiss: vi.fn() },
|
|
514
664
|
'./DevLoadingView': { hide: vi.fn(), showMessage: vi.fn() },
|
|
@@ -528,9 +678,7 @@ async function setupClient(enabled = true) {
|
|
|
528
678
|
},
|
|
529
679
|
};
|
|
530
680
|
vm.runInNewContext(clientCode, context);
|
|
531
|
-
|
|
532
|
-
client.setup('ios', 'index.bundle', 'localhost', 8081, enabled);
|
|
533
|
-
return { runtime, client, socket: FakeWebSocket.instances[0] };
|
|
681
|
+
return context.exports.default;
|
|
534
682
|
}
|
|
535
683
|
|
|
536
684
|
function emitPatch(socket: FakeWebSocket, seq: number) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import * as prettyFormatModule from 'pretty-format';
|
|
2
2
|
|
|
3
3
|
import LogBox from '../LogBox/LogBox';
|
|
4
4
|
import NativeRedBox from '../NativeModules/specs/NativeRedBox';
|
|
@@ -35,6 +35,16 @@ interface HMRConnection {
|
|
|
35
35
|
pendingUpdatesCount: number;
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
+
function getPrettyFormat() {
|
|
39
|
+
// This replaces RN's HMRClient and uses RN's pretty-format version,
|
|
40
|
+
// which may expose named exports or only a default export independently of Rollipop's version.
|
|
41
|
+
return typeof prettyFormatModule.format === 'function'
|
|
42
|
+
? prettyFormatModule
|
|
43
|
+
: (prettyFormatModule.default as unknown as typeof prettyFormatModule);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const prettyFormat = getPrettyFormat();
|
|
47
|
+
|
|
38
48
|
class HMRClient implements HMRClientNativeInterface {
|
|
39
49
|
static readonly STARTUP_ERROR = 'Expected HMRClient.setup() call at startup';
|
|
40
50
|
static readonly MAX_PENDING_LOGS = 100;
|
|
@@ -89,7 +99,7 @@ class HMRClient implements HMRClientNativeInterface {
|
|
|
89
99
|
|
|
90
100
|
log(level: HMRClientLogLevel, data: any[]) {
|
|
91
101
|
const host = this.getHostConnection();
|
|
92
|
-
if (host == null) {
|
|
102
|
+
if (host == null || host.socket.readyState !== WebSocket.OPEN) {
|
|
93
103
|
this.pendingLogs.push([level, data]);
|
|
94
104
|
if (this.pendingLogs.length > HMRClient.MAX_PENDING_LOGS) {
|
|
95
105
|
this.pendingLogs.shift();
|
|
@@ -226,10 +236,9 @@ class HMRClient implements HMRClientNativeInterface {
|
|
|
226
236
|
) {
|
|
227
237
|
return;
|
|
228
238
|
}
|
|
229
|
-
for (const [level, data] of this.pendingLogs) {
|
|
230
|
-
this.
|
|
239
|
+
for (const [level, data] of this.pendingLogs.splice(0)) {
|
|
240
|
+
this.log(level, data);
|
|
231
241
|
}
|
|
232
|
-
this.pendingLogs.length = 0;
|
|
233
242
|
}
|
|
234
243
|
|
|
235
244
|
private dismissRedbox() {
|
|
@@ -24,14 +24,3 @@ declare module '*/Platform' {
|
|
|
24
24
|
|
|
25
25
|
export default Platform;
|
|
26
26
|
}
|
|
27
|
-
|
|
28
|
-
declare module 'pretty-format' {
|
|
29
|
-
const prettyFormat: {
|
|
30
|
-
format: (value: any, options?: any) => string;
|
|
31
|
-
plugins: {
|
|
32
|
-
ReactElement: any;
|
|
33
|
-
};
|
|
34
|
-
};
|
|
35
|
-
|
|
36
|
-
export default prettyFormat;
|
|
37
|
-
}
|