rollipop 1.0.0-alpha.25 → 1.0.0-alpha.27

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.
@@ -1,6 +1,7 @@
1
1
  import { getDefaultConfig } from "./defaults.js";
2
2
  import { createPluginContext } from "../core/plugins/context.js";
3
3
  import { mergeConfig } from "./merge-config.js";
4
+ import { printConfigNotice } from "./notice.js";
4
5
  import path from "node:path";
5
6
  import { invariant, omit } from "es-toolkit";
6
7
  import * as c12 from "c12";
@@ -38,6 +39,7 @@ async function loadConfig(options = {}) {
38
39
  };
39
40
  if (!path.isAbsolute(resolvedConfig.entry)) resolvedConfig.entry = path.resolve(resolvedConfig.root, resolvedConfig.entry);
40
41
  await invokeConfigResolved(resolvedConfig, plugins);
42
+ printConfigNotice(resolvedConfig);
41
43
  return resolvedConfig;
42
44
  }
43
45
  async function flattenPluginOption(pluginOption) {
@@ -0,0 +1,7 @@
1
+ import { logger } from "../logger.js";
2
+ //#region src/config/notice.ts
3
+ function printConfigNotice(config) {
4
+ if (config.transformer.reactCompiler != null) logger.info("✨ React Compiler is enabled");
5
+ }
6
+ //#endregion
7
+ export { printConfigNotice };
package/dist/constants.js CHANGED
@@ -19,7 +19,7 @@ var constants_exports = /* @__PURE__ */ __exportAll({
19
19
  ROLLIPOP_VIRTUAL_ENTRY_ID: () => ROLLIPOP_VIRTUAL_ENTRY_ID,
20
20
  ROLLIPOP_VIRTUAL_PREFIX: () => ROLLIPOP_VIRTUAL_PREFIX
21
21
  });
22
- const ROLLIPOP_VERSION = "1.0.0-alpha.25";
22
+ const ROLLIPOP_VERSION = "1.0.0-alpha.27";
23
23
  const ROLLIPOP_VIRTUAL_PREFIX = "\0rollipop/";
24
24
  const ROLLIPOP_VIRTUAL_ENTRY_ID = `${ROLLIPOP_VIRTUAL_PREFIX}entry`;
25
25
  /**
@@ -1,5 +1,5 @@
1
1
  declare namespace assets_d_exports {
2
- export { AssetContext, AssetData, AssetDataFiltered, AssetDataWithoutFiles, AssetInfo, AssetScale, copyAssetsToDestination, generateAssetRegistryCode, getAssetPriority, getSuffixedPath, platformSuffixPattern, resolveAssetPath, resolveScaledAssets, stripSuffix };
2
+ export { AssetContext, AssetData, AssetDataFiltered, AssetDataWithoutFiles, AssetInfo, AssetScale, copyAssetsToDestination, filterPlatformAssetScales, generateAssetRegistryCode, getAssetPriority, getSuffixedPath, platformSuffixPattern, resolveAssetPath, resolveScaledAssets, stripSuffix };
3
3
  }
4
4
  /**
5
5
  * **NOTE**: Type definitions are ported from `metro` implementation.
@@ -42,7 +42,7 @@ interface AssetData extends AssetDataWithoutFiles {
42
42
  id: string;
43
43
  files: string[];
44
44
  }
45
- type AssetScale = 0.75 | 1 | 1.5 | 2 | 3;
45
+ type AssetScale = number;
46
46
  interface ResolveScaledAssetsOptions {
47
47
  projectRoot: string;
48
48
  assetPath: string;
@@ -75,7 +75,7 @@ interface GetSuffixedPathOptions {
75
75
  * ```
76
76
  */
77
77
  declare function getSuffixedPath(assetPath: string, context: AssetContext, options: GetSuffixedPathOptions): string;
78
- declare function resolveAssetPath(assetPath: string, context: AssetContext, scale: AssetScale): string;
78
+ declare function resolveAssetPath(assetPath: string, context: AssetContext, scale?: AssetScale): string;
79
79
  interface CopyAssetsToDestinationOptions {
80
80
  assets: AssetData[];
81
81
  assetsDir: string;
@@ -86,6 +86,7 @@ interface CopyAssetsToDestinationOptions {
86
86
  * @see https://github.com/facebook/react-native/blob/0.83-stable/packages/community-cli-plugin/src/commands/bundle/assetPathUtils.js
87
87
  */
88
88
  declare function copyAssetsToDestination(options: CopyAssetsToDestinationOptions): Promise<undefined>;
89
+ declare function filterPlatformAssetScales(platform: string, scales: readonly AssetScale[]): AssetScale[];
89
90
  declare function generateAssetRegistryCode(assetRegistryPath: string, asset: AssetData): string;
90
91
  //#endregion
91
92
  export { assets_d_exports };
@@ -15,6 +15,7 @@ import { imageSize } from "image-size";
15
15
  */
16
16
  var assets_exports = /* @__PURE__ */ __exportAll({
17
17
  copyAssetsToDestination: () => copyAssetsToDestination,
18
+ filterPlatformAssetScales: () => filterPlatformAssetScales,
18
19
  generateAssetRegistryCode: () => generateAssetRegistryCode,
19
20
  getAssetPriority: () => getAssetPriority,
20
21
  getSuffixedPath: () => getSuffixedPath,
@@ -24,6 +25,8 @@ var assets_exports = /* @__PURE__ */ __exportAll({
24
25
  stripSuffix: () => stripSuffix
25
26
  });
26
27
  const SCALE_PATTERN = "@(\\d+\\.?\\d*)x";
28
+ const ASSET_BASE_NAME_PATTERN = /(.+?)(@([\d.]+)x)?$/;
29
+ const PLATFORM_FILE_PATH_PATTERN = /^(.+?)(\.([^.]+))?\.([^.]+)$/;
27
30
  const IMAGE_ASSET_TYPES = new Set(IMAGE_EXTENSIONS);
28
31
  /**
29
32
  * key: platform,
@@ -53,51 +56,114 @@ async function resolveScaledAssets(options) {
53
56
  platform,
54
57
  preferNativePlatform
55
58
  };
56
- const extension = path.extname(assetPath);
57
- const type = extension.substring(1);
59
+ const { name, type } = parseAssetPath(path.basename(assetPath), context);
58
60
  const relativePath = path.relative(projectRoot, assetPath);
59
- const dirname = path.dirname(assetPath);
60
- const files = fs.readdirSync(dirname);
61
- const stripedBasename = stripSuffix(assetPath, context);
62
- const suffixPattern = platformSuffixPattern(context);
63
- const assetRegExp = new RegExp(`${stripedBasename}(${SCALE_PATTERN})?(?:${suffixPattern})?${extension}$`);
64
- const scaledAssets = {};
65
- for (const file of files.sort((a, b) => getAssetPriority(b, context) - getAssetPriority(a, context))) {
66
- const match = assetRegExp.exec(file);
67
- if (match) {
68
- const [, , scale = "1"] = match;
69
- if (scaledAssets[scale]) continue;
70
- scaledAssets[scale] = file;
71
- }
72
- }
73
- if (!(Object.keys(scaledAssets).length && scaledAssets[1])) throw new Error(`cannot resolve base asset of ${assetPath}`);
74
- const imageData = fs.readFileSync(assetPath);
75
- const dimensions = IMAGE_ASSET_TYPES.has(type) ? imageSize(imageData) : void 0;
76
- const filteredScaledAssets = Object.entries(scaledAssets).map(([scale, file]) => ({
77
- scale: parseFloat(scale),
78
- file
79
- })).filter(({ scale }) => ALLOW_SCALES[platform]?.includes(scale) ?? true).reduce((acc, { scale, file }) => {
80
- acc.files.push(file);
81
- acc.scales.push(scale);
82
- return acc;
83
- }, {
84
- scales: [],
85
- files: []
86
- });
61
+ const { files, scales } = getAbsoluteAssetRecord(assetPath, context);
62
+ const fileData = await Promise.all(files.map((file) => fs.promises.readFile(file)));
63
+ const hash = md5(Buffer.concat(fileData));
64
+ const firstScale = scales[0] ?? 1;
65
+ const dimensions = IMAGE_ASSET_TYPES.has(type) ? imageSize(fileData[0]) : void 0;
87
66
  return {
88
67
  __packager_asset: true,
89
68
  id: assetPath,
90
- name: stripedBasename.replace(extension, ""),
69
+ name,
91
70
  type,
92
- width: dimensions?.width,
93
- height: dimensions?.height,
94
- files: filteredScaledAssets.files,
95
- scales: filteredScaledAssets.scales,
71
+ width: dimensions?.width == null ? void 0 : dimensions.width / firstScale,
72
+ height: dimensions?.height == null ? void 0 : dimensions.height / firstScale,
73
+ files,
74
+ scales,
96
75
  fileSystemLocation: path.dirname(assetPath),
97
- httpServerLocation: path.join(DEV_SERVER_ASSET_PATH, path.dirname(relativePath)),
98
- hash: md5(imageData)
76
+ httpServerLocation: getHttpServerLocation(relativePath),
77
+ hash
78
+ };
79
+ }
80
+ function getHttpServerLocation(relativePath) {
81
+ const dirname = path.dirname(relativePath);
82
+ return normalizePathSeparatorsToPosix(relativePath.startsWith("..") ? `/${DEV_SERVER_ASSET_PATH}/${dirname}` : path.join("/", DEV_SERVER_ASSET_PATH, dirname));
83
+ }
84
+ function normalizePathSeparatorsToPosix(filePath) {
85
+ return filePath.replace(/\\/g, "/");
86
+ }
87
+ function getAssetPlatforms(context) {
88
+ return [context.platform, context.preferNativePlatform ? "native" : null].filter(isNotNil);
89
+ }
90
+ function parseBaseName(baseName) {
91
+ const match = ASSET_BASE_NAME_PATTERN.exec(baseName);
92
+ if (match == null) throw new Error(`invalid asset name: ${baseName}`);
93
+ const [, rootName, , resolution] = match;
94
+ if (resolution != null) {
95
+ const parsedResolution = Number.parseFloat(resolution);
96
+ if (!Number.isNaN(parsedResolution)) return {
97
+ resolution: parsedResolution,
98
+ rootName
99
+ };
100
+ }
101
+ return {
102
+ resolution: 1,
103
+ rootName
104
+ };
105
+ }
106
+ function tryParseAssetPath(filePath, context) {
107
+ const dirname = path.dirname(filePath);
108
+ const fileName = path.basename(filePath);
109
+ const match = PLATFORM_FILE_PATH_PATTERN.exec(fileName);
110
+ if (match == null) return null;
111
+ const [, baseName, , platformCandidate, extension] = match;
112
+ const platforms = new Set(getAssetPlatforms(context));
113
+ const platform = platformCandidate != null && platforms.has(platformCandidate) ? platformCandidate : void 0;
114
+ const { resolution, rootName } = parseBaseName(platform == null && platformCandidate != null ? `${baseName}.${platformCandidate}` : baseName);
115
+ return {
116
+ assetName: path.join(dirname, `${rootName}.${extension}`),
117
+ name: rootName,
118
+ platform,
119
+ resolution,
120
+ type: extension
99
121
  };
100
122
  }
123
+ function parseAssetPath(filePath, context) {
124
+ const assetPath = tryParseAssetPath(filePath, context);
125
+ if (assetPath == null) throw new Error(`invalid asset file path: ${filePath}`);
126
+ return assetPath;
127
+ }
128
+ function getAssetKey(assetName, platform) {
129
+ return platform == null ? assetName : `${assetName} : ${platform}`;
130
+ }
131
+ function buildAssetMap(dirname, files, context) {
132
+ const assetMap = /* @__PURE__ */ new Map();
133
+ for (const file of files) {
134
+ const asset = tryParseAssetPath(file, context);
135
+ if (asset == null) continue;
136
+ const assetKey = getAssetKey(asset.assetName, asset.platform);
137
+ let record = assetMap.get(assetKey);
138
+ if (record == null) {
139
+ record = {
140
+ files: [],
141
+ scales: []
142
+ };
143
+ assetMap.set(assetKey, record);
144
+ }
145
+ let insertIndex = 0;
146
+ while (insertIndex < record.scales.length && asset.resolution >= record.scales[insertIndex]) insertIndex += 1;
147
+ record.scales.splice(insertIndex, 0, asset.resolution);
148
+ record.files.splice(insertIndex, 0, path.join(dirname, file));
149
+ }
150
+ return assetMap;
151
+ }
152
+ function getAbsoluteAssetRecord(assetPath, context) {
153
+ const dirname = path.dirname(assetPath);
154
+ const files = fs.readdirSync(dirname);
155
+ const asset = parseAssetPath(path.basename(assetPath), context);
156
+ const assetMap = buildAssetMap(dirname, files, context);
157
+ const platformRecord = assetMap.get(getAssetKey(asset.assetName, context.platform));
158
+ if (platformRecord != null) return platformRecord;
159
+ if (context.preferNativePlatform) {
160
+ const nativeRecord = assetMap.get(getAssetKey(asset.assetName, "native"));
161
+ if (nativeRecord != null) return nativeRecord;
162
+ }
163
+ const defaultRecord = assetMap.get(asset.assetName);
164
+ if (defaultRecord != null) return defaultRecord;
165
+ throw new Error(`Asset not found: ${assetPath} for platform: ${context.platform}`);
166
+ }
101
167
  function platformSuffixPattern(context) {
102
168
  return [context.platform, context.preferNativePlatform ? "native" : null].filter(isNotNil).map((platform) => `.${platform}`).join("|");
103
169
  }
@@ -144,68 +210,47 @@ function getSuffixedPath(assetPath, context, options) {
144
210
  return path.join(dirname, suffixedBasename);
145
211
  }
146
212
  function resolveAssetPath(assetPath, context, scale) {
147
- const suffixedPaths = [
148
- getSuffixedPath(assetPath, context, {
149
- scale,
150
- platform: context.platform
151
- }),
152
- context.preferNativePlatform ? getSuffixedPath(assetPath, context, {
153
- scale,
154
- platform: "native"
155
- }) : null,
156
- getSuffixedPath(assetPath, context, { scale })
157
- ].filter(isNotNil);
158
- /**
159
- * When scale is 1, filename can be suffixed or non-suffixed(`image.png`).
160
- *
161
- * - Suffixed
162
- * - `filename.<platform>@<scale>x.ext`
163
- * - `filename.<platform>.ext`
164
- * - `filename@<scale>x.ext`
165
- * - Non suffixed
166
- * - `filename.ext`
167
- *
168
- * 1. Resolve non-suffixed asset first.
169
- * 2. If file is not exist, resolve suffixed path.
170
- */
171
- if (scale === 1) try {
172
- fs.statSync(assetPath);
173
- return assetPath;
174
- } catch {}
175
- for (const suffixedPath of suffixedPaths) try {
176
- fs.statSync(suffixedPath);
177
- return suffixedPath;
178
- } catch {}
213
+ const requestedScale = scale ?? parseAssetPath(path.basename(assetPath), context).resolution;
214
+ const record = getAbsoluteAssetRecord(assetPath, context);
215
+ for (let index = 0; index < record.scales.length; index += 1) if (record.scales[index] >= requestedScale) return record.files[index];
216
+ const fallback = record.files.at(-1);
217
+ if (fallback != null) return fallback;
179
218
  throw new Error(`cannot resolve asset path for ${assetPath}`);
180
219
  }
181
220
  /**
182
221
  * @see https://github.com/facebook/react-native/blob/0.83-stable/packages/community-cli-plugin/src/commands/bundle/assetPathUtils.js
183
222
  */
184
223
  async function copyAssetsToDestination(options) {
185
- const { assets, platform, assetsDir, preferNativePlatform } = options;
186
- const context = {
187
- platform,
188
- preferNativePlatform
189
- };
224
+ const { assets, platform, assetsDir } = options;
190
225
  const mkdirWithAssertPath = (targetPath) => {
191
226
  const dirname = path.dirname(targetPath);
192
227
  fs.mkdirSync(dirname, { recursive: true });
193
228
  };
194
229
  return Promise.all(assets.map((asset) => {
195
- return Promise.all(asset.scales.map(async (scale) => {
230
+ const validScales = new Set(filterPlatformAssetScales(platform, asset.scales));
231
+ return Promise.all(asset.scales.map(async (scale, index) => {
232
+ if (!validScales.has(scale)) return;
196
233
  if (platform !== "android") {
197
- const from = resolveAssetPath(asset.id, context, scale);
234
+ const from = asset.files[index];
198
235
  const to = path.join(assetsDir, getIosAssetDestinationPath(asset, scale));
199
236
  mkdirWithAssertPath(to);
200
237
  return fs.copyFileSync(from, to);
201
238
  }
202
- const from = resolveAssetPath(asset.id, context, scale);
239
+ const from = asset.files[index];
203
240
  const to = path.join(assetsDir, getAndroidAssetDestinationPath(asset, scale));
204
241
  mkdirWithAssertPath(to);
205
242
  fs.copyFileSync(from, to);
206
243
  })).then(() => void 0);
207
244
  })).then(() => void 0);
208
245
  }
246
+ function filterPlatformAssetScales(platform, scales) {
247
+ const allowlist = ALLOW_SCALES[platform];
248
+ if (allowlist == null) return [...scales];
249
+ const filteredScales = scales.filter((scale) => allowlist.includes(scale));
250
+ if (filteredScales.length > 0 || scales.length === 0) return filteredScales;
251
+ const maxAllowedScale = allowlist[allowlist.length - 1];
252
+ return [scales.find((scale) => scale > maxAllowedScale) ?? scales[scales.length - 1]];
253
+ }
209
254
  /**
210
255
  * @see https://github.com/facebook/react-native/blob/0.83-stable/packages/community-cli-plugin/src/commands/bundle/getAssetDestPathIOS.js
211
256
  */
@@ -238,7 +283,8 @@ function isDrawable(type) {
238
283
  ].includes(type);
239
284
  }
240
285
  function generateAssetRegistryCode(assetRegistryPath, asset) {
241
- return `module.exports = require('${assetRegistryPath}').registerAsset(${JSON.stringify(asset)});`;
286
+ const { files: _files, fileSystemLocation: _fileSystemLocation, id: _id, ...registryAsset } = asset;
287
+ return `module.exports = require('${assetRegistryPath}').registerAsset(${JSON.stringify(registryAsset)});`;
242
288
  }
243
289
  //#endregion
244
290
  export { assets_exports, copyAssetsToDestination, generateAssetRegistryCode, resolveAssetPath, resolveScaledAssets };
@@ -67,7 +67,10 @@ async function resolveRolldownOptions(context, config, buildOptions, devEngineOp
67
67
  ...defineEnvFromObject(builtInEnv)
68
68
  },
69
69
  helpers: { mode: "Runtime" }
70
- }, rolldownTransform);
70
+ }, {
71
+ ...rolldownTransform,
72
+ reactCompiler: resolveReactCompilerTransformOptions(rolldownTransform.reactCompiler)
73
+ });
71
74
  const entryPluginOptions = resolveEntryPluginOptions(config);
72
75
  const reactNativePluginOptions = await resolveReactNativePluginOptions(config, context, buildOptions);
73
76
  const babelPluginOptions = resolveBabelPluginOptions(config, context);
@@ -195,6 +198,13 @@ function resolveWorkletsConfig(config) {
195
198
  pluginVersion: resolvePackageJson(config.root, "react-native-worklets")?.version
196
199
  }, worklets);
197
200
  }
201
+ function resolveReactCompilerTransformOptions(reactCompiler) {
202
+ if (reactCompiler == null) return;
203
+ return {
204
+ ...reactCompiler,
205
+ exclude: reactCompiler.exclude ?? [/node_modules/]
206
+ };
207
+ }
198
208
  function resolveBabelPluginOptions(config, context) {
199
209
  return {
200
210
  context,
package/dist/package.js CHANGED
@@ -1,4 +1,4 @@
1
1
  //#region package.json
2
- var version = "1.0.0-alpha.25";
2
+ var version = "1.0.0-alpha.27";
3
3
  //#endregion
4
4
  export { version };
@@ -5,13 +5,11 @@ import { BundlerPool } from "./bundler-pool.js";
5
5
  import { DEFAULT_HOST, DEFAULT_PORT } from "./constants.js";
6
6
  import { errorHandler } from "./error.js";
7
7
  import { ServerEventBus } from "./events/event-bus.js";
8
- import { toSSEEvent } from "./sse/adapter.js";
9
8
  import { plugin } from "./mcp/server.js";
10
9
  import { plugin as plugin$1 } from "./middlewares/dashboard.js";
11
10
  import { requestLogger } from "./middlewares/request-logger.js";
12
11
  import { plugin as plugin$2 } from "./middlewares/serve-assets.js";
13
12
  import { plugin as plugin$3 } from "./middlewares/serve-bundle.js";
14
- import { SSEEventPublisher } from "./sse/event-bus.js";
15
13
  import { plugin as plugin$4 } from "./middlewares/sse.js";
16
14
  import { plugin as plugin$5 } from "./middlewares/symbolicate.js";
17
15
  import { plugin as plugin$6 } from "./rest/index.js";
@@ -49,12 +47,7 @@ async function createDevServer(config, options) {
49
47
  host,
50
48
  port
51
49
  }, eventBus);
52
- const ssePublisher = new SSEEventPublisher();
53
50
  const reporter = config.reporter;
54
- eventBus.subscribe((event) => {
55
- const sseEvent = toSSEEvent(event);
56
- if (sseEvent != null) ssePublisher.publish(sseEvent);
57
- });
58
51
  const { middleware: communityMiddleware, websocketEndpoints: communityWebsocketEndpoints, messageSocketEndpoint: { server: messageServer, broadcast }, eventsSocketEndpoint: { server: eventsServer, reportEvent } } = createDevServerMiddleware({
59
52
  port,
60
53
  host,
@@ -9,7 +9,7 @@ import fp from "fastify-plugin";
9
9
  function createMcpServer(options) {
10
10
  const server = new McpServer({
11
11
  name: "rollipop",
12
- version: "1.0.0-alpha.25"
12
+ version: "1.0.0-alpha.27"
13
13
  }, { capabilities: { logging: {} } });
14
14
  registerTools(server, options);
15
15
  return server;
@@ -18,8 +18,14 @@ const plugin = fp((fastify, options) => {
18
18
  index: [INDEX_FILE],
19
19
  wildcard: false
20
20
  });
21
- fastify.get(DASHBOARD_PATH, (_request, reply) => reply.sendFile(INDEX_FILE));
22
- fastify.get(`${DASHBOARD_PATH}/analyze-report/:reportFile`, async (request, reply) => {
21
+ fastify.get(DASHBOARD_PATH, (_request, reply) => reply.sendFile(INDEX_FILE)).get(`${DASHBOARD_PATH}/*`, (request, reply) => {
22
+ if (shouldServeNotFoundPage(request)) return reply.sendFile(INDEX_FILE);
23
+ return reply.status(404).send({
24
+ statusCode: 404,
25
+ error: "Not Found",
26
+ message: "Not Found"
27
+ });
28
+ }).get(`${DASHBOARD_PATH}/analyze-report/:reportFile`, async (request, reply) => {
23
29
  const { reportFile } = request.params;
24
30
  if (!reportFile.endsWith(".html")) return reply.status(400).send({ error: {
25
31
  code: "INVALID_ANALYZE_REPORT_ID",
@@ -29,15 +29,16 @@ const plugin = fp((fastify, options) => {
29
29
  const assetPath = resolveAsset(pathname.replace(new RegExp(`^/${DEV_SERVER_ASSET_PATH}/?`), ""));
30
30
  let handle = null;
31
31
  try {
32
- handle = await fs.promises.open(resolveAssetPath(assetPath, {
32
+ const resolvedAssetPath = resolveAssetPath(assetPath, {
33
33
  platform: query.platform,
34
34
  preferNativePlatform: context.config.resolver.preferNativePlatform
35
- }, 1), "r");
35
+ });
36
+ handle = await fs.promises.open(resolvedAssetPath, "r");
36
37
  const assetData = await handle.readFile();
37
38
  const { size } = await handle.stat();
38
- await reply.header("Content-Type", mime.getType(assetPath) ?? "").header("Content-Length", size).send(assetData);
39
+ await reply.header("Content-Type", mime.getType(resolvedAssetPath) ?? "").header("Content-Length", size).send(assetData);
39
40
  } catch (error) {
40
- fastify.log.error(error, "Failed to serve asset (scale assets resolving is not implemented yet)");
41
+ fastify.log.error(error, "Failed to serve asset");
41
42
  await reply.status(500).send();
42
43
  } finally {
43
44
  await handle?.close();
@@ -1,7 +1,7 @@
1
1
  import { isDebugEnabled } from "../../common/env.js";
2
2
  import { getBaseBundleName } from "../../utils/bundle.js";
3
3
  import { parseUrl } from "../../utils/url.js";
4
- import { symbolicate } from "../symbolicate.js";
4
+ import { symbolicateWithBundleResolver } from "../symbolicate.js";
5
5
  import chalk from "chalk";
6
6
  import fp from "fastify-plugin";
7
7
  import { asConst } from "json-schema-to-ts";
@@ -23,31 +23,35 @@ const plugin = fp((fastify, options) => {
23
23
  schema: { body: bodySchema },
24
24
  async handler(request, reply) {
25
25
  const { stack } = request.body;
26
- const bundleUrl = findBundleUrl(stack);
27
- if (bundleUrl == null) {
26
+ const bundleStores = getBundleStoresByFrameUrl(stack, context);
27
+ if (bundleStores.size === 0) {
28
28
  await reply.header("Content-Type", "application/json").send(createFallbackResult(stack));
29
29
  return;
30
30
  }
31
- const { pathname, query } = bundleUrl;
32
- const platform = query.platform;
33
- const dev = query.dev == null ? context.config.mode === "development" : query.dev === "true";
34
- const bundleName = getBaseBundleName(pathname);
35
- const symbolicateResult = await symbolicate(await context.bundlerPool.get(bundleName, {
36
- platform,
37
- dev
38
- }).getBundle(), stack);
31
+ const symbolicateResult = await symbolicateWithBundleResolver(stack, async (frame) => frame.file == null ? void 0 : await bundleStores.get(frame.file));
39
32
  if (isDebugEnabled()) printSymbolicateResult(stack, symbolicateResult);
40
33
  await reply.header("Content-Type", "application/json").send(symbolicateResult);
41
34
  }
42
35
  });
43
36
  }, { name: "symbolicate" });
44
- function findBundleUrl(stack) {
37
+ function getBundleStoresByFrameUrl(stack, context) {
38
+ const bundleStores = /* @__PURE__ */ new Map();
45
39
  for (const frame of stack) {
46
- if (!frame.file?.startsWith("http")) continue;
47
- const parsed = parseStackFrameFile(frame.file);
48
- if (parsed?.query.platform) return parsed;
40
+ const file = frame.file;
41
+ if (!file?.startsWith("http") || bundleStores.has(file)) continue;
42
+ const parsed = parseStackFrameFile(file);
43
+ if (parsed?.query.platform == null) continue;
44
+ const { pathname, query } = parsed;
45
+ const platform = query.platform;
46
+ const dev = query.dev == null ? context.config.mode === "development" : query.dev === "true";
47
+ const bundleName = getBaseBundleName(pathname);
48
+ const bundler = context.bundlerPool.get(bundleName, {
49
+ platform,
50
+ dev
51
+ });
52
+ bundleStores.set(file, bundler.getBundle());
49
53
  }
50
- return null;
54
+ return bundleStores;
51
55
  }
52
56
  function parseStackFrameFile(file) {
53
57
  try {
@@ -28,7 +28,15 @@ var DevServerState = class {
28
28
  this.clearBuilds();
29
29
  }
30
30
  handleEvent(event) {
31
- this.builds.handleEvent(event);
31
+ switch (event.type) {
32
+ case "bundle_build_started":
33
+ case "bundle_build_done":
34
+ case "bundle_build_failed":
35
+ case "build_log":
36
+ case "build_error":
37
+ this.builds.handleEvent(event);
38
+ break;
39
+ }
32
40
  }
33
41
  };
34
42
  var BuildStore = class {
@@ -26,11 +26,21 @@ const INTERNAL_CALLSITES_REGEX = new RegExp([
26
26
  "^\\[native code\\]$"
27
27
  ].map((pathPattern) => pathPattern.replaceAll("/", "[/\\\\]")).join("|"));
28
28
  async function symbolicate(bundleStore, stack) {
29
- const sourceMapConsumer = await bundleStore.sourceMapConsumer;
30
- const symbolicatedStack = stack.filter((frame) => frame.file?.startsWith("http")).map((frame) => sourceMapConsumer ? originalPositionFor(sourceMapConsumer, frame) : frame).map((frame) => collapseFrame(frame));
29
+ return symbolicateWithBundleResolver(stack, (frame) => frame.file?.startsWith("http") ? bundleStore : void 0);
30
+ }
31
+ async function symbolicateWithBundleResolver(stack, resolveBundleStore) {
32
+ const symbolicatedStack = await Promise.all(stack.map(async (frame) => {
33
+ const bundleStore = frame.file?.startsWith("http") ? await resolveBundleStore(frame) : void 0;
34
+ const sourceMapConsumer = await bundleStore?.sourceMapConsumer;
35
+ return {
36
+ bundleStore,
37
+ frame: collapseFrame(sourceMapConsumer ? originalPositionFor(sourceMapConsumer, frame) : frame),
38
+ sourceMapConsumer
39
+ };
40
+ }));
31
41
  return {
32
- stack: symbolicatedStack,
33
- codeFrame: getCodeFrame(symbolicatedStack, bundleStore, sourceMapConsumer)
42
+ stack: symbolicatedStack.map(({ frame }) => frame),
43
+ codeFrame: getCodeFrame(symbolicatedStack)
34
44
  };
35
45
  }
36
46
  function originalPositionFor(sourceMapConsumer, frame) {
@@ -43,7 +53,7 @@ function originalPositionFor(sourceMapConsumer, frame) {
43
53
  const targetKey = convertFrameKey(key);
44
54
  return {
45
55
  ...frame,
46
- ...value ? { [targetKey]: value } : null
56
+ ...value != null ? { [targetKey]: value } : null
47
57
  };
48
58
  }, frame);
49
59
  }
@@ -62,23 +72,29 @@ function convertFrameKey(key) {
62
72
  else if (key === "name") return "methodName";
63
73
  return key;
64
74
  }
65
- function getCodeFrame(frames, bundleStore, sourceMapConsumer) {
66
- const frame = frames.find((frame) => {
67
- return frame.lineNumber != null && frame.column != null && !isCollapsed(frame);
68
- });
69
- if (frame?.file == null || frame.column == null || frame.lineNumber == null) return null;
75
+ function getCodeFrame(frames) {
76
+ for (const match of frames) {
77
+ const frame = match.frame;
78
+ if (frame.file == null || frame.column == null || frame.lineNumber == null) continue;
79
+ if (isCollapsed(frame)) continue;
80
+ const codeFrame = createCodeFrame(match);
81
+ if (codeFrame != null) return codeFrame;
82
+ }
83
+ return null;
84
+ }
85
+ function createCodeFrame({ bundleStore, frame, sourceMapConsumer }) {
70
86
  try {
71
87
  const { lineNumber, column, file } = frame;
72
- const unresolved = file.startsWith("http");
73
- const source = sourceMapConsumer == null || unresolved ? bundleStore.code : sourceMapConsumer.sourceContentFor(frame.file);
88
+ if (file == null || lineNumber == null || column == null) return null;
89
+ const unresolved = file?.startsWith("http") ?? false;
90
+ const source = sourceMapConsumer == null || unresolved ? bundleStore?.code : sourceMapConsumer.sourceContentFor(file, true);
91
+ if (!source) return null;
74
92
  const fileName = unresolved ? parseUrl(file).pathname ?? "unknown" : file;
75
- let content = "";
76
- if (source) content = codeFrameColumns(source, { start: {
77
- column,
78
- line: lineNumber
79
- } }, { highlightCode: true });
80
93
  return {
81
- content,
94
+ content: codeFrameColumns(source, { start: {
95
+ column: column + 1,
96
+ line: lineNumber
97
+ } }, { highlightCode: true }),
82
98
  fileName,
83
99
  location: {
84
100
  column,
@@ -90,4 +106,4 @@ function getCodeFrame(frames, bundleStore, sourceMapConsumer) {
90
106
  }
91
107
  }
92
108
  //#endregion
93
- export { symbolicate };
109
+ export { symbolicate, symbolicateWithBundleResolver };
@@ -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.25") this.data = loadedData;
31
+ if ("version" in loadedData && loadedData.version === "1.0.0-alpha.27") 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.25",
3
+ "version": "1.0.0-alpha.27",
4
4
  "stableVersion": "1.0.0-alpha.23",
5
5
  "homepage": "https://github.com/rollipop-dev/rollipop#readme",
6
6
  "bugs": {
@@ -70,8 +70,8 @@
70
70
  "@react-native-community/cli-types": "20.1.3",
71
71
  "@react-native/babel-plugin-codegen": "0.86.0",
72
72
  "@react-native/dev-middleware": "0.86.0",
73
- "@rollipop/dashboard": "0.0.0",
74
- "@rollipop/rolldown": "1.0.19",
73
+ "@rollipop/dashboard": "1.0.0-alpha.27",
74
+ "@rollipop/rolldown": "1.0.20",
75
75
  "@swc/core": "1.15.41",
76
76
  "@swc/helpers": "0.5.23",
77
77
  "@types/ws": "8.18.1",