reflet-sdk 0.7.4 → 0.7.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/README.md +35 -4
  3. package/dist/desktop.d.ts +24 -0
  4. package/dist/desktop.d.ts.map +1 -0
  5. package/dist/desktop.js +118 -0
  6. package/dist/desktop.js.map +1 -0
  7. package/dist/{devtools-layer-CcTmlikp.js → devtools-layer-xN47FKpW.js} +5 -5
  8. package/dist/devtools-layer-xN47FKpW.js.map +1 -0
  9. package/dist/devtools-next.d.ts.map +1 -1
  10. package/dist/devtools-next.js +1 -1
  11. package/dist/{devtools-styles-teRMfSzW.js → devtools-styles-DQpiubGB.js} +26 -8
  12. package/dist/devtools-styles-DQpiubGB.js.map +1 -0
  13. package/dist/devtools-vite.js +2 -1
  14. package/dist/devtools-vite.js.map +1 -1
  15. package/dist/feedback.d.ts +7 -3
  16. package/dist/feedback.d.ts.map +1 -1
  17. package/dist/feedback.js +23 -13
  18. package/dist/feedback.js.map +1 -1
  19. package/dist/{handler-B3_PQB1q.js → handler-ndF3gZBx.js} +11 -416
  20. package/dist/handler-ndF3gZBx.js.map +1 -0
  21. package/dist/reflet-proxy-BW6q9xds.js +467 -0
  22. package/dist/reflet-proxy-BW6q9xds.js.map +1 -0
  23. package/dist/{submit-CG4LXioK.js → submit-DsJwTy3X.js} +25 -9
  24. package/dist/submit-DsJwTy3X.js.map +1 -0
  25. package/dist/{team-layer-zLj_QPQX.js → team-layer-VlQtvR5O.js} +127 -57
  26. package/dist/team-layer-VlQtvR5O.js.map +1 -0
  27. package/dist/team-tools-bridge-C51PpkUI.d.ts +31 -0
  28. package/dist/team-tools-bridge-C51PpkUI.d.ts.map +1 -0
  29. package/package.json +5 -1
  30. package/dist/devtools-layer-CcTmlikp.js.map +0 -1
  31. package/dist/devtools-styles-teRMfSzW.js.map +0 -1
  32. package/dist/handler-B3_PQB1q.js.map +0 -1
  33. package/dist/submit-CG4LXioK.js.map +0 -1
  34. package/dist/team-layer-zLj_QPQX.js.map +0 -1
@@ -1,411 +1,11 @@
1
- import { a as EDITORS, i as DEVTOOLS_STATUS_MARKER, n as DEVTOOLS_REQUEST_HEADER, o as PROXIED_API_PATHS, r as DEVTOOLS_ROUTE_BASE, t as DEVTOOLS_ENDPOINTS } from "./protocol-cJVVwnPe.js";
2
- import "./client-oFFfRxAM.js";
1
+ import { i as DEVTOOLS_STATUS_MARKER, r as DEVTOOLS_ROUTE_BASE, t as DEVTOOLS_ENDPOINTS } from "./protocol-cJVVwnPe.js";
2
+ import { _ as rejectUntrustedHost, b as jsonResponse, c as finishConnect, d as startConnect, g as rejectUntrustedCaller, n as boardAccess, r as resolveDevtoolsOptions, s as disconnect, t as proxyWithBoardCredential, v as errorResponse, y as failure } from "./reflet-proxy-BW6q9xds.js";
3
3
  import { SourceMap } from "node:module";
4
- import { createHash, randomBytes, randomUUID } from "node:crypto";
5
- import { mkdir, readFile, realpath, rename, rm, stat, writeFile } from "node:fs/promises";
6
- import { homedir } from "node:os";
4
+ import { readFile, realpath, stat } from "node:fs/promises";
7
5
  import { dirname, extname, isAbsolute, join, relative, resolve, sep } from "node:path";
8
- import { z } from "zod";
9
6
  import { execFile } from "node:child_process";
10
7
  import { existsSync, realpathSync } from "node:fs";
11
8
  import { fileURLToPath } from "node:url";
12
- //#region src/devtools/server/json-response.ts
13
- const JSON_RESPONSE_HEADERS = {
14
- "cache-control": "no-store",
15
- "content-type": "application/json; charset=utf-8",
16
- "x-content-type-options": "nosniff"
17
- };
18
- function jsonTextResponse(text, status) {
19
- return new Response(text, {
20
- headers: JSON_RESPONSE_HEADERS,
21
- status
22
- });
23
- }
24
- function jsonResponse(body, status = 200) {
25
- return jsonTextResponse(JSON.stringify(body), status);
26
- }
27
- function errorResponse(error, status) {
28
- return jsonResponse({ error }, status);
29
- }
30
- function failure(error, status) {
31
- return {
32
- error,
33
- ok: false,
34
- status
35
- };
36
- }
37
- //#endregion
38
- //#region src/devtools/server/request-guard.ts
39
- const LOOPBACK_HOSTNAMES = ["localhost", "[::1]"];
40
- const LOOPBACK_IPV4 = /^127(?:\.\d{1,3}){3}$/;
41
- const IPV4_MAPPED_PREFIX = "::ffff:";
42
- const TRUSTED_FETCH_SITES = ["same-origin", "none"];
43
- function hostnameOf(request) {
44
- const host = request.headers.get("host") ?? new URL(request.url).host;
45
- try {
46
- return new URL(`http://${host}`).hostname.toLowerCase();
47
- } catch {
48
- return null;
49
- }
50
- }
51
- function isLoopbackHostname(hostname) {
52
- return LOOPBACK_HOSTNAMES.includes(hostname) || hostname.endsWith(".localhost") || LOOPBACK_IPV4.test(hostname);
53
- }
54
- function isLoopbackAddress(address) {
55
- if (!address) return false;
56
- const ipv4 = address.startsWith(IPV4_MAPPED_PREFIX) ? address.slice(7) : address;
57
- return address === "::1" || LOOPBACK_IPV4.test(ipv4);
58
- }
59
- /**
60
- * The Host check stops DNS rebinding: a hostile domain resolved to 127.0.0.1
61
- * is same-origin with itself, so origin checks alone would let it through.
62
- * Host and header checks only stop browsers; keeping other machines out is the
63
- * job of a dev server bound to loopback.
64
- */
65
- function rejectUntrustedHost(request, allowedHosts) {
66
- const hostname = hostnameOf(request);
67
- if (!(hostname !== null && (isLoopbackHostname(hostname) || allowedHosts.includes(hostname)))) return errorResponse("Reflet devtools only answers on localhost. Add other dev hostnames to allowedHosts or REFLET_DEVTOOLS_HOSTS.", 403);
68
- return null;
69
- }
70
- function rejectUntrustedCaller(request, allowedHosts) {
71
- if (request.headers.get("x-reflet-devtools") !== "1") return errorResponse(`Reflet devtools requests must send the ${DEVTOOLS_REQUEST_HEADER} header.`, 403);
72
- const fetchSite = request.headers.get("sec-fetch-site");
73
- const hostname = hostnameOf(request);
74
- const mayOmitFetchSite = hostname !== null && allowedHosts.includes(hostname) && !isLoopbackHostname(hostname);
75
- if (!(fetchSite === null ? mayOmitFetchSite : TRUSTED_FETCH_SITES.includes(fetchSite))) return errorResponse("Reflet devtools only answers requests from the app's own origin.", 403);
76
- return null;
77
- }
78
- //#endregion
79
- //#region src/devtools/server/connect/connectable-host.ts
80
- /** Mirrors the backend redirect allowlist, which stays the authority; this only decides whether Connect is offered. */
81
- function isConnectableHost(hostname) {
82
- return isLoopbackHostname(hostname) || hostname.endsWith(".test");
83
- }
84
- //#endregion
85
- //#region src/devtools/server/connect/connection-store.ts
86
- const storedConnectionSchema = z.object({
87
- apiUrl: z.url(),
88
- organizationName: z.string(),
89
- token: z.string().startsWith("fb_dev_")
90
- });
91
- const connectionsDirectory = () => join(homedir(), ".reflet", "devtools");
92
- function connectionFile(root, apiUrl) {
93
- const key = createHash("sha256").update(JSON.stringify([root, apiUrl])).digest("hex");
94
- return join(connectionsDirectory(), `${key}.json`);
95
- }
96
- async function readConnection(root, apiUrl) {
97
- try {
98
- const connection = storedConnectionSchema.parse(JSON.parse(await readFile(connectionFile(root, apiUrl), "utf8")));
99
- return connection.apiUrl === apiUrl ? connection : null;
100
- } catch (error) {
101
- if (error instanceof Error && "code" in error && error.code === "ENOENT") return null;
102
- throw error;
103
- }
104
- }
105
- async function saveConnection(root, connection) {
106
- await mkdir(connectionsDirectory(), {
107
- mode: 448,
108
- recursive: true
109
- });
110
- const file = connectionFile(root, connection.apiUrl);
111
- const temporaryFile = `${file}.${randomUUID()}.tmp`;
112
- try {
113
- await writeFile(temporaryFile, `${JSON.stringify(connection)}\n`, {
114
- flag: "wx",
115
- mode: 384
116
- });
117
- await rename(temporaryFile, file);
118
- } finally {
119
- await rm(temporaryFile, { force: true });
120
- }
121
- }
122
- async function removeConnection(root, apiUrl, rejectedToken) {
123
- const removed = await readConnection(root, apiUrl);
124
- if (!removed || rejectedToken && removed.token !== rejectedToken) return null;
125
- await rm(connectionFile(root, apiUrl), { force: true });
126
- return removed;
127
- }
128
- //#endregion
129
- //#region src/devtools/server/connect/board-credential.ts
130
- async function boardCredential(settings) {
131
- if (settings.secretKey) return {
132
- isStored: false,
133
- token: settings.secretKey
134
- };
135
- const connection = await readConnection(settings.root, settings.apiUrl);
136
- return connection ? {
137
- isStored: true,
138
- token: connection.token
139
- } : null;
140
- }
141
- async function boardAccess(request, settings) {
142
- if (settings.secretKey) return { kind: "secretKey" };
143
- const connection = await readConnection(settings.root, settings.apiUrl);
144
- if (connection) return {
145
- kind: "connected",
146
- organizationName: connection.organizationName
147
- };
148
- return {
149
- canConnect: isConnectableHost(new URL(request.url).hostname),
150
- kind: "disconnected"
151
- };
152
- }
153
- //#endregion
154
- //#region src/devtools/server/connect/html-page.ts
155
- const HTML_ESCAPES = {
156
- "'": "'",
157
- "\"": """,
158
- "&": "&",
159
- "<": "&lt;",
160
- ">": "&gt;"
161
- };
162
- const HTML_SPECIAL_CHARACTERS = /[&<>"']/g;
163
- const escapeHtml = (text) => text.replace(HTML_SPECIAL_CHARACTERS, (character) => HTML_ESCAPES[character] ?? character);
164
- function htmlPage(title, message, status) {
165
- const safeTitle = escapeHtml(title);
166
- const body = `<!doctype html>
167
- <html lang="en">
168
- <head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><title>${safeTitle} · Reflet devtools</title></head>
169
- <body style="margin:0;min-height:100vh;display:flex;align-items:center;justify-content:center;font-family:system-ui,sans-serif;background:#fafafa;color:#18181b">
170
- <main style="max-width:28rem;padding:1.5rem">
171
- <h1 style="font-size:1.25rem;margin:0 0 .5rem">${safeTitle}</h1>
172
- <p style="margin:0;line-height:1.5;color:#52525b">${escapeHtml(message)}</p>
173
- </main>
174
- </body>
175
- </html>
176
- `;
177
- return new Response(body, {
178
- headers: {
179
- "cache-control": "no-store",
180
- "content-security-policy": "default-src 'none'; style-src 'unsafe-inline'",
181
- "content-type": "text/html; charset=utf-8",
182
- "referrer-policy": "no-referrer",
183
- "x-content-type-options": "nosniff"
184
- },
185
- status
186
- });
187
- }
188
- //#endregion
189
- //#region src/devtools/server/connect/pending-connects.ts
190
- const PENDING_CONNECT_TTL_MS = 6e5;
191
- const MAX_PENDING_CONNECTS = 10;
192
- function pendingConnects() {
193
- globalThis.refletDevtoolsPendingConnects ??= /* @__PURE__ */ new Map();
194
- return globalThis.refletDevtoolsPendingConnects;
195
- }
196
- function beginConnect(binding) {
197
- const state = randomBytes(32).toString("base64url");
198
- const codeVerifier = randomBytes(32).toString("base64url");
199
- const codeChallenge = createHash("sha256").update(codeVerifier).digest("base64url");
200
- const pending = pendingConnects();
201
- while (pending.size >= MAX_PENDING_CONNECTS) {
202
- const oldestState = pending.keys().next().value;
203
- if (oldestState === void 0) break;
204
- pending.delete(oldestState);
205
- }
206
- pending.set(state, {
207
- binding,
208
- codeVerifier,
209
- startedAt: Date.now()
210
- });
211
- return {
212
- codeChallenge,
213
- codeVerifier,
214
- state
215
- };
216
- }
217
- function takeConnect(state, binding) {
218
- const pending = pendingConnects();
219
- const connect = pending.get(state);
220
- if (!connect) return null;
221
- if (Date.now() - connect.startedAt > PENDING_CONNECT_TTL_MS) {
222
- pending.delete(state);
223
- return null;
224
- }
225
- if (!(connect.binding.root === binding.root && connect.binding.apiUrl === binding.apiUrl && connect.binding.redirectUri === binding.redirectUri)) return null;
226
- pending.delete(state);
227
- return { codeVerifier: connect.codeVerifier };
228
- }
229
- //#endregion
230
- //#region src/devtools/server/connect/connect-routes.ts
231
- const publicKeySchema = z.object({ publicKey: z.string().startsWith("fb_pub_").max(200) });
232
- const tokenExchangeSchema = z.object({
233
- organizationName: z.string(),
234
- token: z.string().startsWith("fb_dev_")
235
- });
236
- const upstreamErrorSchema = z.object({ error: z.string() });
237
- const redirectUriFor = (request) => `${new URL(request.url).origin}${DEVTOOLS_ROUTE_BASE}${DEVTOOLS_ENDPOINTS.connectCallback}`;
238
- async function readPublicKey(request) {
239
- const parsed = publicKeySchema.safeParse(await request.json().catch(() => null));
240
- return parsed.success ? parsed.data.publicKey : null;
241
- }
242
- async function readJson(response) {
243
- try {
244
- return await response.json();
245
- } catch {
246
- return null;
247
- }
248
- }
249
- async function startConnect(request, settings) {
250
- const publicKey = await readPublicKey(request);
251
- if (!publicKey) return errorResponse("Pass the widget's fb_pub_ public key to connect.", 400);
252
- if (settings.secretKey) return errorResponse("REFLET_SECRET_KEY is set, so this dev server already reaches your board.", 409);
253
- if (!isConnectableHost(new URL(request.url).hostname)) return errorResponse("Connecting from the browser works on localhost, *.localhost and *.test. Set REFLET_SECRET_KEY instead.", 403);
254
- const { codeChallenge, state } = beginConnect({
255
- apiUrl: settings.apiUrl,
256
- redirectUri: redirectUriFor(request),
257
- root: settings.root
258
- });
259
- const authorizeUrl = new URL("/auth/devtools", settings.appUrl);
260
- authorizeUrl.searchParams.set("public_key", publicKey);
261
- authorizeUrl.searchParams.set("redirect_uri", redirectUriFor(request));
262
- authorizeUrl.searchParams.set("state", state);
263
- authorizeUrl.searchParams.set("code_challenge", codeChallenge);
264
- return jsonResponse({ authorizeUrl: authorizeUrl.toString() });
265
- }
266
- async function finishConnect(request, settings) {
267
- const { searchParams } = new URL(request.url);
268
- const code = searchParams.get("code");
269
- const state = searchParams.get("state");
270
- const pending = code && state ? takeConnect(state, {
271
- apiUrl: settings.apiUrl,
272
- redirectUri: redirectUriFor(request),
273
- root: settings.root
274
- }) : null;
275
- if (!(code && pending)) return htmlPage("Link expired", "This connect link expired or was already used. Start again from the Reflet devtools.", 400);
276
- let upstream;
277
- try {
278
- upstream = await fetch(`${settings.apiUrl}/api/v1/devtools/token`, {
279
- body: JSON.stringify({
280
- code,
281
- codeVerifier: pending.codeVerifier,
282
- redirectUri: redirectUriFor(request)
283
- }),
284
- headers: {
285
- Accept: "application/json",
286
- "Content-Type": "application/json"
287
- },
288
- method: "POST",
289
- signal: AbortSignal.timeout(1e4)
290
- });
291
- } catch {
292
- return htmlPage("Connection failed", `Could not reach Reflet at ${settings.apiUrl}.`, 502);
293
- }
294
- const payload = await readJson(upstream);
295
- if (!upstream.ok) return htmlPage("Connection refused", upstreamErrorSchema.safeParse(payload).data?.error ?? "Reflet refused this connect code.", 400);
296
- const exchange = tokenExchangeSchema.safeParse(payload);
297
- if (!exchange.success) return htmlPage("Connection failed", "Reflet answered with an unexpected response.", 502);
298
- await saveConnection(settings.root, {
299
- apiUrl: settings.apiUrl,
300
- organizationName: exchange.data.organizationName,
301
- token: exchange.data.token
302
- });
303
- return htmlPage("Connected", `Connected to ${exchange.data.organizationName}. Go back to your app: the Reflet devtools are ready.`, 200);
304
- }
305
- async function revokeUpstream(apiUrl, token) {
306
- try {
307
- return (await fetch(`${apiUrl}/api/v1/devtools/revoke`, {
308
- headers: { Authorization: `Bearer ${token}` },
309
- method: "POST",
310
- signal: AbortSignal.timeout(1e4)
311
- })).ok;
312
- } catch {
313
- return false;
314
- }
315
- }
316
- async function disconnect(settings) {
317
- const removed = await removeConnection(settings.root, settings.apiUrl);
318
- return jsonResponse({ revoked: removed ? await revokeUpstream(removed.apiUrl, removed.token) : false });
319
- }
320
- //#endregion
321
- //#region src/devtools/server/options.ts
322
- const DEFAULT_APP_URL = "https://www.reflet.app";
323
- const DEFAULT_EDITOR = "vscode";
324
- const TRAILING_SLASHES$1 = /\/+$/;
325
- function isEditorId(value) {
326
- return EDITORS.some((editor) => editor === value);
327
- }
328
- function presentValue(value) {
329
- const trimmed = value?.trim();
330
- return trimmed ? trimmed : void 0;
331
- }
332
- function resolveEditor(optionEditor, envEditor) {
333
- if (isEditorId(optionEditor)) return optionEditor;
334
- const normalizedEnvEditor = presentValue(envEditor)?.toLowerCase();
335
- return isEditorId(normalizedEnvEditor) ? normalizedEnvEditor : DEFAULT_EDITOR;
336
- }
337
- function resolveDevtoolsOptions(options, env) {
338
- const apiUrl = presentValue(options.apiUrl) ?? presentValue(env.REFLET_API_URL) ?? "https://harmless-clam-802.convex.site";
339
- const appUrl = presentValue(options.appUrl) ?? presentValue(env.REFLET_APP_URL) ?? DEFAULT_APP_URL;
340
- return {
341
- allowedHosts: (options.allowedHosts ?? presentValue(env.REFLET_DEVTOOLS_HOSTS)?.split(",") ?? []).map((host) => host.trim().toLowerCase()).filter(Boolean),
342
- apiUrl: apiUrl.replace(TRAILING_SLASHES$1, ""),
343
- appUrl: appUrl.replace(TRAILING_SLASHES$1, ""),
344
- editor: resolveEditor(options.editor, env.REFLET_EDITOR),
345
- root: resolve(presentValue(options.root) ?? process.cwd()),
346
- secretKey: presentValue(options.secretKey) ?? presentValue(env.REFLET_SECRET_KEY) ?? null
347
- };
348
- }
349
- //#endregion
350
- //#region src/devtools/server/reflet-proxy.ts
351
- const CREATE_FEEDBACK_PATH = "/api/v1/feedback/create";
352
- const MISSING_BOARD_ACCESS_ERROR = "Connect to Reflet from the devtools panel footer, or set REFLET_SECRET_KEY in the dev server environment.";
353
- const BODYLESS_STATUSES = [
354
- 204,
355
- 205,
356
- 304
357
- ];
358
- function isProxiedApiPath(path) {
359
- return PROXIED_API_PATHS.some((proxiedPath) => proxiedPath === path);
360
- }
361
- function isJsonObject(value) {
362
- return typeof value === "object" && value !== null && !Array.isArray(value);
363
- }
364
- async function readForwardedBody(request, apiPath) {
365
- if (request.method !== "POST") return { body: void 0 };
366
- const text = await request.text();
367
- if (apiPath !== CREATE_FEEDBACK_PATH) return { body: text || void 0 };
368
- let payload;
369
- try {
370
- payload = JSON.parse(text);
371
- } catch {
372
- return { error: "Feedback must be a JSON object." };
373
- }
374
- if (!isJsonObject(payload)) return { error: "Feedback must be a JSON object." };
375
- return { body: JSON.stringify({
376
- ...payload,
377
- internal: true
378
- }) };
379
- }
380
- /** Forwards an allowlisted Reflet API call with the board token; devtools notes are always internal. */
381
- async function proxyToReflet(request, apiPath, target) {
382
- if (!isProxiedApiPath(apiPath)) return errorResponse(`Reflet devtools does not forward ${apiPath || "/"}.`, 404);
383
- if (!target.token) return errorResponse(MISSING_BOARD_ACCESS_ERROR, 503);
384
- if (request.method !== "GET" && request.method !== "POST") return errorResponse(`${request.method} is not supported here.`, 405);
385
- const forwarded = await readForwardedBody(request, apiPath);
386
- if ("error" in forwarded) return errorResponse(forwarded.error, 400);
387
- const { search } = new URL(request.url);
388
- let upstream;
389
- try {
390
- upstream = await fetch(`${target.apiUrl}${apiPath}${search}`, {
391
- body: forwarded.body,
392
- headers: {
393
- Accept: "application/json",
394
- Authorization: `Bearer ${target.token}`,
395
- "Content-Type": "application/json"
396
- },
397
- method: request.method,
398
- signal: AbortSignal.timeout(15e3)
399
- });
400
- } catch {
401
- return errorResponse(`Could not reach Reflet at ${target.apiUrl}.`, 502);
402
- }
403
- return new Response(BODYLESS_STATUSES.includes(upstream.status) ? null : upstream.body, {
404
- headers: JSON_RESPONSE_HEADERS,
405
- status: upstream.status
406
- });
407
- }
408
- //#endregion
409
9
  //#region src/devtools/server/source/code-search.ts
410
10
  const MIN_QUERY_LENGTH = 2;
411
11
  const MAX_QUERY_LENGTH = 200;
@@ -944,18 +544,12 @@ function readInteger(value, minimum) {
944
544
  const parsed = Number(value);
945
545
  return Number.isInteger(parsed) && parsed >= minimum ? parsed : null;
946
546
  }
947
- async function proxyWithBoardCredential(request, apiPath, settings) {
948
- const credential = await boardCredential(settings);
949
- const response = await proxyToReflet(request, apiPath, {
950
- apiUrl: settings.apiUrl,
951
- token: credential?.token ?? null
952
- });
953
- if (credential?.isStored && response.status === 401) await removeConnection(settings.root, settings.apiUrl, credential.token);
954
- return response;
955
- }
956
547
  async function routeRequest(request, subPath, settings, projectRoots) {
957
548
  const url = new URL(request.url);
958
- if (subPath?.startsWith(`${DEVTOOLS_ENDPOINTS.proxy}/`)) return await proxyWithBoardCredential(request, subPath.slice(DEVTOOLS_ENDPOINTS.proxy.length), settings);
549
+ if (subPath?.startsWith(`${DEVTOOLS_ENDPOINTS.proxy}/`)) {
550
+ const apiPath = subPath.slice(DEVTOOLS_ENDPOINTS.proxy.length);
551
+ return await proxyWithBoardCredential(request, apiPath, settings);
552
+ }
959
553
  const requiredMethod = subPath ? ENDPOINT_METHODS[subPath] : void 0;
960
554
  if (requiredMethod && request.method !== requiredMethod) return errorResponse(`${subPath} only answers ${requiredMethod} requests.`, 405);
961
555
  switch (subPath) {
@@ -992,11 +586,12 @@ function createDevtoolsHandler(options = {}, env = process.env) {
992
586
  try {
993
587
  return await routeRequest(request, subPath, settings, projectRoots);
994
588
  } catch (error) {
995
- return errorResponse(`Reflet devtools route failed: ${error instanceof Error ? error.message : String(error)}`, 500);
589
+ const reason = error instanceof Error ? error.message : String(error);
590
+ return errorResponse(`Reflet devtools route failed: ${reason}`, 500);
996
591
  }
997
592
  };
998
593
  }
999
594
  //#endregion
1000
- export { isLoopbackAddress as n, errorResponse as r, createDevtoolsHandler as t };
595
+ export { createDevtoolsHandler as t };
1001
596
 
1002
- //# sourceMappingURL=handler-B3_PQB1q.js.map
597
+ //# sourceMappingURL=handler-ndF3gZBx.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"handler-ndF3gZBx.js","names":["QUERY_OR_HASH"],"sources":["../src/devtools/server/source/code-search.ts","../src/devtools/server/source/project-roots.ts","../src/devtools/server/source/jsx-range.ts","../src/devtools/server/source/workspace-path.ts","../src/devtools/server/source/server-frame.ts","../src/devtools/server/source/source-file.ts","../src/devtools/server/handler.ts"],"sourcesContent":["import { type ExecFileException, execFile } from \"node:child_process\";\nimport { join } from \"node:path\";\nimport type { CodeMatch, CodeSearchResult } from \"../../protocol\";\nimport { type DevtoolsOutcome, failure } from \"../json-response\";\n\nconst MIN_QUERY_LENGTH = 2;\nconst MAX_QUERY_LENGTH = 200;\nconst MAX_MATCHES = 20;\nconst PREVIEW_LENGTH = 160;\nconst PREVIEW_LEAD = 40;\nconst SEARCH_TIMEOUT_MS = 4000;\nconst MAX_OUTPUT_BYTES = 1_048_576;\nconst NO_MATCHES_EXIT_CODE = 1;\nconst NOT_A_REPOSITORY_EXIT_CODE = 128;\nconst SEARCHED_FILES = [\n \"*.tsx\",\n \"*.jsx\",\n \"*.ts\",\n \"*.js\",\n \"*.vue\",\n \"*.svelte\",\n \"*.astro\",\n \"*.mdx\",\n];\nconst LINE_BREAK = /[\\r\\n]/;\n\ninterface GitGrepRun {\n error: ExecFileException | null;\n stdout: string;\n}\n\nfunction runGitGrep(workspaceRoot: string, query: string): Promise<GitGrepRun> {\n const args = [\n \"grep\",\n \"-n\",\n \"-z\",\n \"-I\",\n \"-F\",\n \"--no-color\",\n \"--untracked\",\n \"-e\",\n query,\n \"--\",\n ...SEARCHED_FILES,\n ];\n return new Promise((resolveRun) => {\n execFile(\n \"git\",\n args,\n {\n cwd: workspaceRoot,\n encoding: \"utf8\",\n maxBuffer: MAX_OUTPUT_BYTES,\n timeout: SEARCH_TIMEOUT_MS,\n },\n (error, stdout) => resolveRun({ error, stdout })\n );\n });\n}\n\nfunction previewLine(content: string, query: string): string {\n const text = content.trim();\n if (text.length <= PREVIEW_LENGTH) {\n return text;\n }\n const matchIndex = text.indexOf(query);\n const start = Math.max(\n 0,\n Math.min(matchIndex - PREVIEW_LEAD, text.length - PREVIEW_LENGTH)\n );\n const end = start + PREVIEW_LENGTH;\n const head = start > 0 ? \"…\" : \"\";\n const tail = end < text.length ? \"…\" : \"\";\n return `${head}${text.slice(start, end)}${tail}`;\n}\n\nfunction parseMatches(\n stdout: string,\n workspaceRoot: string,\n query: string\n): CodeMatch[] {\n const matches: CodeMatch[] = [];\n for (const record of stdout.split(\"\\n\")) {\n const [path, lineText, content] = record.split(\"\\0\");\n const line = Number(lineText);\n const isComplete =\n path !== undefined && content !== undefined && Number.isInteger(line);\n if (isComplete && !path.split(\"/\").includes(\"node_modules\")) {\n matches.push({\n absolutePath: join(workspaceRoot, path),\n line,\n path,\n preview: previewLine(content, query),\n });\n if (matches.length === MAX_MATCHES) {\n break;\n }\n }\n }\n return matches;\n}\n\nfunction describeGitFailure(error: ExecFileException): DevtoolsOutcome<never> {\n if (error.code === \"ENOENT\") {\n return failure(\"Code search needs git installed on the dev server.\", 503);\n }\n if (error.code === NOT_A_REPOSITORY_EXIT_CODE) {\n return failure(\n \"Code search needs the app to live in a git repository.\",\n 503\n );\n }\n if (error.killed) {\n return failure(\"Code search timed out.\", 504);\n }\n return failure(`Code search failed: ${error.message}`, 500);\n}\n\nexport async function searchCode(\n workspaceRoot: string,\n rawQuery: string\n): Promise<DevtoolsOutcome<CodeSearchResult>> {\n const query = rawQuery.trim();\n if (query.length < MIN_QUERY_LENGTH || query.length > MAX_QUERY_LENGTH) {\n return failure(\n `Search for ${MIN_QUERY_LENGTH} to ${MAX_QUERY_LENGTH} characters.`,\n 400\n );\n }\n if (LINE_BREAK.test(query)) {\n return failure(\"Search for a single line of text.\", 400);\n }\n\n const { error, stdout } = await runGitGrep(workspaceRoot, query);\n const matches = parseMatches(stdout, workspaceRoot, query);\n const hasNoMatches = error?.code === NO_MATCHES_EXIT_CODE;\n if (!error || hasNoMatches || matches.length > 0) {\n return { ok: true, value: { matches } };\n }\n return describeGitFailure(error);\n}\n","import { existsSync, realpathSync } from \"node:fs\";\nimport { dirname, join, resolve } from \"node:path\";\n\nexport interface ProjectRoots {\n /** The app directory, where the dev server runs. */\n root: string;\n /** Nearest ancestor of the app directory holding `.git`, else the app directory. */\n workspaceRoot: string;\n}\n\nexport function resolveProjectRoots(appRoot: string): ProjectRoots {\n const resolvedRoot = resolve(appRoot);\n const root = existsSync(resolvedRoot)\n ? realpathSync(resolvedRoot)\n : resolvedRoot;\n\n for (let directory = root; ; directory = dirname(directory)) {\n if (existsSync(join(directory, \".git\"))) {\n return { root, workspaceRoot: directory };\n }\n if (dirname(directory) === directory) {\n return { root, workspaceRoot: root };\n }\n }\n}\n","import type { LineRange } from \"../../protocol\";\n\nconst FAILED = -1;\nconst MAX_TAG_LOOKBACK = 2;\nconst TAG_NAME_START = /[A-Za-z_$]/;\nconst TAG_NAME_CHAR = /[\\w$.:-]/;\nconst IDENTIFIER_CHAR = /[\\w$]/;\nconst WHITESPACE = /\\s/;\nconst JSX_EXPRESSION_LEAD = /[([{,;=?:&|!>]/;\nconst JSX_KEYWORD_LEADS = [\"return\", \"yield\", \"default\", \"await\"];\n\ninterface OpeningTag {\n end: number;\n name: string;\n selfClosing: boolean;\n}\n\nfunction isTagStart(code: string, index: number): boolean {\n if (code.charAt(index) !== \"<\") {\n return false;\n }\n const next = code.charAt(index + 1);\n return next === \">\" || TAG_NAME_START.test(next);\n}\n\nfunction startsJsxInExpression(code: string, index: number): boolean {\n let cursor = index - 1;\n while (cursor >= 0 && WHITESPACE.test(code.charAt(cursor))) {\n cursor -= 1;\n }\n if (cursor < 0) {\n return true;\n }\n const previous = code.charAt(cursor);\n if (JSX_EXPRESSION_LEAD.test(previous)) {\n return true;\n }\n let wordStart = cursor;\n while (wordStart > 0 && IDENTIFIER_CHAR.test(code.charAt(wordStart - 1))) {\n wordStart -= 1;\n }\n return JSX_KEYWORD_LEADS.includes(code.slice(wordStart, cursor + 1));\n}\n\nfunction skipJsString(code: string, index: number): number {\n const quote = code.charAt(index);\n let cursor = index + 1;\n while (cursor < code.length) {\n const char = code.charAt(cursor);\n if (char === \"\\\\\") {\n cursor += 2;\n } else if (char === quote) {\n return cursor + 1;\n } else if (char === \"\\n\") {\n return index + 1;\n } else {\n cursor += 1;\n }\n }\n return index + 1;\n}\n\nfunction skipTemplate(code: string, index: number): number {\n let cursor = index + 1;\n while (cursor < code.length) {\n const char = code.charAt(cursor);\n if (char === \"\\\\\") {\n cursor += 2;\n } else if (char === \"`\") {\n return cursor + 1;\n } else if (char === \"$\" && code.charAt(cursor + 1) === \"{\") {\n cursor = skipJsExpression(code, cursor + 1);\n if (cursor === FAILED) {\n return FAILED;\n }\n } else {\n cursor += 1;\n }\n }\n return FAILED;\n}\n\nfunction skipJsToken(code: string, index: number): number {\n const char = code.charAt(index);\n const next = code.charAt(index + 1);\n if (char === '\"' || char === \"'\") {\n return skipJsString(code, index);\n }\n if (char === \"`\") {\n return skipTemplate(code, index);\n }\n if (char === \"/\" && next === \"/\") {\n const lineEnd = code.indexOf(\"\\n\", index);\n return lineEnd === -1 ? code.length : lineEnd;\n }\n if (char === \"/\" && next === \"*\") {\n const commentEnd = code.indexOf(\"*/\", index + 2);\n return commentEnd === -1 ? FAILED : commentEnd + 2;\n }\n if (isTagStart(code, index) && startsJsxInExpression(code, index)) {\n return skipElement(code, index);\n }\n return index + 1;\n}\n\nfunction skipJsExpression(code: string, index: number): number {\n let depth = 0;\n let cursor = index;\n while (cursor < code.length) {\n const char = code.charAt(cursor);\n if (char === \"{\" || char === \"}\") {\n depth += char === \"{\" ? 1 : -1;\n cursor += 1;\n if (depth === 0) {\n return cursor;\n }\n } else {\n cursor = skipJsToken(code, cursor);\n if (cursor === FAILED) {\n return FAILED;\n }\n }\n }\n return FAILED;\n}\n\nfunction skipTypeArguments(code: string, index: number): number {\n let depth = 0;\n for (let cursor = index; cursor < code.length; cursor += 1) {\n const char = code.charAt(cursor);\n if (char === \"<\") {\n depth += 1;\n } else if (char === \">\") {\n depth -= 1;\n if (depth === 0) {\n return cursor + 1;\n }\n }\n }\n return FAILED;\n}\n\nfunction skipAttributeToken(code: string, index: number): number {\n const char = code.charAt(index);\n if (char === \"{\") {\n return skipJsExpression(code, index);\n }\n if (char === '\"' || char === \"'\") {\n const closingQuote = code.indexOf(char, index + 1);\n return closingQuote === -1 ? FAILED : closingQuote + 1;\n }\n return index + 1;\n}\n\nfunction readTagName(code: string, index: number): number {\n let cursor = index;\n while (TAG_NAME_CHAR.test(code.charAt(cursor))) {\n cursor += 1;\n }\n return cursor;\n}\n\nfunction readOpeningTag(code: string, index: number): OpeningTag | null {\n const nameEnd = readTagName(code, index + 1);\n const name = code.slice(index + 1, nameEnd);\n let cursor =\n code.charAt(nameEnd) === \"<\" ? skipTypeArguments(code, nameEnd) : nameEnd;\n\n while (cursor !== FAILED && cursor < code.length) {\n const char = code.charAt(cursor);\n if (char === \">\") {\n return { end: cursor + 1, name, selfClosing: false };\n }\n if (char === \"/\" && code.charAt(cursor + 1) === \">\") {\n return { end: cursor + 2, name, selfClosing: true };\n }\n cursor = skipAttributeToken(code, cursor);\n }\n return null;\n}\n\nfunction readClosingTagEnd(code: string, index: number, name: string): number {\n let cursor = index + 2;\n while (WHITESPACE.test(code.charAt(cursor))) {\n cursor += 1;\n }\n const nameEnd = readTagName(code, cursor);\n if (code.slice(cursor, nameEnd) !== name) {\n return FAILED;\n }\n cursor = nameEnd;\n while (WHITESPACE.test(code.charAt(cursor))) {\n cursor += 1;\n }\n return code.charAt(cursor) === \">\" ? cursor + 1 : FAILED;\n}\n\nfunction skipChildren(code: string, index: number, name: string): number {\n let cursor = index;\n while (cursor !== FAILED && cursor < code.length) {\n const char = code.charAt(cursor);\n if (char === \"<\" && code.charAt(cursor + 1) === \"/\") {\n return readClosingTagEnd(code, cursor, name);\n }\n if (char === \"{\") {\n cursor = skipJsExpression(code, cursor);\n } else if (isTagStart(code, cursor)) {\n cursor = skipElement(code, cursor);\n } else {\n cursor += 1;\n }\n }\n return FAILED;\n}\n\nfunction skipElement(code: string, index: number): number {\n const tag = readOpeningTag(code, index);\n if (!tag) {\n return FAILED;\n }\n return tag.selfClosing ? tag.end : skipChildren(code, tag.end, tag.name);\n}\n\nfunction lineOffset(code: string, line: number): number {\n if (!Number.isInteger(line) || line < 1) {\n return FAILED;\n }\n let offset = 0;\n for (let current = 1; current < line; current += 1) {\n const newline = code.indexOf(\"\\n\", offset);\n if (newline === -1) {\n return FAILED;\n }\n offset = newline + 1;\n }\n return offset;\n}\n\nfunction findTagStart(\n code: string,\n lineStart: number,\n lineEnd: number,\n column: number\n): number {\n const origin = Math.min(lineStart + Math.max(column, 0), lineEnd);\n for (let back = 0; back <= MAX_TAG_LOOKBACK; back += 1) {\n const candidate = origin - back;\n if (candidate >= lineStart && isTagStart(code, candidate)) {\n return candidate;\n }\n }\n for (let candidate = origin + 1; candidate < lineEnd; candidate += 1) {\n if (isTagStart(code, candidate)) {\n return candidate;\n }\n }\n return FAILED;\n}\n\nfunction countNewlines(code: string, from: number, to: number): number {\n let count = 0;\n let newline = code.indexOf(\"\\n\", from);\n while (newline !== -1 && newline < to) {\n count += 1;\n newline = code.indexOf(\"\\n\", newline + 1);\n }\n return count;\n}\n\n/** Lines spanned by the JSX element at a 1-based line and 0-based column. */\nexport function jsxElementLines(\n code: string,\n line: number,\n column: number\n): LineRange | null {\n const lineStart = lineOffset(code, line);\n if (lineStart === FAILED) {\n return null;\n }\n const newline = code.indexOf(\"\\n\", lineStart);\n const lineEnd = newline === -1 ? code.length : newline;\n const tagStart = findTagStart(code, lineStart, lineEnd, column);\n if (tagStart === FAILED) {\n return null;\n }\n const elementEnd = skipElement(code, tagStart);\n if (elementEnd === FAILED) {\n return null;\n }\n return {\n end: line + countNewlines(code, tagStart, elementEnd - 1),\n start: line,\n };\n}\n","import { isAbsolute, relative, sep } from \"node:path\";\n\n/** Forward-slash path from the workspace root, or null when `realPath` escapes it (including another Windows drive). */\nexport function workspaceRelativePath(\n realPath: string,\n workspaceRoot: string\n): string | null {\n const relativePath = relative(workspaceRoot, realPath);\n const escapes =\n relativePath === \"\" ||\n relativePath === \"..\" ||\n relativePath.startsWith(`..${sep}`) ||\n isAbsolute(relativePath);\n return escapes ? null : relativePath.split(sep).join(\"/\");\n}\n","import { readFile, realpath, stat } from \"node:fs/promises\";\nimport { SourceMap } from \"node:module\";\nimport { dirname, join } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { type DevtoolsOutcome, failure } from \"../json-response\";\nimport type { SourceRequest } from \"./source-file\";\nimport { workspaceRelativePath } from \"./workspace-path\";\n\nconst SERVER_FRAME_PREFIX = /^(?:about|rsc):\\/\\/React\\/Server\\//;\nconst QUERY_OR_HASH = /[?#].*$/;\nconst SOURCE_MAPPING_URL = /\\/\\/# sourceMappingURL=(\\S+)\\s*$/;\nconst MAX_MAP_BYTES = 50 * 1_048_576;\n\nconst mapCache = new Map<string, { map: SourceMap; mtimeMs: number }>();\n\nexport function isServerFrame(fileName: string): boolean {\n return SERVER_FRAME_PREFIX.test(fileName);\n}\n\nasync function mapPathFor(chunkPath: string): Promise<string> {\n const code = await readFile(chunkPath, \"utf8\");\n const declared = SOURCE_MAPPING_URL.exec(code)?.[1];\n return declared\n ? join(dirname(chunkPath), decodeURIComponent(declared))\n : `${chunkPath}.map`;\n}\n\nasync function loadMap(mapPath: string): Promise<SourceMap> {\n const { mtimeMs, size } = await stat(mapPath);\n if (size > MAX_MAP_BYTES) {\n throw new Error(\"source map too large\");\n }\n const cached = mapCache.get(mapPath);\n if (cached?.mtimeMs === mtimeMs) {\n return cached.map;\n }\n const map = new SourceMap(JSON.parse(await readFile(mapPath, \"utf8\")));\n mapCache.set(mapPath, { map, mtimeMs });\n return map;\n}\n\n/**\n * Server Components stamp their elements with frames into the dev server's\n * own chunks (`about://React/Server/file:///…/.next/…`); the browser cannot\n * fetch those maps, so they are resolved here from disk.\n */\nexport async function mapServerFrame(\n request: SourceRequest,\n workspaceRoot: string\n): Promise<DevtoolsOutcome<SourceRequest>> {\n const unreadable = failure<SourceRequest>(\n \"This element comes from a Server Component whose source map could not be read.\",\n 404\n );\n if (request.line === null) {\n return unreadable;\n }\n try {\n const chunkUrl = request.fileName\n .replace(SERVER_FRAME_PREFIX, \"\")\n .replace(QUERY_OR_HASH, \"\");\n const chunkPath = await realpath(fileURLToPath(chunkUrl));\n if (workspaceRelativePath(chunkPath, workspaceRoot) === null) {\n return failure<SourceRequest>(\n \"Reflet devtools only reads files inside your repository.\",\n 403\n );\n }\n const map = await loadMap(await mapPathFor(chunkPath));\n const origin = map.findOrigin(request.line, (request.column ?? 0) + 1);\n if (!(\"fileName\" in origin)) {\n return unreadable;\n }\n return {\n ok: true,\n value: {\n column: origin.columnNumber - 1,\n fileName: origin.fileName,\n line: origin.lineNumber,\n },\n };\n } catch {\n return unreadable;\n }\n}\n","import { readFile, realpath, stat } from \"node:fs/promises\";\nimport { extname, isAbsolute, join } from \"node:path\";\nimport type { SourceFile } from \"../../protocol\";\nimport { type DevtoolsOutcome, failure } from \"../json-response\";\nimport { jsxElementLines } from \"./jsx-range\";\nimport type { ProjectRoots } from \"./project-roots\";\nimport { isServerFrame, mapServerFrame } from \"./server-frame\";\nimport { workspaceRelativePath } from \"./workspace-path\";\n\nconst MAX_SOURCE_BYTES = 1_048_576;\nconst MAX_FILE_NAME_LENGTH = 2048;\nconst MAX_NORMALIZE_PASSES = 8;\nconst SOURCE_EXTENSIONS = [\n \".astro\",\n \".cjs\",\n \".js\",\n \".jsx\",\n \".mdx\",\n \".mjs\",\n \".svelte\",\n \".ts\",\n \".tsx\",\n \".vue\",\n];\nconst EXTENSION_REFUSAL = `Reflet devtools only reads source files (${SOURCE_EXTENSIONS.join(\", \")}).`;\nconst OUTSIDE_WORKSPACE_REFUSAL =\n \"Reflet devtools only reads files inside your repository.\";\nconst NODE_MODULES_REFUSAL =\n \"Reflet devtools does not read files inside node_modules.\";\nconst QUERY_OR_HASH = /[?#].*$/;\nconst TURBOPACK_MODULE_SUFFIX = /(?: \\[[\\w-]+\\])+(?: \\([\\w-]+\\))*$/;\nconst URL_PREFIXES = [/^file:\\/\\//, /^https?:\\/\\/[^/]*/];\nconst SLASH_BEFORE_DRIVE = /^\\/(?=[a-z]:[\\\\/])/i;\nconst BUNDLER_PREFIXES = [\n /^rsc:\\/\\/React\\/[^/]+\\//,\n /^webpack-internal:\\/{2,}/,\n /^webpack:\\/\\/[^/]*\\//,\n /^turbopack:\\/{2,}/,\n /^\\[project\\]\\//,\n /^\\(app-pages-browser\\)\\//,\n /^\\([\\w-]+\\)\\/(?=\\.\\/)/,\n /^\\/@fs(?=\\/)/,\n /^\\.\\//,\n];\n\ninterface LocatedFile {\n realPath: string;\n relativePath: string;\n size: number;\n}\n\ntype CandidateCheck =\n | ({ kind: \"found\" } & LocatedFile)\n | { kind: \"missing\" }\n | { kind: \"refused\"; reason: string };\n\nexport interface SourceRequest {\n column: number | null;\n fileName: string;\n line: number | null;\n}\n\nfunction decodePath(path: string): string {\n try {\n return decodeURIComponent(path);\n } catch {\n return path;\n }\n}\n\nfunction stripFirstPrefix(fileName: string, prefixes: RegExp[]): string {\n for (const prefix of prefixes) {\n if (prefix.test(fileName)) {\n return fileName.replace(prefix, \"\");\n }\n }\n return fileName;\n}\n\n/** Turns a file name from a React debug stack into a filesystem path. */\nexport function normalizeSourceFileName(rawFileName: string): string {\n let fileName = rawFileName\n .trim()\n .replace(QUERY_OR_HASH, \"\")\n .replace(TURBOPACK_MODULE_SUFFIX, \"\");\n let isUrlEncoded = false;\n\n for (let pass = 0; pass < MAX_NORMALIZE_PASSES; pass += 1) {\n const withoutUrl = stripFirstPrefix(fileName, URL_PREFIXES);\n isUrlEncoded ||= withoutUrl !== fileName;\n const stripped = stripFirstPrefix(withoutUrl, BUNDLER_PREFIXES);\n if (stripped === fileName) {\n break;\n }\n fileName = stripped;\n }\n\n const path = isUrlEncoded ? decodePath(fileName) : fileName;\n return path.replace(SLASH_BEFORE_DRIVE, \"\");\n}\n\nfunction candidatePaths(fileName: string, roots: ProjectRoots): string[] {\n const candidates = [\n join(roots.root, fileName),\n join(roots.workspaceRoot, fileName),\n ];\n if (isAbsolute(fileName)) {\n candidates.unshift(fileName);\n }\n return [...new Set(candidates)];\n}\n\nfunction isSourceExtension(path: string): boolean {\n return SOURCE_EXTENSIONS.includes(extname(path).toLowerCase());\n}\n\nasync function checkCandidate(\n candidate: string,\n workspaceRoot: string\n): Promise<CandidateCheck> {\n let realPath: string;\n let size: number;\n try {\n realPath = await realpath(candidate);\n const stats = await stat(realPath);\n if (!stats.isFile()) {\n return { kind: \"missing\" };\n }\n size = stats.size;\n } catch {\n return { kind: \"missing\" };\n }\n\n const relativePath = workspaceRelativePath(realPath, workspaceRoot);\n if (relativePath === null) {\n return { kind: \"refused\", reason: OUTSIDE_WORKSPACE_REFUSAL };\n }\n if (relativePath.split(\"/\").includes(\"node_modules\")) {\n return { kind: \"refused\", reason: NODE_MODULES_REFUSAL };\n }\n if (!isSourceExtension(realPath)) {\n return { kind: \"refused\", reason: EXTENSION_REFUSAL };\n }\n return { kind: \"found\", realPath, relativePath, size };\n}\n\nasync function locateSourceFile(\n fileName: string,\n roots: ProjectRoots\n): Promise<DevtoolsOutcome<LocatedFile>> {\n let refusal: string | null = null;\n for (const candidate of candidatePaths(fileName, roots)) {\n const check = await checkCandidate(candidate, roots.workspaceRoot);\n if (check.kind === \"found\") {\n return { ok: true, value: check };\n }\n if (check.kind === \"refused\") {\n refusal ??= check.reason;\n }\n }\n if (refusal) {\n return failure(refusal, 403);\n }\n return failure(`Could not find ${fileName} in ${roots.workspaceRoot}.`, 404);\n}\n\n/** Server-map columns can point past the line; the element then starts at its first tag. */\nfunction columnWithinLine(code: string, line: number, column: number | null) {\n const text = code.split(\"\\n\")[line - 1] ?? \"\";\n return column !== null && column < text.length ? column : null;\n}\n\nexport async function readSourceFile(\n frame: SourceRequest,\n roots: ProjectRoots\n): Promise<DevtoolsOutcome<SourceFile>> {\n if (\n frame.fileName.length > MAX_FILE_NAME_LENGTH ||\n frame.fileName.includes(\"\\0\")\n ) {\n return failure(\"That source file name is not a valid path.\", 400);\n }\n const mapped = isServerFrame(frame.fileName)\n ? await mapServerFrame(frame, roots.workspaceRoot)\n : { ok: true as const, value: frame };\n if (!mapped.ok) {\n return mapped;\n }\n const request = mapped.value;\n const fileName = normalizeSourceFileName(request.fileName);\n if (!fileName) {\n return failure(\"A source file name is required.\", 400);\n }\n if (!isSourceExtension(fileName)) {\n return failure(EXTENSION_REFUSAL, 403);\n }\n\n const located = await locateSourceFile(fileName, roots);\n if (!located.ok) {\n return located;\n }\n const { realPath, relativePath, size } = located.value;\n if (size > MAX_SOURCE_BYTES) {\n return failure(\n `${relativePath} is larger than 1 MB, too big to preview.`,\n 413\n );\n }\n\n const code = await readFile(realPath, \"utf8\");\n const column =\n request.line === null\n ? request.column\n : columnWithinLine(code, request.line, request.column);\n const elementLines =\n request.line === null\n ? null\n : jsxElementLines(code, request.line, column ?? 0);\n\n return {\n ok: true,\n value: {\n absolutePath: realPath,\n code,\n column,\n elementLines,\n line: request.line,\n path: relativePath,\n },\n };\n}\n","import {\n DEVTOOLS_ENDPOINTS,\n DEVTOOLS_ROUTE_BASE,\n DEVTOOLS_STATUS_MARKER,\n type DevtoolsStatus,\n} from \"../protocol\";\nimport { boardAccess } from \"./connect/board-credential\";\nimport {\n disconnect,\n finishConnect,\n startConnect,\n} from \"./connect/connect-routes\";\nimport {\n type DevtoolsOutcome,\n errorResponse,\n jsonResponse,\n} from \"./json-response\";\nimport {\n type DevtoolsEnv,\n type DevtoolsServerOptions,\n type ResolvedDevtoolsOptions,\n resolveDevtoolsOptions,\n} from \"./options\";\nimport { proxyWithBoardCredential } from \"./reflet-proxy\";\nimport { rejectUntrustedCaller, rejectUntrustedHost } from \"./request-guard\";\nimport { searchCode } from \"./source/code-search\";\nimport { type ProjectRoots, resolveProjectRoots } from \"./source/project-roots\";\nimport { readSourceFile } from \"./source/source-file\";\n\nexport type DevtoolsHandler = (request: Request) => Promise<Response>;\n\nconst TRAILING_SLASHES = /\\/+$/;\n\nconst ENDPOINT_METHODS: Record<string, \"GET\" | \"POST\"> = {\n [DEVTOOLS_ENDPOINTS.connectCallback]: \"GET\",\n [DEVTOOLS_ENDPOINTS.connectStart]: \"POST\",\n [DEVTOOLS_ENDPOINTS.disconnect]: \"POST\",\n [DEVTOOLS_ENDPOINTS.search]: \"GET\",\n [DEVTOOLS_ENDPOINTS.source]: \"GET\",\n [DEVTOOLS_ENDPOINTS.status]: \"GET\",\n};\n\nfunction outcomeResponse<T>(outcome: DevtoolsOutcome<T>): Response {\n return outcome.ok\n ? jsonResponse(outcome.value)\n : errorResponse(outcome.error, outcome.status);\n}\n\nfunction routeSubPath(pathname: string): string | null {\n const baseIndex = pathname.indexOf(DEVTOOLS_ROUTE_BASE);\n if (baseIndex === -1) {\n return null;\n }\n return pathname\n .slice(baseIndex + DEVTOOLS_ROUTE_BASE.length)\n .replace(TRAILING_SLASHES, \"\");\n}\n\nfunction readInteger(value: string | null, minimum: number): number | null {\n if (value === null || value === \"\") {\n return null;\n }\n const parsed = Number(value);\n return Number.isInteger(parsed) && parsed >= minimum ? parsed : null;\n}\n\nasync function routeRequest(\n request: Request,\n subPath: string | null,\n settings: ResolvedDevtoolsOptions,\n projectRoots: () => ProjectRoots\n): Promise<Response> {\n const url = new URL(request.url);\n\n if (subPath?.startsWith(`${DEVTOOLS_ENDPOINTS.proxy}/`)) {\n const apiPath = subPath.slice(DEVTOOLS_ENDPOINTS.proxy.length);\n return await proxyWithBoardCredential(request, apiPath, settings);\n }\n\n const requiredMethod = subPath ? ENDPOINT_METHODS[subPath] : undefined;\n if (requiredMethod && request.method !== requiredMethod) {\n return errorResponse(\n `${subPath} only answers ${requiredMethod} requests.`,\n 405\n );\n }\n\n switch (subPath) {\n case DEVTOOLS_ENDPOINTS.status:\n return jsonResponse({\n board: await boardAccess(request, settings),\n editor: settings.editor,\n marker: DEVTOOLS_STATUS_MARKER,\n } satisfies DevtoolsStatus);\n case DEVTOOLS_ENDPOINTS.connectStart:\n return await startConnect(request, settings);\n case DEVTOOLS_ENDPOINTS.connectCallback:\n return await finishConnect(request, settings);\n case DEVTOOLS_ENDPOINTS.disconnect:\n return await disconnect(settings);\n case DEVTOOLS_ENDPOINTS.source:\n return outcomeResponse(\n await readSourceFile(\n {\n column: readInteger(url.searchParams.get(\"column\"), 0),\n fileName: url.searchParams.get(\"file\") ?? \"\",\n line: readInteger(url.searchParams.get(\"line\"), 1),\n },\n projectRoots()\n )\n );\n case DEVTOOLS_ENDPOINTS.search:\n return outcomeResponse(\n await searchCode(\n projectRoots().workspaceRoot,\n url.searchParams.get(\"q\") ?? \"\"\n )\n );\n default:\n return errorResponse(\n `No Reflet devtools endpoint at ${url.pathname}.`,\n 404\n );\n }\n}\n\nexport function createDevtoolsHandler(\n options: DevtoolsServerOptions = {},\n env: DevtoolsEnv = process.env\n): DevtoolsHandler {\n const settings = resolveDevtoolsOptions(options, env);\n let roots: ProjectRoots | null = null;\n const projectRoots = (): ProjectRoots => {\n roots ??= resolveProjectRoots(settings.root);\n return roots;\n };\n\n return async (request) => {\n const hostRejection = rejectUntrustedHost(request, settings.allowedHosts);\n if (hostRejection) {\n return hostRejection;\n }\n const subPath = routeSubPath(new URL(request.url).pathname);\n const isConnectCallback =\n request.method === \"GET\" &&\n subPath === DEVTOOLS_ENDPOINTS.connectCallback;\n const callerRejection = isConnectCallback\n ? null\n : rejectUntrustedCaller(request, settings.allowedHosts);\n if (callerRejection) {\n return callerRejection;\n }\n try {\n return await routeRequest(request, subPath, settings, projectRoots);\n } catch (error) {\n const reason = error instanceof Error ? error.message : String(error);\n return errorResponse(`Reflet devtools route failed: ${reason}`, 500);\n }\n };\n}\n"],"mappings":"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"}