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.
Files changed (161) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/dist/{chunk-DEq-mXcV.js → _virtual/_rolldown/runtime.js} +1 -1
  3. package/dist/commands.d.ts +2 -4
  4. package/dist/commands.js +10 -3957
  5. package/dist/common/code.js +21 -0
  6. package/dist/common/constants.js +5 -0
  7. package/dist/common/env.js +33 -0
  8. package/dist/common/logger.d.ts +34 -0
  9. package/dist/common/logger.js +82 -0
  10. package/dist/common/logo.js +54 -0
  11. package/dist/common/progress-bar.js +167 -0
  12. package/dist/common/transformer.js +13 -0
  13. package/dist/common/types.d.ts +10 -0
  14. package/dist/config/compose-override.js +18 -0
  15. package/dist/config/defaults.d.ts +74 -0
  16. package/dist/config/defaults.js +74 -0
  17. package/dist/config/define-config.d.ts +13 -0
  18. package/dist/config/define-config.js +6 -0
  19. package/dist/config/index.d.ts +5 -0
  20. package/dist/config/index.js +5 -0
  21. package/dist/config/load-config.d.ts +19 -0
  22. package/dist/config/load-config.js +73 -0
  23. package/dist/config/merge-config.d.ts +12 -0
  24. package/dist/config/merge-config.js +20 -0
  25. package/dist/config/types.d.ts +452 -0
  26. package/dist/constants.d.ts +35 -0
  27. package/dist/constants.js +146 -0
  28. package/dist/core/assets.d.ts +91 -0
  29. package/dist/core/assets.js +244 -0
  30. package/dist/core/bundler.d.ts +15 -0
  31. package/dist/core/bundler.js +80 -0
  32. package/dist/core/env.d.ts +11 -0
  33. package/dist/core/env.js +36 -0
  34. package/dist/core/fs/data.js +9 -0
  35. package/dist/core/fs/storage.d.ts +15 -0
  36. package/dist/core/fs/storage.js +31 -0
  37. package/dist/core/plugins/babel-plugin.d.ts +22 -0
  38. package/dist/core/plugins/babel-plugin.js +74 -0
  39. package/dist/core/plugins/context.d.ts +10 -0
  40. package/dist/core/plugins/context.js +24 -0
  41. package/dist/core/plugins/dev-server-plugin.d.ts +13 -0
  42. package/dist/core/plugins/dev-server-plugin.js +27 -0
  43. package/dist/core/plugins/index.d.ts +13 -0
  44. package/dist/core/plugins/index.js +18 -0
  45. package/dist/core/plugins/prelude-plugin.d.ts +10 -0
  46. package/dist/core/plugins/prelude-plugin.js +23 -0
  47. package/dist/core/plugins/react-native-plugin.d.ts +36 -0
  48. package/dist/core/plugins/react-native-plugin.js +81 -0
  49. package/dist/core/plugins/reporter-plugin.d.ts +11 -0
  50. package/dist/core/plugins/reporter-plugin.js +87 -0
  51. package/dist/core/plugins/shared/filters.js +5 -0
  52. package/dist/core/plugins/swc-plugin.d.ts +26 -0
  53. package/dist/core/plugins/swc-plugin.js +108 -0
  54. package/dist/core/plugins/types.d.ts +18 -0
  55. package/dist/core/plugins/utils/source.js +10 -0
  56. package/dist/core/plugins/utils/transform-utils.js +56 -0
  57. package/dist/core/rolldown.js +313 -0
  58. package/dist/core/settings.js +19 -0
  59. package/dist/core/types.d.ts +83 -0
  60. package/dist/filter.d.ts +1 -0
  61. package/dist/filter.js +2 -0
  62. package/dist/hmr-runtime.iife.js +5 -5
  63. package/dist/index.d.ts +24 -1221
  64. package/dist/index.js +19 -4029
  65. package/dist/internal/react-native.js +24 -0
  66. package/dist/logger.js +5 -0
  67. package/dist/node/cli-utils.d.ts +10 -0
  68. package/dist/node/cli-utils.js +28 -0
  69. package/dist/node/cli.d.ts +6 -0
  70. package/dist/node/cli.js +23 -0
  71. package/dist/node/commands/agent/action.js +91 -0
  72. package/dist/node/commands/agent/command.js +10 -0
  73. package/dist/node/commands/agent/index.js +2 -0
  74. package/dist/node/commands/bundle/action.js +33 -0
  75. package/dist/node/commands/bundle/command.js +96 -0
  76. package/dist/node/commands/bundle/index.js +2 -0
  77. package/dist/node/commands/start/action.js +37 -0
  78. package/dist/node/commands/start/command.js +93 -0
  79. package/dist/node/commands/start/debugger.js +79 -0
  80. package/dist/node/commands/start/index.js +2 -0
  81. package/dist/node/commands/start/setup-interactive-mode.d.ts +20 -0
  82. package/dist/node/commands/start/setup-interactive-mode.js +107 -0
  83. package/dist/node/constants.js +4 -0
  84. package/dist/node/logger.js +5 -0
  85. package/dist/node/types.d.ts +23 -0
  86. package/dist/node/utils.js +23 -0
  87. package/dist/package.js +4 -0
  88. package/dist/runtime.js +1 -1
  89. package/dist/server/bundle.d.ts +12 -0
  90. package/dist/server/bundle.js +55 -0
  91. package/dist/server/bundler-pool.d.ts +51 -0
  92. package/dist/server/bundler-pool.js +197 -0
  93. package/dist/server/common/schema.js +19 -0
  94. package/dist/server/constants.d.ts +6 -0
  95. package/dist/server/constants.js +6 -0
  96. package/dist/server/create-dev-server.d.ts +6 -0
  97. package/dist/server/create-dev-server.js +185 -0
  98. package/dist/server/error.js +9 -0
  99. package/dist/server/events/event-bus.d.ts +12 -0
  100. package/dist/server/events/event-bus.js +16 -0
  101. package/dist/server/events/types.d.ts +37 -0
  102. package/dist/server/events/types.js +6 -0
  103. package/dist/server/index.d.ts +3 -0
  104. package/dist/server/index.js +3 -0
  105. package/dist/server/logger.js +33 -0
  106. package/dist/server/mcp/context.js +14 -0
  107. package/dist/server/mcp/server.js +86 -0
  108. package/dist/server/mcp/tools/app-log-diagnostics.js +37 -0
  109. package/dist/server/mcp/tools/build-diagnostics.js +97 -0
  110. package/dist/server/mcp/tools/build-info.js +33 -0
  111. package/dist/server/mcp/tools/device-diagnostics.js +52 -0
  112. package/dist/server/mcp/tools/index.js +277 -0
  113. package/dist/server/middlewares/request-logger.js +15 -0
  114. package/dist/server/middlewares/serve-assets.js +49 -0
  115. package/dist/server/middlewares/serve-bundle.js +72 -0
  116. package/dist/server/middlewares/sse.js +34 -0
  117. package/dist/server/middlewares/symbolicate.js +71 -0
  118. package/dist/server/sse/adapter.js +74 -0
  119. package/dist/server/sse/event-bus.js +26 -0
  120. package/dist/server/symbolicate.js +93 -0
  121. package/dist/server/types.d.ts +125 -0
  122. package/dist/server/wss/hmr-server.js +209 -0
  123. package/dist/server/wss/server.d.ts +9 -0
  124. package/dist/server/wss/server.js +70 -0
  125. package/dist/{runtime.d.cts → types/hmr.d.ts} +1 -12
  126. package/dist/types.d.ts +78 -0
  127. package/dist/utils/babel.js +11 -0
  128. package/dist/utils/build-options.js +17 -0
  129. package/dist/utils/bundle.js +6 -0
  130. package/dist/utils/config.d.ts +5 -0
  131. package/dist/utils/config.js +32 -0
  132. package/dist/utils/dev-server.js +51 -0
  133. package/dist/utils/env.js +7 -0
  134. package/dist/utils/errors.js +9 -0
  135. package/dist/utils/hash.js +8 -0
  136. package/dist/utils/id.js +28 -0
  137. package/dist/utils/node-resolve.js +42 -0
  138. package/dist/utils/promise.js +15 -0
  139. package/dist/utils/reporters.js +120 -0
  140. package/dist/utils/reset-cache.d.ts +8 -0
  141. package/dist/utils/reset-cache.js +25 -0
  142. package/dist/utils/response.js +91 -0
  143. package/dist/utils/run-build.d.ts +8 -0
  144. package/dist/utils/run-build.js +7 -0
  145. package/dist/utils/run-server.d.ts +6 -0
  146. package/dist/utils/run-server.js +20 -0
  147. package/dist/utils/runtime-target.js +9 -0
  148. package/dist/utils/serialize.js +10 -0
  149. package/dist/utils/server.js +6 -0
  150. package/dist/utils/storage.js +6 -0
  151. package/dist/utils/string.js +6 -0
  152. package/dist/utils/swc.js +10 -0
  153. package/dist/utils/terminal.js +86 -0
  154. package/dist/utils/url.js +23 -0
  155. package/package.json +56 -68
  156. package/dist/commands.cjs +0 -4008
  157. package/dist/commands.d.cts +0 -5
  158. package/dist/pluginutils.d.ts +0 -1
  159. package/dist/pluginutils.js +0 -2
  160. package/dist/runtime.cjs +0 -34
  161. /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 };