@j0hanz/fetch-url-mcp 1.10.2 → 1.10.3
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/http/native.d.ts.map +1 -1
- package/dist/http/native.js +20 -26
- package/dist/http/session-teardown.d.ts +17 -0
- package/dist/http/session-teardown.d.ts.map +1 -0
- package/dist/http/session-teardown.js +30 -0
- package/dist/lib/core.d.ts.map +1 -1
- package/dist/lib/core.js +2 -0
- package/dist/lib/dom-prep.d.ts.map +1 -1
- package/dist/lib/dom-prep.js +19 -14
- package/dist/lib/progress.d.ts.map +1 -1
- package/dist/lib/progress.js +59 -32
- package/dist/lib/sdk-interop.d.ts +8 -0
- package/dist/lib/sdk-interop.d.ts.map +1 -0
- package/dist/lib/sdk-interop.js +73 -0
- package/dist/lib/task-handlers.d.ts +0 -2
- package/dist/lib/task-handlers.d.ts.map +1 -1
- package/dist/lib/task-handlers.js +9 -94
- package/dist/resources/index.js +1 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +10 -3
- package/dist/tasks/call-contract.d.ts +25 -0
- package/dist/tasks/call-contract.d.ts.map +1 -0
- package/dist/tasks/call-contract.js +58 -0
- package/dist/tasks/cursor-codec.d.ts +5 -0
- package/dist/tasks/cursor-codec.d.ts.map +1 -0
- package/dist/tasks/cursor-codec.js +41 -0
- package/dist/tasks/execution.d.ts +1 -20
- package/dist/tasks/execution.d.ts.map +1 -1
- package/dist/tasks/execution.js +7 -32
- package/dist/tasks/manager.d.ts +1 -5
- package/dist/tasks/manager.d.ts.map +1 -1
- package/dist/tasks/manager.js +16 -144
- package/dist/tasks/owner.d.ts +12 -2
- package/dist/tasks/owner.d.ts.map +1 -1
- package/dist/tasks/owner.js +52 -3
- package/dist/tasks/tool-registry.d.ts +1 -0
- package/dist/tasks/tool-registry.d.ts.map +1 -1
- package/dist/tasks/waiters.d.ts +27 -0
- package/dist/tasks/waiters.d.ts.map +1 -0
- package/dist/tasks/waiters.js +113 -0
- package/dist/tools/fetch-url-progress.d.ts +17 -0
- package/dist/tools/fetch-url-progress.d.ts.map +1 -0
- package/dist/tools/fetch-url-progress.js +78 -0
- package/dist/tools/fetch-url.d.ts +5 -9
- package/dist/tools/fetch-url.d.ts.map +1 -1
- package/dist/tools/fetch-url.js +43 -96
- package/dist/transform/next-flight.d.ts +2 -0
- package/dist/transform/next-flight.d.ts.map +1 -0
- package/dist/transform/next-flight.js +285 -0
- package/dist/transform/transform.d.ts.map +1 -1
- package/dist/transform/transform.js +21 -336
- package/package.json +4 -4
- package/dist/tools/index.d.ts +0 -3
- package/dist/tools/index.d.ts.map +0 -1
- package/dist/tools/index.js +0 -4
package/dist/tools/fetch-url.js
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
|
-
import { randomUUID } from 'node:crypto';
|
|
2
1
|
import { ErrorCode, McpError } from '@modelcontextprotocol/sdk/types.js';
|
|
3
2
|
import { z } from 'zod';
|
|
4
|
-
import { config,
|
|
5
|
-
import { appendTruncationMarker, markdownTransform, parseCachedMarkdownResult, performSharedFetch,
|
|
3
|
+
import { config, logDebug, logError, logWarn } from '../lib/core.js';
|
|
4
|
+
import { appendTruncationMarker, markdownTransform, parseCachedMarkdownResult, performSharedFetch, serializeMarkdownResult, TRUNCATION_MARKER, withSignal, } from '../lib/fetch-pipeline.js';
|
|
6
5
|
import { handleToolError } from '../lib/mcp-tools.js';
|
|
7
6
|
import { createProgressReporter, } from '../lib/progress.js';
|
|
8
|
-
import { isAbortError,
|
|
7
|
+
import { isAbortError, toError } from '../lib/utils.js';
|
|
9
8
|
import { formatZodError } from '../lib/zod.js';
|
|
10
9
|
import { fetchUrlInputSchema, fetchUrlOutputSchema, normalizeExtractedMetadata, normalizePageTitle, } from '../schemas.js';
|
|
10
|
+
import { withRequestContextIfMissing } from '../tasks/owner.js';
|
|
11
11
|
import { registerTaskCapableTool, unregisterTaskCapableTool, } from '../tasks/tool-registry.js';
|
|
12
|
+
import { FetchUrlProgressPlan, getFetchCompletionStatusMessage, } from './fetch-url-progress.js';
|
|
12
13
|
export const FETCH_URL_TOOL_NAME = 'fetch-url';
|
|
13
14
|
const FETCH_URL_TOOL_DESCRIPTION = `
|
|
14
15
|
<role>Web Content Extractor</role>
|
|
@@ -27,9 +28,6 @@ const TOOL_ICON = {
|
|
|
27
28
|
};
|
|
28
29
|
const HARD_TOOL_TIMEOUT_MS = 300_000;
|
|
29
30
|
const CODE_HOSTS = new Set(['github.com', 'gitlab.com', 'bitbucket.org']);
|
|
30
|
-
/* -------------------------------------------------------------------------------------------------
|
|
31
|
-
* URL context & progress
|
|
32
|
-
* ------------------------------------------------------------------------------------------------- */
|
|
33
31
|
function getUrlContext(urlStr) {
|
|
34
32
|
try {
|
|
35
33
|
const u = new URL(urlStr);
|
|
@@ -55,28 +53,6 @@ function getUrlContext(urlStr) {
|
|
|
55
53
|
return 'unknown';
|
|
56
54
|
}
|
|
57
55
|
}
|
|
58
|
-
function mapFetchStageToProgress(stage, context) {
|
|
59
|
-
switch (stage) {
|
|
60
|
-
case 'resolve_url':
|
|
61
|
-
return { step: 2, message: 'Resolving URL' };
|
|
62
|
-
case 'check_cache':
|
|
63
|
-
return { step: 3, message: 'Checking cache' };
|
|
64
|
-
case 'cache_hit':
|
|
65
|
-
return { step: 4, message: 'Loaded from cache' };
|
|
66
|
-
case 'cache_restore':
|
|
67
|
-
return { step: 5, message: 'Restoring cached content' };
|
|
68
|
-
case 'fetch_remote':
|
|
69
|
-
return { step: 4, message: `Fetching ${context}` };
|
|
70
|
-
case 'response_ready':
|
|
71
|
-
return { step: 5, message: 'Received response' };
|
|
72
|
-
case 'transform_start':
|
|
73
|
-
return { step: 6, message: 'Parsing HTML → Markdown' };
|
|
74
|
-
case 'prepare_output':
|
|
75
|
-
return { step: 6, message: 'Preparing output' };
|
|
76
|
-
case 'finalize_output':
|
|
77
|
-
return { step: 7, message: 'Finalizing output' };
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
56
|
/* -------------------------------------------------------------------------------------------------
|
|
81
57
|
* Response assembly
|
|
82
58
|
* ------------------------------------------------------------------------------------------------- */
|
|
@@ -132,14 +108,13 @@ function buildToolAbortSignal(extraSignal) {
|
|
|
132
108
|
? AbortSignal.any([extraSignal, timeoutSignal])
|
|
133
109
|
: timeoutSignal;
|
|
134
110
|
}
|
|
135
|
-
function buildFetchOptions(url,
|
|
111
|
+
function buildFetchOptions(url, signal, progressPlan, forceRefresh) {
|
|
136
112
|
return {
|
|
137
113
|
url,
|
|
138
114
|
...withSignal(signal),
|
|
139
115
|
...(forceRefresh ? { forceRefresh: true } : {}),
|
|
140
116
|
onStage: (stage) => {
|
|
141
|
-
|
|
142
|
-
progress?.report(step, message);
|
|
117
|
+
progressPlan.reportStage(stage);
|
|
143
118
|
},
|
|
144
119
|
transform: async ({ buffer, encoding, truncated }, normalizedUrl) => {
|
|
145
120
|
return markdownTransform({ buffer, encoding, ...(truncated ? { truncated } : {}) }, normalizedUrl, signal);
|
|
@@ -153,21 +128,16 @@ async function executeFetch(input, extra) {
|
|
|
153
128
|
const signal = buildToolAbortSignal(extra?.signal);
|
|
154
129
|
const progress = createProgressReporter(extra);
|
|
155
130
|
const context = getUrlContext(url);
|
|
131
|
+
const progressPlan = new FetchUrlProgressPlan(progress, context);
|
|
156
132
|
logDebug('Fetching URL', { url });
|
|
157
133
|
try {
|
|
158
|
-
|
|
159
|
-
const { pipeline, inlineResult } = await performSharedFetch(buildFetchOptions(url,
|
|
160
|
-
|
|
161
|
-
const size = chars < 1000
|
|
162
|
-
? `${chars} chars`
|
|
163
|
-
: chars < 1_000_000
|
|
164
|
-
? `${(chars / 1024).toFixed(1)} KB`
|
|
165
|
-
: `${(chars / (1024 * 1024)).toFixed(1)} MB`;
|
|
166
|
-
progress.report(8, `Done — ${size}`);
|
|
134
|
+
progressPlan.reportStart();
|
|
135
|
+
const { pipeline, inlineResult } = await performSharedFetch(buildFetchOptions(url, signal, progressPlan, input.forceRefresh));
|
|
136
|
+
progressPlan.reportSuccess(inlineResult.contentSize);
|
|
167
137
|
return buildResponse(pipeline, inlineResult, url);
|
|
168
138
|
}
|
|
169
139
|
catch (error) {
|
|
170
|
-
|
|
140
|
+
progressPlan.reportFailure(isAbortError(error));
|
|
171
141
|
throw error;
|
|
172
142
|
}
|
|
173
143
|
}
|
|
@@ -190,7 +160,6 @@ const TOOL_DEFINITION = {
|
|
|
190
160
|
inputSchema: fetchUrlInputSchema,
|
|
191
161
|
outputSchema: z.toJSONSchema(fetchUrlOutputSchema),
|
|
192
162
|
handler: fetchUrlToolHandler,
|
|
193
|
-
execution: { taskSupport: 'optional' },
|
|
194
163
|
annotations: {
|
|
195
164
|
readOnlyHint: true,
|
|
196
165
|
destructiveHint: false,
|
|
@@ -198,54 +167,8 @@ const TOOL_DEFINITION = {
|
|
|
198
167
|
openWorldHint: true,
|
|
199
168
|
},
|
|
200
169
|
};
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
* in AsyncLocalStorage before invoking the handler. On the HTTP path the SDK
|
|
204
|
-
* populates `extra.requestId`/`extra.requestInfo`, so this is a no-op there.
|
|
205
|
-
* On the stdio path there is no SDK-provided context, so we derive one from
|
|
206
|
-
* the extra fields or generate a fresh UUID.
|
|
207
|
-
*/
|
|
208
|
-
export function withRequestContextIfMissing(handler) {
|
|
209
|
-
return async (params, extra) => {
|
|
210
|
-
const existingRequestId = getRequestId();
|
|
211
|
-
if (existingRequestId) {
|
|
212
|
-
return handler(params, extra);
|
|
213
|
-
}
|
|
214
|
-
const derivedRequestId = resolveRequestIdFromExtra(extra) ?? randomUUID();
|
|
215
|
-
const derivedSessionId = resolveSessionIdFromExtra(extra);
|
|
216
|
-
return runWithRequestContext({
|
|
217
|
-
requestId: derivedRequestId,
|
|
218
|
-
operationId: derivedRequestId,
|
|
219
|
-
...(derivedSessionId ? { sessionId: derivedSessionId } : {}),
|
|
220
|
-
}, () => handler(params, extra));
|
|
221
|
-
};
|
|
222
|
-
}
|
|
223
|
-
function resolveRequestIdFromExtra(extra) {
|
|
224
|
-
if (!isObject(extra))
|
|
225
|
-
return undefined;
|
|
226
|
-
const { requestId } = extra;
|
|
227
|
-
if (typeof requestId === 'string')
|
|
228
|
-
return requestId;
|
|
229
|
-
if (typeof requestId === 'number')
|
|
230
|
-
return String(requestId);
|
|
231
|
-
return undefined;
|
|
232
|
-
}
|
|
233
|
-
function resolveSessionIdFromExtra(extra) {
|
|
234
|
-
if (!isObject(extra))
|
|
235
|
-
return undefined;
|
|
236
|
-
const { sessionId } = extra;
|
|
237
|
-
if (typeof sessionId === 'string')
|
|
238
|
-
return sessionId;
|
|
239
|
-
const headers = readNestedRecord(extra, ['requestInfo', 'headers']);
|
|
240
|
-
const headerValue = headers ? headers['mcp-session-id'] : undefined;
|
|
241
|
-
return typeof headerValue === 'string' ? headerValue : undefined;
|
|
242
|
-
}
|
|
243
|
-
export function registerTools(server) {
|
|
244
|
-
if (!config.tools.enabled.includes(FETCH_URL_TOOL_NAME)) {
|
|
245
|
-
unregisterTaskCapableTool(FETCH_URL_TOOL_NAME);
|
|
246
|
-
return;
|
|
247
|
-
}
|
|
248
|
-
registerTaskCapableTool({
|
|
170
|
+
function createTaskCapableDescriptor() {
|
|
171
|
+
return {
|
|
249
172
|
name: FETCH_URL_TOOL_NAME,
|
|
250
173
|
parseArguments: (args) => {
|
|
251
174
|
const parsed = fetchUrlInputSchema.safeParse(args);
|
|
@@ -255,17 +178,41 @@ export function registerTools(server) {
|
|
|
255
178
|
return parsed.data;
|
|
256
179
|
},
|
|
257
180
|
execute: fetchUrlToolHandler,
|
|
258
|
-
|
|
181
|
+
getCompletionStatusMessage: getFetchCompletionStatusMessage,
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
function setRegisteredToolTaskSupport(registeredTool, support) {
|
|
185
|
+
registeredTool.execution = { taskSupport: support };
|
|
186
|
+
}
|
|
187
|
+
export function registerTools(server) {
|
|
188
|
+
if (!config.tools.enabled.includes(FETCH_URL_TOOL_NAME)) {
|
|
189
|
+
unregisterTaskCapableTool(FETCH_URL_TOOL_NAME);
|
|
190
|
+
return {
|
|
191
|
+
setTaskSupport: () => { },
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
const descriptor = createTaskCapableDescriptor();
|
|
195
|
+
registerTaskCapableTool(descriptor);
|
|
259
196
|
const registeredTool = server.registerTool(TOOL_DEFINITION.name, {
|
|
260
197
|
title: TOOL_DEFINITION.title,
|
|
261
198
|
description: TOOL_DEFINITION.description,
|
|
262
199
|
inputSchema: TOOL_DEFINITION.inputSchema,
|
|
263
200
|
outputSchema: TOOL_DEFINITION.outputSchema,
|
|
264
201
|
annotations: TOOL_DEFINITION.annotations,
|
|
265
|
-
execution:
|
|
202
|
+
execution: { taskSupport: 'optional' },
|
|
266
203
|
icons: [TOOL_ICON],
|
|
267
204
|
}, withRequestContextIfMissing(TOOL_DEFINITION.handler));
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
205
|
+
const registeredToolRecord = registeredTool;
|
|
206
|
+
setRegisteredToolTaskSupport(registeredToolRecord, 'optional');
|
|
207
|
+
return {
|
|
208
|
+
setTaskSupport: (support) => {
|
|
209
|
+
if (support === 'optional') {
|
|
210
|
+
registerTaskCapableTool(descriptor);
|
|
211
|
+
}
|
|
212
|
+
else {
|
|
213
|
+
unregisterTaskCapableTool(FETCH_URL_TOOL_NAME);
|
|
214
|
+
}
|
|
215
|
+
setRegisteredToolTaskSupport(registeredToolRecord, support);
|
|
216
|
+
},
|
|
217
|
+
};
|
|
271
218
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"next-flight.d.ts","sourceRoot":"","sources":["../../src/transform/next-flight.ts"],"names":[],"mappings":"AA0UA,wBAAgB,gCAAgC,CAC9C,QAAQ,EAAE,MAAM,EAChB,YAAY,EAAE,MAAM,GACnB,MAAM,CAwDR"}
|
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
import { detectLanguageFromCode } from '../lib/code-lang.js';
|
|
2
|
+
const NEXT_FLIGHT_PAYLOAD_RE = /self\.__next_f\.push\(\[1,"((?:\\.|[^"\\])*)"\]\)<\/script>/gs;
|
|
3
|
+
const TEMPLATE_ASSIGNMENT_RE = /([A-Za-z_$][\w$]*)=`([\s\S]*?)`;/g;
|
|
4
|
+
const OBJECT_ASSIGNMENT_RE = /([A-Za-z_$][\w$]*)=\{([^{}]+)\}/g;
|
|
5
|
+
const FLIGHT_INSTALL_RE = /commands:\{cli:"([^"]+)",npm:"([^"]+)",yarn:"([^"]+)",pnpm:"([^"]+)",bun:"([^"]+)"\}/;
|
|
6
|
+
const FLIGHT_IMPORT_RE = /commands:\{main:'([^']+)',individual:'([^']+)'\}/;
|
|
7
|
+
const FLIGHT_DEMO_RE = /title:"([^"]+)",files:([A-Za-z_$][\w$]*)\.([A-Za-z_$][\w$]*)/g;
|
|
8
|
+
const FLIGHT_API_RE = /children:"([^"]+)"\}\),`\\n`,\(0,e\.jsx\)\(o,\{data:\[([\s\S]*?)\]\}\)/g;
|
|
9
|
+
const FLIGHT_API_ROW_RE = /attribute:"([^"]+)",type:"([^"]+)",description:"([^"]*)",default:"([^"]*)"/g;
|
|
10
|
+
const FLIGHT_MERMAID_SECTION_RE = /_jsx\(Heading,\{\s*level:"[1-6]",\s*id:"[^"]+",\s*children:"((?:\\.|[^"\\])*)"\s*\}\)(?:(?!_jsx\(Heading,\{)[\s\S]){0,12000}?_jsx\(Mermaid,\{\s*chart:"((?:\\.|[^"\\])*)"\s*\}\)/g;
|
|
11
|
+
function decodeHtmlEntities(value) {
|
|
12
|
+
return value
|
|
13
|
+
.replace(/'|'/g, "'")
|
|
14
|
+
.replace(/"/g, '"')
|
|
15
|
+
.replace(/&/g, '&')
|
|
16
|
+
.replace(/</g, '<')
|
|
17
|
+
.replace(/>/g, '>');
|
|
18
|
+
}
|
|
19
|
+
function decodeFlightStringValue(value) {
|
|
20
|
+
try {
|
|
21
|
+
return JSON.parse(`"${value}"`);
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
return decodeHtmlEntities(value);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
function decodeNextFlightPayloads(html) {
|
|
28
|
+
const payloads = [];
|
|
29
|
+
for (const match of html.matchAll(NEXT_FLIGHT_PAYLOAD_RE)) {
|
|
30
|
+
const rawPayload = match[1];
|
|
31
|
+
if (!rawPayload)
|
|
32
|
+
continue;
|
|
33
|
+
try {
|
|
34
|
+
payloads.push(JSON.parse(`"${rawPayload}"`));
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
// Ignore malformed payload fragments and continue with the rest.
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return payloads;
|
|
41
|
+
}
|
|
42
|
+
function parseFlightObjectRefs(text) {
|
|
43
|
+
const templateMap = new Map();
|
|
44
|
+
const aliasMap = new Map();
|
|
45
|
+
const objectMaps = new Map();
|
|
46
|
+
for (const match of text.matchAll(TEMPLATE_ASSIGNMENT_RE)) {
|
|
47
|
+
const name = match[1];
|
|
48
|
+
const code = match[2];
|
|
49
|
+
if (name && code)
|
|
50
|
+
templateMap.set(name, decodeHtmlEntities(code));
|
|
51
|
+
}
|
|
52
|
+
for (const match of text.matchAll(OBJECT_ASSIGNMENT_RE)) {
|
|
53
|
+
const objectName = match[1];
|
|
54
|
+
const body = match[2]?.trim() ?? '';
|
|
55
|
+
if (!objectName || !body)
|
|
56
|
+
continue;
|
|
57
|
+
const spreadMatch = /^\.\.\.([A-Za-z_$][\w$]*)$/.exec(body);
|
|
58
|
+
if (spreadMatch?.[1]) {
|
|
59
|
+
aliasMap.set(objectName, spreadMatch[1]);
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
const entries = new Map();
|
|
63
|
+
for (const part of body.split(',')) {
|
|
64
|
+
const entryMatch = /(?:"([^"]+)"|([A-Za-z_$][\w$]*)):([A-Za-z_$][\w$]*)$/.exec(part.trim());
|
|
65
|
+
const key = entryMatch?.[1] ?? entryMatch?.[2];
|
|
66
|
+
const value = entryMatch?.[3];
|
|
67
|
+
if (key && value)
|
|
68
|
+
entries.set(key, value);
|
|
69
|
+
}
|
|
70
|
+
if (entries.size > 0)
|
|
71
|
+
objectMaps.set(objectName, entries);
|
|
72
|
+
}
|
|
73
|
+
return { templateMap, aliasMap, objectMaps };
|
|
74
|
+
}
|
|
75
|
+
function resolveFlightCodeRef(name, refs, seen = new Set()) {
|
|
76
|
+
if (!name || seen.has(name))
|
|
77
|
+
return undefined;
|
|
78
|
+
seen.add(name);
|
|
79
|
+
const direct = refs.templateMap.get(name);
|
|
80
|
+
if (direct)
|
|
81
|
+
return direct;
|
|
82
|
+
const alias = refs.aliasMap.get(name);
|
|
83
|
+
if (alias)
|
|
84
|
+
return resolveFlightCodeRef(alias, refs, seen);
|
|
85
|
+
const objectMap = refs.objectMaps.get(name);
|
|
86
|
+
if (!objectMap)
|
|
87
|
+
return undefined;
|
|
88
|
+
for (const ref of objectMap.values()) {
|
|
89
|
+
const resolved = resolveFlightCodeRef(ref, refs, seen);
|
|
90
|
+
if (resolved)
|
|
91
|
+
return resolved;
|
|
92
|
+
}
|
|
93
|
+
return undefined;
|
|
94
|
+
}
|
|
95
|
+
function escapeMarkdownTableCell(value) {
|
|
96
|
+
const normalized = decodeHtmlEntities(value).replace(/\s+/g, ' ').trim();
|
|
97
|
+
return (normalized || '-').replace(/\|/g, '\\|');
|
|
98
|
+
}
|
|
99
|
+
function buildMarkdownTable(rows) {
|
|
100
|
+
if (rows.length === 0)
|
|
101
|
+
return '';
|
|
102
|
+
const lines = [
|
|
103
|
+
'| Prop | Type | Description | Default |',
|
|
104
|
+
'| ---- | ---- | ----------- | ------- |',
|
|
105
|
+
];
|
|
106
|
+
for (const row of rows) {
|
|
107
|
+
lines.push(`| ${escapeMarkdownTableCell(row.attribute)} | ${escapeMarkdownTableCell(row.type)} | ${escapeMarkdownTableCell(row.description)} | ${escapeMarkdownTableCell(row.defaultValue)} |`);
|
|
108
|
+
}
|
|
109
|
+
return lines.join('\n');
|
|
110
|
+
}
|
|
111
|
+
function buildCodeBlock(code) {
|
|
112
|
+
const trimmed = code.trim();
|
|
113
|
+
if (!trimmed)
|
|
114
|
+
return '';
|
|
115
|
+
const language = detectLanguageFromCode(trimmed) ?? 'tsx';
|
|
116
|
+
return `\`\`\`${language}\n${trimmed}\n\`\`\``;
|
|
117
|
+
}
|
|
118
|
+
function buildMermaidBlock(chart) {
|
|
119
|
+
const normalized = decodeFlightStringValue(chart).trim();
|
|
120
|
+
if (!normalized)
|
|
121
|
+
return '';
|
|
122
|
+
return `\`\`\`mermaid\n${normalized}\n\`\`\``;
|
|
123
|
+
}
|
|
124
|
+
function normalizeSupplementHeadingText(value) {
|
|
125
|
+
return value
|
|
126
|
+
.replace(/\[([^\]]+)\]\([^)]*\)/g, '$1')
|
|
127
|
+
.replace(/\s+/g, ' ')
|
|
128
|
+
.trim()
|
|
129
|
+
.toLowerCase();
|
|
130
|
+
}
|
|
131
|
+
function getMarkdownHeadingInfo(line) {
|
|
132
|
+
const match = /^(#{1,6})\s+(.+?)\s*$/.exec(line.trim());
|
|
133
|
+
if (!match)
|
|
134
|
+
return null;
|
|
135
|
+
return {
|
|
136
|
+
level: match[1]?.length ?? 0,
|
|
137
|
+
title: normalizeSupplementHeadingText(match[2] ?? ''),
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
function findMarkdownSection(lines, title) {
|
|
141
|
+
const target = normalizeSupplementHeadingText(title);
|
|
142
|
+
for (let i = 0; i < lines.length; i += 1) {
|
|
143
|
+
const heading = getMarkdownHeadingInfo(lines[i] ?? '');
|
|
144
|
+
if (heading?.title !== target)
|
|
145
|
+
continue;
|
|
146
|
+
let end = lines.length;
|
|
147
|
+
for (let j = i + 1; j < lines.length; j += 1) {
|
|
148
|
+
const nextLine = lines[j];
|
|
149
|
+
const nextHeading = nextLine !== undefined ? getMarkdownHeadingInfo(nextLine) : null;
|
|
150
|
+
if (nextHeading && nextHeading.level <= heading.level) {
|
|
151
|
+
end = j;
|
|
152
|
+
break;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
return { start: i, end };
|
|
156
|
+
}
|
|
157
|
+
return null;
|
|
158
|
+
}
|
|
159
|
+
function getSectionBody(lines, section) {
|
|
160
|
+
return lines
|
|
161
|
+
.slice(section.start + 1, section.end)
|
|
162
|
+
.join('\n')
|
|
163
|
+
.trim();
|
|
164
|
+
}
|
|
165
|
+
function replaceMarkdownSection(lines, title, body) {
|
|
166
|
+
const section = findMarkdownSection(lines, title);
|
|
167
|
+
if (!section)
|
|
168
|
+
return false;
|
|
169
|
+
const replacement = body.trim().length > 0 ? ['', ...body.trim().split('\n'), ''] : [''];
|
|
170
|
+
lines.splice(section.start + 1, section.end - section.start - 1, ...replacement);
|
|
171
|
+
return true;
|
|
172
|
+
}
|
|
173
|
+
function appendMarkdownSection(lines, title, body) {
|
|
174
|
+
const section = findMarkdownSection(lines, title);
|
|
175
|
+
if (!section)
|
|
176
|
+
return false;
|
|
177
|
+
const bodyText = getSectionBody(lines, section);
|
|
178
|
+
if (bodyText.includes('```'))
|
|
179
|
+
return false;
|
|
180
|
+
const nextBody = bodyText ? `${bodyText}\n\n${body.trim()}` : body.trim();
|
|
181
|
+
return replaceMarkdownSection(lines, title, nextBody);
|
|
182
|
+
}
|
|
183
|
+
function extractNextFlightSupplement(originalHtml) {
|
|
184
|
+
const payloads = decodeNextFlightPayloads(originalHtml);
|
|
185
|
+
if (payloads.length === 0)
|
|
186
|
+
return null;
|
|
187
|
+
const text = payloads.join('\n');
|
|
188
|
+
const refs = parseFlightObjectRefs(text);
|
|
189
|
+
const installMatch = FLIGHT_INSTALL_RE.exec(text);
|
|
190
|
+
const importMatch = FLIGHT_IMPORT_RE.exec(text);
|
|
191
|
+
const apiTables = new Map();
|
|
192
|
+
for (const match of text.matchAll(FLIGHT_API_RE)) {
|
|
193
|
+
const title = match[1];
|
|
194
|
+
const rawRows = match[2] ?? '';
|
|
195
|
+
if (!title)
|
|
196
|
+
continue;
|
|
197
|
+
const rows = [];
|
|
198
|
+
for (const rowMatch of rawRows.matchAll(FLIGHT_API_ROW_RE)) {
|
|
199
|
+
const attribute = rowMatch[1];
|
|
200
|
+
const type = rowMatch[2];
|
|
201
|
+
const description = rowMatch[3];
|
|
202
|
+
const defaultValue = rowMatch[4];
|
|
203
|
+
if (!attribute ||
|
|
204
|
+
!type ||
|
|
205
|
+
description === undefined ||
|
|
206
|
+
defaultValue === undefined) {
|
|
207
|
+
continue;
|
|
208
|
+
}
|
|
209
|
+
rows.push({ attribute, type, description, defaultValue });
|
|
210
|
+
}
|
|
211
|
+
const table = buildMarkdownTable(rows);
|
|
212
|
+
if (table)
|
|
213
|
+
apiTables.set(title, table);
|
|
214
|
+
}
|
|
215
|
+
const mermaidDiagrams = new Map();
|
|
216
|
+
for (const match of text.matchAll(FLIGHT_MERMAID_SECTION_RE)) {
|
|
217
|
+
const title = match[1] ? decodeFlightStringValue(match[1]).trim() : '';
|
|
218
|
+
const chart = match[2] ? buildMermaidBlock(match[2]) : '';
|
|
219
|
+
if (title && chart)
|
|
220
|
+
mermaidDiagrams.set(title, chart);
|
|
221
|
+
}
|
|
222
|
+
const demoCodeBlocks = new Map();
|
|
223
|
+
for (const match of text.matchAll(FLIGHT_DEMO_RE)) {
|
|
224
|
+
const title = match[1];
|
|
225
|
+
const objectName = match[2];
|
|
226
|
+
const key = match[3];
|
|
227
|
+
const ref = objectName
|
|
228
|
+
? refs.objectMaps.get(objectName)?.get(key ?? '')
|
|
229
|
+
: undefined;
|
|
230
|
+
const code = resolveFlightCodeRef(ref, refs);
|
|
231
|
+
const codeBlock = code ? buildCodeBlock(code) : '';
|
|
232
|
+
if (title && codeBlock)
|
|
233
|
+
demoCodeBlocks.set(title, codeBlock);
|
|
234
|
+
}
|
|
235
|
+
return {
|
|
236
|
+
...(installMatch ? { installationCommands: installMatch.slice(1) } : {}),
|
|
237
|
+
...(importMatch ? { importCommands: importMatch.slice(1) } : {}),
|
|
238
|
+
apiTables,
|
|
239
|
+
demoCodeBlocks,
|
|
240
|
+
mermaidDiagrams,
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
export function supplementMarkdownFromNextFlight(markdown, originalHtml) {
|
|
244
|
+
const supplement = extractNextFlightSupplement(originalHtml);
|
|
245
|
+
if (!supplement)
|
|
246
|
+
return markdown;
|
|
247
|
+
const lines = markdown.split('\n');
|
|
248
|
+
if (supplement.installationCommands?.length) {
|
|
249
|
+
const installationSection = findMarkdownSection(lines, 'Installation');
|
|
250
|
+
if (installationSection) {
|
|
251
|
+
const installBody = getSectionBody(lines, installationSection);
|
|
252
|
+
if (!/(npm|pnpm|yarn|bun|npx)\s+(install|add)/.test(installBody)) {
|
|
253
|
+
appendMarkdownSection(lines, 'Installation', buildCodeBlock(supplement.installationCommands.join('\n')));
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
if (supplement.importCommands?.length) {
|
|
258
|
+
const importSection = findMarkdownSection(lines, 'Import');
|
|
259
|
+
if (importSection) {
|
|
260
|
+
const importBody = getSectionBody(lines, importSection);
|
|
261
|
+
if (!/import\s+\{/.test(importBody)) {
|
|
262
|
+
appendMarkdownSection(lines, 'Import', buildCodeBlock(supplement.importCommands.join('\n\n')));
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
for (const [title, table] of supplement.apiTables) {
|
|
267
|
+
replaceMarkdownSection(lines, title, table);
|
|
268
|
+
}
|
|
269
|
+
for (const [title, mermaidBlock] of supplement.mermaidDiagrams) {
|
|
270
|
+
const section = findMarkdownSection(lines, title);
|
|
271
|
+
if (!section)
|
|
272
|
+
continue;
|
|
273
|
+
const sectionBody = getSectionBody(lines, section);
|
|
274
|
+
if (sectionBody.includes('```mermaid'))
|
|
275
|
+
continue;
|
|
276
|
+
const nextBody = sectionBody
|
|
277
|
+
? `${sectionBody}\n\n${mermaidBlock}`
|
|
278
|
+
: mermaidBlock;
|
|
279
|
+
replaceMarkdownSection(lines, title, nextBody);
|
|
280
|
+
}
|
|
281
|
+
for (const [title, codeBlock] of supplement.demoCodeBlocks) {
|
|
282
|
+
appendMarkdownSection(lines, title, codeBlock);
|
|
283
|
+
}
|
|
284
|
+
return lines.join('\n');
|
|
285
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"transform.d.ts","sourceRoot":"","sources":["../../src/transform/transform.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"transform.d.ts","sourceRoot":"","sources":["../../src/transform/transform.ts"],"names":[],"mappings":"AA8CA,OAAO,KAAK,EACV,gBAAgB,EAChB,iBAAiB,EACjB,gBAAgB,EAChB,uBAAuB,EACvB,aAAa,EACb,gBAAgB,EAChB,qBAAqB,EAEtB,MAAM,YAAY,CAAC;AAqCpB,UAAU,WAAW;IACnB,aAAa,EAAE,MAAM,CAAC;IACtB,SAAS,EAAE,MAAM,CAAC;CACnB;AAqJD,wBAAgB,mBAAmB,CACjC,GAAG,EAAE,MAAM,EACX,KAAK,EAAE,MAAM,EACb,MAAM,CAAC,EAAE,WAAW,GACnB,qBAAqB,GAAG,IAAI,CAE9B;AAED,wBAAgB,iBAAiB,CAC/B,OAAO,EAAE,qBAAqB,GAAG,IAAI,EACrC,OAAO,CAAC,EAAE;IAAE,SAAS,CAAC,EAAE,OAAO,CAAA;CAAE,GAChC,MAAM,CAER;AAwYD,wBAAgB,cAAc,CAC5B,IAAI,EAAE,MAAM,EACZ,GAAG,EAAE,MAAM,EACX,OAAO,GAAE;IAAE,cAAc,CAAC,EAAE,OAAO,CAAC;IAAC,MAAM,CAAC,EAAE,WAAW,CAAA;CAExD,GACA,gBAAgB,CAGlB;AAgLD,wBAAgB,cAAc,CAC5B,IAAI,EAAE,MAAM,EACZ,QAAQ,CAAC,EAAE,aAAa,EACxB,OAAO,CAAC,EAAE;IACR,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC5B,GACA,MAAM,CAsBR;AAkKD,wBAAgB,sBAAsB,CACpC,OAAO,EAAE,gBAAgB,GAAG,IAAI,EAChC,sBAAsB,EAAE,MAAM,GAAG,QAAQ,GACxC,OAAO,CAQT;AAiED,wBAAgB,gCAAgC,CAC9C,OAAO,EAAE,gBAAgB,GAAG,IAAI,GAC/B,OAAO,IAAI,gBAAgB,CAE7B;AAED,wBAAgB,0BAA0B,CACxC,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,gBAAgB,GAAG,IAAI,EAChC,aAAa,EAAE,iBAAiB,EAChC,wBAAwB,EAAE,OAAO,EACjC,eAAe,EAAE,OAAO,GACvB,aAAa,GAAG,SAAS,CAuB3B;AA+fD,wBAAgB,gCAAgC,CAC9C,IAAI,EAAE,MAAM,EACZ,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,gBAAgB,GACxB,uBAAuB,CAqCzB;AAaD,UAAU,kBAAkB;IAC1B,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,EAAE,MAAM,CAAC;IACtB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,wBAAgB,qBAAqB,IAAI,kBAAkB,GAAG,IAAI,CAEjE;AAED,wBAAsB,2BAA2B,IAAI,OAAO,CAAC,IAAI,CAAC,CAEjE;AAED,KAAK,yBAAyB,GAAG,gBAAgB,GAAG;IAAE,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAkH1E,wBAAsB,uBAAuB,CAC3C,IAAI,EAAE,MAAM,EACZ,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,gBAAgB,GACxB,OAAO,CAAC,uBAAuB,CAAC,CAElC;AAED,wBAAsB,yBAAyB,CAC7C,UAAU,EAAE,UAAU,EACtB,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,yBAAyB,GACjC,OAAO,CAAC,uBAAuB,CAAC,CAElC"}
|