mdorigin 0.1.8 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +14 -0
- package/dist/adapters/cloudflare.d.ts +42 -5
- package/dist/adapters/cloudflare.js +250 -10
- package/dist/adapters/node.js +2 -2
- package/dist/cli/build-cloudflare.js +27 -2
- package/dist/cli/build-search.js +15 -4
- package/dist/cli/help.js +8 -7
- package/dist/cli/init-cloudflare.js +7 -1
- package/dist/cli/main.js +5 -0
- package/dist/cli/search.js +16 -4
- package/dist/cli/sync-cloudflare-r2.d.ts +1 -0
- package/dist/cli/sync-cloudflare-r2.js +47 -0
- package/dist/cloudflare-runtime.d.ts +1 -1
- package/dist/cloudflare.d.ts +44 -6
- package/dist/cloudflare.js +340 -19
- package/dist/core/api.js +34 -1
- package/dist/core/content-store.d.ts +1 -0
- package/dist/core/content-store.js +8 -1
- package/dist/core/extensions.d.ts +5 -9
- package/dist/core/markdown.d.ts +1 -0
- package/dist/core/markdown.js +9 -2
- package/dist/core/request-handler.js +46 -60
- package/dist/core/site-config.d.ts +4 -10
- package/dist/core/site-config.js +18 -10
- package/dist/html/template.d.ts +5 -9
- package/dist/html/template.js +25 -25
- package/dist/html/theme.d.ts +1 -2
- package/dist/html/theme.js +2 -642
- package/dist/index-builder.js +5 -4
- package/dist/search.d.ts +27 -4
- package/dist/search.js +301 -38
- package/package.json +5 -3
- package/dist/html/template-kind.d.ts +0 -1
- package/dist/html/template-kind.js +0 -1
package/dist/search.d.ts
CHANGED
|
@@ -19,11 +19,13 @@ export interface SearchHit {
|
|
|
19
19
|
score: number;
|
|
20
20
|
};
|
|
21
21
|
}
|
|
22
|
+
export interface SearchQueryOptions {
|
|
23
|
+
topK?: number;
|
|
24
|
+
relativePathPrefix?: string;
|
|
25
|
+
metadata?: Record<string, string>;
|
|
26
|
+
}
|
|
22
27
|
export interface SearchApi {
|
|
23
|
-
search(query: string, options?:
|
|
24
|
-
topK?: number;
|
|
25
|
-
relativePathPrefix?: string;
|
|
26
|
-
}): Promise<SearchHit[]>;
|
|
28
|
+
search(query: string, options?: SearchQueryOptions): Promise<SearchHit[]>;
|
|
27
29
|
}
|
|
28
30
|
export interface SearchBundleEntry {
|
|
29
31
|
path: string;
|
|
@@ -32,6 +34,13 @@ export interface SearchBundleEntry {
|
|
|
32
34
|
text?: string;
|
|
33
35
|
base64?: string;
|
|
34
36
|
}
|
|
37
|
+
export interface ExternalSearchBundleEntry {
|
|
38
|
+
path: string;
|
|
39
|
+
mediaType: string;
|
|
40
|
+
storageKind: 'assets' | 'r2';
|
|
41
|
+
storageKey: string;
|
|
42
|
+
byteSize: number;
|
|
43
|
+
}
|
|
35
44
|
export interface BuildSearchBundleOptions {
|
|
36
45
|
rootDir: string;
|
|
37
46
|
outDir: string;
|
|
@@ -39,21 +48,35 @@ export interface BuildSearchBundleOptions {
|
|
|
39
48
|
draftMode?: 'include' | 'exclude';
|
|
40
49
|
embeddingBackend?: 'hashing' | 'model2vec';
|
|
41
50
|
model?: string;
|
|
51
|
+
incremental?: boolean;
|
|
52
|
+
cachePath?: string;
|
|
42
53
|
}
|
|
43
54
|
export interface BuildSearchBundleResult {
|
|
44
55
|
outputDir: string;
|
|
45
56
|
documentCount: number;
|
|
46
57
|
chunkCount: number;
|
|
47
58
|
vectorDimensions: number;
|
|
59
|
+
cachePath?: string;
|
|
60
|
+
incremental?: {
|
|
61
|
+
scannedDocumentCount: number;
|
|
62
|
+
newDocumentCount: number;
|
|
63
|
+
changedDocumentCount: number;
|
|
64
|
+
unchangedDocumentCount: number;
|
|
65
|
+
removedDocumentCount: number;
|
|
66
|
+
activeDocumentCount: number;
|
|
67
|
+
activeChunkCount: number;
|
|
68
|
+
};
|
|
48
69
|
}
|
|
49
70
|
export interface SearchBundleOptions {
|
|
50
71
|
indexDir: string;
|
|
51
72
|
query: string;
|
|
52
73
|
topK?: number;
|
|
53
74
|
relativePathPrefix?: string;
|
|
75
|
+
metadata?: Record<string, string>;
|
|
54
76
|
}
|
|
55
77
|
export declare function buildSearchBundle(options: BuildSearchBundleOptions): Promise<BuildSearchBundleResult>;
|
|
56
78
|
export declare function searchBundle(options: SearchBundleOptions): Promise<SearchHit[]>;
|
|
57
79
|
export declare function createSearchApiFromDirectory(indexDir: string): Promise<SearchApi>;
|
|
58
80
|
export declare function createSearchApiFromBundle(bundleEntries: SearchBundleEntry[]): SearchApi;
|
|
81
|
+
export declare function createSearchApiFromExternalBundle(bundleEntries: ExternalSearchBundleEntry[], loadResponse: (entry: ExternalSearchBundleEntry) => Promise<Response>): SearchApi;
|
|
59
82
|
export {};
|
package/dist/search.js
CHANGED
|
@@ -1,59 +1,100 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { rmSync } from 'node:fs';
|
|
2
|
+
import { mkdtemp, mkdir, readdir, readFile, realpath, stat, writeFile, } from 'node:fs/promises';
|
|
3
|
+
import os from 'node:os';
|
|
2
4
|
import path from 'node:path';
|
|
3
5
|
import { inferDirectoryContentType } from './core/content-type.js';
|
|
4
6
|
import { getDirectoryIndexCandidates } from './core/directory-index.js';
|
|
5
|
-
import { getDocumentSummary, getDocumentTitle, parseMarkdownDocument, } from './core/markdown.js';
|
|
7
|
+
import { getDocumentSummary, getDocumentTitle, parseMarkdownDocument, stripMachineOnlyMarkdownComments, stripManagedIndexBlock, } from './core/markdown.js';
|
|
8
|
+
import { isIgnoredContentName } from './core/content-store.js';
|
|
9
|
+
const OVERVIEW_CONTENT_FILENAMES = new Set(['readme.md', 'index.md', 'skill.md']);
|
|
10
|
+
const materializedSearchBundleDirectories = new Set();
|
|
11
|
+
let searchBundleDirectoryCleanupRegistered = false;
|
|
6
12
|
export async function buildSearchBundle(options) {
|
|
7
13
|
const buildModule = await loadIndexbindBuildModule();
|
|
8
14
|
const rootDir = path.resolve(options.rootDir);
|
|
15
|
+
const outputDir = path.resolve(options.outDir);
|
|
9
16
|
const documents = await collectSearchDocuments(rootDir, options.siteConfig, {
|
|
10
17
|
draftMode: options.draftMode ?? 'exclude',
|
|
11
18
|
});
|
|
12
|
-
const
|
|
19
|
+
const buildOptions = {
|
|
13
20
|
embeddingBackend: options.embeddingBackend ?? 'model2vec',
|
|
14
21
|
model: options.model,
|
|
15
22
|
sourceRootId: path.basename(rootDir),
|
|
16
23
|
sourceRootPath: rootDir,
|
|
17
|
-
}
|
|
24
|
+
};
|
|
25
|
+
if (options.incremental) {
|
|
26
|
+
const cachePath = path.resolve(options.cachePath ?? defaultSearchBuildCachePath(outputDir));
|
|
27
|
+
await mkdir(path.dirname(cachePath), { recursive: true });
|
|
28
|
+
const removedRelativePaths = await resolveRemovedRelativePaths(cachePath, documents.map((document) => document.relativePath));
|
|
29
|
+
const incrementalStats = await buildModule.updateBuildCache(cachePath, documents, buildOptions, removedRelativePaths);
|
|
30
|
+
const stats = await buildModule.exportCanonicalBundleFromBuildCache(cachePath, outputDir);
|
|
31
|
+
await writeSearchBuildState(cachePath, documents.map((document) => document.relativePath));
|
|
32
|
+
return {
|
|
33
|
+
outputDir,
|
|
34
|
+
documentCount: stats.documentCount,
|
|
35
|
+
chunkCount: stats.chunkCount,
|
|
36
|
+
vectorDimensions: stats.vectorDimensions,
|
|
37
|
+
cachePath,
|
|
38
|
+
incremental: incrementalStats,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
const stats = await buildModule.buildCanonicalBundle(outputDir, documents, buildOptions);
|
|
18
42
|
return {
|
|
19
|
-
outputDir
|
|
43
|
+
outputDir,
|
|
20
44
|
documentCount: stats.documentCount,
|
|
21
45
|
chunkCount: stats.chunkCount,
|
|
22
46
|
vectorDimensions: stats.vectorDimensions,
|
|
23
47
|
};
|
|
24
48
|
}
|
|
25
49
|
export async function searchBundle(options) {
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
return rerankSearchHits(await index.search(options.query, {
|
|
50
|
+
const searchContext = await openSearchContextFromDirectory(path.resolve(options.indexDir));
|
|
51
|
+
return rerankSearchHits(hydrateSearchHits(await searchContext.index.search(options.query, buildIndexbindSearchOptions({
|
|
29
52
|
topK: options.topK ?? 10,
|
|
30
53
|
relativePathPrefix: options.relativePathPrefix,
|
|
31
|
-
|
|
54
|
+
metadata: options.metadata,
|
|
55
|
+
})), searchContext.documentsById));
|
|
32
56
|
}
|
|
33
57
|
export async function createSearchApiFromDirectory(indexDir) {
|
|
34
|
-
const
|
|
35
|
-
const index = await webModule.openWebIndex(path.resolve(indexDir));
|
|
58
|
+
const searchContext = await openSearchContextFromDirectory(path.resolve(indexDir));
|
|
36
59
|
return {
|
|
37
60
|
async search(query, options) {
|
|
38
|
-
return rerankSearchHits(await index.search(query, {
|
|
61
|
+
return rerankSearchHits(hydrateSearchHits(await searchContext.index.search(query, buildIndexbindSearchOptions({
|
|
39
62
|
topK: options?.topK,
|
|
40
63
|
relativePathPrefix: options?.relativePathPrefix,
|
|
41
|
-
|
|
64
|
+
metadata: options?.metadata,
|
|
65
|
+
})), searchContext.documentsById));
|
|
42
66
|
},
|
|
43
67
|
};
|
|
44
68
|
}
|
|
45
69
|
export function createSearchApiFromBundle(bundleEntries) {
|
|
46
|
-
let
|
|
70
|
+
let searchContextPromise = null;
|
|
71
|
+
return {
|
|
72
|
+
async search(query, options) {
|
|
73
|
+
if (searchContextPromise === null) {
|
|
74
|
+
searchContextPromise = openSearchContextFromBundle(bundleEntries, async (entry) => createInlineSearchBundleResponse(entry));
|
|
75
|
+
}
|
|
76
|
+
const searchContext = await searchContextPromise;
|
|
77
|
+
return rerankSearchHits(hydrateSearchHits(await searchContext.index.search(query, buildIndexbindSearchOptions({
|
|
78
|
+
topK: options?.topK,
|
|
79
|
+
relativePathPrefix: options?.relativePathPrefix,
|
|
80
|
+
metadata: options?.metadata,
|
|
81
|
+
})), searchContext.documentsById));
|
|
82
|
+
},
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
export function createSearchApiFromExternalBundle(bundleEntries, loadResponse) {
|
|
86
|
+
let searchContextPromise = null;
|
|
47
87
|
return {
|
|
48
88
|
async search(query, options) {
|
|
49
|
-
if (
|
|
50
|
-
|
|
89
|
+
if (searchContextPromise === null) {
|
|
90
|
+
searchContextPromise = openSearchContextFromBundle(bundleEntries, loadResponse);
|
|
51
91
|
}
|
|
52
|
-
const
|
|
53
|
-
return rerankSearchHits(await index.search(query, {
|
|
92
|
+
const searchContext = await searchContextPromise;
|
|
93
|
+
return rerankSearchHits(hydrateSearchHits(await searchContext.index.search(query, buildIndexbindSearchOptions({
|
|
54
94
|
topK: options?.topK,
|
|
55
95
|
relativePathPrefix: options?.relativePathPrefix,
|
|
56
|
-
|
|
96
|
+
metadata: options?.metadata,
|
|
97
|
+
})), searchContext.documentsById));
|
|
57
98
|
},
|
|
58
99
|
};
|
|
59
100
|
}
|
|
@@ -77,12 +118,15 @@ async function collectSearchDocuments(rootDir, siteConfig, options) {
|
|
|
77
118
|
canonicalUrl: absoluteCanonicalUrl,
|
|
78
119
|
title: getDocumentTitle(parsed.meta, parsed.body, fallbackTitleFromRelativePath(document.relativePath)),
|
|
79
120
|
summary: getDocumentSummary(parsed.meta, parsed.body),
|
|
80
|
-
content:
|
|
121
|
+
content: buildSearchableMarkdownBody(parsed.body),
|
|
81
122
|
metadata: buildSearchMetadata(document.relativePath, canonicalPath, parsed.meta, siteConfig),
|
|
82
123
|
});
|
|
83
124
|
}
|
|
84
125
|
return documents;
|
|
85
126
|
}
|
|
127
|
+
function buildSearchableMarkdownBody(markdownBody) {
|
|
128
|
+
return stripMachineOnlyMarkdownComments(stripManagedIndexBlock(markdownBody)).trim();
|
|
129
|
+
}
|
|
86
130
|
async function listSearchDocuments(rootDir) {
|
|
87
131
|
const results = [];
|
|
88
132
|
await walkDirectory(rootDir, '', new Set(), results);
|
|
@@ -102,7 +146,7 @@ async function walkDirectory(absoluteDirectoryPath, relativeDirectoryPath, visit
|
|
|
102
146
|
results.push(indexFile);
|
|
103
147
|
}
|
|
104
148
|
for (const entry of entries) {
|
|
105
|
-
if (entry.name
|
|
149
|
+
if (isIgnoredContentName(entry.name)) {
|
|
106
150
|
continue;
|
|
107
151
|
}
|
|
108
152
|
const absoluteEntryPath = path.join(absoluteDirectoryPath, entry.name);
|
|
@@ -160,7 +204,7 @@ async function inspectDirectoryShape(directoryPath) {
|
|
|
160
204
|
let hasExtraMarkdownFiles = false;
|
|
161
205
|
let hasAssetFiles = false;
|
|
162
206
|
for (const entry of entries) {
|
|
163
|
-
if (entry.name
|
|
207
|
+
if (isIgnoredContentName(entry.name)) {
|
|
164
208
|
continue;
|
|
165
209
|
}
|
|
166
210
|
const absoluteEntryPath = path.join(directoryPath, entry.name);
|
|
@@ -196,6 +240,8 @@ function buildSearchMetadata(relativePath, canonicalPath, meta, siteConfig) {
|
|
|
196
240
|
markdownPath: `/${relativePath}`,
|
|
197
241
|
canonicalPath,
|
|
198
242
|
siteTitle: siteConfig.siteTitle,
|
|
243
|
+
section: getSearchSection(relativePath),
|
|
244
|
+
isOverview: isOverviewContentPath(relativePath),
|
|
199
245
|
};
|
|
200
246
|
if (meta.type === 'page' || meta.type === 'post') {
|
|
201
247
|
metadata.type = meta.type;
|
|
@@ -211,6 +257,17 @@ function buildSearchMetadata(relativePath, canonicalPath, meta, siteConfig) {
|
|
|
211
257
|
}
|
|
212
258
|
return metadata;
|
|
213
259
|
}
|
|
260
|
+
function getSearchSection(relativePath) {
|
|
261
|
+
const directory = path.posix.dirname(relativePath.replaceAll('\\', '/'));
|
|
262
|
+
if (directory === '.') {
|
|
263
|
+
return '';
|
|
264
|
+
}
|
|
265
|
+
const [firstSegment] = directory.split('/', 1);
|
|
266
|
+
return firstSegment ?? '';
|
|
267
|
+
}
|
|
268
|
+
function isOverviewContentPath(relativePath) {
|
|
269
|
+
return OVERVIEW_CONTENT_FILENAMES.has(path.posix.basename(relativePath).toLowerCase());
|
|
270
|
+
}
|
|
214
271
|
function fallbackTitleFromRelativePath(relativePath) {
|
|
215
272
|
const baseName = path.posix.basename(relativePath);
|
|
216
273
|
if (DIRECTORY_INDEX_FILENAMES_LOWER.has(baseName.toLowerCase())) {
|
|
@@ -249,6 +306,33 @@ async function pathExists(filePath) {
|
|
|
249
306
|
throw error;
|
|
250
307
|
}
|
|
251
308
|
}
|
|
309
|
+
function defaultSearchBuildCachePath(outputDir) {
|
|
310
|
+
return path.join(path.dirname(outputDir), `${path.basename(outputDir)}.indexbind-cache.sqlite`);
|
|
311
|
+
}
|
|
312
|
+
function getSearchBuildStatePath(cachePath) {
|
|
313
|
+
return `${cachePath}.state.json`;
|
|
314
|
+
}
|
|
315
|
+
async function resolveRemovedRelativePaths(cachePath, currentRelativePaths) {
|
|
316
|
+
const previousRelativePaths = await readSearchBuildState(cachePath);
|
|
317
|
+
const current = new Set(currentRelativePaths);
|
|
318
|
+
return previousRelativePaths.filter((relativePath) => !current.has(relativePath));
|
|
319
|
+
}
|
|
320
|
+
async function readSearchBuildState(cachePath) {
|
|
321
|
+
const statePath = getSearchBuildStatePath(cachePath);
|
|
322
|
+
if (!(await pathExists(statePath))) {
|
|
323
|
+
return [];
|
|
324
|
+
}
|
|
325
|
+
const parsed = JSON.parse(await readFile(statePath, 'utf8'));
|
|
326
|
+
return Array.isArray(parsed.relativePaths)
|
|
327
|
+
? parsed.relativePaths.filter((value) => typeof value === 'string')
|
|
328
|
+
: [];
|
|
329
|
+
}
|
|
330
|
+
async function writeSearchBuildState(cachePath, relativePaths) {
|
|
331
|
+
await writeFile(getSearchBuildStatePath(cachePath), JSON.stringify({
|
|
332
|
+
version: 1,
|
|
333
|
+
relativePaths: [...relativePaths].sort((left, right) => left.localeCompare(right)),
|
|
334
|
+
}, null, 2), 'utf8');
|
|
335
|
+
}
|
|
252
336
|
function isIgnoredSkillSupportDirectory(name) {
|
|
253
337
|
return (name === 'scripts' ||
|
|
254
338
|
name === 'references' ||
|
|
@@ -279,8 +363,10 @@ async function loadIndexbindCloudflareModule() {
|
|
|
279
363
|
throw new Error(`Search query requires the optional package "indexbind". Install it first, for example: npm install indexbind`, { cause: error instanceof Error ? error : undefined });
|
|
280
364
|
}
|
|
281
365
|
}
|
|
282
|
-
async function
|
|
283
|
-
|
|
366
|
+
async function openWebIndexFromVirtualBundle(bundleEntries, loadResponse) {
|
|
367
|
+
if (isNodeRuntime()) {
|
|
368
|
+
return openWebIndexFromMaterializedBundle(bundleEntries, loadResponse);
|
|
369
|
+
}
|
|
284
370
|
const baseUrl = 'https://mdorigin-search.invalid/';
|
|
285
371
|
const originalFetch = globalThis.fetch;
|
|
286
372
|
const bundleMap = new Map(bundleEntries.map((entry) => [new URL(entry.path, baseUrl).toString(), entry]));
|
|
@@ -292,31 +378,209 @@ async function openWebIndexFromBundle(bundleEntries) {
|
|
|
292
378
|
: input.url;
|
|
293
379
|
const entry = bundleMap.get(requestUrl);
|
|
294
380
|
if (entry) {
|
|
295
|
-
|
|
296
|
-
const binaryBody = decodeBase64(entry.base64 ?? '');
|
|
297
|
-
const body = entry.kind === 'text'
|
|
298
|
-
? entry.text ?? ''
|
|
299
|
-
: new Blob([new Uint8Array(binaryBody)], {
|
|
300
|
-
type: entry.mediaType,
|
|
301
|
-
});
|
|
302
|
-
return new Response(body, {
|
|
303
|
-
status: 200,
|
|
304
|
-
headers,
|
|
305
|
-
});
|
|
381
|
+
return loadResponse(entry);
|
|
306
382
|
}
|
|
307
383
|
return originalFetch(input, init);
|
|
308
384
|
};
|
|
309
385
|
try {
|
|
310
|
-
|
|
386
|
+
try {
|
|
387
|
+
const cloudflareModule = await loadIndexbindCloudflareModule();
|
|
388
|
+
return await cloudflareModule.openWebIndex(new URL(baseUrl));
|
|
389
|
+
}
|
|
390
|
+
catch (error) {
|
|
391
|
+
if (!isCloudflareWasmImportError(error)) {
|
|
392
|
+
throw error;
|
|
393
|
+
}
|
|
394
|
+
const webModule = await loadIndexbindWebModule();
|
|
395
|
+
return await webModule.openWebIndex(new URL(baseUrl));
|
|
396
|
+
}
|
|
311
397
|
}
|
|
312
398
|
finally {
|
|
313
399
|
globalThis.fetch = originalFetch;
|
|
314
400
|
}
|
|
315
401
|
}
|
|
402
|
+
async function openSearchContextFromDirectory(indexDir) {
|
|
403
|
+
const webModule = await loadIndexbindWebModule();
|
|
404
|
+
const [index, documentsById] = await Promise.all([
|
|
405
|
+
webModule.openWebIndex(indexDir),
|
|
406
|
+
readSearchBundleDocumentsFromDirectory(indexDir),
|
|
407
|
+
]);
|
|
408
|
+
return { index, documentsById };
|
|
409
|
+
}
|
|
410
|
+
async function openSearchContextFromBundle(bundleEntries, loadResponse) {
|
|
411
|
+
const [index, documentsById] = await Promise.all([
|
|
412
|
+
openWebIndexFromVirtualBundle(bundleEntries, loadResponse),
|
|
413
|
+
readSearchBundleDocumentsFromResponses(bundleEntries, loadResponse),
|
|
414
|
+
]);
|
|
415
|
+
return { index, documentsById };
|
|
416
|
+
}
|
|
417
|
+
async function openWebIndexFromMaterializedBundle(bundleEntries, loadResponse) {
|
|
418
|
+
const tempDir = await mkdtemp(path.join(os.tmpdir(), 'mdorigin-search-bundle-'));
|
|
419
|
+
registerMaterializedSearchBundleDirectory(tempDir);
|
|
420
|
+
for (const entry of bundleEntries) {
|
|
421
|
+
const response = await loadResponse(entry);
|
|
422
|
+
if (!response.ok) {
|
|
423
|
+
throw new Error(`Failed to materialize search bundle file ${entry.path}: ${response.status} ${response.statusText}`);
|
|
424
|
+
}
|
|
425
|
+
const outputPath = resolveMaterializedSearchBundlePath(tempDir, entry.path);
|
|
426
|
+
await mkdir(path.dirname(outputPath), { recursive: true });
|
|
427
|
+
if (entry.mediaType.startsWith('text/') || entry.mediaType.includes('json')) {
|
|
428
|
+
await writeFile(outputPath, await response.text(), 'utf8');
|
|
429
|
+
continue;
|
|
430
|
+
}
|
|
431
|
+
await writeFile(outputPath, new Uint8Array(await response.arrayBuffer()));
|
|
432
|
+
}
|
|
433
|
+
const webModule = await loadIndexbindWebModule();
|
|
434
|
+
return webModule.openWebIndex(tempDir);
|
|
435
|
+
}
|
|
436
|
+
function createInlineSearchBundleResponse(entry) {
|
|
437
|
+
const headers = new Headers({ 'content-type': entry.mediaType });
|
|
438
|
+
const binaryBody = decodeBase64(entry.base64 ?? '');
|
|
439
|
+
const body = entry.kind === 'text'
|
|
440
|
+
? entry.text ?? ''
|
|
441
|
+
: new Blob([new Uint8Array(binaryBody)], {
|
|
442
|
+
type: entry.mediaType,
|
|
443
|
+
});
|
|
444
|
+
return new Response(body, {
|
|
445
|
+
status: 200,
|
|
446
|
+
headers,
|
|
447
|
+
});
|
|
448
|
+
}
|
|
449
|
+
function isCloudflareWasmImportError(error) {
|
|
450
|
+
const targetMessages = [
|
|
451
|
+
'Unknown file extension ".wasm"',
|
|
452
|
+
'WebAssembly.Module(): Argument 0 must be a buffer source',
|
|
453
|
+
];
|
|
454
|
+
const visited = new Set();
|
|
455
|
+
let current = error;
|
|
456
|
+
while (current && typeof current === 'object' && !visited.has(current)) {
|
|
457
|
+
visited.add(current);
|
|
458
|
+
if (current instanceof Error) {
|
|
459
|
+
const currentMessage = current.message;
|
|
460
|
+
if (targetMessages.some((message) => currentMessage.includes(message))) {
|
|
461
|
+
return true;
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
const cause = current.cause;
|
|
465
|
+
if (!cause) {
|
|
466
|
+
break;
|
|
467
|
+
}
|
|
468
|
+
current = cause;
|
|
469
|
+
}
|
|
470
|
+
return false;
|
|
471
|
+
}
|
|
316
472
|
function decodeBase64(value) {
|
|
317
473
|
const decoded = atob(value);
|
|
318
474
|
return Uint8Array.from(decoded, (character) => character.charCodeAt(0));
|
|
319
475
|
}
|
|
476
|
+
function buildIndexbindSearchOptions(options) {
|
|
477
|
+
const normalized = {};
|
|
478
|
+
if (typeof options.topK === 'number' &&
|
|
479
|
+
Number.isFinite(options.topK) &&
|
|
480
|
+
Number.isInteger(options.topK) &&
|
|
481
|
+
options.topK > 0) {
|
|
482
|
+
normalized.topK = options.topK;
|
|
483
|
+
}
|
|
484
|
+
if (typeof options.relativePathPrefix === 'string' && options.relativePathPrefix !== '') {
|
|
485
|
+
normalized.relativePathPrefix = options.relativePathPrefix;
|
|
486
|
+
}
|
|
487
|
+
if (options.metadata && Object.keys(options.metadata).length > 0) {
|
|
488
|
+
normalized.metadata = options.metadata;
|
|
489
|
+
}
|
|
490
|
+
return Object.keys(normalized).length > 0 ? normalized : undefined;
|
|
491
|
+
}
|
|
492
|
+
function hydrateSearchHits(hits, documentsById) {
|
|
493
|
+
return hits.map((hit) => {
|
|
494
|
+
const document = documentsById.get(hit.docId);
|
|
495
|
+
if (!document) {
|
|
496
|
+
return hit;
|
|
497
|
+
}
|
|
498
|
+
return {
|
|
499
|
+
...hit,
|
|
500
|
+
canonicalUrl: hit.canonicalUrl ?? document.canonicalUrl ?? undefined,
|
|
501
|
+
title: hit.title ?? document.title ?? undefined,
|
|
502
|
+
summary: hit.summary ?? document.summary ?? undefined,
|
|
503
|
+
metadata: Object.keys(hit.metadata).length > 0
|
|
504
|
+
? hit.metadata
|
|
505
|
+
: document.metadata ?? hit.metadata,
|
|
506
|
+
};
|
|
507
|
+
});
|
|
508
|
+
}
|
|
509
|
+
async function readSearchBundleDocumentsFromDirectory(indexDir) {
|
|
510
|
+
const manifest = JSON.parse(await readFile(path.join(indexDir, 'manifest.json'), 'utf8'));
|
|
511
|
+
const documentsFile = manifest.files?.documents;
|
|
512
|
+
if (!documentsFile) {
|
|
513
|
+
return new Map();
|
|
514
|
+
}
|
|
515
|
+
const documents = JSON.parse(await readFile(path.join(indexDir, documentsFile), 'utf8'));
|
|
516
|
+
return new Map(documents.map((document) => [document.docId, document]));
|
|
517
|
+
}
|
|
518
|
+
async function readSearchBundleDocumentsFromResponses(bundleEntries, loadResponse) {
|
|
519
|
+
const manifestEntry = bundleEntries.find((entry) => entry.path === 'manifest.json');
|
|
520
|
+
if (!manifestEntry) {
|
|
521
|
+
return new Map();
|
|
522
|
+
}
|
|
523
|
+
const manifest = JSON.parse(await loadBundleResponseText(manifestEntry, loadResponse));
|
|
524
|
+
const documentsFile = manifest.files?.documents;
|
|
525
|
+
if (!documentsFile) {
|
|
526
|
+
return new Map();
|
|
527
|
+
}
|
|
528
|
+
const documentsEntry = bundleEntries.find((entry) => entry.path === documentsFile);
|
|
529
|
+
if (!documentsEntry) {
|
|
530
|
+
return new Map();
|
|
531
|
+
}
|
|
532
|
+
const documents = JSON.parse(await loadBundleResponseText(documentsEntry, loadResponse));
|
|
533
|
+
return new Map(documents.map((document) => [document.docId, document]));
|
|
534
|
+
}
|
|
535
|
+
async function loadBundleResponseText(entry, loadResponse) {
|
|
536
|
+
const response = await loadResponse(entry);
|
|
537
|
+
if (!response.ok) {
|
|
538
|
+
throw new Error(`Failed to read search bundle file ${entry.path}: ${response.status} ${response.statusText}`);
|
|
539
|
+
}
|
|
540
|
+
return response.text();
|
|
541
|
+
}
|
|
542
|
+
function isNodeRuntime() {
|
|
543
|
+
return (typeof process !== 'undefined' &&
|
|
544
|
+
typeof process.versions === 'object' &&
|
|
545
|
+
typeof process.versions.node === 'string');
|
|
546
|
+
}
|
|
547
|
+
function resolveMaterializedSearchBundlePath(baseDir, entryPath) {
|
|
548
|
+
if (path.isAbsolute(entryPath)) {
|
|
549
|
+
throw new Error(`Search bundle path must be relative: ${entryPath}`);
|
|
550
|
+
}
|
|
551
|
+
const normalizedEntryPath = entryPath.replaceAll('/', path.sep);
|
|
552
|
+
const outputPath = path.resolve(baseDir, normalizedEntryPath);
|
|
553
|
+
const relativeOutputPath = path.relative(baseDir, outputPath);
|
|
554
|
+
if (relativeOutputPath === '' ||
|
|
555
|
+
relativeOutputPath.startsWith(`..${path.sep}`) ||
|
|
556
|
+
relativeOutputPath === '..' ||
|
|
557
|
+
path.isAbsolute(relativeOutputPath)) {
|
|
558
|
+
throw new Error(`Search bundle path escapes materialized bundle directory: ${entryPath}`);
|
|
559
|
+
}
|
|
560
|
+
return outputPath;
|
|
561
|
+
}
|
|
562
|
+
function registerMaterializedSearchBundleDirectory(directoryPath) {
|
|
563
|
+
materializedSearchBundleDirectories.add(directoryPath);
|
|
564
|
+
if (searchBundleDirectoryCleanupRegistered || !isNodeRuntime()) {
|
|
565
|
+
return;
|
|
566
|
+
}
|
|
567
|
+
const cleanup = () => {
|
|
568
|
+
for (const stagedDirectory of materializedSearchBundleDirectories) {
|
|
569
|
+
rmSync(stagedDirectory, { recursive: true, force: true });
|
|
570
|
+
}
|
|
571
|
+
materializedSearchBundleDirectories.clear();
|
|
572
|
+
};
|
|
573
|
+
process.once('exit', cleanup);
|
|
574
|
+
process.once('SIGINT', () => {
|
|
575
|
+
cleanup();
|
|
576
|
+
process.exit(130);
|
|
577
|
+
});
|
|
578
|
+
process.once('SIGTERM', () => {
|
|
579
|
+
cleanup();
|
|
580
|
+
process.exit(143);
|
|
581
|
+
});
|
|
582
|
+
searchBundleDirectoryCleanupRegistered = true;
|
|
583
|
+
}
|
|
320
584
|
function rerankSearchHits(hits) {
|
|
321
585
|
const remaining = [...hits];
|
|
322
586
|
const ordered = [];
|
|
@@ -359,8 +623,7 @@ function compareHits(left, right) {
|
|
|
359
623
|
return left.relativePath.localeCompare(right.relativePath);
|
|
360
624
|
}
|
|
361
625
|
function isOverviewSearchHit(hit) {
|
|
362
|
-
|
|
363
|
-
return baseName === 'readme.md' || baseName === 'index.md';
|
|
626
|
+
return OVERVIEW_CONTENT_FILENAMES.has(path.posix.basename(hit.relativePath).toLowerCase());
|
|
364
627
|
}
|
|
365
628
|
function getTopLevelSection(relativePath) {
|
|
366
629
|
const normalized = relativePath.replaceAll('\\', '/');
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mdorigin",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Markdown-first publishing for humans and agents.",
|
|
6
6
|
"repository": {
|
|
@@ -66,9 +66,11 @@
|
|
|
66
66
|
},
|
|
67
67
|
"dependencies": {
|
|
68
68
|
"gray-matter": "^4.0.3",
|
|
69
|
+
"rehype-raw": "^7.0.0",
|
|
70
|
+
"rehype-stringify": "^10.0.1",
|
|
69
71
|
"remark": "^15.0.1",
|
|
70
72
|
"remark-gfm": "^4.0.1",
|
|
71
|
-
"remark-
|
|
73
|
+
"remark-rehype": "^11.1.2",
|
|
72
74
|
"tsx": "^4.20.5"
|
|
73
75
|
},
|
|
74
76
|
"devDependencies": {
|
|
@@ -76,6 +78,6 @@
|
|
|
76
78
|
"typescript": "^5.9.2"
|
|
77
79
|
},
|
|
78
80
|
"optionalDependencies": {
|
|
79
|
-
"indexbind": "^0.
|
|
81
|
+
"indexbind": "^0.3.0"
|
|
80
82
|
}
|
|
81
83
|
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export type TemplateName = 'document' | 'catalog';
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|