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.
- package/dist/config/load-config.js +2 -0
- package/dist/config/notice.js +7 -0
- package/dist/constants.js +1 -1
- package/dist/core/assets.d.ts +4 -3
- package/dist/core/assets.js +124 -78
- package/dist/core/rolldown.js +11 -1
- package/dist/package.js +1 -1
- package/dist/server/create-dev-server.js +0 -7
- package/dist/server/mcp/server.js +1 -1
- package/dist/server/middlewares/dashboard.js +8 -2
- package/dist/server/middlewares/serve-assets.js +5 -4
- package/dist/server/middlewares/symbolicate.js +20 -16
- package/dist/server/state/store.js +9 -1
- package/dist/server/symbolicate.js +35 -19
- package/dist/storage/file-storage.js +1 -1
- package/package.json +3 -3
|
@@ -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) {
|
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.
|
|
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
|
/**
|
package/dist/core/assets.d.ts
CHANGED
|
@@ -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 =
|
|
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
|
|
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 };
|
package/dist/core/assets.js
CHANGED
|
@@ -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
|
|
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
|
|
60
|
-
const
|
|
61
|
-
const
|
|
62
|
-
const
|
|
63
|
-
const
|
|
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
|
|
69
|
+
name,
|
|
91
70
|
type,
|
|
92
|
-
width: dimensions?.width,
|
|
93
|
-
height: dimensions?.height,
|
|
94
|
-
files
|
|
95
|
-
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:
|
|
98
|
-
hash
|
|
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
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
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
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
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 };
|
package/dist/core/rolldown.js
CHANGED
|
@@ -67,7 +67,10 @@ async function resolveRolldownOptions(context, config, buildOptions, devEngineOp
|
|
|
67
67
|
...defineEnvFromObject(builtInEnv)
|
|
68
68
|
},
|
|
69
69
|
helpers: { mode: "Runtime" }
|
|
70
|
-
},
|
|
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
|
@@ -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.
|
|
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
|
-
|
|
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
|
-
|
|
32
|
+
const resolvedAssetPath = resolveAssetPath(assetPath, {
|
|
33
33
|
platform: query.platform,
|
|
34
34
|
preferNativePlatform: context.config.resolver.preferNativePlatform
|
|
35
|
-
}
|
|
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(
|
|
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
|
|
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 {
|
|
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
|
|
27
|
-
if (
|
|
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
|
|
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
|
|
37
|
+
function getBundleStoresByFrameUrl(stack, context) {
|
|
38
|
+
const bundleStores = /* @__PURE__ */ new Map();
|
|
45
39
|
for (const frame of stack) {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
30
|
-
|
|
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
|
|
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
|
|
66
|
-
const
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
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
|
-
|
|
73
|
-
const
|
|
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.
|
|
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.
|
|
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.
|
|
74
|
-
"@rollipop/rolldown": "1.0.
|
|
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",
|