@morlay/dsh-desktop-host 0.2.4 → 0.2.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.
package/lib/index.js DELETED
@@ -1,390 +0,0 @@
1
- import { DESKTOP_PIPE_CHUNK_BYTES, DESKTOP_STREAM_PATH, DesktopHostRequestDecoder, DesktopStreamBodyDecoder, encodeDesktopResponseData, encodeDesktopResponseEnd, encodeDesktopResponseError, encodeDesktopResponseStart, isDesktopHostCommand } from "./wire.js";
2
- import { once } from "node:events";
3
- import { createReadStream, createWriteStream } from "node:fs";
4
- import { join } from "node:path";
5
- import { fileURLToPath } from "node:url";
6
- import { inspect } from "node:util";
7
- import { loadLayeredEnv, loadProfileDirectory, reportSkippedBundles } from "@deepseek-ai/dsh-app-boot";
8
- import { runProfile } from "@deepseek-ai/dsh/profile-boot";
9
- //#region src/transport.ts
10
- const DESKTOP_TRANSPORT_SCRIPT = `globalThis.__DSH_TRANSPORT__={
11
- _desktop:true,
12
- ownsHost:true,
13
- async *openStream(endpoint,payload,signal,uplink){
14
- console.info('[dsh-desktop] openStream',endpoint)
15
- const carrier=globalThis.__DSH_DESKTOP_STREAM__
16
- if(carrier===undefined){
17
- console.error('[dsh-desktop] stream carrier missing for',endpoint)
18
- throw new Error('desktop stream carrier is unavailable')
19
- }
20
- const queue=[]
21
- let buffer=''
22
- let ended=false
23
- let failure
24
- let wake
25
- const notify=()=>{const pending=wake;wake=undefined;if(pending)pending()}
26
- const append=(text)=>{
27
- buffer+=text
28
- let at
29
- while((at=buffer.indexOf('\\n'))!==-1){
30
- const line=buffer.slice(0,at)
31
- buffer=buffer.slice(at+1)
32
- if(line!=='')queue.push(JSON.parse(line))
33
- }
34
- notify()
35
- }
36
- const stream=carrier.open(endpoint,payload,{
37
- chunk(text){append(text)},
38
- end(){
39
- if(buffer!==''){queue.push(JSON.parse(buffer));buffer=''}
40
- ended=true
41
- notify()
42
- },
43
- fail(message){failure=new Error(message);ended=true;notify()},
44
- })
45
- // 取消必须结束迭代:不叫醒它,等在这一句上的消费方收不到 end 帧,dispose(await iterator.return)
46
- // 也永不落定——窗口重建会卡在这里。
47
- const onAbort=()=>{stream.cancel();ended=true;notify()}
48
- signal.addEventListener('abort',onAbort,{once:true})
49
- if(signal.aborted)onAbort()
50
- // 上行(客户端 → 宿主)是逻辑流的输入项:逐条交给主进程,收尾时结束请求体;
51
- // 没有 uplink 的流(如 $events)立刻结束请求体,宿主侧不用等一个永不来的 end。
52
- if(uplink===undefined)stream.end()
53
- else void (async()=>{
54
- try{
55
- for await(const item of uplink){
56
- // 取消后不再上行:宿主那边这条逻辑流已经作废。
57
- if(signal.aborted)break
58
- stream.send(item)
59
- }
60
- }catch(error){
61
- console.error('[dsh-desktop] stream uplink failed for',endpoint,error)
62
- }finally{
63
- stream.end()
64
- }
65
- })()
66
- try{
67
- for(;;){
68
- while(queue.length>0)yield queue.shift()
69
- if(failure!==undefined)throw failure
70
- if(ended)return
71
- await new Promise(resolve=>{wake=resolve})
72
- }
73
- }finally{
74
- signal.removeEventListener('abort',onAbort)
75
- }
76
- }
77
- }
78
- console.info('[dsh-desktop] transport installed',Object.keys(globalThis.__DSH_TRANSPORT__))`;
79
- function takeOverDesktopAuthentication(ctx) {
80
- const connection = ctx.get("connection");
81
- if (connection === void 0) throw new Error("dsh desktop: the composition did not provide the connection service");
82
- if (typeof connection.requestRejection !== "function" || typeof connection.authorizeIndex !== "function") throw new Error("dsh desktop: the connection service no longer exposes requestRejection / authorizeIndex; desktop authentication takeover needs an update");
83
- connection.requestRejection = () => void 0;
84
- connection.authorizeIndex = () => true;
85
- }
86
- function isRecord(value) {
87
- return typeof value === "object" && value !== null && !Array.isArray(value);
88
- }
89
- async function* readStreamFrames(request) {
90
- const decoder = new DesktopStreamBodyDecoder();
91
- for await (const chunk of request) for (const frame of decoder.push(chunk)) yield frame;
92
- for (const frame of decoder.finish()) yield frame;
93
- }
94
- function parseStreamOpening(value, subject) {
95
- if (!isRecord(value) || typeof value.endpoint !== "string") throw new Error(`dsh desktop: invalid stream request ${subject}`);
96
- return {
97
- endpoint: value.endpoint,
98
- payload: value.payload ?? { args: {} }
99
- };
100
- }
101
- function reportStreamFailure(subject, detail) {
102
- console.error(`[dsh-desktop] ${subject}: ${errorText(detail)}`);
103
- }
104
- function errorText(value) {
105
- return value instanceof Error ? value.stack ?? value.message : String(value);
106
- }
107
- function streamHandler(ctx) {
108
- return async (request, response) => {
109
- if (request.method !== "POST") {
110
- response.writeHead(405);
111
- response.end();
112
- return;
113
- }
114
- const gateway = ctx.get("typertGateway");
115
- if (gateway === void 0) {
116
- reportStreamFailure("stream failed", "typertGateway service is missing");
117
- response.writeHead(503);
118
- response.end("gateway unavailable");
119
- return;
120
- }
121
- const endpointFromQuery = new URL(request.url ?? "/", "http://127.0.0.1").searchParams.get("endpoint");
122
- const frames = readStreamFrames(request);
123
- let opening;
124
- try {
125
- const first = await frames.next();
126
- opening = first.done ? parseStreamOpening(endpointFromQuery === null ? void 0 : { endpoint: endpointFromQuery }, String(request.url)) : parseStreamOpening(first.value, String(request.url));
127
- } catch (error) {
128
- reportStreamFailure("stream body is not a valid opener", error);
129
- response.writeHead(400);
130
- response.end("invalid stream request");
131
- return;
132
- }
133
- const endpoint = opening.endpoint;
134
- const abort = new AbortController();
135
- const cancel = () => {
136
- abort.abort();
137
- };
138
- request.once("aborted", cancel);
139
- response.once("close", cancel);
140
- try {
141
- console.error(`[dsh-desktop] stream opening ${endpoint}`);
142
- const values = await gateway.wireStream.open(endpoint, opening.payload, frames, void 0, abort.signal);
143
- console.error(`[dsh-desktop] stream opened ${endpoint}`);
144
- response.writeHead(200, {
145
- "content-type": "application/x-ndjson",
146
- "cache-control": "no-store"
147
- });
148
- for await (const value of values) response.write(`${JSON.stringify(value)}\n`);
149
- response.end();
150
- } catch (error) {
151
- reportStreamFailure(`stream ${endpoint} failed`, error);
152
- if (response.headersSent) {
153
- response.destroy(error instanceof Error ? error : new Error(String(error)));
154
- return;
155
- }
156
- response.writeHead(502);
157
- response.end(error instanceof Error ? error.message : String(error));
158
- }
159
- };
160
- }
161
- function installDesktopTransport(ctx) {
162
- ctx.on("webserver/index-inject", (table) => {
163
- table.push({
164
- kind: "script",
165
- placement: "head",
166
- text: DESKTOP_TRANSPORT_SCRIPT
167
- });
168
- });
169
- ctx.effect(() => ctx.webServer.register({
170
- kind: "exact",
171
- path: DESKTOP_STREAM_PATH,
172
- handler: streamHandler(ctx)
173
- }), "dsh-desktop: remote stream route");
174
- }
175
- //#endregion
176
- //#region src/index.ts
177
- const DESKTOP_PATCH = fileURLToPath(new URL("../config/desktop.cordis.patch.yml", import.meta.url));
178
- function errorMessage(error) {
179
- return error instanceof Error ? error.message : String(error);
180
- }
181
- const MAX_FATAL_DIAGNOSTIC_CHARS = 65536;
182
- function fatalEvent(error) {
183
- return {
184
- type: "fatal",
185
- message: errorMessage(error),
186
- diagnostic: inspect(error, {
187
- depth: 4,
188
- maxArrayLength: 50
189
- }).slice(0, MAX_FATAL_DIAGNOSTIC_CHARS)
190
- };
191
- }
192
- async function main() {
193
- const runtimeDir = process.argv[2];
194
- const projectDir = process.argv[3];
195
- if (runtimeDir === void 0 || projectDir === void 0 || process.send === void 0) throw new Error("dsh desktop: expected runtime and profile directories, byte pipes, and a Node IPC channel");
196
- const pnpmEntry = process.argv[4];
197
- const nodeBin = process.argv[5];
198
- const requestPipe = createReadStream("", {
199
- fd: 3,
200
- autoClose: false
201
- });
202
- const responsePipe = createWriteStream("", {
203
- fd: 4,
204
- autoClose: false
205
- });
206
- let responseTail = Promise.resolve();
207
- let closing = false;
208
- responsePipe.on("error", (error) => {
209
- if (!closing) {
210
- console.error(error);
211
- process.exitCode = 1;
212
- }
213
- });
214
- const writeResponse = (frame) => {
215
- if (closing || responsePipe.destroyed) return Promise.resolve();
216
- const write = responseTail.then(async () => {
217
- if (closing || responsePipe.destroyed) return;
218
- if (!responsePipe.write(frame)) await once(responsePipe, "drain");
219
- });
220
- responseTail = write.catch(() => void 0);
221
- return write;
222
- };
223
- const send = (event) => {
224
- if (process.send === void 0 || !process.connected) return;
225
- try {
226
- process.send(event);
227
- } catch (error) {
228
- if (error.code !== "ERR_IPC_CHANNEL_CLOSED") throw error;
229
- }
230
- };
231
- const installAnchor = join(runtimeDir, "node_modules", "@deepseek-ai", "dsh", "package.json");
232
- const profile = loadProfileDirectory("dsh", projectDir, installAnchor);
233
- reportSkippedBundles("dsh", profile);
234
- const application = runProfile({
235
- environment: loadLayeredEnv("dsh"),
236
- profile: "desktop",
237
- resolvedProfile: {
238
- profile,
239
- installAnchor
240
- },
241
- patchFiles: [DESKTOP_PATCH],
242
- args: ["--no-open"],
243
- ...pnpmEntry === void 0 ? {} : { packageManager: {
244
- command: process.execPath,
245
- args: ["--expose-internals", pnpmEntry],
246
- env: {
247
- ELECTRON_RUN_AS_NODE: "1",
248
- DSH_DESKTOP_NODE_EXECUTABLE: process.execPath,
249
- PATH: `${nodeBin ?? ""}${nodeBin === void 0 ? "" : ":"}${process.env.PATH ?? ""}`
250
- }
251
- } }
252
- });
253
- const { ctx } = await application;
254
- takeOverDesktopAuthentication(ctx);
255
- installDesktopTransport(ctx);
256
- const webServer = ctx.get("webServer");
257
- const pending = /* @__PURE__ */ new Map();
258
- const runs = /* @__PURE__ */ new Set();
259
- const decoder = new DesktopHostRequestDecoder();
260
- let lastStreamId = 0;
261
- let stopping;
262
- const dispatch = async (streamId, request, entry) => {
263
- try {
264
- const response = await webServer.dispatch(request);
265
- await writeResponse(encodeDesktopResponseStart(streamId, {
266
- status: response.status,
267
- headers: [...response.headers.entries()],
268
- hasBody: response.body !== null
269
- }));
270
- if (response.body !== null) for await (const chunk of response.body) {
271
- const bytes = Buffer.from(chunk);
272
- for (let offset = 0; offset < bytes.byteLength; offset += DESKTOP_PIPE_CHUNK_BYTES) {
273
- if (!pending.has(streamId)) return;
274
- await writeResponse(encodeDesktopResponseData(streamId, bytes.subarray(offset, offset + DESKTOP_PIPE_CHUNK_BYTES)));
275
- }
276
- }
277
- await writeResponse(encodeDesktopResponseEnd(streamId));
278
- } catch (error) {
279
- if (!entry.abort.signal.aborted) await writeResponse(encodeDesktopResponseError(streamId, errorMessage(error))).catch(() => {});
280
- } finally {
281
- pending.delete(streamId);
282
- }
283
- };
284
- const handleFrame = (frame) => {
285
- if (frame.type === "start") {
286
- if (frame.streamId <= lastStreamId) throw new Error(`dsh desktop: request stream ${String(frame.streamId)} is not in order`);
287
- lastStreamId = frame.streamId;
288
- const entry = { abort: new AbortController() };
289
- const streamId = frame.streamId;
290
- const body = frame.hasBody ? new ReadableStream({
291
- start: (controller) => {
292
- entry.body = controller;
293
- },
294
- cancel: () => {
295
- entry.abort.abort();
296
- }
297
- }) : void 0;
298
- pending.set(streamId, entry);
299
- const request = new Request(frame.url, {
300
- method: frame.method,
301
- headers: frame.headers,
302
- signal: entry.abort.signal,
303
- ...body === void 0 ? {} : {
304
- body,
305
- duplex: "half"
306
- }
307
- });
308
- const run = dispatch(streamId, request, entry).catch((error) => {
309
- ctx.logger.warn(error instanceof Error ? error : new Error(errorMessage(error)));
310
- });
311
- runs.add(run);
312
- run.finally(() => runs.delete(run));
313
- return;
314
- }
315
- const entry = pending.get(frame.streamId);
316
- if (entry === void 0) return;
317
- switch (frame.type) {
318
- case "data":
319
- entry.body?.enqueue(frame.data);
320
- return;
321
- case "end":
322
- entry.body?.close();
323
- return;
324
- case "cancel":
325
- entry.abort.abort();
326
- return;
327
- default: return;
328
- }
329
- };
330
- const stop = (exitCode = 0) => {
331
- stopping ??= (async () => {
332
- closing = true;
333
- requestPipe.pause();
334
- requestPipe.destroy();
335
- for (const entry of pending.values()) entry.abort.abort();
336
- pending.clear();
337
- await (await application.catch(() => void 0))?.shutdown.shutdown(exitCode);
338
- await Promise.allSettled(runs);
339
- await responseTail.catch(() => void 0);
340
- if (!responsePipe.destroyed) responsePipe.destroy();
341
- if (process.connected) process.disconnect?.();
342
- process.exitCode = exitCode;
343
- })();
344
- return stopping;
345
- };
346
- requestPipe.on("data", (chunk) => {
347
- let frames;
348
- try {
349
- frames = decoder.push(chunk);
350
- for (const frame of frames) handleFrame(frame);
351
- } catch (error) {
352
- send(fatalEvent(error));
353
- stop(1);
354
- }
355
- });
356
- requestPipe.once("error", (error) => {
357
- send(fatalEvent(error));
358
- stop(1);
359
- });
360
- requestPipe.once("end", () => {
361
- try {
362
- decoder.finish();
363
- } catch (error) {
364
- send(fatalEvent(error));
365
- stop(1);
366
- }
367
- });
368
- process.on("message", (message) => {
369
- if (isDesktopHostCommand(message)) stop();
370
- });
371
- process.once("disconnect", () => {
372
- stop();
373
- });
374
- const gateway = ctx.get("typertGateway");
375
- console.error(`[dsh-desktop] transport ready: webServer=${webServer.constructor.name} gateway=${gateway === void 0 ? "missing" : "present"} streamRoute=${DESKTOP_STREAM_PATH}`);
376
- send({
377
- type: "ready",
378
- protocolVersion: 1
379
- });
380
- }
381
- if (import.meta.main) main().catch((error) => {
382
- if (process.send !== void 0 && process.connected) process.send(fatalEvent(error), (sendError) => {
383
- if (sendError !== null) console.error(sendError);
384
- });
385
- console.error(error);
386
- process.exitCode = 1;
387
- if (process.connected) process.disconnect?.();
388
- });
389
- //#endregion
390
- export {};
package/lib/webserver.js DELETED
@@ -1,250 +0,0 @@
1
- import { EventEmitter } from "node:events";
2
- import { Readable } from "node:stream";
3
- import { Service } from "@deepseek-ai/cordis";
4
- import z from "@deepseek-ai/schemastery";
5
- import { renderIndexInjections } from "@deepseek-ai/dsh-host-webserver";
6
- //#region src/webserver.ts
7
- function deferred() {
8
- let resolve;
9
- return {
10
- promise: new Promise((settle) => {
11
- resolve = settle;
12
- }),
13
- resolve
14
- };
15
- }
16
- var SyntheticResponse = class extends EventEmitter {
17
- statusCode = 200;
18
- statusMessage = "";
19
- headersSent = false;
20
- writableEnded = false;
21
- destroyed = false;
22
- socket = void 0;
23
- body;
24
- started;
25
- done;
26
- startSettle;
27
- doneSettle;
28
- headers = new Headers();
29
- controller;
30
- startedValue;
31
- constructor() {
32
- super();
33
- this.startSettle = deferred();
34
- this.doneSettle = deferred();
35
- this.started = this.startSettle.promise;
36
- this.done = this.doneSettle.promise;
37
- this.body = new ReadableStream({
38
- start: (controller) => {
39
- this.controller = controller;
40
- },
41
- cancel: () => {
42
- this.destroy();
43
- }
44
- });
45
- }
46
- setHeader(name, value) {
47
- this.headers.set(name, Array.isArray(value) ? value.join(", ") : String(value));
48
- }
49
- getHeader(name) {
50
- return this.headers.get(name);
51
- }
52
- getHeaders() {
53
- return new Headers(this.headers);
54
- }
55
- removeHeader(name) {
56
- this.headers.delete(name);
57
- }
58
- getHeaderNames() {
59
- return [...this.headers.keys()];
60
- }
61
- writeHead(status, headers) {
62
- this.statusCode = status;
63
- if (headers !== void 0) for (const [name, value] of Object.entries(headers)) this.setHeader(name, value);
64
- this.settleStart();
65
- return this;
66
- }
67
- write(chunk) {
68
- if (this.writableEnded) throw new Error("dsh desktop: response already ended");
69
- this.settleStart();
70
- if (!this.destroyed) this.controller.enqueue(toBytes(chunk));
71
- return true;
72
- }
73
- end(chunk) {
74
- if (this.writableEnded) return this;
75
- this.settleStart();
76
- if (chunk !== void 0 && !this.destroyed) this.controller.enqueue(toBytes(chunk));
77
- this.writableEnded = true;
78
- if (!this.destroyed) this.controller.close();
79
- this.doneSettle.resolve();
80
- this.emit("finish");
81
- this.emit("close");
82
- return this;
83
- }
84
- destroy(error) {
85
- if (this.destroyed) return this;
86
- this.destroyed = true;
87
- if (!this.writableEnded) {
88
- this.settleStart(500);
89
- this.controller.error(error ?? /* @__PURE__ */ new Error("dsh desktop: response destroyed"));
90
- this.doneSettle.resolve();
91
- }
92
- this.emit("close");
93
- return this;
94
- }
95
- fail(error) {
96
- if (this.destroyed) return;
97
- if (!this.headersSent) {
98
- this.statusCode = 500;
99
- this.settleStart();
100
- this.writableEnded = true;
101
- this.controller.enqueue(new TextEncoder().encode(errorMessage(error)));
102
- this.controller.close();
103
- this.doneSettle.resolve();
104
- return;
105
- }
106
- this.destroy(error instanceof Error ? error : new Error(String(error)));
107
- }
108
- settleStart(forcedStatus) {
109
- if (this.startedValue !== void 0) return;
110
- this.headersSent = true;
111
- if (forcedStatus !== void 0) this.statusCode = forcedStatus;
112
- this.startedValue = {
113
- status: this.statusCode,
114
- headers: new Headers(this.headers)
115
- };
116
- this.startSettle.resolve(this.startedValue);
117
- }
118
- };
119
- function toBytes(chunk) {
120
- return typeof chunk === "string" ? new TextEncoder().encode(chunk) : chunk;
121
- }
122
- function errorMessage(error) {
123
- return error instanceof Error ? error.message : String(error);
124
- }
125
- function createRequest(request, url) {
126
- const stream = Readable.from(request.body === null ? [] : consume(request.body));
127
- const headers = {};
128
- for (const [name, value] of request.headers) headers[name.toLowerCase()] = value;
129
- return Object.assign(stream, {
130
- url: `${url.pathname}${url.search}`,
131
- method: request.method,
132
- headers,
133
- httpVersion: "1.1",
134
- httpVersionMajor: 1,
135
- httpVersionMinor: 1,
136
- complete: false,
137
- aborted: false
138
- });
139
- }
140
- async function* consume(body) {
141
- const reader = body.getReader();
142
- try {
143
- for (;;) {
144
- const next = await reader.read();
145
- if (next.done) return;
146
- yield next.value;
147
- }
148
- } finally {
149
- reader.releaseLock();
150
- }
151
- }
152
- var PortlessWebServer = class extends Service {
153
- config;
154
- static Config = z.object({
155
- host: z.union([z.const("127.0.0.1"), z.const("0.0.0.0")]).default("127.0.0.1"),
156
- port: z.natural().max(65535).default(0)
157
- });
158
- exact = /* @__PURE__ */ new Map();
159
- prefixes = /* @__PURE__ */ new Map();
160
- upgrades = /* @__PURE__ */ new Map();
161
- indexTaps = [];
162
- fallback;
163
- constructor(ctx, config) {
164
- super(ctx, "webServer");
165
- this.config = config;
166
- }
167
- get port() {
168
- return this.config.port ?? 0;
169
- }
170
- get host() {
171
- return this.config.host ?? "127.0.0.1";
172
- }
173
- register(route) {
174
- const table = route.kind === "exact" ? this.exact : this.prefixes;
175
- if (table.has(route.path)) throw new Error(`webserver: duplicate ${route.kind} route "${route.path}"`);
176
- table.set(route.path, route);
177
- return () => {
178
- table.delete(route.path);
179
- };
180
- }
181
- registerUpgrade(route) {
182
- if (this.upgrades.has(route.path)) throw new Error(`webserver: duplicate upgrade route "${route.path}"`);
183
- this.upgrades.set(route.path, route);
184
- return () => {
185
- this.upgrades.delete(route.path);
186
- };
187
- }
188
- registerFallback(handler) {
189
- if (this.fallback !== void 0) throw new Error("webserver: fallback already registered");
190
- this.fallback = handler;
191
- return () => {
192
- this.fallback = void 0;
193
- };
194
- }
195
- tapIndex(transform) {
196
- this.indexTaps.push(transform);
197
- return () => {
198
- const at = this.indexTaps.indexOf(transform);
199
- if (at !== -1) this.indexTaps.splice(at, 1);
200
- };
201
- }
202
- applyIndexTaps(html) {
203
- let out = html;
204
- for (const transform of this.indexTaps) out = transform(out);
205
- return out;
206
- }
207
- collectIndexInjections() {
208
- const table = [];
209
- this.ctx.emit("webserver/index-inject", table);
210
- return table;
211
- }
212
- renderIndex(html) {
213
- return this.applyIndexTaps(renderIndexInjections(html, this.collectIndexInjections()));
214
- }
215
- async dispatch(request) {
216
- const url = new URL(request.url);
217
- let pathname;
218
- try {
219
- pathname = decodeURIComponent(url.pathname);
220
- } catch {
221
- return new Response(null, { status: 400 });
222
- }
223
- const handler = this.match(pathname)?.handler ?? this.fallback;
224
- if (process.env.DSH_DESKTOP_DEBUG === "1") console.error(`[dsh-desktop] dispatch ${request.method} ${pathname} → ${handler === void 0 ? "404" : "handler"}`);
225
- if (handler === void 0) return new Response("not found", { status: 404 });
226
- const response = new SyntheticResponse();
227
- const req = createRequest(request, url);
228
- Promise.resolve().then(() => handler(req, response)).catch((error) => {
229
- this.ctx.logger.warn(error instanceof Error ? error : new Error(errorMessage(error)));
230
- response.fail(error);
231
- });
232
- const started = await response.started;
233
- return new Response(response.body, {
234
- status: started.status,
235
- headers: started.headers
236
- });
237
- }
238
- match(pathname) {
239
- const exact = this.exact.get(pathname);
240
- if (exact !== void 0) return exact;
241
- let best;
242
- for (const [prefix, route] of this.prefixes) {
243
- if (pathname !== prefix && !pathname.startsWith(`${prefix}/`)) continue;
244
- if (best === void 0 || prefix.length > best.path.length) best = route;
245
- }
246
- return best;
247
- }
248
- };
249
- //#endregion
250
- export { PortlessWebServer, PortlessWebServer as default };