@vercel/next-browser 0.1.0 → 0.1.1
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/README.md +16 -26
- package/dist/browser.js +377 -0
- package/dist/cli.js +219 -0
- package/dist/client.js +72 -0
- package/dist/daemon.js +119 -0
- package/dist/mcp.js +31 -0
- package/dist/network.js +101 -0
- package/{src/paths.ts → dist/paths.js} +0 -2
- package/dist/sourcemap.js +67 -0
- package/dist/suspense.js +314 -0
- package/dist/tree.js +231 -0
- package/package.json +7 -5
- package/src/browser.ts +0 -408
- package/src/cli.ts +0 -233
- package/src/client.ts +0 -80
- package/src/daemon.ts +0 -140
- package/src/mcp.ts +0 -37
- package/src/network.ts +0 -124
- package/src/sourcemap.ts +0 -84
- package/src/suspense.ts +0 -361
- package/src/tree.ts +0 -240
package/src/client.ts
DELETED
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
import { connect as netConnect, type Socket } from "node:net";
|
|
2
|
-
import { readFileSync, existsSync, rmSync } from "node:fs";
|
|
3
|
-
import { spawn } from "node:child_process";
|
|
4
|
-
import { setTimeout as sleep } from "node:timers/promises";
|
|
5
|
-
import { fileURLToPath } from "node:url";
|
|
6
|
-
import { socketPath, pidFile } from "./paths.ts";
|
|
7
|
-
|
|
8
|
-
export type Response = { ok: true; data?: unknown } | { ok: false; error: string };
|
|
9
|
-
|
|
10
|
-
export async function send(
|
|
11
|
-
action: string,
|
|
12
|
-
payload: Record<string, unknown> = {},
|
|
13
|
-
): Promise<Response> {
|
|
14
|
-
await ensureDaemon();
|
|
15
|
-
const socket = await connect();
|
|
16
|
-
const id = String(Date.now());
|
|
17
|
-
socket.write(JSON.stringify({ id, action, ...payload }) + "\n");
|
|
18
|
-
const line = await readLine(socket);
|
|
19
|
-
socket.end();
|
|
20
|
-
return JSON.parse(line);
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
async function ensureDaemon() {
|
|
24
|
-
if (daemonAlive() && (await connect().then(ok, no))) return;
|
|
25
|
-
|
|
26
|
-
const daemon = fileURLToPath(new URL("./daemon.ts", import.meta.url));
|
|
27
|
-
const child = spawn(process.execPath, [daemon], {
|
|
28
|
-
detached: true,
|
|
29
|
-
stdio: "ignore",
|
|
30
|
-
});
|
|
31
|
-
child.unref();
|
|
32
|
-
|
|
33
|
-
for (let i = 0; i < 50; i++) {
|
|
34
|
-
if (await connect().then(ok, no)) return;
|
|
35
|
-
await sleep(100);
|
|
36
|
-
}
|
|
37
|
-
throw new Error(`daemon failed to start (${socketPath})`);
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
function daemonAlive() {
|
|
41
|
-
if (!existsSync(pidFile)) return false;
|
|
42
|
-
const pid = Number(readFileSync(pidFile, "utf-8"));
|
|
43
|
-
try {
|
|
44
|
-
process.kill(pid, 0);
|
|
45
|
-
return true;
|
|
46
|
-
} catch {
|
|
47
|
-
rmSync(pidFile, { force: true });
|
|
48
|
-
rmSync(socketPath, { force: true });
|
|
49
|
-
return false;
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
function connect(): Promise<Socket> {
|
|
54
|
-
return new Promise((resolve, reject) => {
|
|
55
|
-
const socket = netConnect(socketPath);
|
|
56
|
-
socket.once("connect", () => resolve(socket));
|
|
57
|
-
socket.once("error", reject);
|
|
58
|
-
});
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
function readLine(socket: Socket): Promise<string> {
|
|
62
|
-
return new Promise((resolve, reject) => {
|
|
63
|
-
let buffer = "";
|
|
64
|
-
socket.on("data", (chunk) => {
|
|
65
|
-
buffer += chunk;
|
|
66
|
-
const newline = buffer.indexOf("\n");
|
|
67
|
-
if (newline >= 0) resolve(buffer.slice(0, newline));
|
|
68
|
-
});
|
|
69
|
-
socket.on("error", reject);
|
|
70
|
-
});
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
function ok(s: Socket) {
|
|
74
|
-
s.destroy();
|
|
75
|
-
return true;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
function no() {
|
|
79
|
-
return false;
|
|
80
|
-
}
|
package/src/daemon.ts
DELETED
|
@@ -1,140 +0,0 @@
|
|
|
1
|
-
import { createServer } from "node:net";
|
|
2
|
-
import { mkdirSync, writeFileSync, rmSync } from "node:fs";
|
|
3
|
-
import type { Socket } from "node:net";
|
|
4
|
-
import * as browser from "./browser.ts";
|
|
5
|
-
import { socketDir, socketPath, pidFile } from "./paths.ts";
|
|
6
|
-
|
|
7
|
-
mkdirSync(socketDir, { recursive: true, mode: 0o700 });
|
|
8
|
-
rmSync(socketPath, { force: true });
|
|
9
|
-
rmSync(pidFile, { force: true });
|
|
10
|
-
|
|
11
|
-
writeFileSync(pidFile, String(process.pid));
|
|
12
|
-
|
|
13
|
-
const server = createServer((socket) => {
|
|
14
|
-
let buffer = "";
|
|
15
|
-
socket.on("data", (chunk) => {
|
|
16
|
-
buffer += chunk;
|
|
17
|
-
let newline: number;
|
|
18
|
-
while ((newline = buffer.indexOf("\n")) >= 0) {
|
|
19
|
-
const line = buffer.slice(0, newline);
|
|
20
|
-
buffer = buffer.slice(newline + 1);
|
|
21
|
-
if (line) dispatch(line, socket);
|
|
22
|
-
}
|
|
23
|
-
});
|
|
24
|
-
socket.on("error", () => {});
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
server.listen(socketPath);
|
|
28
|
-
|
|
29
|
-
process.on("SIGINT", shutdown);
|
|
30
|
-
process.on("SIGTERM", shutdown);
|
|
31
|
-
process.on("exit", cleanup);
|
|
32
|
-
|
|
33
|
-
async function dispatch(line: string, socket: Socket) {
|
|
34
|
-
const cmd = JSON.parse(line);
|
|
35
|
-
const result = await run(cmd).catch((err) => ({ ok: false, error: cleanError(err) }));
|
|
36
|
-
socket.write(JSON.stringify({ id: cmd.id, ...result }) + "\n");
|
|
37
|
-
if (cmd.action === "close") setImmediate(shutdown);
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
function cleanError(err: Error) {
|
|
41
|
-
const msg = err.message;
|
|
42
|
-
const m = msg.match(/^page\.\w+: (?:Error: )?(.+?)(?:\n|$)/);
|
|
43
|
-
return m ? m[1] : msg;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
type Cmd = {
|
|
47
|
-
action: string;
|
|
48
|
-
url?: string;
|
|
49
|
-
nodeId?: number;
|
|
50
|
-
tool?: string;
|
|
51
|
-
args?: Record<string, unknown>;
|
|
52
|
-
script?: string;
|
|
53
|
-
idx?: number;
|
|
54
|
-
cookies?: { name: string; value: string }[];
|
|
55
|
-
domain?: string;
|
|
56
|
-
};
|
|
57
|
-
|
|
58
|
-
async function run(cmd: Cmd) {
|
|
59
|
-
if (cmd.action === "open") {
|
|
60
|
-
await browser.open(cmd.url);
|
|
61
|
-
return { ok: true };
|
|
62
|
-
}
|
|
63
|
-
if (cmd.action === "cookies") {
|
|
64
|
-
const data = await browser.cookies(cmd.cookies!, cmd.domain!);
|
|
65
|
-
return { ok: true, data };
|
|
66
|
-
}
|
|
67
|
-
if (cmd.action === "lock") {
|
|
68
|
-
await browser.lock();
|
|
69
|
-
return { ok: true };
|
|
70
|
-
}
|
|
71
|
-
if (cmd.action === "unlock") {
|
|
72
|
-
const data = await browser.unlock();
|
|
73
|
-
return { ok: true, data };
|
|
74
|
-
}
|
|
75
|
-
if (cmd.action === "reload") {
|
|
76
|
-
const data = await browser.reload();
|
|
77
|
-
return { ok: true, data };
|
|
78
|
-
}
|
|
79
|
-
if (cmd.action === "restart") {
|
|
80
|
-
const data = await browser.restart();
|
|
81
|
-
return { ok: true, data };
|
|
82
|
-
}
|
|
83
|
-
if (cmd.action === "screenshot") {
|
|
84
|
-
const data = await browser.screenshot();
|
|
85
|
-
return { ok: true, data };
|
|
86
|
-
}
|
|
87
|
-
if (cmd.action === "links") {
|
|
88
|
-
const data = await browser.links();
|
|
89
|
-
return { ok: true, data };
|
|
90
|
-
}
|
|
91
|
-
if (cmd.action === "push") {
|
|
92
|
-
const data = await browser.push(cmd.url!);
|
|
93
|
-
return { ok: true, data };
|
|
94
|
-
}
|
|
95
|
-
if (cmd.action === "goto") {
|
|
96
|
-
const data = await browser.goto(cmd.url!);
|
|
97
|
-
return { ok: true, data };
|
|
98
|
-
}
|
|
99
|
-
if (cmd.action === "back") {
|
|
100
|
-
await browser.back();
|
|
101
|
-
return { ok: true };
|
|
102
|
-
}
|
|
103
|
-
if (cmd.action === "tree") {
|
|
104
|
-
const data = await browser.tree();
|
|
105
|
-
return { ok: true, data };
|
|
106
|
-
}
|
|
107
|
-
if (cmd.action === "node") {
|
|
108
|
-
const data = await browser.node(cmd.nodeId!);
|
|
109
|
-
return { ok: true, data };
|
|
110
|
-
}
|
|
111
|
-
if (cmd.action === "eval") {
|
|
112
|
-
const data = await browser.evaluate(cmd.script!);
|
|
113
|
-
return { ok: true, data };
|
|
114
|
-
}
|
|
115
|
-
if (cmd.action === "mcp") {
|
|
116
|
-
const data = await browser.mcp(cmd.tool!, cmd.args);
|
|
117
|
-
return { ok: true, data };
|
|
118
|
-
}
|
|
119
|
-
if (cmd.action === "network") {
|
|
120
|
-
const data = await browser.network(cmd.idx);
|
|
121
|
-
return { ok: true, data };
|
|
122
|
-
}
|
|
123
|
-
if (cmd.action === "close") {
|
|
124
|
-
await browser.close();
|
|
125
|
-
return { ok: true };
|
|
126
|
-
}
|
|
127
|
-
return { ok: false, error: `unknown action: ${cmd.action}` };
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
async function shutdown() {
|
|
131
|
-
await browser.close();
|
|
132
|
-
server.close();
|
|
133
|
-
cleanup();
|
|
134
|
-
process.exit(0);
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
function cleanup() {
|
|
138
|
-
rmSync(socketPath, { force: true });
|
|
139
|
-
rmSync(pidFile, { force: true });
|
|
140
|
-
}
|
package/src/mcp.ts
DELETED
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Next.js dev-server MCP client. POSTs JSON-RPC to /_next/mcp
|
|
3
|
-
* (StreamableHTTP transport, SSE response).
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
export async function call(
|
|
7
|
-
origin: string,
|
|
8
|
-
tool: string,
|
|
9
|
-
args: Record<string, unknown> = {},
|
|
10
|
-
): Promise<unknown> {
|
|
11
|
-
const res = await fetch(`${origin}/_next/mcp`, {
|
|
12
|
-
method: "POST",
|
|
13
|
-
headers: {
|
|
14
|
-
"Content-Type": "application/json",
|
|
15
|
-
Accept: "application/json, text/event-stream",
|
|
16
|
-
},
|
|
17
|
-
body: JSON.stringify({
|
|
18
|
-
jsonrpc: "2.0",
|
|
19
|
-
id: 1,
|
|
20
|
-
method: "tools/call",
|
|
21
|
-
params: { name: tool, arguments: args },
|
|
22
|
-
}),
|
|
23
|
-
signal: AbortSignal.timeout(10_000),
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
if (!res.ok) throw new Error(`MCP ${res.status} ${res.statusText}`);
|
|
27
|
-
|
|
28
|
-
const body = await res.text();
|
|
29
|
-
const data = body.match(/^data: (.+)$/m)?.[1];
|
|
30
|
-
if (!data) throw new Error("no data frame in MCP response");
|
|
31
|
-
|
|
32
|
-
const parsed = JSON.parse(data);
|
|
33
|
-
if (parsed.error) throw new Error(parsed.error.message);
|
|
34
|
-
|
|
35
|
-
const text = parsed.result?.content?.[0]?.text;
|
|
36
|
-
return text ? JSON.parse(text) : parsed.result;
|
|
37
|
-
}
|
package/src/network.ts
DELETED
|
@@ -1,124 +0,0 @@
|
|
|
1
|
-
import { writeFileSync } from "node:fs";
|
|
2
|
-
import { tmpdir } from "node:os";
|
|
3
|
-
import { join } from "node:path";
|
|
4
|
-
import type { Page, Request, Response } from "playwright";
|
|
5
|
-
|
|
6
|
-
const BODY_INLINE_LIMIT = 4000;
|
|
7
|
-
|
|
8
|
-
type Entry = {
|
|
9
|
-
req: Request;
|
|
10
|
-
res: Response | null;
|
|
11
|
-
ms: number | null;
|
|
12
|
-
};
|
|
13
|
-
|
|
14
|
-
let entries: Entry[] = [];
|
|
15
|
-
let startTime = new Map<Request, number>();
|
|
16
|
-
|
|
17
|
-
export function attach(page: Page) {
|
|
18
|
-
page.on("request", (req) => {
|
|
19
|
-
if (req.resourceType() === "document" && req.frame() === page.mainFrame()) {
|
|
20
|
-
clear();
|
|
21
|
-
}
|
|
22
|
-
startTime.set(req, Date.now());
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
page.on("response", (res) => {
|
|
26
|
-
const req = res.request();
|
|
27
|
-
const t0 = startTime.get(req);
|
|
28
|
-
if (t0 == null) return;
|
|
29
|
-
entries.push({ req, res, ms: Date.now() - t0 });
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
page.on("requestfailed", (req) => {
|
|
33
|
-
if (!startTime.has(req)) return;
|
|
34
|
-
entries.push({ req, res: null, ms: null });
|
|
35
|
-
});
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
export function clear() {
|
|
39
|
-
entries = [];
|
|
40
|
-
startTime = new Map();
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
export async function detail(idx: number): Promise<string> {
|
|
44
|
-
const e = entries[idx];
|
|
45
|
-
if (!e) throw new Error(`no request at index ${idx}`);
|
|
46
|
-
|
|
47
|
-
const { req, res, ms } = e;
|
|
48
|
-
const lines: string[] = [
|
|
49
|
-
`${req.method()} ${req.url()}`,
|
|
50
|
-
`type: ${req.resourceType()}${ms != null ? ` ${ms}ms` : ""}`,
|
|
51
|
-
"",
|
|
52
|
-
"request headers:",
|
|
53
|
-
...indent(await req.allHeaders()),
|
|
54
|
-
];
|
|
55
|
-
|
|
56
|
-
const postData = req.postData();
|
|
57
|
-
if (postData) {
|
|
58
|
-
lines.push("", "request body:", truncate(postData, 2000));
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
if (!res) {
|
|
62
|
-
lines.push("", `FAILED: ${req.failure()?.errorText ?? "unknown"}`);
|
|
63
|
-
return lines.join("\n");
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
lines.push(
|
|
67
|
-
"",
|
|
68
|
-
`response: ${res.status()} ${res.statusText()}`,
|
|
69
|
-
"response headers:",
|
|
70
|
-
...indent(await res.allHeaders()),
|
|
71
|
-
);
|
|
72
|
-
|
|
73
|
-
const body = await res.text().catch((err) => `(body unavailable: ${err.message})`);
|
|
74
|
-
lines.push("", "response body:", spillIfLong(body, idx, res));
|
|
75
|
-
|
|
76
|
-
return lines.join("\n");
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
function indent(headers: Record<string, string>): string[] {
|
|
80
|
-
return Object.entries(headers).map(([k, v]) => ` ${k}: ${v}`);
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
function truncate(s: string, max: number): string {
|
|
84
|
-
return s.length > max ? s.slice(0, max) + `\n… (${s.length - max} more bytes)` : s;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
function spillIfLong(body: string, idx: number, res: Response): string {
|
|
88
|
-
if (body.length <= BODY_INLINE_LIMIT) return body;
|
|
89
|
-
const ext = extFor(res.headers()["content-type"]);
|
|
90
|
-
const path = join(tmpdir(), `next-browser-${process.pid}-${idx}${ext}`);
|
|
91
|
-
writeFileSync(path, body);
|
|
92
|
-
return `(${body.length} bytes written to ${path})`;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
function extFor(contentType: string | undefined): string {
|
|
96
|
-
if (!contentType) return ".txt";
|
|
97
|
-
if (contentType.includes("json")) return ".json";
|
|
98
|
-
if (contentType.includes("html")) return ".html";
|
|
99
|
-
if (contentType.includes("javascript")) return ".js";
|
|
100
|
-
if (contentType.includes("x-component")) return ".rsc";
|
|
101
|
-
return ".txt";
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
export function format(): string {
|
|
105
|
-
if (entries.length === 0) return "(no requests)";
|
|
106
|
-
|
|
107
|
-
const lines = [
|
|
108
|
-
"# Network requests since last navigation",
|
|
109
|
-
"# Columns: idx status method type ms url [next-action=...]",
|
|
110
|
-
"# Use `network <idx>` for headers and body.",
|
|
111
|
-
"",
|
|
112
|
-
];
|
|
113
|
-
|
|
114
|
-
entries.forEach((e, i) => {
|
|
115
|
-
const { req, res, ms } = e;
|
|
116
|
-
const status = res?.status() ?? "FAIL";
|
|
117
|
-
const time = ms != null ? `${ms}ms` : "-";
|
|
118
|
-
const action = req.headers()["next-action"];
|
|
119
|
-
const suffix = action ? ` next-action=${action}` : "";
|
|
120
|
-
lines.push(`${i} ${status} ${req.method()} ${req.resourceType()} ${time} ${req.url()}${suffix}`);
|
|
121
|
-
});
|
|
122
|
-
|
|
123
|
-
return lines.join("\n");
|
|
124
|
-
}
|
package/src/sourcemap.ts
DELETED
|
@@ -1,84 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Resolve a bundle location to its original source file via Next.js's
|
|
3
|
-
* dev-server endpoint. Returns null if it didn't resolve (Next internals,
|
|
4
|
-
* prod build, non-Next app).
|
|
5
|
-
*/
|
|
6
|
-
export async function resolve(
|
|
7
|
-
origin: string,
|
|
8
|
-
file: string,
|
|
9
|
-
line: number,
|
|
10
|
-
column: number,
|
|
11
|
-
): Promise<{ file: string; line: number; column: number } | null> {
|
|
12
|
-
const { path, isServer } = normalize(file, origin);
|
|
13
|
-
|
|
14
|
-
const res = await fetch(`${origin}/__nextjs_original-stack-frames`, {
|
|
15
|
-
method: "POST",
|
|
16
|
-
headers: { "Content-Type": "application/json" },
|
|
17
|
-
body: JSON.stringify({
|
|
18
|
-
frames: [{ file: path, methodName: "", arguments: [], line1: line, column1: column }],
|
|
19
|
-
isServer,
|
|
20
|
-
isEdgeServer: false,
|
|
21
|
-
isAppDirectory: true,
|
|
22
|
-
}),
|
|
23
|
-
signal: AbortSignal.timeout(5000),
|
|
24
|
-
}).catch(() => null);
|
|
25
|
-
|
|
26
|
-
if (!res?.ok) return null;
|
|
27
|
-
|
|
28
|
-
const [result] = await res.json();
|
|
29
|
-
const frame = result?.status === "fulfilled" ? result.value.originalStackFrame : null;
|
|
30
|
-
if (!frame || frame.file === path) return null;
|
|
31
|
-
|
|
32
|
-
return { file: frame.file, line: frame.line1, column: frame.column1 };
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
function normalize(file: string, origin: string): { path: string; isServer: boolean } {
|
|
36
|
-
const stripped = file.replace(/^about:\/\/React\/[^/]+\//, "");
|
|
37
|
-
const isServer = file !== stripped;
|
|
38
|
-
|
|
39
|
-
if (stripped.startsWith("file://")) return { path: stripped, isServer };
|
|
40
|
-
if (stripped.startsWith(origin)) return { path: stripped.slice(origin.length), isServer };
|
|
41
|
-
if (stripped.startsWith("http")) return { path: new URL(stripped).pathname, isServer };
|
|
42
|
-
return { path: stripped, isServer };
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
import { SourceMapConsumer } from "source-map-js";
|
|
46
|
-
|
|
47
|
-
const consumers = new Map<string, SourceMapConsumer | null>();
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* Fallback for locations the Next API won't resolve (node_modules).
|
|
51
|
-
* Fetches the .map directly and decodes the mappings.
|
|
52
|
-
*/
|
|
53
|
-
export async function resolveViaMap(
|
|
54
|
-
origin: string,
|
|
55
|
-
file: string,
|
|
56
|
-
line: number,
|
|
57
|
-
column: number,
|
|
58
|
-
): Promise<{ file: string; line: number; column: number } | null> {
|
|
59
|
-
const consumer = await load(origin, normalize(file, origin).path);
|
|
60
|
-
if (!consumer) return null;
|
|
61
|
-
|
|
62
|
-
const pos = consumer.originalPositionFor({ line, column });
|
|
63
|
-
if (!pos.source) return null;
|
|
64
|
-
|
|
65
|
-
return { file: cleanPath(pos.source), line: pos.line, column: pos.column };
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
async function load(origin: string, path: string) {
|
|
69
|
-
if (consumers.has(path)) return consumers.get(path)!;
|
|
70
|
-
|
|
71
|
-
const res = await fetch(`${origin}${path}.map`, {
|
|
72
|
-
signal: AbortSignal.timeout(5000),
|
|
73
|
-
}).catch(() => null);
|
|
74
|
-
|
|
75
|
-
const consumer = res?.ok ? new SourceMapConsumer(await res.json()) : null;
|
|
76
|
-
consumers.set(path, consumer);
|
|
77
|
-
return consumer;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
function cleanPath(src: string): string {
|
|
81
|
-
const path = decodeURIComponent(src.replace(/^file:\/\//, ""));
|
|
82
|
-
const nm = path.lastIndexOf("/node_modules/");
|
|
83
|
-
return nm >= 0 ? path.slice(nm + 1) : path;
|
|
84
|
-
}
|