metro-mcp 0.6.2 → 0.6.4
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/bin/metro-mcp.js +223 -35
- package/dist/index.js +223 -35
- package/dist/metro/proxy.d.ts.map +1 -1
- package/dist/plugins/console.d.ts.map +1 -1
- package/dist/plugins/devtools.d.ts.map +1 -1
- package/dist/server.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/bin/metro-mcp.js
CHANGED
|
@@ -3317,6 +3317,7 @@ function mergeConfig(target, source) {
|
|
|
3317
3317
|
// src/server.ts
|
|
3318
3318
|
import { exec } from "child_process";
|
|
3319
3319
|
import { promisify } from "util";
|
|
3320
|
+
import fs4 from "fs";
|
|
3320
3321
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3321
3322
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
3322
3323
|
import { z as z23 } from "zod";
|
|
@@ -3740,7 +3741,7 @@ function extractCDPExceptionMessage(details, fallback = "Evaluation failed") {
|
|
|
3740
3741
|
// package.json
|
|
3741
3742
|
var package_default = {
|
|
3742
3743
|
name: "metro-mcp",
|
|
3743
|
-
version: "0.6.
|
|
3744
|
+
version: "0.6.4",
|
|
3744
3745
|
description: "Plugin-based MCP server for React Native/Expo runtime debugging, inspection, and automation via Metro/CDP",
|
|
3745
3746
|
homepage: "https://metromcp.dev",
|
|
3746
3747
|
repository: {
|
|
@@ -3941,45 +3942,103 @@ class DeviceBufferManager {
|
|
|
3941
3942
|
}
|
|
3942
3943
|
|
|
3943
3944
|
// src/plugins/console.ts
|
|
3945
|
+
function formatPreview(preview) {
|
|
3946
|
+
if (!preview.properties)
|
|
3947
|
+
return null;
|
|
3948
|
+
const props = preview.properties.map((p) => {
|
|
3949
|
+
if (p.type === "object" && p.valuePreview) {
|
|
3950
|
+
const nested = formatPreview(p.valuePreview);
|
|
3951
|
+
return `${p.name}: ${nested || p.value || "[object]"}`;
|
|
3952
|
+
}
|
|
3953
|
+
return `${p.name}: ${p.value}`;
|
|
3954
|
+
});
|
|
3955
|
+
const overflow = preview.overflow ? ", ..." : "";
|
|
3956
|
+
if (preview.subtype === "array")
|
|
3957
|
+
return `[${props.join(", ")}${overflow}]`;
|
|
3958
|
+
return `{${props.join(", ")}${overflow}}`;
|
|
3959
|
+
}
|
|
3960
|
+
function formatRemoteObject(obj) {
|
|
3961
|
+
if (obj.type === "string")
|
|
3962
|
+
return obj.value;
|
|
3963
|
+
if (obj.type === "number")
|
|
3964
|
+
return String(obj.value);
|
|
3965
|
+
if (obj.type === "boolean")
|
|
3966
|
+
return String(obj.value);
|
|
3967
|
+
if (obj.type === "undefined")
|
|
3968
|
+
return "undefined";
|
|
3969
|
+
if (obj.subtype === "null")
|
|
3970
|
+
return "null";
|
|
3971
|
+
if (obj.preview) {
|
|
3972
|
+
const formatted = formatPreview(obj.preview);
|
|
3973
|
+
if (formatted)
|
|
3974
|
+
return formatted;
|
|
3975
|
+
}
|
|
3976
|
+
if (obj.description)
|
|
3977
|
+
return obj.description;
|
|
3978
|
+
if (obj.value !== undefined)
|
|
3979
|
+
return JSON.stringify(obj.value);
|
|
3980
|
+
return obj.className || obj.type || "[object]";
|
|
3981
|
+
}
|
|
3944
3982
|
function formatCDPArgs(args) {
|
|
3945
3983
|
return args.map((arg) => {
|
|
3946
3984
|
if (typeof arg === "object" && arg !== null) {
|
|
3947
|
-
|
|
3948
|
-
if (remoteObj.type === "string")
|
|
3949
|
-
return remoteObj.value;
|
|
3950
|
-
if (remoteObj.type === "number")
|
|
3951
|
-
return String(remoteObj.value);
|
|
3952
|
-
if (remoteObj.type === "boolean")
|
|
3953
|
-
return String(remoteObj.value);
|
|
3954
|
-
if (remoteObj.type === "undefined")
|
|
3955
|
-
return "undefined";
|
|
3956
|
-
if (remoteObj.subtype === "null")
|
|
3957
|
-
return "null";
|
|
3958
|
-
if (remoteObj.description)
|
|
3959
|
-
return remoteObj.description;
|
|
3960
|
-
if (remoteObj.value !== undefined)
|
|
3961
|
-
return JSON.stringify(remoteObj.value);
|
|
3962
|
-
return remoteObj.className || remoteObj.type || "[object]";
|
|
3985
|
+
return formatRemoteObject(arg);
|
|
3963
3986
|
}
|
|
3964
3987
|
return String(arg);
|
|
3965
3988
|
}).join(" ");
|
|
3966
3989
|
}
|
|
3990
|
+
async function resolveRemoteObject(cdpSend, objectId) {
|
|
3991
|
+
try {
|
|
3992
|
+
const result = await cdpSend("Runtime.callFunctionOn", {
|
|
3993
|
+
objectId,
|
|
3994
|
+
functionDeclaration: 'function() { try { return JSON.stringify(this, null, 2); } catch(e) { return "[unserializable]"; } }',
|
|
3995
|
+
returnByValue: true
|
|
3996
|
+
});
|
|
3997
|
+
const inner = result.result;
|
|
3998
|
+
return inner?.value ? inner.value : null;
|
|
3999
|
+
} catch {
|
|
4000
|
+
return null;
|
|
4001
|
+
}
|
|
4002
|
+
}
|
|
4003
|
+
async function formatCDPArgsDeep(cdpSend, args) {
|
|
4004
|
+
const parts = await Promise.all(args.map(async (arg) => {
|
|
4005
|
+
if (typeof arg !== "object" || arg === null)
|
|
4006
|
+
return String(arg);
|
|
4007
|
+
const remoteObj = arg;
|
|
4008
|
+
if (remoteObj.objectId && remoteObj.type === "object") {
|
|
4009
|
+
const deep = await resolveRemoteObject(cdpSend, remoteObj.objectId);
|
|
4010
|
+
if (deep)
|
|
4011
|
+
return deep;
|
|
4012
|
+
}
|
|
4013
|
+
return formatRemoteObject(remoteObj);
|
|
4014
|
+
}));
|
|
4015
|
+
return parts.join(" ");
|
|
4016
|
+
}
|
|
3967
4017
|
var consolePlugin = definePlugin({
|
|
3968
4018
|
name: "console",
|
|
3969
4019
|
description: "Console log collection and filtering",
|
|
3970
4020
|
async setup(ctx) {
|
|
3971
4021
|
const buffers = new DeviceBufferManager(500);
|
|
4022
|
+
const cdpSend = ctx.cdp.send.bind(ctx.cdp);
|
|
3972
4023
|
ctx.cdp.on("Runtime.consoleAPICalled", (params) => {
|
|
3973
4024
|
const key = ctx.getActiveDeviceKey();
|
|
3974
4025
|
if (!key)
|
|
3975
4026
|
return;
|
|
3976
4027
|
const args = params.args || [];
|
|
3977
|
-
|
|
4028
|
+
const entry = {
|
|
3978
4029
|
timestamp: Date.now(),
|
|
3979
4030
|
level: params.type,
|
|
3980
4031
|
message: formatCDPArgs(args),
|
|
3981
4032
|
stackTrace: params.stackTrace ? JSON.stringify(params.stackTrace.callFrames) : undefined
|
|
3982
|
-
}
|
|
4033
|
+
};
|
|
4034
|
+
buffers.getOrCreate(key).push(entry);
|
|
4035
|
+
const hasResolvable = args.some((arg) => typeof arg === "object" && arg !== null && arg.objectId);
|
|
4036
|
+
if (hasResolvable) {
|
|
4037
|
+
formatCDPArgsDeep(cdpSend, args).then((deep) => {
|
|
4038
|
+
if (deep !== entry.message)
|
|
4039
|
+
entry.message = deep;
|
|
4040
|
+
}).catch(() => {});
|
|
4041
|
+
}
|
|
3983
4042
|
});
|
|
3984
4043
|
ctx.events.on("bundle_transform_progressed", (event) => {
|
|
3985
4044
|
if (event.transformedFileCount === 1) {
|
|
@@ -8591,9 +8650,10 @@ var inspectPointPlugin = definePlugin({
|
|
|
8591
8650
|
});
|
|
8592
8651
|
|
|
8593
8652
|
// src/plugins/devtools.ts
|
|
8594
|
-
import
|
|
8653
|
+
import fs3 from "fs";
|
|
8595
8654
|
import { z as z22 } from "zod";
|
|
8596
8655
|
var logger6 = createLogger("devtools");
|
|
8656
|
+
var DEVTOOLS_STATE_FILE = "/tmp/metro-mcp-devtools.json";
|
|
8597
8657
|
async function findBrowserPath() {
|
|
8598
8658
|
try {
|
|
8599
8659
|
const { Launcher: Launcher2 } = await Promise.resolve().then(() => (init_dist(), exports_dist));
|
|
@@ -8609,6 +8669,43 @@ async function findBrowserPath() {
|
|
|
8609
8669
|
} catch {}
|
|
8610
8670
|
return null;
|
|
8611
8671
|
}
|
|
8672
|
+
async function tryFocusExisting(frontendUrl) {
|
|
8673
|
+
try {
|
|
8674
|
+
const state = JSON.parse(fs3.readFileSync(DEVTOOLS_STATE_FILE, "utf8"));
|
|
8675
|
+
if (!state.pid || !state.remoteDebuggingPort)
|
|
8676
|
+
return false;
|
|
8677
|
+
try {
|
|
8678
|
+
process.kill(state.pid, 0);
|
|
8679
|
+
} catch {
|
|
8680
|
+
return false;
|
|
8681
|
+
}
|
|
8682
|
+
const resp = await fetch(`http://localhost:${state.remoteDebuggingPort}/json`, {
|
|
8683
|
+
signal: AbortSignal.timeout(1000)
|
|
8684
|
+
});
|
|
8685
|
+
if (!resp.ok)
|
|
8686
|
+
return false;
|
|
8687
|
+
const targets = await resp.json();
|
|
8688
|
+
const target = targets.find((t) => t.url?.includes("rn_fusebox") || t.url === frontendUrl);
|
|
8689
|
+
if (!target?.id)
|
|
8690
|
+
return false;
|
|
8691
|
+
const activate = await fetch(`http://localhost:${state.remoteDebuggingPort}/json/activate/${target.id}`, { signal: AbortSignal.timeout(1000) });
|
|
8692
|
+
return activate.ok;
|
|
8693
|
+
} catch {
|
|
8694
|
+
return false;
|
|
8695
|
+
}
|
|
8696
|
+
}
|
|
8697
|
+
async function launchDevTools(frontendUrl) {
|
|
8698
|
+
const { launch: launch2 } = await Promise.resolve().then(() => (init_dist(), exports_dist));
|
|
8699
|
+
const chrome2 = await launch2({
|
|
8700
|
+
chromeFlags: [`--app=${frontendUrl}`, "--window-size=1200,600"]
|
|
8701
|
+
});
|
|
8702
|
+
chrome2.process.unref();
|
|
8703
|
+
try {
|
|
8704
|
+
fs3.writeFileSync(DEVTOOLS_STATE_FILE, JSON.stringify({ pid: chrome2.pid, remoteDebuggingPort: chrome2.port }));
|
|
8705
|
+
} catch (err) {
|
|
8706
|
+
logger6.warn("Failed to write devtools state:", err);
|
|
8707
|
+
}
|
|
8708
|
+
}
|
|
8612
8709
|
var devtoolsPlugin = definePlugin({
|
|
8613
8710
|
name: "devtools",
|
|
8614
8711
|
description: "Open React Native DevTools via the CDP proxy",
|
|
@@ -8630,11 +8727,13 @@ var devtoolsPlugin = definePlugin({
|
|
|
8630
8727
|
const browserPath = await findBrowserPath();
|
|
8631
8728
|
if (browserPath) {
|
|
8632
8729
|
try {
|
|
8633
|
-
const
|
|
8634
|
-
|
|
8730
|
+
const focused = await tryFocusExisting(frontendUrl);
|
|
8731
|
+
if (!focused) {
|
|
8732
|
+
await launchDevTools(frontendUrl);
|
|
8733
|
+
}
|
|
8635
8734
|
return { opened: true, url: frontendUrl };
|
|
8636
8735
|
} catch (err) {
|
|
8637
|
-
logger6.debug("Failed to
|
|
8736
|
+
logger6.debug("Failed to open DevTools:", err);
|
|
8638
8737
|
}
|
|
8639
8738
|
} else {
|
|
8640
8739
|
logger6.debug("No Chrome/Edge installation found");
|
|
@@ -8686,20 +8785,34 @@ class CDPProxy {
|
|
|
8686
8785
|
return this._port;
|
|
8687
8786
|
}
|
|
8688
8787
|
async start(port = 0) {
|
|
8689
|
-
|
|
8690
|
-
|
|
8691
|
-
|
|
8788
|
+
const tryPort = (p) => new Promise((resolve, reject) => {
|
|
8789
|
+
const httpServer = http.createServer((req, res) => this.handleHttpRequest(req, res));
|
|
8790
|
+
httpServer.on("error", (err) => {
|
|
8791
|
+
httpServer.close();
|
|
8792
|
+
reject(err);
|
|
8692
8793
|
});
|
|
8693
|
-
|
|
8694
|
-
|
|
8695
|
-
|
|
8696
|
-
|
|
8697
|
-
|
|
8698
|
-
this.
|
|
8699
|
-
|
|
8700
|
-
|
|
8794
|
+
httpServer.listen(p, () => {
|
|
8795
|
+
const addr = httpServer.address();
|
|
8796
|
+
const actualPort = typeof addr === "object" && addr ? addr.port : p;
|
|
8797
|
+
const wss = new WebSocketServer({ server: httpServer });
|
|
8798
|
+
wss.on("connection", (ws) => this.handleNewClient(ws));
|
|
8799
|
+
this.httpServer = httpServer;
|
|
8800
|
+
this.wss = wss;
|
|
8801
|
+
this._port = actualPort;
|
|
8802
|
+
logger7.info(`CDP proxy listening on port ${actualPort}`);
|
|
8803
|
+
resolve(actualPort);
|
|
8701
8804
|
});
|
|
8702
8805
|
});
|
|
8806
|
+
if (port !== 0) {
|
|
8807
|
+
try {
|
|
8808
|
+
return await tryPort(port);
|
|
8809
|
+
} catch (err) {
|
|
8810
|
+
if (err.code !== "EADDRINUSE")
|
|
8811
|
+
throw err;
|
|
8812
|
+
logger7.debug(`Preferred proxy port ${port} in use, falling back to auto-assign`);
|
|
8813
|
+
}
|
|
8814
|
+
}
|
|
8815
|
+
return tryPort(0);
|
|
8703
8816
|
}
|
|
8704
8817
|
async stop() {
|
|
8705
8818
|
for (const client of this.clients.values()) {
|
|
@@ -9110,6 +9223,61 @@ async function startServer(config) {
|
|
|
9110
9223
|
logger8.error(`Failed to initialize plugin ${plugin.name}:`, err);
|
|
9111
9224
|
}
|
|
9112
9225
|
}
|
|
9226
|
+
const PROXY_LOCK_FILE = "/tmp/metro-mcp-proxy.json";
|
|
9227
|
+
let isPrimaryInstance = false;
|
|
9228
|
+
async function tryConnectViaProxy() {
|
|
9229
|
+
try {
|
|
9230
|
+
const lockData = JSON.parse(fs4.readFileSync(PROXY_LOCK_FILE, "utf8"));
|
|
9231
|
+
if (lockData.pid && lockData.port) {
|
|
9232
|
+
try {
|
|
9233
|
+
process.kill(lockData.pid, 0);
|
|
9234
|
+
} catch {
|
|
9235
|
+
return false;
|
|
9236
|
+
}
|
|
9237
|
+
const resp = await fetch(`http://127.0.0.1:${lockData.port}/json`, {
|
|
9238
|
+
signal: AbortSignal.timeout(2000)
|
|
9239
|
+
});
|
|
9240
|
+
if (!resp.ok)
|
|
9241
|
+
return false;
|
|
9242
|
+
const targets = await resp.json();
|
|
9243
|
+
if (targets.length > 0 && targets[0].webSocketDebuggerUrl) {
|
|
9244
|
+
logger8.info(`Found existing metro-mcp proxy (PID ${lockData.pid}, port ${lockData.port}) — connecting as secondary`);
|
|
9245
|
+
await cdpClient.connect(targets[0]);
|
|
9246
|
+
if (lockData.metroPort) {
|
|
9247
|
+
eventsClient.connect(config.metro.host, lockData.metroPort);
|
|
9248
|
+
config.metro.port = lockData.metroPort;
|
|
9249
|
+
}
|
|
9250
|
+
config.proxy = {
|
|
9251
|
+
...config.proxy,
|
|
9252
|
+
port: lockData.port
|
|
9253
|
+
};
|
|
9254
|
+
activeDeviceKey = targets[0].id ? `${lockData.port}-${targets[0].id}` : null;
|
|
9255
|
+
activeDeviceName = targets[0].title || targets[0].id || "secondary";
|
|
9256
|
+
return true;
|
|
9257
|
+
}
|
|
9258
|
+
}
|
|
9259
|
+
} catch {}
|
|
9260
|
+
return false;
|
|
9261
|
+
}
|
|
9262
|
+
function writeProxyLock(proxyPort, metroPort) {
|
|
9263
|
+
try {
|
|
9264
|
+
fs4.writeFileSync(PROXY_LOCK_FILE, JSON.stringify({ pid: process.pid, port: proxyPort, metroPort }));
|
|
9265
|
+
isPrimaryInstance = true;
|
|
9266
|
+
logger8.info(`Wrote proxy lock (port ${proxyPort})`);
|
|
9267
|
+
} catch (err) {
|
|
9268
|
+
logger8.warn("Failed to write proxy lock:", err);
|
|
9269
|
+
}
|
|
9270
|
+
}
|
|
9271
|
+
function cleanProxyLock() {
|
|
9272
|
+
if (!isPrimaryInstance)
|
|
9273
|
+
return;
|
|
9274
|
+
try {
|
|
9275
|
+
const lockData = JSON.parse(fs4.readFileSync(PROXY_LOCK_FILE, "utf8"));
|
|
9276
|
+
if (lockData.pid === process.pid) {
|
|
9277
|
+
fs4.unlinkSync(PROXY_LOCK_FILE);
|
|
9278
|
+
}
|
|
9279
|
+
} catch {}
|
|
9280
|
+
}
|
|
9113
9281
|
async function connectToMetro() {
|
|
9114
9282
|
if (isReconnecting) {
|
|
9115
9283
|
await waitForReconnect();
|
|
@@ -9117,6 +9285,9 @@ async function startServer(config) {
|
|
|
9117
9285
|
}
|
|
9118
9286
|
isReconnecting = true;
|
|
9119
9287
|
try {
|
|
9288
|
+
if (await tryConnectViaProxy()) {
|
|
9289
|
+
return true;
|
|
9290
|
+
}
|
|
9120
9291
|
let servers;
|
|
9121
9292
|
if (config.metro.autoDiscover) {
|
|
9122
9293
|
servers = await scanMetroPorts(config.metro.host);
|
|
@@ -9139,6 +9310,10 @@ async function startServer(config) {
|
|
|
9139
9310
|
eventsClient.connect(server.host, server.port);
|
|
9140
9311
|
activeDeviceKey = `${server.port}-${target.id}`;
|
|
9141
9312
|
activeDeviceName = target.title || target.deviceName || target.id;
|
|
9313
|
+
const proxyPort = config.proxy;
|
|
9314
|
+
if (proxyPort?.port) {
|
|
9315
|
+
writeProxyLock(proxyPort.port, server.port);
|
|
9316
|
+
}
|
|
9142
9317
|
return true;
|
|
9143
9318
|
} catch (err) {
|
|
9144
9319
|
logger8.warn("Could not connect to Metro:", err);
|
|
@@ -9171,7 +9346,15 @@ async function startServer(config) {
|
|
|
9171
9346
|
if (config.proxy?.enabled !== false) {
|
|
9172
9347
|
cdpProxy = new CDPProxy(cdpClient);
|
|
9173
9348
|
try {
|
|
9174
|
-
|
|
9349
|
+
let preferredProxyPort = config.proxy?.port ?? 0;
|
|
9350
|
+
if (preferredProxyPort === 0) {
|
|
9351
|
+
try {
|
|
9352
|
+
const stale = JSON.parse(fs4.readFileSync(PROXY_LOCK_FILE, "utf8"));
|
|
9353
|
+
if (stale.port)
|
|
9354
|
+
preferredProxyPort = stale.port;
|
|
9355
|
+
} catch {}
|
|
9356
|
+
}
|
|
9357
|
+
const proxyPort = await cdpProxy.start(preferredProxyPort);
|
|
9175
9358
|
const devtoolsUrl = cdpProxy.getDevToolsUrl();
|
|
9176
9359
|
logger8.info(`CDP proxy started on port ${proxyPort}`);
|
|
9177
9360
|
if (devtoolsUrl) {
|
|
@@ -9190,13 +9373,18 @@ async function startServer(config) {
|
|
|
9190
9373
|
await mcpServer.connect(transport);
|
|
9191
9374
|
logger8.info("MCP server started");
|
|
9192
9375
|
process.on("SIGINT", () => {
|
|
9376
|
+
cleanProxyLock();
|
|
9193
9377
|
cdpProxy?.stop();
|
|
9194
9378
|
process.exit(0);
|
|
9195
9379
|
});
|
|
9196
9380
|
process.on("SIGTERM", () => {
|
|
9381
|
+
cleanProxyLock();
|
|
9197
9382
|
cdpProxy?.stop();
|
|
9198
9383
|
process.exit(0);
|
|
9199
9384
|
});
|
|
9385
|
+
process.on("exit", () => {
|
|
9386
|
+
cleanProxyLock();
|
|
9387
|
+
});
|
|
9200
9388
|
connectToMetro();
|
|
9201
9389
|
}
|
|
9202
9390
|
|
package/dist/index.js
CHANGED
|
@@ -3325,6 +3325,7 @@ function mergeConfig(target, source) {
|
|
|
3325
3325
|
// src/server.ts
|
|
3326
3326
|
import { exec } from "child_process";
|
|
3327
3327
|
import { promisify } from "util";
|
|
3328
|
+
import fs4 from "fs";
|
|
3328
3329
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3329
3330
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
3330
3331
|
import { z as z23 } from "zod";
|
|
@@ -3748,7 +3749,7 @@ function extractCDPExceptionMessage(details, fallback = "Evaluation failed") {
|
|
|
3748
3749
|
// package.json
|
|
3749
3750
|
var package_default = {
|
|
3750
3751
|
name: "metro-mcp",
|
|
3751
|
-
version: "0.6.
|
|
3752
|
+
version: "0.6.4",
|
|
3752
3753
|
description: "Plugin-based MCP server for React Native/Expo runtime debugging, inspection, and automation via Metro/CDP",
|
|
3753
3754
|
homepage: "https://metromcp.dev",
|
|
3754
3755
|
repository: {
|
|
@@ -3944,45 +3945,103 @@ class DeviceBufferManager {
|
|
|
3944
3945
|
}
|
|
3945
3946
|
|
|
3946
3947
|
// src/plugins/console.ts
|
|
3948
|
+
function formatPreview(preview) {
|
|
3949
|
+
if (!preview.properties)
|
|
3950
|
+
return null;
|
|
3951
|
+
const props = preview.properties.map((p) => {
|
|
3952
|
+
if (p.type === "object" && p.valuePreview) {
|
|
3953
|
+
const nested = formatPreview(p.valuePreview);
|
|
3954
|
+
return `${p.name}: ${nested || p.value || "[object]"}`;
|
|
3955
|
+
}
|
|
3956
|
+
return `${p.name}: ${p.value}`;
|
|
3957
|
+
});
|
|
3958
|
+
const overflow = preview.overflow ? ", ..." : "";
|
|
3959
|
+
if (preview.subtype === "array")
|
|
3960
|
+
return `[${props.join(", ")}${overflow}]`;
|
|
3961
|
+
return `{${props.join(", ")}${overflow}}`;
|
|
3962
|
+
}
|
|
3963
|
+
function formatRemoteObject(obj) {
|
|
3964
|
+
if (obj.type === "string")
|
|
3965
|
+
return obj.value;
|
|
3966
|
+
if (obj.type === "number")
|
|
3967
|
+
return String(obj.value);
|
|
3968
|
+
if (obj.type === "boolean")
|
|
3969
|
+
return String(obj.value);
|
|
3970
|
+
if (obj.type === "undefined")
|
|
3971
|
+
return "undefined";
|
|
3972
|
+
if (obj.subtype === "null")
|
|
3973
|
+
return "null";
|
|
3974
|
+
if (obj.preview) {
|
|
3975
|
+
const formatted = formatPreview(obj.preview);
|
|
3976
|
+
if (formatted)
|
|
3977
|
+
return formatted;
|
|
3978
|
+
}
|
|
3979
|
+
if (obj.description)
|
|
3980
|
+
return obj.description;
|
|
3981
|
+
if (obj.value !== undefined)
|
|
3982
|
+
return JSON.stringify(obj.value);
|
|
3983
|
+
return obj.className || obj.type || "[object]";
|
|
3984
|
+
}
|
|
3947
3985
|
function formatCDPArgs(args) {
|
|
3948
3986
|
return args.map((arg) => {
|
|
3949
3987
|
if (typeof arg === "object" && arg !== null) {
|
|
3950
|
-
|
|
3951
|
-
if (remoteObj.type === "string")
|
|
3952
|
-
return remoteObj.value;
|
|
3953
|
-
if (remoteObj.type === "number")
|
|
3954
|
-
return String(remoteObj.value);
|
|
3955
|
-
if (remoteObj.type === "boolean")
|
|
3956
|
-
return String(remoteObj.value);
|
|
3957
|
-
if (remoteObj.type === "undefined")
|
|
3958
|
-
return "undefined";
|
|
3959
|
-
if (remoteObj.subtype === "null")
|
|
3960
|
-
return "null";
|
|
3961
|
-
if (remoteObj.description)
|
|
3962
|
-
return remoteObj.description;
|
|
3963
|
-
if (remoteObj.value !== undefined)
|
|
3964
|
-
return JSON.stringify(remoteObj.value);
|
|
3965
|
-
return remoteObj.className || remoteObj.type || "[object]";
|
|
3988
|
+
return formatRemoteObject(arg);
|
|
3966
3989
|
}
|
|
3967
3990
|
return String(arg);
|
|
3968
3991
|
}).join(" ");
|
|
3969
3992
|
}
|
|
3993
|
+
async function resolveRemoteObject(cdpSend, objectId) {
|
|
3994
|
+
try {
|
|
3995
|
+
const result = await cdpSend("Runtime.callFunctionOn", {
|
|
3996
|
+
objectId,
|
|
3997
|
+
functionDeclaration: 'function() { try { return JSON.stringify(this, null, 2); } catch(e) { return "[unserializable]"; } }',
|
|
3998
|
+
returnByValue: true
|
|
3999
|
+
});
|
|
4000
|
+
const inner = result.result;
|
|
4001
|
+
return inner?.value ? inner.value : null;
|
|
4002
|
+
} catch {
|
|
4003
|
+
return null;
|
|
4004
|
+
}
|
|
4005
|
+
}
|
|
4006
|
+
async function formatCDPArgsDeep(cdpSend, args) {
|
|
4007
|
+
const parts = await Promise.all(args.map(async (arg) => {
|
|
4008
|
+
if (typeof arg !== "object" || arg === null)
|
|
4009
|
+
return String(arg);
|
|
4010
|
+
const remoteObj = arg;
|
|
4011
|
+
if (remoteObj.objectId && remoteObj.type === "object") {
|
|
4012
|
+
const deep = await resolveRemoteObject(cdpSend, remoteObj.objectId);
|
|
4013
|
+
if (deep)
|
|
4014
|
+
return deep;
|
|
4015
|
+
}
|
|
4016
|
+
return formatRemoteObject(remoteObj);
|
|
4017
|
+
}));
|
|
4018
|
+
return parts.join(" ");
|
|
4019
|
+
}
|
|
3970
4020
|
var consolePlugin = definePlugin({
|
|
3971
4021
|
name: "console",
|
|
3972
4022
|
description: "Console log collection and filtering",
|
|
3973
4023
|
async setup(ctx) {
|
|
3974
4024
|
const buffers = new DeviceBufferManager(500);
|
|
4025
|
+
const cdpSend = ctx.cdp.send.bind(ctx.cdp);
|
|
3975
4026
|
ctx.cdp.on("Runtime.consoleAPICalled", (params) => {
|
|
3976
4027
|
const key = ctx.getActiveDeviceKey();
|
|
3977
4028
|
if (!key)
|
|
3978
4029
|
return;
|
|
3979
4030
|
const args = params.args || [];
|
|
3980
|
-
|
|
4031
|
+
const entry = {
|
|
3981
4032
|
timestamp: Date.now(),
|
|
3982
4033
|
level: params.type,
|
|
3983
4034
|
message: formatCDPArgs(args),
|
|
3984
4035
|
stackTrace: params.stackTrace ? JSON.stringify(params.stackTrace.callFrames) : undefined
|
|
3985
|
-
}
|
|
4036
|
+
};
|
|
4037
|
+
buffers.getOrCreate(key).push(entry);
|
|
4038
|
+
const hasResolvable = args.some((arg) => typeof arg === "object" && arg !== null && arg.objectId);
|
|
4039
|
+
if (hasResolvable) {
|
|
4040
|
+
formatCDPArgsDeep(cdpSend, args).then((deep) => {
|
|
4041
|
+
if (deep !== entry.message)
|
|
4042
|
+
entry.message = deep;
|
|
4043
|
+
}).catch(() => {});
|
|
4044
|
+
}
|
|
3986
4045
|
});
|
|
3987
4046
|
ctx.events.on("bundle_transform_progressed", (event) => {
|
|
3988
4047
|
if (event.transformedFileCount === 1) {
|
|
@@ -8594,9 +8653,10 @@ var inspectPointPlugin = definePlugin({
|
|
|
8594
8653
|
});
|
|
8595
8654
|
|
|
8596
8655
|
// src/plugins/devtools.ts
|
|
8597
|
-
import
|
|
8656
|
+
import fs3 from "fs";
|
|
8598
8657
|
import { z as z22 } from "zod";
|
|
8599
8658
|
var logger6 = createLogger("devtools");
|
|
8659
|
+
var DEVTOOLS_STATE_FILE = "/tmp/metro-mcp-devtools.json";
|
|
8600
8660
|
async function findBrowserPath() {
|
|
8601
8661
|
try {
|
|
8602
8662
|
const { Launcher: Launcher2 } = await Promise.resolve().then(() => (init_dist(), exports_dist));
|
|
@@ -8612,6 +8672,43 @@ async function findBrowserPath() {
|
|
|
8612
8672
|
} catch {}
|
|
8613
8673
|
return null;
|
|
8614
8674
|
}
|
|
8675
|
+
async function tryFocusExisting(frontendUrl) {
|
|
8676
|
+
try {
|
|
8677
|
+
const state = JSON.parse(fs3.readFileSync(DEVTOOLS_STATE_FILE, "utf8"));
|
|
8678
|
+
if (!state.pid || !state.remoteDebuggingPort)
|
|
8679
|
+
return false;
|
|
8680
|
+
try {
|
|
8681
|
+
process.kill(state.pid, 0);
|
|
8682
|
+
} catch {
|
|
8683
|
+
return false;
|
|
8684
|
+
}
|
|
8685
|
+
const resp = await fetch(`http://localhost:${state.remoteDebuggingPort}/json`, {
|
|
8686
|
+
signal: AbortSignal.timeout(1000)
|
|
8687
|
+
});
|
|
8688
|
+
if (!resp.ok)
|
|
8689
|
+
return false;
|
|
8690
|
+
const targets = await resp.json();
|
|
8691
|
+
const target = targets.find((t) => t.url?.includes("rn_fusebox") || t.url === frontendUrl);
|
|
8692
|
+
if (!target?.id)
|
|
8693
|
+
return false;
|
|
8694
|
+
const activate = await fetch(`http://localhost:${state.remoteDebuggingPort}/json/activate/${target.id}`, { signal: AbortSignal.timeout(1000) });
|
|
8695
|
+
return activate.ok;
|
|
8696
|
+
} catch {
|
|
8697
|
+
return false;
|
|
8698
|
+
}
|
|
8699
|
+
}
|
|
8700
|
+
async function launchDevTools(frontendUrl) {
|
|
8701
|
+
const { launch: launch2 } = await Promise.resolve().then(() => (init_dist(), exports_dist));
|
|
8702
|
+
const chrome2 = await launch2({
|
|
8703
|
+
chromeFlags: [`--app=${frontendUrl}`, "--window-size=1200,600"]
|
|
8704
|
+
});
|
|
8705
|
+
chrome2.process.unref();
|
|
8706
|
+
try {
|
|
8707
|
+
fs3.writeFileSync(DEVTOOLS_STATE_FILE, JSON.stringify({ pid: chrome2.pid, remoteDebuggingPort: chrome2.port }));
|
|
8708
|
+
} catch (err) {
|
|
8709
|
+
logger6.warn("Failed to write devtools state:", err);
|
|
8710
|
+
}
|
|
8711
|
+
}
|
|
8615
8712
|
var devtoolsPlugin = definePlugin({
|
|
8616
8713
|
name: "devtools",
|
|
8617
8714
|
description: "Open React Native DevTools via the CDP proxy",
|
|
@@ -8633,11 +8730,13 @@ var devtoolsPlugin = definePlugin({
|
|
|
8633
8730
|
const browserPath = await findBrowserPath();
|
|
8634
8731
|
if (browserPath) {
|
|
8635
8732
|
try {
|
|
8636
|
-
const
|
|
8637
|
-
|
|
8733
|
+
const focused = await tryFocusExisting(frontendUrl);
|
|
8734
|
+
if (!focused) {
|
|
8735
|
+
await launchDevTools(frontendUrl);
|
|
8736
|
+
}
|
|
8638
8737
|
return { opened: true, url: frontendUrl };
|
|
8639
8738
|
} catch (err) {
|
|
8640
|
-
logger6.debug("Failed to
|
|
8739
|
+
logger6.debug("Failed to open DevTools:", err);
|
|
8641
8740
|
}
|
|
8642
8741
|
} else {
|
|
8643
8742
|
logger6.debug("No Chrome/Edge installation found");
|
|
@@ -8689,20 +8788,34 @@ class CDPProxy {
|
|
|
8689
8788
|
return this._port;
|
|
8690
8789
|
}
|
|
8691
8790
|
async start(port = 0) {
|
|
8692
|
-
|
|
8693
|
-
|
|
8694
|
-
|
|
8791
|
+
const tryPort = (p) => new Promise((resolve, reject) => {
|
|
8792
|
+
const httpServer = http.createServer((req, res) => this.handleHttpRequest(req, res));
|
|
8793
|
+
httpServer.on("error", (err) => {
|
|
8794
|
+
httpServer.close();
|
|
8795
|
+
reject(err);
|
|
8695
8796
|
});
|
|
8696
|
-
|
|
8697
|
-
|
|
8698
|
-
|
|
8699
|
-
|
|
8700
|
-
|
|
8701
|
-
this.
|
|
8702
|
-
|
|
8703
|
-
|
|
8797
|
+
httpServer.listen(p, () => {
|
|
8798
|
+
const addr = httpServer.address();
|
|
8799
|
+
const actualPort = typeof addr === "object" && addr ? addr.port : p;
|
|
8800
|
+
const wss = new WebSocketServer({ server: httpServer });
|
|
8801
|
+
wss.on("connection", (ws) => this.handleNewClient(ws));
|
|
8802
|
+
this.httpServer = httpServer;
|
|
8803
|
+
this.wss = wss;
|
|
8804
|
+
this._port = actualPort;
|
|
8805
|
+
logger7.info(`CDP proxy listening on port ${actualPort}`);
|
|
8806
|
+
resolve(actualPort);
|
|
8704
8807
|
});
|
|
8705
8808
|
});
|
|
8809
|
+
if (port !== 0) {
|
|
8810
|
+
try {
|
|
8811
|
+
return await tryPort(port);
|
|
8812
|
+
} catch (err) {
|
|
8813
|
+
if (err.code !== "EADDRINUSE")
|
|
8814
|
+
throw err;
|
|
8815
|
+
logger7.debug(`Preferred proxy port ${port} in use, falling back to auto-assign`);
|
|
8816
|
+
}
|
|
8817
|
+
}
|
|
8818
|
+
return tryPort(0);
|
|
8706
8819
|
}
|
|
8707
8820
|
async stop() {
|
|
8708
8821
|
for (const client of this.clients.values()) {
|
|
@@ -9113,6 +9226,61 @@ async function startServer(config) {
|
|
|
9113
9226
|
logger8.error(`Failed to initialize plugin ${plugin.name}:`, err);
|
|
9114
9227
|
}
|
|
9115
9228
|
}
|
|
9229
|
+
const PROXY_LOCK_FILE = "/tmp/metro-mcp-proxy.json";
|
|
9230
|
+
let isPrimaryInstance = false;
|
|
9231
|
+
async function tryConnectViaProxy() {
|
|
9232
|
+
try {
|
|
9233
|
+
const lockData = JSON.parse(fs4.readFileSync(PROXY_LOCK_FILE, "utf8"));
|
|
9234
|
+
if (lockData.pid && lockData.port) {
|
|
9235
|
+
try {
|
|
9236
|
+
process.kill(lockData.pid, 0);
|
|
9237
|
+
} catch {
|
|
9238
|
+
return false;
|
|
9239
|
+
}
|
|
9240
|
+
const resp = await fetch(`http://127.0.0.1:${lockData.port}/json`, {
|
|
9241
|
+
signal: AbortSignal.timeout(2000)
|
|
9242
|
+
});
|
|
9243
|
+
if (!resp.ok)
|
|
9244
|
+
return false;
|
|
9245
|
+
const targets = await resp.json();
|
|
9246
|
+
if (targets.length > 0 && targets[0].webSocketDebuggerUrl) {
|
|
9247
|
+
logger8.info(`Found existing metro-mcp proxy (PID ${lockData.pid}, port ${lockData.port}) — connecting as secondary`);
|
|
9248
|
+
await cdpClient.connect(targets[0]);
|
|
9249
|
+
if (lockData.metroPort) {
|
|
9250
|
+
eventsClient.connect(config.metro.host, lockData.metroPort);
|
|
9251
|
+
config.metro.port = lockData.metroPort;
|
|
9252
|
+
}
|
|
9253
|
+
config.proxy = {
|
|
9254
|
+
...config.proxy,
|
|
9255
|
+
port: lockData.port
|
|
9256
|
+
};
|
|
9257
|
+
activeDeviceKey = targets[0].id ? `${lockData.port}-${targets[0].id}` : null;
|
|
9258
|
+
activeDeviceName = targets[0].title || targets[0].id || "secondary";
|
|
9259
|
+
return true;
|
|
9260
|
+
}
|
|
9261
|
+
}
|
|
9262
|
+
} catch {}
|
|
9263
|
+
return false;
|
|
9264
|
+
}
|
|
9265
|
+
function writeProxyLock(proxyPort, metroPort) {
|
|
9266
|
+
try {
|
|
9267
|
+
fs4.writeFileSync(PROXY_LOCK_FILE, JSON.stringify({ pid: process.pid, port: proxyPort, metroPort }));
|
|
9268
|
+
isPrimaryInstance = true;
|
|
9269
|
+
logger8.info(`Wrote proxy lock (port ${proxyPort})`);
|
|
9270
|
+
} catch (err) {
|
|
9271
|
+
logger8.warn("Failed to write proxy lock:", err);
|
|
9272
|
+
}
|
|
9273
|
+
}
|
|
9274
|
+
function cleanProxyLock() {
|
|
9275
|
+
if (!isPrimaryInstance)
|
|
9276
|
+
return;
|
|
9277
|
+
try {
|
|
9278
|
+
const lockData = JSON.parse(fs4.readFileSync(PROXY_LOCK_FILE, "utf8"));
|
|
9279
|
+
if (lockData.pid === process.pid) {
|
|
9280
|
+
fs4.unlinkSync(PROXY_LOCK_FILE);
|
|
9281
|
+
}
|
|
9282
|
+
} catch {}
|
|
9283
|
+
}
|
|
9116
9284
|
async function connectToMetro() {
|
|
9117
9285
|
if (isReconnecting) {
|
|
9118
9286
|
await waitForReconnect();
|
|
@@ -9120,6 +9288,9 @@ async function startServer(config) {
|
|
|
9120
9288
|
}
|
|
9121
9289
|
isReconnecting = true;
|
|
9122
9290
|
try {
|
|
9291
|
+
if (await tryConnectViaProxy()) {
|
|
9292
|
+
return true;
|
|
9293
|
+
}
|
|
9123
9294
|
let servers;
|
|
9124
9295
|
if (config.metro.autoDiscover) {
|
|
9125
9296
|
servers = await scanMetroPorts(config.metro.host);
|
|
@@ -9142,6 +9313,10 @@ async function startServer(config) {
|
|
|
9142
9313
|
eventsClient.connect(server.host, server.port);
|
|
9143
9314
|
activeDeviceKey = `${server.port}-${target.id}`;
|
|
9144
9315
|
activeDeviceName = target.title || target.deviceName || target.id;
|
|
9316
|
+
const proxyPort = config.proxy;
|
|
9317
|
+
if (proxyPort?.port) {
|
|
9318
|
+
writeProxyLock(proxyPort.port, server.port);
|
|
9319
|
+
}
|
|
9145
9320
|
return true;
|
|
9146
9321
|
} catch (err) {
|
|
9147
9322
|
logger8.warn("Could not connect to Metro:", err);
|
|
@@ -9174,7 +9349,15 @@ async function startServer(config) {
|
|
|
9174
9349
|
if (config.proxy?.enabled !== false) {
|
|
9175
9350
|
cdpProxy = new CDPProxy(cdpClient);
|
|
9176
9351
|
try {
|
|
9177
|
-
|
|
9352
|
+
let preferredProxyPort = config.proxy?.port ?? 0;
|
|
9353
|
+
if (preferredProxyPort === 0) {
|
|
9354
|
+
try {
|
|
9355
|
+
const stale = JSON.parse(fs4.readFileSync(PROXY_LOCK_FILE, "utf8"));
|
|
9356
|
+
if (stale.port)
|
|
9357
|
+
preferredProxyPort = stale.port;
|
|
9358
|
+
} catch {}
|
|
9359
|
+
}
|
|
9360
|
+
const proxyPort = await cdpProxy.start(preferredProxyPort);
|
|
9178
9361
|
const devtoolsUrl = cdpProxy.getDevToolsUrl();
|
|
9179
9362
|
logger8.info(`CDP proxy started on port ${proxyPort}`);
|
|
9180
9363
|
if (devtoolsUrl) {
|
|
@@ -9193,13 +9376,18 @@ async function startServer(config) {
|
|
|
9193
9376
|
await mcpServer.connect(transport);
|
|
9194
9377
|
logger8.info("MCP server started");
|
|
9195
9378
|
process.on("SIGINT", () => {
|
|
9379
|
+
cleanProxyLock();
|
|
9196
9380
|
cdpProxy?.stop();
|
|
9197
9381
|
process.exit(0);
|
|
9198
9382
|
});
|
|
9199
9383
|
process.on("SIGTERM", () => {
|
|
9384
|
+
cleanProxyLock();
|
|
9200
9385
|
cdpProxy?.stop();
|
|
9201
9386
|
process.exit(0);
|
|
9202
9387
|
});
|
|
9388
|
+
process.on("exit", () => {
|
|
9389
|
+
cleanProxyLock();
|
|
9390
|
+
});
|
|
9203
9391
|
connectToMetro();
|
|
9204
9392
|
}
|
|
9205
9393
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"proxy.d.ts","sourceRoot":"","sources":["../../src/metro/proxy.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAyBjD;;;;;;;;;;;GAWG;AACH,qBAAa,QAAQ;IAUP,OAAO,CAAC,QAAQ,CAAC,SAAS;IATtC,OAAO,CAAC,UAAU,CAA4B;IAC9C,OAAO,CAAC,GAAG,CAAgC;IAC3C,OAAO,CAAC,OAAO,CAAqC;IACpD,OAAO,CAAC,eAAe,CAA0C;IACjE,OAAO,CAAC,eAAe,CAA6B;IACpD,OAAO,CAAC,YAAY,CAAa;IACjC,OAAO,CAAC,aAAa,CAAK;IAC1B,OAAO,CAAC,KAAK,CAAuB;gBAEP,SAAS,EAAE,SAAS;IAsBjD,IAAI,IAAI,IAAI,MAAM,GAAG,IAAI,CAExB;IAED;;OAEG;IACG,KAAK,CAAC,IAAI,SAAI,GAAG,OAAO,CAAC,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"proxy.d.ts","sourceRoot":"","sources":["../../src/metro/proxy.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAyBjD;;;;;;;;;;;GAWG;AACH,qBAAa,QAAQ;IAUP,OAAO,CAAC,QAAQ,CAAC,SAAS;IATtC,OAAO,CAAC,UAAU,CAA4B;IAC9C,OAAO,CAAC,GAAG,CAAgC;IAC3C,OAAO,CAAC,OAAO,CAAqC;IACpD,OAAO,CAAC,eAAe,CAA0C;IACjE,OAAO,CAAC,eAAe,CAA6B;IACpD,OAAO,CAAC,YAAY,CAAa;IACjC,OAAO,CAAC,aAAa,CAAK;IAC1B,OAAO,CAAC,KAAK,CAAuB;gBAEP,SAAS,EAAE,SAAS;IAsBjD,IAAI,IAAI,IAAI,MAAM,GAAG,IAAI,CAExB;IAED;;OAEG;IACG,KAAK,CAAC,IAAI,SAAI,GAAG,OAAO,CAAC,MAAM,CAAC;IAiCtC;;OAEG;IACG,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IA8B3B;;OAEG;IACH,cAAc,IAAI,MAAM,GAAG,IAAI;IAO/B,OAAO,CAAC,iBAAiB;IAiCzB,OAAO,CAAC,eAAe;IAoBvB,OAAO,CAAC,mBAAmB;IA2E3B;;;OAGG;IACH,OAAO,CAAC,qBAAqB;IA+B7B,OAAO,CAAC,YAAY;IAMpB,OAAO,CAAC,aAAa;IA6BrB;;OAEG;IACH,OAAO,CAAC,eAAe;CAgBxB"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"console.d.ts","sourceRoot":"","sources":["../../src/plugins/console.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"console.d.ts","sourceRoot":"","sources":["../../src/plugins/console.ts"],"names":[],"mappings":"AAuHA,eAAO,MAAM,aAAa,yCAiIxB,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"devtools.d.ts","sourceRoot":"","sources":["../../src/plugins/devtools.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"devtools.d.ts","sourceRoot":"","sources":["../../src/plugins/devtools.ts"],"names":[],"mappings":"AAuEA,eAAO,MAAM,cAAc,yCAyDzB,CAAC"}
|
package/dist/server.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EACV,cAAc,EAOf,MAAM,aAAa,CAAC;AAgErB,wBAAsB,WAAW,CAAC,MAAM,EAAE,QAAQ,CAAC,cAAc,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAyZjF"}
|
package/package.json
CHANGED