rollipop 1.0.0-alpha.21 → 1.0.0-alpha.23
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 +19 -0
- package/dist/{chunk-DEq-mXcV.js → _virtual/_rolldown/runtime.js} +1 -1
- package/dist/commands.d.ts +2 -4
- package/dist/commands.js +10 -3957
- package/dist/common/code.js +21 -0
- package/dist/common/constants.js +5 -0
- package/dist/common/env.js +33 -0
- package/dist/common/logger.d.ts +34 -0
- package/dist/common/logger.js +82 -0
- package/dist/common/logo.js +54 -0
- package/dist/common/progress-bar.js +167 -0
- package/dist/common/transformer.js +13 -0
- package/dist/common/types.d.ts +10 -0
- package/dist/config/compose-override.js +18 -0
- package/dist/config/defaults.d.ts +74 -0
- package/dist/config/defaults.js +74 -0
- package/dist/config/define-config.d.ts +13 -0
- package/dist/config/define-config.js +6 -0
- package/dist/config/index.d.ts +5 -0
- package/dist/config/index.js +5 -0
- package/dist/config/load-config.d.ts +19 -0
- package/dist/config/load-config.js +73 -0
- package/dist/config/merge-config.d.ts +12 -0
- package/dist/config/merge-config.js +20 -0
- package/dist/config/types.d.ts +452 -0
- package/dist/constants.d.ts +35 -0
- package/dist/constants.js +146 -0
- package/dist/core/assets.d.ts +91 -0
- package/dist/core/assets.js +244 -0
- package/dist/core/bundler.d.ts +15 -0
- package/dist/core/bundler.js +80 -0
- package/dist/core/env.d.ts +11 -0
- package/dist/core/env.js +36 -0
- package/dist/core/fs/data.js +9 -0
- package/dist/core/fs/storage.d.ts +15 -0
- package/dist/core/fs/storage.js +31 -0
- package/dist/core/plugins/babel-plugin.d.ts +22 -0
- package/dist/core/plugins/babel-plugin.js +74 -0
- package/dist/core/plugins/context.d.ts +10 -0
- package/dist/core/plugins/context.js +24 -0
- package/dist/core/plugins/dev-server-plugin.d.ts +13 -0
- package/dist/core/plugins/dev-server-plugin.js +27 -0
- package/dist/core/plugins/index.d.ts +13 -0
- package/dist/core/plugins/index.js +18 -0
- package/dist/core/plugins/prelude-plugin.d.ts +10 -0
- package/dist/core/plugins/prelude-plugin.js +23 -0
- package/dist/core/plugins/react-native-plugin.d.ts +36 -0
- package/dist/core/plugins/react-native-plugin.js +81 -0
- package/dist/core/plugins/reporter-plugin.d.ts +11 -0
- package/dist/core/plugins/reporter-plugin.js +87 -0
- package/dist/core/plugins/shared/filters.js +5 -0
- package/dist/core/plugins/swc-plugin.d.ts +26 -0
- package/dist/core/plugins/swc-plugin.js +108 -0
- package/dist/core/plugins/types.d.ts +18 -0
- package/dist/core/plugins/utils/source.js +10 -0
- package/dist/core/plugins/utils/transform-utils.js +56 -0
- package/dist/core/rolldown.js +313 -0
- package/dist/core/settings.js +19 -0
- package/dist/core/types.d.ts +83 -0
- package/dist/filter.d.ts +1 -0
- package/dist/filter.js +2 -0
- package/dist/hmr-runtime.iife.js +5 -5
- package/dist/index.d.ts +24 -1221
- package/dist/index.js +19 -4029
- package/dist/internal/react-native.js +24 -0
- package/dist/logger.js +5 -0
- package/dist/node/cli-utils.d.ts +10 -0
- package/dist/node/cli-utils.js +28 -0
- package/dist/node/cli.d.ts +6 -0
- package/dist/node/cli.js +23 -0
- package/dist/node/commands/agent/action.js +91 -0
- package/dist/node/commands/agent/command.js +10 -0
- package/dist/node/commands/agent/index.js +2 -0
- package/dist/node/commands/bundle/action.js +33 -0
- package/dist/node/commands/bundle/command.js +96 -0
- package/dist/node/commands/bundle/index.js +2 -0
- package/dist/node/commands/start/action.js +37 -0
- package/dist/node/commands/start/command.js +93 -0
- package/dist/node/commands/start/debugger.js +79 -0
- package/dist/node/commands/start/index.js +2 -0
- package/dist/node/commands/start/setup-interactive-mode.d.ts +20 -0
- package/dist/node/commands/start/setup-interactive-mode.js +107 -0
- package/dist/node/constants.js +4 -0
- package/dist/node/logger.js +5 -0
- package/dist/node/types.d.ts +23 -0
- package/dist/node/utils.js +23 -0
- package/dist/package.js +4 -0
- package/dist/runtime.js +1 -1
- package/dist/server/bundle.d.ts +12 -0
- package/dist/server/bundle.js +55 -0
- package/dist/server/bundler-pool.d.ts +51 -0
- package/dist/server/bundler-pool.js +197 -0
- package/dist/server/common/schema.js +19 -0
- package/dist/server/constants.d.ts +6 -0
- package/dist/server/constants.js +6 -0
- package/dist/server/create-dev-server.d.ts +6 -0
- package/dist/server/create-dev-server.js +185 -0
- package/dist/server/error.js +9 -0
- package/dist/server/events/event-bus.d.ts +12 -0
- package/dist/server/events/event-bus.js +16 -0
- package/dist/server/events/types.d.ts +37 -0
- package/dist/server/events/types.js +6 -0
- package/dist/server/index.d.ts +3 -0
- package/dist/server/index.js +3 -0
- package/dist/server/logger.js +33 -0
- package/dist/server/mcp/context.js +14 -0
- package/dist/server/mcp/server.js +86 -0
- package/dist/server/mcp/tools/app-log-diagnostics.js +37 -0
- package/dist/server/mcp/tools/build-diagnostics.js +97 -0
- package/dist/server/mcp/tools/build-info.js +33 -0
- package/dist/server/mcp/tools/device-diagnostics.js +52 -0
- package/dist/server/mcp/tools/index.js +277 -0
- package/dist/server/middlewares/request-logger.js +15 -0
- package/dist/server/middlewares/serve-assets.js +49 -0
- package/dist/server/middlewares/serve-bundle.js +72 -0
- package/dist/server/middlewares/sse.js +34 -0
- package/dist/server/middlewares/symbolicate.js +71 -0
- package/dist/server/sse/adapter.js +74 -0
- package/dist/server/sse/event-bus.js +26 -0
- package/dist/server/symbolicate.js +93 -0
- package/dist/server/types.d.ts +125 -0
- package/dist/server/wss/hmr-server.js +209 -0
- package/dist/server/wss/server.d.ts +9 -0
- package/dist/server/wss/server.js +70 -0
- package/dist/{runtime.d.cts → types/hmr.d.ts} +1 -12
- package/dist/types.d.ts +78 -0
- package/dist/utils/babel.js +11 -0
- package/dist/utils/build-options.js +17 -0
- package/dist/utils/bundle.js +6 -0
- package/dist/utils/config.d.ts +5 -0
- package/dist/utils/config.js +32 -0
- package/dist/utils/dev-server.js +51 -0
- package/dist/utils/env.js +7 -0
- package/dist/utils/errors.js +9 -0
- package/dist/utils/hash.js +8 -0
- package/dist/utils/id.js +28 -0
- package/dist/utils/node-resolve.js +42 -0
- package/dist/utils/promise.js +15 -0
- package/dist/utils/reporters.js +120 -0
- package/dist/utils/reset-cache.d.ts +8 -0
- package/dist/utils/reset-cache.js +25 -0
- package/dist/utils/response.js +91 -0
- package/dist/utils/run-build.d.ts +8 -0
- package/dist/utils/run-build.js +7 -0
- package/dist/utils/run-server.d.ts +6 -0
- package/dist/utils/run-server.js +20 -0
- package/dist/utils/runtime-target.js +9 -0
- package/dist/utils/serialize.js +10 -0
- package/dist/utils/server.js +6 -0
- package/dist/utils/storage.js +6 -0
- package/dist/utils/string.js +6 -0
- package/dist/utils/swc.js +10 -0
- package/dist/utils/terminal.js +86 -0
- package/dist/utils/url.js +23 -0
- package/package.json +56 -68
- package/dist/commands.cjs +0 -4008
- package/dist/commands.d.cts +0 -5
- package/dist/pluginutils.d.ts +0 -1
- package/dist/pluginutils.js +0 -2
- package/dist/runtime.cjs +0 -34
- /package/dist/{chunk-DXpK5_cz.js → chunk-DJV587Yu.js} +0 -0
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
import { resetCache } from "../../../utils/reset-cache.js";
|
|
2
|
+
import { getBaseBundleName } from "../../../utils/bundle.js";
|
|
3
|
+
import { parseUrl } from "../../../utils/url.js";
|
|
4
|
+
import { toSSEEvent } from "../../sse/adapter.js";
|
|
5
|
+
import { symbolicate } from "../../symbolicate.js";
|
|
6
|
+
import { getBuildInfo } from "./build-info.js";
|
|
7
|
+
import { CallToolRequestSchema, ErrorCode, ListToolsRequestSchema, McpError } from "@modelcontextprotocol/sdk/types.js";
|
|
8
|
+
import { type } from "arktype";
|
|
9
|
+
//#region src/server/mcp/tools/index.ts
|
|
10
|
+
const emptyArgs = type({});
|
|
11
|
+
const durationArgs = type({ duration: "number >= 1000 & number <= 60000 = 10000" });
|
|
12
|
+
const bundlerIdArgs = type({ bundlerId: "string" });
|
|
13
|
+
const limitBundlerArgs = type({
|
|
14
|
+
limit: "number >= 1 & number <= 500 = 100",
|
|
15
|
+
"bundlerId?": "string"
|
|
16
|
+
});
|
|
17
|
+
const optionalBundlerIdArgs = type({ "bundlerId?": "string" });
|
|
18
|
+
const symbolicateStackArgs = type({
|
|
19
|
+
stack: "unknown[]",
|
|
20
|
+
"bundleUrl?": "string",
|
|
21
|
+
"bundleName?": "string",
|
|
22
|
+
"platform?": "string",
|
|
23
|
+
"dev?": "boolean"
|
|
24
|
+
});
|
|
25
|
+
function registerTools(server, options) {
|
|
26
|
+
const { context, appLogDiagnostics, buildDiagnostics, deviceDiagnostics } = options;
|
|
27
|
+
const tools = [
|
|
28
|
+
defineTool({
|
|
29
|
+
name: "reset_cache",
|
|
30
|
+
title: "Reset Cache",
|
|
31
|
+
description: "Clear the build cache.",
|
|
32
|
+
inputSchema: emptyArgs,
|
|
33
|
+
async handler() {
|
|
34
|
+
resetCache(context.config.root);
|
|
35
|
+
context.eventBus.emit({ type: "cache_reset" });
|
|
36
|
+
return textResult("Cache cleared successfully.");
|
|
37
|
+
}
|
|
38
|
+
}),
|
|
39
|
+
defineTool({
|
|
40
|
+
name: "get_build_events",
|
|
41
|
+
title: "Get Build Events",
|
|
42
|
+
description: "Collect dev-server events for a duration.",
|
|
43
|
+
inputSchema: durationArgs,
|
|
44
|
+
async handler({ duration }) {
|
|
45
|
+
const events = [];
|
|
46
|
+
const unsubscribe = context.eventBus.subscribe((event) => {
|
|
47
|
+
const sseEvent = toSSEEvent(event);
|
|
48
|
+
if (sseEvent != null) events.push(sseEvent);
|
|
49
|
+
});
|
|
50
|
+
await new Promise((resolve) => setTimeout(resolve, duration));
|
|
51
|
+
unsubscribe();
|
|
52
|
+
if (events.length === 0) return textResult("No events received during the listening period.");
|
|
53
|
+
return jsonResult(events);
|
|
54
|
+
}
|
|
55
|
+
}),
|
|
56
|
+
defineTool({
|
|
57
|
+
name: "get_bundler_status",
|
|
58
|
+
title: "Get Bundler Status",
|
|
59
|
+
description: "Return a bundler status by id.",
|
|
60
|
+
inputSchema: bundlerIdArgs,
|
|
61
|
+
async handler({ bundlerId }) {
|
|
62
|
+
const bundler = context.bundlerPool.getInstanceById(bundlerId);
|
|
63
|
+
if (bundler == null) return jsonResult({ error: "not found" });
|
|
64
|
+
return jsonResult({
|
|
65
|
+
id: bundler.id,
|
|
66
|
+
status: bundler.status
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
}),
|
|
70
|
+
defineTool({
|
|
71
|
+
name: "build_logs",
|
|
72
|
+
title: "Build Logs",
|
|
73
|
+
description: "Return buffered Rolldown logs.",
|
|
74
|
+
inputSchema: limitBundlerArgs,
|
|
75
|
+
async handler({ limit, bundlerId }) {
|
|
76
|
+
return jsonResult(buildDiagnostics.getBuildLogs({
|
|
77
|
+
limit,
|
|
78
|
+
bundlerId
|
|
79
|
+
}));
|
|
80
|
+
}
|
|
81
|
+
}),
|
|
82
|
+
defineTool({
|
|
83
|
+
name: "build_errors",
|
|
84
|
+
title: "Build Errors",
|
|
85
|
+
description: "Return buffered Rolldown errors.",
|
|
86
|
+
inputSchema: limitBundlerArgs,
|
|
87
|
+
async handler({ limit, bundlerId }) {
|
|
88
|
+
return jsonResult(buildDiagnostics.getBuildErrors({
|
|
89
|
+
limit,
|
|
90
|
+
bundlerId
|
|
91
|
+
}));
|
|
92
|
+
}
|
|
93
|
+
}),
|
|
94
|
+
defineTool({
|
|
95
|
+
name: "clear_build_logs",
|
|
96
|
+
title: "Clear Build Logs",
|
|
97
|
+
description: "Clear buffered Rolldown logs.",
|
|
98
|
+
inputSchema: optionalBundlerIdArgs,
|
|
99
|
+
async handler({ bundlerId }) {
|
|
100
|
+
buildDiagnostics.clearBuildLogs({ bundlerId });
|
|
101
|
+
return jsonResult({ cleared: true });
|
|
102
|
+
}
|
|
103
|
+
}),
|
|
104
|
+
defineTool({
|
|
105
|
+
name: "clear_build_errors",
|
|
106
|
+
title: "Clear Build Errors",
|
|
107
|
+
description: "Clear buffered Rolldown errors.",
|
|
108
|
+
inputSchema: optionalBundlerIdArgs,
|
|
109
|
+
async handler({ bundlerId }) {
|
|
110
|
+
buildDiagnostics.clearBuildErrors({ bundlerId });
|
|
111
|
+
return jsonResult({ cleared: true });
|
|
112
|
+
}
|
|
113
|
+
}),
|
|
114
|
+
defineTool({
|
|
115
|
+
name: "clear_build_diagnostics",
|
|
116
|
+
title: "Clear Build Diagnostics",
|
|
117
|
+
description: "Clear buffered Rolldown diagnostics.",
|
|
118
|
+
inputSchema: optionalBundlerIdArgs,
|
|
119
|
+
async handler({ bundlerId }) {
|
|
120
|
+
buildDiagnostics.clearBuildDiagnostics({ bundlerId });
|
|
121
|
+
return jsonResult({ cleared: true });
|
|
122
|
+
}
|
|
123
|
+
}),
|
|
124
|
+
defineTool({
|
|
125
|
+
name: "get_console_logs",
|
|
126
|
+
title: "Get Console Logs",
|
|
127
|
+
description: "Return buffered app logs.",
|
|
128
|
+
inputSchema: limitBundlerArgs,
|
|
129
|
+
async handler({ limit, bundlerId }) {
|
|
130
|
+
return jsonResult(appLogDiagnostics.getConsoleLogs({
|
|
131
|
+
limit,
|
|
132
|
+
bundlerId
|
|
133
|
+
}));
|
|
134
|
+
}
|
|
135
|
+
}),
|
|
136
|
+
defineTool({
|
|
137
|
+
name: "clear_console_logs",
|
|
138
|
+
title: "Clear Console Logs",
|
|
139
|
+
description: "Clear buffered app logs.",
|
|
140
|
+
inputSchema: optionalBundlerIdArgs,
|
|
141
|
+
async handler({ bundlerId }) {
|
|
142
|
+
appLogDiagnostics.clearConsoleLogs({ bundlerId });
|
|
143
|
+
return jsonResult({ cleared: true });
|
|
144
|
+
}
|
|
145
|
+
}),
|
|
146
|
+
defineTool({
|
|
147
|
+
name: "list_devices",
|
|
148
|
+
title: "List Devices",
|
|
149
|
+
description: "Return known HMR clients.",
|
|
150
|
+
inputSchema: emptyArgs,
|
|
151
|
+
async handler() {
|
|
152
|
+
return jsonResult(deviceDiagnostics.getDevices());
|
|
153
|
+
}
|
|
154
|
+
}),
|
|
155
|
+
defineTool({
|
|
156
|
+
name: "reload",
|
|
157
|
+
title: "Reload App",
|
|
158
|
+
description: "Reload connected apps.",
|
|
159
|
+
inputSchema: emptyArgs,
|
|
160
|
+
async handler() {
|
|
161
|
+
context.message.broadcast("reload");
|
|
162
|
+
return jsonResult({
|
|
163
|
+
reloaded: true,
|
|
164
|
+
devices: deviceDiagnostics.getDevices()
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
}),
|
|
168
|
+
defineTool({
|
|
169
|
+
name: "get_build_info",
|
|
170
|
+
title: "Get Build Info",
|
|
171
|
+
description: "Return Rollipop build config.",
|
|
172
|
+
inputSchema: optionalBundlerIdArgs,
|
|
173
|
+
async handler({ bundlerId }) {
|
|
174
|
+
const bundler = bundlerId != null ? context.bundlerPool.getInstanceById(bundlerId) : void 0;
|
|
175
|
+
return jsonResult({
|
|
176
|
+
...getBuildInfo(context.config),
|
|
177
|
+
bundler: bundler != null ? {
|
|
178
|
+
id: bundler.id,
|
|
179
|
+
status: bundler.status
|
|
180
|
+
} : void 0
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
}),
|
|
184
|
+
defineTool({
|
|
185
|
+
name: "symbolicate_stack",
|
|
186
|
+
title: "Symbolicate Stack",
|
|
187
|
+
description: "Symbolicate React Native stack frames.",
|
|
188
|
+
inputSchema: symbolicateStackArgs,
|
|
189
|
+
async handler({ stack, bundleUrl, bundleName, platform, dev }) {
|
|
190
|
+
const buildOptions = resolveSymbolicateBuildOptions(stack, {
|
|
191
|
+
bundleUrl,
|
|
192
|
+
bundleName,
|
|
193
|
+
platform,
|
|
194
|
+
dev
|
|
195
|
+
});
|
|
196
|
+
return jsonResult(await symbolicate(await context.bundlerPool.get(buildOptions.bundleName, buildOptions).getBundle(), stack));
|
|
197
|
+
}
|
|
198
|
+
})
|
|
199
|
+
];
|
|
200
|
+
const toolsByName = new Map(tools.map((tool) => [tool.name, tool]));
|
|
201
|
+
server.server.registerCapabilities({ tools: { listChanged: true } });
|
|
202
|
+
server.server.setRequestHandler(ListToolsRequestSchema, () => ({ tools: tools.map((tool) => ({
|
|
203
|
+
name: tool.name,
|
|
204
|
+
title: tool.title,
|
|
205
|
+
description: tool.description,
|
|
206
|
+
inputSchema: toMcpInputSchema(tool.inputSchema)
|
|
207
|
+
})) }));
|
|
208
|
+
server.server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
209
|
+
try {
|
|
210
|
+
const tool = toolsByName.get(request.params.name);
|
|
211
|
+
if (tool == null) throw new McpError(ErrorCode.InvalidParams, `Tool ${request.params.name} not found`);
|
|
212
|
+
const args = parseToolArgs(tool.inputSchema, request.params.arguments ?? {});
|
|
213
|
+
return await tool.handler(args);
|
|
214
|
+
} catch (error) {
|
|
215
|
+
return errorResult(error instanceof Error ? error.message : String(error));
|
|
216
|
+
}
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
function defineTool(definition) {
|
|
220
|
+
return definition;
|
|
221
|
+
}
|
|
222
|
+
function parseToolArgs(schema, args) {
|
|
223
|
+
const result = schema(args);
|
|
224
|
+
if (isArkErrors(result)) throw new McpError(ErrorCode.InvalidParams, result.summary);
|
|
225
|
+
return result;
|
|
226
|
+
}
|
|
227
|
+
function isArkErrors(value) {
|
|
228
|
+
return typeof value === "object" && value != null && " arkKind" in value && value[" arkKind"] === "errors";
|
|
229
|
+
}
|
|
230
|
+
function toMcpInputSchema(schema) {
|
|
231
|
+
const { $schema: _schema, type: _type, ...jsonSchema } = schema.toJsonSchema();
|
|
232
|
+
return {
|
|
233
|
+
type: "object",
|
|
234
|
+
...jsonSchema
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
function textResult(text) {
|
|
238
|
+
return { content: [{
|
|
239
|
+
type: "text",
|
|
240
|
+
text
|
|
241
|
+
}] };
|
|
242
|
+
}
|
|
243
|
+
function errorResult(text) {
|
|
244
|
+
return {
|
|
245
|
+
content: [{
|
|
246
|
+
type: "text",
|
|
247
|
+
text
|
|
248
|
+
}],
|
|
249
|
+
isError: true
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
function jsonResult(value) {
|
|
253
|
+
return { content: [{
|
|
254
|
+
type: "text",
|
|
255
|
+
text: JSON.stringify(value, null, 2)
|
|
256
|
+
}] };
|
|
257
|
+
}
|
|
258
|
+
function resolveSymbolicateBuildOptions(stack, options) {
|
|
259
|
+
const file = options.bundleUrl ?? stack.find((frame) => frame.file?.startsWith("http"))?.file;
|
|
260
|
+
if (file != null) {
|
|
261
|
+
const { pathname, query } = parseUrl(file);
|
|
262
|
+
if (pathname == null || query.platform == null || query.dev == null) throw new Error("Bundle URL must include pathname, platform, and dev query parameters");
|
|
263
|
+
return {
|
|
264
|
+
bundleName: getBaseBundleName(pathname),
|
|
265
|
+
platform: String(query.platform),
|
|
266
|
+
dev: query.dev === "true"
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
if (options.bundleName == null || options.platform == null || options.dev == null) throw new Error("bundleName, platform, and dev are required when stack frames do not include a bundle URL");
|
|
270
|
+
return {
|
|
271
|
+
bundleName: options.bundleName,
|
|
272
|
+
platform: options.platform,
|
|
273
|
+
dev: options.dev
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
//#endregion
|
|
277
|
+
export { registerTools };
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { isDebugEnabled } from "../../common/env.js";
|
|
2
|
+
import { logger } from "../logger.js";
|
|
3
|
+
import chalk from "chalk";
|
|
4
|
+
//#region src/server/middlewares/request-logger.ts
|
|
5
|
+
const requestLogger = (req, _res, next) => {
|
|
6
|
+
if (isDebugEnabled()) {
|
|
7
|
+
logger.trace(chalk.bgBlue(` ${req.method} `), chalk.gray(req.url));
|
|
8
|
+
Object.entries(req.headers).forEach(([key, value]) => {
|
|
9
|
+
logger.trace(`${chalk.bold(key)}: ${chalk.gray(value)}`);
|
|
10
|
+
});
|
|
11
|
+
}
|
|
12
|
+
next();
|
|
13
|
+
};
|
|
14
|
+
//#endregion
|
|
15
|
+
export { requestLogger };
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { DEV_SERVER_ASSET_PATH } from "../constants.js";
|
|
2
|
+
import { resolveAssetPath } from "../../core/assets.js";
|
|
3
|
+
import fs from "node:fs";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import fp from "fastify-plugin";
|
|
6
|
+
import { asConst } from "json-schema-to-ts";
|
|
7
|
+
import mime from "mime";
|
|
8
|
+
//#region src/server/middlewares/serve-assets.ts
|
|
9
|
+
const queryParamSchema = asConst({
|
|
10
|
+
type: "object",
|
|
11
|
+
properties: {
|
|
12
|
+
platform: { type: "string" },
|
|
13
|
+
hash: { type: "string" }
|
|
14
|
+
},
|
|
15
|
+
required: ["platform"]
|
|
16
|
+
});
|
|
17
|
+
const plugin = fp((fastify, options) => {
|
|
18
|
+
const { context } = options;
|
|
19
|
+
const { host, port, https } = context.options;
|
|
20
|
+
const baseUrl = https ? `https://${host}:${port}` : `http://${host}:${port}`;
|
|
21
|
+
function resolveAsset(asset) {
|
|
22
|
+
return path.resolve(context.config.root, asset);
|
|
23
|
+
}
|
|
24
|
+
fastify.get(`/${DEV_SERVER_ASSET_PATH}/*`, {
|
|
25
|
+
schema: { querystring: queryParamSchema },
|
|
26
|
+
async handler(request, reply) {
|
|
27
|
+
const { url, query } = request;
|
|
28
|
+
const { pathname } = new URL(url, baseUrl);
|
|
29
|
+
const assetPath = resolveAsset(pathname.replace(new RegExp(`^/${DEV_SERVER_ASSET_PATH}/?`), ""));
|
|
30
|
+
let handle = null;
|
|
31
|
+
try {
|
|
32
|
+
handle = await fs.promises.open(resolveAssetPath(assetPath, {
|
|
33
|
+
platform: query.platform,
|
|
34
|
+
preferNativePlatform: context.config.resolver.preferNativePlatform
|
|
35
|
+
}, 1), "r");
|
|
36
|
+
const assetData = await handle.readFile();
|
|
37
|
+
const { size } = await handle.stat();
|
|
38
|
+
await reply.header("Content-Type", mime.getType(assetPath) ?? "").header("Content-Length", size).send(assetData);
|
|
39
|
+
} catch (error) {
|
|
40
|
+
fastify.log.error(error, "Failed to serve asset (scale assets resolving is not implemented yet)");
|
|
41
|
+
await reply.status(500).send();
|
|
42
|
+
} finally {
|
|
43
|
+
await handle?.close();
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
}, { name: "serve-assets" });
|
|
48
|
+
//#endregion
|
|
49
|
+
export { plugin };
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { BundleResponse } from "../../utils/response.js";
|
|
2
|
+
import { bundleRequestSchema } from "../common/schema.js";
|
|
3
|
+
import { isBundlerEventForId } from "../events/types.js";
|
|
4
|
+
import { invariant } from "es-toolkit";
|
|
5
|
+
import fp from "fastify-plugin";
|
|
6
|
+
import { asConst } from "json-schema-to-ts";
|
|
7
|
+
//#region src/server/middlewares/serve-bundle.ts
|
|
8
|
+
const routeParamSchema = asConst({
|
|
9
|
+
type: "object",
|
|
10
|
+
properties: { name: { type: "string" } }
|
|
11
|
+
});
|
|
12
|
+
function withGetBundleErrorHandler(reply, task) {
|
|
13
|
+
return task.catch((error) => {
|
|
14
|
+
return reply.status(500).send(error instanceof Error ? error.message : "Internal Server Error");
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
const plugin = fp((fastify, options) => {
|
|
18
|
+
const { context } = options;
|
|
19
|
+
const getBundleOptions = (buildOptions) => {
|
|
20
|
+
return {
|
|
21
|
+
platform: buildOptions.platform,
|
|
22
|
+
dev: buildOptions.dev,
|
|
23
|
+
minify: buildOptions.minify,
|
|
24
|
+
sourcemap: buildOptions.inlineSourceMap ? "inline" : true
|
|
25
|
+
};
|
|
26
|
+
};
|
|
27
|
+
fastify.get("/:name.bundle", {
|
|
28
|
+
schema: {
|
|
29
|
+
params: routeParamSchema,
|
|
30
|
+
querystring: bundleRequestSchema
|
|
31
|
+
},
|
|
32
|
+
async handler(request, reply) {
|
|
33
|
+
const { params, query, headers: { accept } } = request;
|
|
34
|
+
if (!params.name) {
|
|
35
|
+
await reply.status(400).send("invalid bundle name");
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
const buildOptions = getBundleOptions(query);
|
|
39
|
+
const bundler = context.bundlerPool.get(params.name, buildOptions);
|
|
40
|
+
if (accept?.includes("multipart/mixed") ?? false) {
|
|
41
|
+
const bundleResponse = new BundleResponse(reply);
|
|
42
|
+
const unsubscribe = context.eventBus.subscribe((event) => {
|
|
43
|
+
if (isBundlerEventForId(event, bundler.id) && event.type === "transform") bundleResponse.writeBundleState(event.transformedModules, event.totalModules ?? 0);
|
|
44
|
+
});
|
|
45
|
+
await bundler.getBundle().then((bundle) => bundleResponse.endWithBundle(bundle.code)).catch((error) => bundleResponse.endWithError(error)).finally(unsubscribe);
|
|
46
|
+
} else {
|
|
47
|
+
this.log.debug(`client is not support multipart/mixed content: ${accept ?? "<empty>"}`);
|
|
48
|
+
const code = (await withGetBundleErrorHandler(reply, bundler.getBundle())).code;
|
|
49
|
+
await reply.header("Content-Type", "application/javascript").header("Content-Length", Buffer.byteLength(code)).status(200).send(code);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
fastify.get("/:name.map", {
|
|
54
|
+
schema: {
|
|
55
|
+
params: routeParamSchema,
|
|
56
|
+
querystring: bundleRequestSchema
|
|
57
|
+
},
|
|
58
|
+
async handler(request, reply) {
|
|
59
|
+
const { params, query } = request;
|
|
60
|
+
if (!params.name) {
|
|
61
|
+
await reply.status(400).send("invalid bundle name");
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
const buildOptions = getBundleOptions(query);
|
|
65
|
+
const sourceMap = (await withGetBundleErrorHandler(reply, context.bundlerPool.get(params.name, buildOptions).getBundle())).sourceMap;
|
|
66
|
+
invariant(sourceMap, "Source map is not available");
|
|
67
|
+
await reply.header("Access-Control-Allow-Origin", "devtools://devtools").header("Content-Type", "application/json").header("Content-Length", Buffer.byteLength(sourceMap)).status(200).send(sourceMap);
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
}, { name: "serve-bundle" });
|
|
71
|
+
//#endregion
|
|
72
|
+
export { plugin };
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { toSSEClientLogEvent, toSSEEvent } from "../sse/adapter.js";
|
|
2
|
+
import { SSEEventPublisher } from "../sse/event-bus.js";
|
|
3
|
+
import fp from "fastify-plugin";
|
|
4
|
+
//#region src/server/middlewares/sse.ts
|
|
5
|
+
const plugin = fp((fastify, options) => {
|
|
6
|
+
const { context } = options;
|
|
7
|
+
const publisher = new SSEEventPublisher();
|
|
8
|
+
const clientLogPublisher = new SSEEventPublisher();
|
|
9
|
+
context.eventBus.subscribe((event) => {
|
|
10
|
+
const sseEvent = toSSEEvent(event);
|
|
11
|
+
if (sseEvent != null) publisher.publish(sseEvent);
|
|
12
|
+
const clientLogEvent = toSSEClientLogEvent(event);
|
|
13
|
+
if (clientLogEvent != null) clientLogPublisher.publish(clientLogEvent);
|
|
14
|
+
});
|
|
15
|
+
const registerStream = (path, streamPublisher) => {
|
|
16
|
+
fastify.get(path, (request, reply) => {
|
|
17
|
+
const res = reply.raw;
|
|
18
|
+
res.writeHead(200, {
|
|
19
|
+
Connection: "keep-alive",
|
|
20
|
+
"Content-Type": "text/event-stream",
|
|
21
|
+
"Cache-Control": "no-cache",
|
|
22
|
+
"X-Accel-Buffering": "no"
|
|
23
|
+
});
|
|
24
|
+
request.raw.socket.setNoDelay(true);
|
|
25
|
+
res.write(":ok\n\n");
|
|
26
|
+
streamPublisher.addClient(res);
|
|
27
|
+
request.raw.on("close", () => streamPublisher.removeClient(res));
|
|
28
|
+
});
|
|
29
|
+
};
|
|
30
|
+
registerStream("/sse/events", publisher);
|
|
31
|
+
registerStream("/sse/client-logs", clientLogPublisher);
|
|
32
|
+
}, { name: "sse" });
|
|
33
|
+
//#endregion
|
|
34
|
+
export { plugin };
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { isDebugEnabled } from "../../common/env.js";
|
|
2
|
+
import { getBaseBundleName } from "../../utils/bundle.js";
|
|
3
|
+
import { parseUrl } from "../../utils/url.js";
|
|
4
|
+
import { symbolicate } from "../symbolicate.js";
|
|
5
|
+
import { invariant } from "es-toolkit";
|
|
6
|
+
import chalk from "chalk";
|
|
7
|
+
import fp from "fastify-plugin";
|
|
8
|
+
import { asConst } from "json-schema-to-ts";
|
|
9
|
+
//#region src/server/middlewares/symbolicate.ts
|
|
10
|
+
const bodySchema = asConst({
|
|
11
|
+
type: "object",
|
|
12
|
+
properties: {
|
|
13
|
+
stack: {
|
|
14
|
+
type: "array",
|
|
15
|
+
items: {}
|
|
16
|
+
},
|
|
17
|
+
extraData: {}
|
|
18
|
+
},
|
|
19
|
+
required: ["stack"]
|
|
20
|
+
});
|
|
21
|
+
const plugin = fp((fastify, options) => {
|
|
22
|
+
const { context } = options;
|
|
23
|
+
fastify.post("/symbolicate", {
|
|
24
|
+
schema: { body: bodySchema },
|
|
25
|
+
async handler(request, reply) {
|
|
26
|
+
const { stack } = request.body;
|
|
27
|
+
const bundleUrl = stack.find((frame) => frame.file?.startsWith("http"));
|
|
28
|
+
invariant(bundleUrl?.file, "No bundle URL found in stack frames");
|
|
29
|
+
const { pathname, query } = parseUrl(bundleUrl.file);
|
|
30
|
+
invariant(pathname, "No pathname found in bundle URL");
|
|
31
|
+
invariant(query.platform, "No platform found in query");
|
|
32
|
+
invariant(query.dev, "No dev found in query");
|
|
33
|
+
const platform = query.platform;
|
|
34
|
+
const dev = query.dev === "true";
|
|
35
|
+
const bundleName = getBaseBundleName(pathname);
|
|
36
|
+
const symbolicateResult = await symbolicate(await context.bundlerPool.get(bundleName, {
|
|
37
|
+
platform,
|
|
38
|
+
dev
|
|
39
|
+
}).getBundle(), stack);
|
|
40
|
+
if (isDebugEnabled()) printSymbolicateResult(stack, symbolicateResult);
|
|
41
|
+
await reply.header("Content-Type", "application/json").send(symbolicateResult);
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
}, { name: "symbolicate" });
|
|
45
|
+
function printSymbolicateResult(rawStackFrame, symbolicateResult) {
|
|
46
|
+
console.log();
|
|
47
|
+
console.log("Symbolicate result:");
|
|
48
|
+
console.log();
|
|
49
|
+
if (symbolicateResult.codeFrame != null) {
|
|
50
|
+
console.log(symbolicateResult.codeFrame.content);
|
|
51
|
+
console.log();
|
|
52
|
+
}
|
|
53
|
+
console.log("Stack trace:");
|
|
54
|
+
symbolicateResult.stack.forEach((stackFrame) => {
|
|
55
|
+
const symbol = stackFrame.methodName ?? "<anonymous>";
|
|
56
|
+
const file = stackFrame.file ?? "unknown";
|
|
57
|
+
const location = stackFrame.lineNumber != null && stackFrame.column != null ? `(${chalk.gray.underline(`${file}:${stackFrame.lineNumber}:${stackFrame.column}`)})` : "";
|
|
58
|
+
console.log(` at ${symbol} ${location}`);
|
|
59
|
+
});
|
|
60
|
+
console.log();
|
|
61
|
+
console.log("Raw stack trace:");
|
|
62
|
+
rawStackFrame.filter((stackFrame) => stackFrame.file?.startsWith("http")).forEach((stackFrame) => {
|
|
63
|
+
const bundleName = new URL(stackFrame.file).pathname.slice(1);
|
|
64
|
+
const symbol = stackFrame.methodName ?? "<anonymous>";
|
|
65
|
+
const location = stackFrame.lineNumber != null && stackFrame.column != null ? `(${chalk.gray.underline(`${bundleName}:${stackFrame.lineNumber}:${stackFrame.column}`)})` : "";
|
|
66
|
+
console.log(` at ${symbol} ${location}`);
|
|
67
|
+
});
|
|
68
|
+
console.log();
|
|
69
|
+
}
|
|
70
|
+
//#endregion
|
|
71
|
+
export { plugin };
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import stripAnsi from "strip-ansi";
|
|
2
|
+
//#region src/server/sse/adapter.ts
|
|
3
|
+
function toSSEEvent(event) {
|
|
4
|
+
switch (event.type) {
|
|
5
|
+
case "client_log": return null;
|
|
6
|
+
case "device_connected": return {
|
|
7
|
+
type: "device_connected",
|
|
8
|
+
clientId: event.client.id
|
|
9
|
+
};
|
|
10
|
+
case "device_disconnected": return {
|
|
11
|
+
type: "device_disconnected",
|
|
12
|
+
clientId: event.client.id
|
|
13
|
+
};
|
|
14
|
+
case "server_ready":
|
|
15
|
+
case "cache_reset": return event;
|
|
16
|
+
case "bundle_build_started":
|
|
17
|
+
case "bundle_build_done":
|
|
18
|
+
case "bundle_build_failed":
|
|
19
|
+
case "hmr_failed":
|
|
20
|
+
case "watch_change": return reporterEventToSSEEvent(event);
|
|
21
|
+
case "hmr_updates":
|
|
22
|
+
case "device_message":
|
|
23
|
+
case "device_error":
|
|
24
|
+
case "transform":
|
|
25
|
+
case "build_log":
|
|
26
|
+
case "build_error": return null;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
function toSSEClientLogEvent(event) {
|
|
30
|
+
if (event.type !== "client_log") return null;
|
|
31
|
+
return {
|
|
32
|
+
type: "client_log",
|
|
33
|
+
...event.bundlerId != null ? { bundlerId: event.bundlerId } : {},
|
|
34
|
+
data: event.data
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
function reporterEventToSSEEvent(event) {
|
|
38
|
+
switch (event.type) {
|
|
39
|
+
case "bundle_build_started": return {
|
|
40
|
+
type: "bundle_build_started",
|
|
41
|
+
bundlerId: event.bundlerId
|
|
42
|
+
};
|
|
43
|
+
case "bundle_build_done": return {
|
|
44
|
+
type: "bundle_build_done",
|
|
45
|
+
bundlerId: event.bundlerId,
|
|
46
|
+
totalModules: event.totalModules,
|
|
47
|
+
transformedModules: event.transformedModules,
|
|
48
|
+
cacheHitModules: event.cacheHitModules,
|
|
49
|
+
duration: event.duration,
|
|
50
|
+
...event.bundleFilePath != null ? { bundleFilePath: event.bundleFilePath } : {}
|
|
51
|
+
};
|
|
52
|
+
case "bundle_build_failed": return {
|
|
53
|
+
type: "bundle_build_failed",
|
|
54
|
+
bundlerId: event.bundlerId,
|
|
55
|
+
error: stripAnsi(event.error.message)
|
|
56
|
+
};
|
|
57
|
+
case "hmr_failed": return {
|
|
58
|
+
type: "hmr_failed",
|
|
59
|
+
bundlerId: event.bundlerId,
|
|
60
|
+
error: stripAnsi(event.error.message)
|
|
61
|
+
};
|
|
62
|
+
case "watch_change": return {
|
|
63
|
+
type: "watch_change",
|
|
64
|
+
bundlerId: event.bundlerId,
|
|
65
|
+
file: event.id
|
|
66
|
+
};
|
|
67
|
+
case "client_log":
|
|
68
|
+
case "transform":
|
|
69
|
+
case "build_log":
|
|
70
|
+
case "build_error": return null;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
//#endregion
|
|
74
|
+
export { toSSEClientLogEvent, toSSEEvent };
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
//#region src/server/sse/event-bus.ts
|
|
2
|
+
var SSEEventPublisher = class {
|
|
3
|
+
clients = /* @__PURE__ */ new Set();
|
|
4
|
+
publish(event) {
|
|
5
|
+
const data = JSON.stringify(event);
|
|
6
|
+
const message = `event: ${event.type}\ndata: ${data}\n\n`;
|
|
7
|
+
for (const client of this.clients) if (!client.closed) client.write(message);
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Subscribe an SSE HTTP response client.
|
|
11
|
+
*/
|
|
12
|
+
addClient(res) {
|
|
13
|
+
this.clients.add(res);
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Unsubscribe an SSE HTTP response client.
|
|
17
|
+
*/
|
|
18
|
+
removeClient(res) {
|
|
19
|
+
this.clients.delete(res);
|
|
20
|
+
}
|
|
21
|
+
get clientCount() {
|
|
22
|
+
return this.clients.size;
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
//#endregion
|
|
26
|
+
export { SSEEventPublisher };
|