@arabold/docs-mcp-server 1.28.0 → 1.29.0
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 +1 -1
- package/dist/assets/main.css +1 -1
- package/dist/assets/main.js +42 -0
- package/dist/assets/main.js.map +1 -1
- package/dist/index.js +558 -210
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/public/assets/main.css +1 -1
- package/public/assets/main.js +42 -0
- package/public/assets/main.js.map +1 -1
package/dist/index.js
CHANGED
|
@@ -56,7 +56,7 @@ import { fastifyTRPCPlugin } from "@trpc/server/adapters/fastify";
|
|
|
56
56
|
import { applyWSSHandler } from "@trpc/server/adapters/ws";
|
|
57
57
|
import { observable } from "@trpc/server/observable";
|
|
58
58
|
import { z as z$1 } from "zod";
|
|
59
|
-
import {
|
|
59
|
+
import { jsx, jsxs, Fragment } from "@kitajs/html/jsx-runtime";
|
|
60
60
|
import DOMPurify from "dompurify";
|
|
61
61
|
import { escapeHtml } from "@kitajs/html";
|
|
62
62
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
@@ -1978,6 +1978,8 @@ const DEFAULT_HOST = "127.0.0.1";
|
|
|
1978
1978
|
const DEFAULT_PAGE_TIMEOUT = 5e3;
|
|
1979
1979
|
const FETCHER_MAX_RETRIES = 6;
|
|
1980
1980
|
const FETCHER_BASE_DELAY = 1e3;
|
|
1981
|
+
const FETCHER_MAX_CACHE_ITEMS = 200;
|
|
1982
|
+
const FETCHER_MAX_CACHE_ITEM_SIZE_BYTES = 500 * 1024;
|
|
1981
1983
|
const SPLITTER_MIN_CHUNK_SIZE = 500;
|
|
1982
1984
|
const SPLITTER_PREFERRED_CHUNK_SIZE = 1500;
|
|
1983
1985
|
const SPLITTER_MAX_CHUNK_SIZE = 5e3;
|
|
@@ -5497,8 +5499,78 @@ var ScrapeMode = /* @__PURE__ */ ((ScrapeMode2) => {
|
|
|
5497
5499
|
ScrapeMode2["Auto"] = "auto";
|
|
5498
5500
|
return ScrapeMode2;
|
|
5499
5501
|
})(ScrapeMode || {});
|
|
5502
|
+
class SimpleMemoryCache {
|
|
5503
|
+
cache;
|
|
5504
|
+
maxSize;
|
|
5505
|
+
constructor(maxSize) {
|
|
5506
|
+
if (maxSize <= 0) {
|
|
5507
|
+
throw new Error("maxSize must be positive");
|
|
5508
|
+
}
|
|
5509
|
+
this.cache = /* @__PURE__ */ new Map();
|
|
5510
|
+
this.maxSize = maxSize;
|
|
5511
|
+
}
|
|
5512
|
+
/**
|
|
5513
|
+
* Retrieve a value from the cache.
|
|
5514
|
+
* Marks the key as recently used (moves to end of Map).
|
|
5515
|
+
*/
|
|
5516
|
+
get(key) {
|
|
5517
|
+
const value = this.cache.get(key);
|
|
5518
|
+
if (value !== void 0) {
|
|
5519
|
+
this.cache.delete(key);
|
|
5520
|
+
this.cache.set(key, value);
|
|
5521
|
+
}
|
|
5522
|
+
return value;
|
|
5523
|
+
}
|
|
5524
|
+
/**
|
|
5525
|
+
* Store a value in the cache.
|
|
5526
|
+
* If cache is full, evicts the oldest entry first.
|
|
5527
|
+
*/
|
|
5528
|
+
set(key, value) {
|
|
5529
|
+
if (this.cache.has(key)) {
|
|
5530
|
+
this.cache.delete(key);
|
|
5531
|
+
} else if (this.cache.size >= this.maxSize) {
|
|
5532
|
+
const oldestKey = this.cache.keys().next().value;
|
|
5533
|
+
if (oldestKey !== void 0) {
|
|
5534
|
+
this.cache.delete(oldestKey);
|
|
5535
|
+
}
|
|
5536
|
+
}
|
|
5537
|
+
this.cache.set(key, value);
|
|
5538
|
+
}
|
|
5539
|
+
/**
|
|
5540
|
+
* Check if a key exists in the cache.
|
|
5541
|
+
* Marks the key as recently used (moves to end of Map) to maintain LRU semantics.
|
|
5542
|
+
*/
|
|
5543
|
+
has(key) {
|
|
5544
|
+
const exists = this.cache.has(key);
|
|
5545
|
+
if (exists) {
|
|
5546
|
+
const value = this.cache.get(key);
|
|
5547
|
+
if (value !== void 0) {
|
|
5548
|
+
this.cache.delete(key);
|
|
5549
|
+
this.cache.set(key, value);
|
|
5550
|
+
}
|
|
5551
|
+
}
|
|
5552
|
+
return exists;
|
|
5553
|
+
}
|
|
5554
|
+
/**
|
|
5555
|
+
* Get current cache size.
|
|
5556
|
+
*/
|
|
5557
|
+
get size() {
|
|
5558
|
+
return this.cache.size;
|
|
5559
|
+
}
|
|
5560
|
+
/**
|
|
5561
|
+
* Clear all entries from the cache.
|
|
5562
|
+
*/
|
|
5563
|
+
clear() {
|
|
5564
|
+
this.cache.clear();
|
|
5565
|
+
}
|
|
5566
|
+
}
|
|
5500
5567
|
class HtmlPlaywrightMiddleware {
|
|
5501
5568
|
browser = null;
|
|
5569
|
+
// Static LRU cache shared across all instances for all fetched resources
|
|
5570
|
+
// Max 200 entries, each limited in size to prevent caching large resources
|
|
5571
|
+
static resourceCache = new SimpleMemoryCache(
|
|
5572
|
+
FETCHER_MAX_CACHE_ITEMS
|
|
5573
|
+
);
|
|
5502
5574
|
/**
|
|
5503
5575
|
* Initializes the Playwright browser instance.
|
|
5504
5576
|
* Consider making this more robust (e.g., lazy initialization, singleton).
|
|
@@ -5843,25 +5915,97 @@ class HtmlPlaywrightMiddleware {
|
|
|
5843
5915
|
return [];
|
|
5844
5916
|
}
|
|
5845
5917
|
}
|
|
5918
|
+
/**
|
|
5919
|
+
* Sets up caching route interception for a Playwright page.
|
|
5920
|
+
* This handles:
|
|
5921
|
+
* - Aborting non-essential resources (images, fonts, media)
|
|
5922
|
+
* - Caching GET requests to speed up subsequent loads
|
|
5923
|
+
* - Forwarding custom headers and credentials for same-origin requests
|
|
5924
|
+
*
|
|
5925
|
+
* @param page The Playwright page to set up routing for
|
|
5926
|
+
* @param customHeaders Custom headers to forward with requests
|
|
5927
|
+
* @param credentials Optional credentials for same-origin requests
|
|
5928
|
+
* @param origin The origin for same-origin credential checking
|
|
5929
|
+
*/
|
|
5930
|
+
async setupCachingRouteInterception(page, customHeaders = {}, credentials, origin) {
|
|
5931
|
+
await page.route("**/*", async (route) => {
|
|
5932
|
+
const reqUrl = route.request().url();
|
|
5933
|
+
const reqOrigin = (() => {
|
|
5934
|
+
try {
|
|
5935
|
+
return new URL(reqUrl).origin;
|
|
5936
|
+
} catch {
|
|
5937
|
+
return null;
|
|
5938
|
+
}
|
|
5939
|
+
})();
|
|
5940
|
+
const resourceType = route.request().resourceType();
|
|
5941
|
+
if (["image", "font", "media"].includes(resourceType)) {
|
|
5942
|
+
return route.abort();
|
|
5943
|
+
}
|
|
5944
|
+
if (route.request().method() === "GET") {
|
|
5945
|
+
const cached = HtmlPlaywrightMiddleware.resourceCache.get(reqUrl);
|
|
5946
|
+
if (cached !== void 0) {
|
|
5947
|
+
logger.debug(`✓ Cache hit for ${resourceType}: ${reqUrl}`);
|
|
5948
|
+
return route.fulfill({
|
|
5949
|
+
status: 200,
|
|
5950
|
+
contentType: cached.contentType,
|
|
5951
|
+
body: cached.body
|
|
5952
|
+
});
|
|
5953
|
+
}
|
|
5954
|
+
const headers2 = mergePlaywrightHeaders(
|
|
5955
|
+
route.request().headers(),
|
|
5956
|
+
customHeaders,
|
|
5957
|
+
credentials,
|
|
5958
|
+
origin,
|
|
5959
|
+
reqOrigin ?? void 0
|
|
5960
|
+
);
|
|
5961
|
+
const response = await route.fetch({ headers: headers2 });
|
|
5962
|
+
const body = await response.text();
|
|
5963
|
+
if (response.status() >= 200 && response.status() < 300 && body.length > 0) {
|
|
5964
|
+
const contentSizeBytes = Buffer.byteLength(body, "utf8");
|
|
5965
|
+
if (contentSizeBytes <= FETCHER_MAX_CACHE_ITEM_SIZE_BYTES) {
|
|
5966
|
+
const contentType = response.headers()["content-type"] || "application/octet-stream";
|
|
5967
|
+
HtmlPlaywrightMiddleware.resourceCache.set(reqUrl, { body, contentType });
|
|
5968
|
+
logger.debug(
|
|
5969
|
+
`Cached ${resourceType}: ${reqUrl} (${contentSizeBytes} bytes, cache size: ${HtmlPlaywrightMiddleware.resourceCache.size})`
|
|
5970
|
+
);
|
|
5971
|
+
} else {
|
|
5972
|
+
logger.debug(
|
|
5973
|
+
`Resource too large to cache: ${reqUrl} (${contentSizeBytes} bytes > ${FETCHER_MAX_CACHE_ITEM_SIZE_BYTES} bytes limit)`
|
|
5974
|
+
);
|
|
5975
|
+
}
|
|
5976
|
+
}
|
|
5977
|
+
return route.fulfill({ response });
|
|
5978
|
+
}
|
|
5979
|
+
const headers = mergePlaywrightHeaders(
|
|
5980
|
+
route.request().headers(),
|
|
5981
|
+
customHeaders,
|
|
5982
|
+
credentials,
|
|
5983
|
+
origin,
|
|
5984
|
+
reqOrigin ?? void 0
|
|
5985
|
+
);
|
|
5986
|
+
return route.continue({ headers });
|
|
5987
|
+
});
|
|
5988
|
+
}
|
|
5846
5989
|
/**
|
|
5847
5990
|
* Fetches content from a frame URL by navigating to it in a new page.
|
|
5991
|
+
* Uses LRU cache to avoid re-fetching identical frames across multiple pages.
|
|
5848
5992
|
*
|
|
5849
5993
|
* @param parentPage The parent page (used to resolve relative URLs and share context)
|
|
5850
5994
|
* @param frameUrl The URL of the frame to fetch content from
|
|
5851
5995
|
* @returns The HTML content of the frame
|
|
5852
5996
|
*/
|
|
5853
5997
|
async fetchFrameContent(parentPage, frameUrl) {
|
|
5998
|
+
const resolvedUrl = new URL(frameUrl, parentPage.url()).href;
|
|
5999
|
+
const cached = HtmlPlaywrightMiddleware.resourceCache.get(resolvedUrl);
|
|
6000
|
+
if (cached !== void 0) {
|
|
6001
|
+
logger.debug(`✓ Cache hit for frame: ${resolvedUrl}`);
|
|
6002
|
+
return cached.body;
|
|
6003
|
+
}
|
|
6004
|
+
logger.debug(`Cache miss for frame: ${resolvedUrl}`);
|
|
5854
6005
|
let framePage = null;
|
|
5855
6006
|
try {
|
|
5856
|
-
const resolvedUrl = new URL(frameUrl, parentPage.url()).href;
|
|
5857
6007
|
framePage = await parentPage.context().newPage();
|
|
5858
|
-
await
|
|
5859
|
-
const resourceType = route.request().resourceType();
|
|
5860
|
-
if (["image", "font", "media"].includes(resourceType)) {
|
|
5861
|
-
return route.abort();
|
|
5862
|
-
}
|
|
5863
|
-
return route.continue();
|
|
5864
|
-
});
|
|
6008
|
+
await this.setupCachingRouteInterception(framePage);
|
|
5865
6009
|
logger.debug(`Fetching frame content from: ${resolvedUrl}`);
|
|
5866
6010
|
await framePage.goto(resolvedUrl, {
|
|
5867
6011
|
waitUntil: "load",
|
|
@@ -5873,8 +6017,23 @@ class HtmlPlaywrightMiddleware {
|
|
|
5873
6017
|
"body",
|
|
5874
6018
|
(el) => el.innerHTML
|
|
5875
6019
|
);
|
|
6020
|
+
const content = bodyContent || "";
|
|
6021
|
+
const contentSizeBytes = Buffer.byteLength(content, "utf8");
|
|
6022
|
+
if (contentSizeBytes <= FETCHER_MAX_CACHE_ITEM_SIZE_BYTES) {
|
|
6023
|
+
HtmlPlaywrightMiddleware.resourceCache.set(resolvedUrl, {
|
|
6024
|
+
body: content,
|
|
6025
|
+
contentType: "text/html; charset=utf-8"
|
|
6026
|
+
});
|
|
6027
|
+
logger.debug(
|
|
6028
|
+
`Cached frame content: ${resolvedUrl} (${contentSizeBytes} bytes, cache size: ${HtmlPlaywrightMiddleware.resourceCache.size})`
|
|
6029
|
+
);
|
|
6030
|
+
} else {
|
|
6031
|
+
logger.debug(
|
|
6032
|
+
`Frame content too large to cache: ${resolvedUrl} (${contentSizeBytes} bytes > ${FETCHER_MAX_CACHE_ITEM_SIZE_BYTES} bytes limit)`
|
|
6033
|
+
);
|
|
6034
|
+
}
|
|
5876
6035
|
logger.debug(`Successfully fetched frame content from: ${resolvedUrl}`);
|
|
5877
|
-
return
|
|
6036
|
+
return content;
|
|
5878
6037
|
} catch (error) {
|
|
5879
6038
|
logger.debug(`Error fetching frame content from ${frameUrl}: ${error}`);
|
|
5880
6039
|
return "";
|
|
@@ -5973,25 +6132,59 @@ ${frame.content}
|
|
|
5973
6132
|
await this.injectShadowDOMExtractor(page);
|
|
5974
6133
|
await page.route("**/*", async (route) => {
|
|
5975
6134
|
const reqUrl = route.request().url();
|
|
5976
|
-
const reqOrigin = (() => {
|
|
5977
|
-
try {
|
|
5978
|
-
return new URL(reqUrl).origin;
|
|
5979
|
-
} catch {
|
|
5980
|
-
return null;
|
|
5981
|
-
}
|
|
5982
|
-
})();
|
|
5983
6135
|
if (reqUrl === context.source) {
|
|
5984
6136
|
return route.fulfill({
|
|
5985
6137
|
status: 200,
|
|
5986
6138
|
contentType: "text/html; charset=utf-8",
|
|
5987
6139
|
body: context.content
|
|
5988
|
-
// context.content is always a string in middleware
|
|
5989
6140
|
});
|
|
5990
6141
|
}
|
|
6142
|
+
const reqOrigin = (() => {
|
|
6143
|
+
try {
|
|
6144
|
+
return new URL(reqUrl).origin;
|
|
6145
|
+
} catch {
|
|
6146
|
+
return null;
|
|
6147
|
+
}
|
|
6148
|
+
})();
|
|
5991
6149
|
const resourceType = route.request().resourceType();
|
|
5992
6150
|
if (["image", "font", "media"].includes(resourceType)) {
|
|
5993
6151
|
return route.abort();
|
|
5994
6152
|
}
|
|
6153
|
+
if (route.request().method() === "GET") {
|
|
6154
|
+
const cached = HtmlPlaywrightMiddleware.resourceCache.get(reqUrl);
|
|
6155
|
+
if (cached !== void 0) {
|
|
6156
|
+
logger.debug(`✓ Cache hit for ${resourceType}: ${reqUrl}`);
|
|
6157
|
+
return route.fulfill({
|
|
6158
|
+
status: 200,
|
|
6159
|
+
contentType: cached.contentType,
|
|
6160
|
+
body: cached.body
|
|
6161
|
+
});
|
|
6162
|
+
}
|
|
6163
|
+
const headers2 = mergePlaywrightHeaders(
|
|
6164
|
+
route.request().headers(),
|
|
6165
|
+
customHeaders,
|
|
6166
|
+
credentials ?? void 0,
|
|
6167
|
+
origin ?? void 0,
|
|
6168
|
+
reqOrigin ?? void 0
|
|
6169
|
+
);
|
|
6170
|
+
const response = await route.fetch({ headers: headers2 });
|
|
6171
|
+
const body = await response.text();
|
|
6172
|
+
if (response.status() >= 200 && response.status() < 300 && body.length > 0) {
|
|
6173
|
+
const contentSizeBytes = Buffer.byteLength(body, "utf8");
|
|
6174
|
+
if (contentSizeBytes <= FETCHER_MAX_CACHE_ITEM_SIZE_BYTES) {
|
|
6175
|
+
const contentType2 = response.headers()["content-type"] || "application/octet-stream";
|
|
6176
|
+
HtmlPlaywrightMiddleware.resourceCache.set(reqUrl, { body, contentType: contentType2 });
|
|
6177
|
+
logger.debug(
|
|
6178
|
+
`Cached ${resourceType}: ${reqUrl} (${contentSizeBytes} bytes, cache size: ${HtmlPlaywrightMiddleware.resourceCache.size})`
|
|
6179
|
+
);
|
|
6180
|
+
} else {
|
|
6181
|
+
logger.debug(
|
|
6182
|
+
`Resource too large to cache: ${reqUrl} (${contentSizeBytes} bytes > ${FETCHER_MAX_CACHE_ITEM_SIZE_BYTES} bytes limit)`
|
|
6183
|
+
);
|
|
6184
|
+
}
|
|
6185
|
+
}
|
|
6186
|
+
return route.fulfill({ response });
|
|
6187
|
+
}
|
|
5995
6188
|
const headers = mergePlaywrightHeaders(
|
|
5996
6189
|
route.request().headers(),
|
|
5997
6190
|
customHeaders,
|
|
@@ -6172,6 +6365,8 @@ class HtmlSanitizerMiddleware {
|
|
|
6172
6365
|
return;
|
|
6173
6366
|
}
|
|
6174
6367
|
try {
|
|
6368
|
+
const bodyBeforeSanitization = $("body").html() || "";
|
|
6369
|
+
const textLengthBefore = $("body").text().trim().length;
|
|
6175
6370
|
const selectorsToRemove = [
|
|
6176
6371
|
...context.options.excludeSelectors || [],
|
|
6177
6372
|
// Use options from the context
|
|
@@ -6184,9 +6379,13 @@ class HtmlSanitizerMiddleware {
|
|
|
6184
6379
|
for (const selector of selectorsToRemove) {
|
|
6185
6380
|
try {
|
|
6186
6381
|
const elements = $(selector);
|
|
6187
|
-
const
|
|
6382
|
+
const filteredElements = elements.filter(function() {
|
|
6383
|
+
const tagName = $(this).prop("tagName")?.toLowerCase();
|
|
6384
|
+
return tagName !== "html" && tagName !== "body";
|
|
6385
|
+
});
|
|
6386
|
+
const count = filteredElements.length;
|
|
6188
6387
|
if (count > 0) {
|
|
6189
|
-
|
|
6388
|
+
filteredElements.remove();
|
|
6190
6389
|
removedCount += count;
|
|
6191
6390
|
}
|
|
6192
6391
|
} catch (selectorError) {
|
|
@@ -6199,6 +6398,13 @@ class HtmlSanitizerMiddleware {
|
|
|
6199
6398
|
}
|
|
6200
6399
|
}
|
|
6201
6400
|
logger.debug(`Removed ${removedCount} elements for ${context.source}`);
|
|
6401
|
+
const textLengthAfter = $("body").text().trim().length;
|
|
6402
|
+
if (textLengthBefore > 0 && textLengthAfter === 0) {
|
|
6403
|
+
logger.warn(
|
|
6404
|
+
`⚠️ Sanitization removed all content from ${context.source}. Reverting to pre-sanitization state.`
|
|
6405
|
+
);
|
|
6406
|
+
$("body").html(bodyBeforeSanitization);
|
|
6407
|
+
}
|
|
6202
6408
|
} catch (error) {
|
|
6203
6409
|
logger.error(
|
|
6204
6410
|
`❌ Error during HTML element removal for ${context.source}: ${error}`
|
|
@@ -6349,6 +6555,29 @@ class MarkdownMetadataExtractorMiddleware {
|
|
|
6349
6555
|
}
|
|
6350
6556
|
}
|
|
6351
6557
|
class HtmlNormalizationMiddleware {
|
|
6558
|
+
// Known tracking/analytics domains and patterns to filter out
|
|
6559
|
+
trackingPatterns = [
|
|
6560
|
+
"adroll.com",
|
|
6561
|
+
"doubleclick.net",
|
|
6562
|
+
"google-analytics.com",
|
|
6563
|
+
"googletagmanager.com",
|
|
6564
|
+
"analytics.twitter.com",
|
|
6565
|
+
"twitter.com/1/i/adsct",
|
|
6566
|
+
"t.co/1/i/adsct",
|
|
6567
|
+
"bat.bing.com",
|
|
6568
|
+
"pixel.rubiconproject.com",
|
|
6569
|
+
"casalemedia.com",
|
|
6570
|
+
"tremorhub.com",
|
|
6571
|
+
"rlcdn.com",
|
|
6572
|
+
"facebook.com/tr",
|
|
6573
|
+
"linkedin.com/px",
|
|
6574
|
+
"quantserve.com",
|
|
6575
|
+
"scorecardresearch.com",
|
|
6576
|
+
"hotjar.com",
|
|
6577
|
+
"mouseflow.com",
|
|
6578
|
+
"crazyegg.com",
|
|
6579
|
+
"clarity.ms"
|
|
6580
|
+
];
|
|
6352
6581
|
async process(context, next) {
|
|
6353
6582
|
if (!context.dom) {
|
|
6354
6583
|
logger.debug(
|
|
@@ -6372,14 +6601,34 @@ class HtmlNormalizationMiddleware {
|
|
|
6372
6601
|
}
|
|
6373
6602
|
await next();
|
|
6374
6603
|
}
|
|
6604
|
+
/**
|
|
6605
|
+
* Checks if an image should be kept based on its source URL.
|
|
6606
|
+
* Filters out tracking pixels and analytics beacons.
|
|
6607
|
+
*/
|
|
6608
|
+
shouldKeepImage(src) {
|
|
6609
|
+
const srcLower = src.toLowerCase();
|
|
6610
|
+
return !this.trackingPatterns.some((pattern) => srcLower.includes(pattern));
|
|
6611
|
+
}
|
|
6375
6612
|
/**
|
|
6376
6613
|
* Normalizes image URLs by converting relative URLs to absolute URLs.
|
|
6614
|
+
* Removes tracking/analytics images.
|
|
6615
|
+
* Preserves data URIs (inline images).
|
|
6377
6616
|
*/
|
|
6378
6617
|
normalizeImageUrls($, baseUrl) {
|
|
6379
6618
|
$("img").each((_index, element) => {
|
|
6380
6619
|
const $img = $(element);
|
|
6381
6620
|
const src = $img.attr("src");
|
|
6382
|
-
if (!src)
|
|
6621
|
+
if (!src) {
|
|
6622
|
+
$img.remove();
|
|
6623
|
+
return;
|
|
6624
|
+
}
|
|
6625
|
+
if (src.startsWith("data:")) {
|
|
6626
|
+
return;
|
|
6627
|
+
}
|
|
6628
|
+
if (!this.shouldKeepImage(src)) {
|
|
6629
|
+
$img.remove();
|
|
6630
|
+
return;
|
|
6631
|
+
}
|
|
6383
6632
|
try {
|
|
6384
6633
|
new URL(src);
|
|
6385
6634
|
} catch {
|
|
@@ -6388,6 +6637,7 @@ class HtmlNormalizationMiddleware {
|
|
|
6388
6637
|
$img.attr("src", absoluteUrl);
|
|
6389
6638
|
} catch (error) {
|
|
6390
6639
|
logger.debug(`Failed to resolve relative image URL: ${src} - ${error}`);
|
|
6640
|
+
$img.remove();
|
|
6391
6641
|
}
|
|
6392
6642
|
}
|
|
6393
6643
|
});
|
|
@@ -8543,8 +8793,8 @@ class DocumentStore {
|
|
|
8543
8793
|
return escapedTokens[0];
|
|
8544
8794
|
}
|
|
8545
8795
|
const exactMatch = `"${tokens.join(" ").replace(/"/g, '""')}"`;
|
|
8546
|
-
const termsQuery = escapedTokens.join(" ");
|
|
8547
|
-
return `${exactMatch} OR
|
|
8796
|
+
const termsQuery = escapedTokens.join(" OR ");
|
|
8797
|
+
return `${exactMatch} OR ${termsQuery}`;
|
|
8548
8798
|
}
|
|
8549
8799
|
/**
|
|
8550
8800
|
* Initializes database connection and ensures readiness
|
|
@@ -8672,6 +8922,35 @@ class DocumentStore {
|
|
|
8672
8922
|
throw new StoreError(`Failed to get library by ID: ${error}`);
|
|
8673
8923
|
}
|
|
8674
8924
|
}
|
|
8925
|
+
/**
|
|
8926
|
+
* Retrieves a library by its name.
|
|
8927
|
+
* @param name The library name to retrieve
|
|
8928
|
+
* @returns The library record, or null if not found
|
|
8929
|
+
*/
|
|
8930
|
+
async getLibrary(name) {
|
|
8931
|
+
try {
|
|
8932
|
+
const normalizedName = name.toLowerCase();
|
|
8933
|
+
const row = this.statements.getLibraryIdByName.get(normalizedName);
|
|
8934
|
+
if (!row) {
|
|
8935
|
+
return null;
|
|
8936
|
+
}
|
|
8937
|
+
return { id: row.id, name: normalizedName };
|
|
8938
|
+
} catch (error) {
|
|
8939
|
+
throw new StoreError(`Failed to get library by name: ${error}`);
|
|
8940
|
+
}
|
|
8941
|
+
}
|
|
8942
|
+
/**
|
|
8943
|
+
* Deletes a library by its ID.
|
|
8944
|
+
* This should only be called when the library has no remaining versions.
|
|
8945
|
+
* @param libraryId The library ID to delete
|
|
8946
|
+
*/
|
|
8947
|
+
async deleteLibrary(libraryId) {
|
|
8948
|
+
try {
|
|
8949
|
+
this.statements.deleteLibraryById.run(libraryId);
|
|
8950
|
+
} catch (error) {
|
|
8951
|
+
throw new StoreError(`Failed to delete library: ${error}`);
|
|
8952
|
+
}
|
|
8953
|
+
}
|
|
8675
8954
|
/**
|
|
8676
8955
|
* Stores scraper options for a version to enable reproducible indexing.
|
|
8677
8956
|
* @param versionId The version ID to update
|
|
@@ -9533,30 +9812,26 @@ class DocumentManagementService {
|
|
|
9533
9812
|
return this.store.findVersionsBySourceUrl(url);
|
|
9534
9813
|
}
|
|
9535
9814
|
/**
|
|
9536
|
-
* Validates if a library exists in the store
|
|
9815
|
+
* Validates if a library exists in the store.
|
|
9816
|
+
* Checks if the library record exists in the database, regardless of whether it has versions or documents.
|
|
9537
9817
|
* Throws LibraryNotFoundInStoreError with suggestions if the library is not found.
|
|
9538
9818
|
* @param library The name of the library to validate.
|
|
9539
9819
|
* @throws {LibraryNotFoundInStoreError} If the library does not exist.
|
|
9540
9820
|
*/
|
|
9541
9821
|
async validateLibraryExists(library) {
|
|
9542
9822
|
logger.info(`🔎 Validating existence of library: ${library}`);
|
|
9543
|
-
const
|
|
9544
|
-
|
|
9545
|
-
const hasUnversioned = await this.exists(normalizedLibrary, "");
|
|
9546
|
-
if (versions.length === 0 && !hasUnversioned) {
|
|
9823
|
+
const libraryRecord = await this.store.getLibrary(library);
|
|
9824
|
+
if (!libraryRecord) {
|
|
9547
9825
|
logger.warn(`⚠️ Library '${library}' not found.`);
|
|
9548
9826
|
const allLibraries = await this.listLibraries();
|
|
9549
9827
|
const libraryNames = allLibraries.map((lib) => lib.library);
|
|
9550
9828
|
let suggestions = [];
|
|
9551
9829
|
if (libraryNames.length > 0) {
|
|
9552
9830
|
const fuse = new Fuse(libraryNames, {
|
|
9553
|
-
// Configure fuse.js options if needed (e.g., threshold)
|
|
9554
|
-
// isCaseSensitive: false, // Handled by normalizing library names
|
|
9555
|
-
// includeScore: true,
|
|
9556
9831
|
threshold: 0.7
|
|
9557
9832
|
// Adjust threshold for desired fuzziness (0=exact, 1=match anything)
|
|
9558
9833
|
});
|
|
9559
|
-
const results = fuse.search(
|
|
9834
|
+
const results = fuse.search(library.toLowerCase());
|
|
9560
9835
|
suggestions = results.slice(0, 3).map((result) => result.item);
|
|
9561
9836
|
logger.info(`🔍 Found suggestions: ${suggestions.join(", ")}`);
|
|
9562
9837
|
}
|
|
@@ -9672,6 +9947,7 @@ class DocumentManagementService {
|
|
|
9672
9947
|
/**
|
|
9673
9948
|
* Completely removes a library version and all associated documents.
|
|
9674
9949
|
* Also removes the library if no other versions remain.
|
|
9950
|
+
* If the specified version doesn't exist but the library exists with no versions, removes the library.
|
|
9675
9951
|
* @param library Library name
|
|
9676
9952
|
* @param version Version string (null/undefined for unversioned)
|
|
9677
9953
|
*/
|
|
@@ -9688,6 +9964,15 @@ class DocumentManagementService {
|
|
|
9688
9964
|
logger.warn(
|
|
9689
9965
|
`⚠️ Version ${library}@${normalizedVersion || "[no version]"} not found`
|
|
9690
9966
|
);
|
|
9967
|
+
const libraryRecord = await this.store.getLibrary(library);
|
|
9968
|
+
if (libraryRecord) {
|
|
9969
|
+
const versions = await this.store.queryUniqueVersions(library);
|
|
9970
|
+
if (versions.length === 0) {
|
|
9971
|
+
logger.info(`🗑️ Library ${library} has no versions, removing library record`);
|
|
9972
|
+
await this.store.deleteLibrary(libraryRecord.id);
|
|
9973
|
+
logger.info(`🗑️ Completely removed library ${library} (had no versions)`);
|
|
9974
|
+
}
|
|
9975
|
+
}
|
|
9691
9976
|
}
|
|
9692
9977
|
this.eventBus.emit(EventType.LIBRARY_CHANGE, void 0);
|
|
9693
9978
|
}
|
|
@@ -10414,13 +10699,135 @@ function registerEventsRoute(server, eventBus) {
|
|
|
10414
10699
|
});
|
|
10415
10700
|
});
|
|
10416
10701
|
}
|
|
10702
|
+
const Toast = () => {
|
|
10703
|
+
return /* @__PURE__ */ jsx(
|
|
10704
|
+
"div",
|
|
10705
|
+
{
|
|
10706
|
+
"x-data": true,
|
|
10707
|
+
"x-show": "$store.toast.visible",
|
|
10708
|
+
"x-transition:enter": "transition ease-out duration-300",
|
|
10709
|
+
"x-transition:enter-start": "opacity-0 transform translate-y-2",
|
|
10710
|
+
"x-transition:enter-end": "opacity-100 transform translate-y-0",
|
|
10711
|
+
"x-transition:leave": "transition ease-in duration-200",
|
|
10712
|
+
"x-transition:leave-start": "opacity-100",
|
|
10713
|
+
"x-transition:leave-end": "opacity-0",
|
|
10714
|
+
class: "fixed top-5 right-5 z-50",
|
|
10715
|
+
style: "display: none;",
|
|
10716
|
+
children: /* @__PURE__ */ jsxs(
|
|
10717
|
+
"div",
|
|
10718
|
+
{
|
|
10719
|
+
class: "flex items-center w-full max-w-xs p-4 text-gray-500 bg-white rounded-lg shadow dark:text-gray-400 dark:bg-gray-800",
|
|
10720
|
+
role: "alert",
|
|
10721
|
+
children: [
|
|
10722
|
+
/* @__PURE__ */ jsxs(
|
|
10723
|
+
"div",
|
|
10724
|
+
{
|
|
10725
|
+
class: "inline-flex items-center justify-center shrink-0 w-8 h-8 rounded-lg",
|
|
10726
|
+
"x-bind:class": "{\n 'text-green-500 bg-green-100 dark:bg-green-800 dark:text-green-200': $store.toast.type === 'success',\n 'text-red-500 bg-red-100 dark:bg-red-800 dark:text-red-200': $store.toast.type === 'error',\n 'text-orange-500 bg-orange-100 dark:bg-orange-700 dark:text-orange-200': $store.toast.type === 'warning',\n 'text-blue-500 bg-blue-100 dark:bg-blue-800 dark:text-blue-200': $store.toast.type === 'info'\n }",
|
|
10727
|
+
children: [
|
|
10728
|
+
/* @__PURE__ */ jsx(
|
|
10729
|
+
"svg",
|
|
10730
|
+
{
|
|
10731
|
+
"x-show": "$store.toast.type === 'success'",
|
|
10732
|
+
class: "w-5 h-5",
|
|
10733
|
+
"aria-hidden": "true",
|
|
10734
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
10735
|
+
fill: "currentColor",
|
|
10736
|
+
viewBox: "0 0 20 20",
|
|
10737
|
+
children: /* @__PURE__ */ jsx("path", { d: "M10 .5a9.5 9.5 0 1 0 9.5 9.5A9.51 9.51 0 0 0 10 .5Zm3.707 8.207-4 4a1 1 0 0 1-1.414 0l-2-2a1 1 0 0 1 1.414-1.414L9 10.586l3.293-3.293a1 1 0 0 1 1.414 1.414Z" })
|
|
10738
|
+
}
|
|
10739
|
+
),
|
|
10740
|
+
/* @__PURE__ */ jsx(
|
|
10741
|
+
"svg",
|
|
10742
|
+
{
|
|
10743
|
+
"x-show": "$store.toast.type === 'error'",
|
|
10744
|
+
class: "w-5 h-5",
|
|
10745
|
+
"aria-hidden": "true",
|
|
10746
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
10747
|
+
fill: "currentColor",
|
|
10748
|
+
viewBox: "0 0 20 20",
|
|
10749
|
+
children: /* @__PURE__ */ jsx("path", { d: "M10 .5a9.5 9.5 0 1 0 9.5 9.5A9.51 9.51 0 0 0 10 .5Zm3.707 11.793a1 1 0 1 1-1.414 1.414L10 11.414l-2.293 2.293a1 1 0 0 1-1.414-1.414L8.586 10 6.293 7.707a1 1 0 0 1 1.414-1.414L10 8.586l2.293-2.293a1 1 0 0 1 1.414 1.414L11.414 10l2.293 2.293Z" })
|
|
10750
|
+
}
|
|
10751
|
+
),
|
|
10752
|
+
/* @__PURE__ */ jsx(
|
|
10753
|
+
"svg",
|
|
10754
|
+
{
|
|
10755
|
+
"x-show": "$store.toast.type === 'warning'",
|
|
10756
|
+
class: "w-5 h-5",
|
|
10757
|
+
"aria-hidden": "true",
|
|
10758
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
10759
|
+
fill: "currentColor",
|
|
10760
|
+
viewBox: "0 0 20 20",
|
|
10761
|
+
children: /* @__PURE__ */ jsx("path", { d: "M10 .5a9.5 9.5 0 1 0 9.5 9.5A9.51 9.51 0 0 0 10 .5ZM10 15a1 1 0 1 1 0-2 1 1 0 0 1 0 2Zm1-4a1 1 0 0 1-2 0V6a1 1 0 0 1 2 0v5Z" })
|
|
10762
|
+
}
|
|
10763
|
+
),
|
|
10764
|
+
/* @__PURE__ */ jsx(
|
|
10765
|
+
"svg",
|
|
10766
|
+
{
|
|
10767
|
+
"x-show": "$store.toast.type === 'info'",
|
|
10768
|
+
class: "w-5 h-5",
|
|
10769
|
+
"aria-hidden": "true",
|
|
10770
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
10771
|
+
fill: "currentColor",
|
|
10772
|
+
viewBox: "0 0 20 20",
|
|
10773
|
+
children: /* @__PURE__ */ jsx("path", { d: "M10 .5a9.5 9.5 0 1 0 9.5 9.5A9.51 9.51 0 0 0 10 .5ZM9.5 4a1.5 1.5 0 1 1 0 3 1.5 1.5 0 0 1 0-3ZM12 15H8a1 1 0 0 1 0-2h1v-3H8a1 1 0 0 1 0-2h2a1 1 0 0 1 1 1v4h1a1 1 0 0 1 0 2Z" })
|
|
10774
|
+
}
|
|
10775
|
+
)
|
|
10776
|
+
]
|
|
10777
|
+
}
|
|
10778
|
+
),
|
|
10779
|
+
/* @__PURE__ */ jsx(
|
|
10780
|
+
"div",
|
|
10781
|
+
{
|
|
10782
|
+
class: "ml-3 text-sm font-normal",
|
|
10783
|
+
"x-text": "$store.toast.message"
|
|
10784
|
+
}
|
|
10785
|
+
),
|
|
10786
|
+
/* @__PURE__ */ jsxs(
|
|
10787
|
+
"button",
|
|
10788
|
+
{
|
|
10789
|
+
type: "button",
|
|
10790
|
+
class: "ml-auto -mx-1.5 -my-1.5 bg-white text-gray-400 hover:text-gray-900 rounded-lg focus:ring-2 focus:ring-gray-300 p-1.5 hover:bg-gray-100 inline-flex items-center justify-center h-8 w-8 dark:text-gray-500 dark:hover:text-white dark:bg-gray-800 dark:hover:bg-gray-700",
|
|
10791
|
+
"x-on:click": "$store.toast.hide()",
|
|
10792
|
+
"aria-label": "Close",
|
|
10793
|
+
children: [
|
|
10794
|
+
/* @__PURE__ */ jsx("span", { class: "sr-only", children: "Close" }),
|
|
10795
|
+
/* @__PURE__ */ jsx(
|
|
10796
|
+
"svg",
|
|
10797
|
+
{
|
|
10798
|
+
class: "w-3 h-3",
|
|
10799
|
+
"aria-hidden": "true",
|
|
10800
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
10801
|
+
fill: "none",
|
|
10802
|
+
viewBox: "0 0 14 14",
|
|
10803
|
+
children: /* @__PURE__ */ jsx(
|
|
10804
|
+
"path",
|
|
10805
|
+
{
|
|
10806
|
+
stroke: "currentColor",
|
|
10807
|
+
"stroke-linecap": "round",
|
|
10808
|
+
"stroke-linejoin": "round",
|
|
10809
|
+
"stroke-width": "2",
|
|
10810
|
+
d: "m1 1 6 6m0 0 6 6M7 7l6-6M7 7l-6 6"
|
|
10811
|
+
}
|
|
10812
|
+
)
|
|
10813
|
+
}
|
|
10814
|
+
)
|
|
10815
|
+
]
|
|
10816
|
+
}
|
|
10817
|
+
)
|
|
10818
|
+
]
|
|
10819
|
+
}
|
|
10820
|
+
)
|
|
10821
|
+
}
|
|
10822
|
+
);
|
|
10823
|
+
};
|
|
10417
10824
|
const Layout = ({
|
|
10418
10825
|
title,
|
|
10419
10826
|
version,
|
|
10420
10827
|
children,
|
|
10421
10828
|
eventClientConfig
|
|
10422
10829
|
}) => {
|
|
10423
|
-
const versionString = version || "1.
|
|
10830
|
+
const versionString = version || "1.29.0";
|
|
10424
10831
|
const versionInitializer = `versionUpdate({ currentVersion: ${`'${versionString}'`} })`;
|
|
10425
10832
|
return /* @__PURE__ */ jsxs("html", { lang: "en", children: [
|
|
10426
10833
|
/* @__PURE__ */ jsxs("head", { children: [
|
|
@@ -10566,6 +10973,7 @@ const Layout = ({
|
|
|
10566
10973
|
` })
|
|
10567
10974
|
] }),
|
|
10568
10975
|
/* @__PURE__ */ jsxs("body", { class: "bg-gray-50 dark:bg-gray-900", children: [
|
|
10976
|
+
/* @__PURE__ */ jsx(Toast, {}),
|
|
10569
10977
|
/* @__PURE__ */ jsx(
|
|
10570
10978
|
"header",
|
|
10571
10979
|
{
|
|
@@ -10917,76 +11325,51 @@ const LoadingSpinner = () => /* @__PURE__ */ jsxs(
|
|
|
10917
11325
|
const JobItem = ({ job }) => {
|
|
10918
11326
|
job.dbStatus || job.status;
|
|
10919
11327
|
const isActiveJob = job.dbStatus ? isActiveStatus(job.dbStatus) : job.status === PipelineJobStatus.QUEUED || job.status === PipelineJobStatus.RUNNING;
|
|
10920
|
-
return /* @__PURE__ */ jsx(
|
|
10921
|
-
|
|
10922
|
-
|
|
10923
|
-
|
|
10924
|
-
|
|
10925
|
-
|
|
10926
|
-
|
|
10927
|
-
|
|
10928
|
-
|
|
10929
|
-
" ",
|
|
10930
|
-
|
|
10931
|
-
|
|
10932
|
-
|
|
10933
|
-
|
|
10934
|
-
|
|
10935
|
-
|
|
10936
|
-
|
|
10937
|
-
|
|
10938
|
-
|
|
10939
|
-
|
|
10940
|
-
|
|
10941
|
-
"
|
|
10942
|
-
|
|
10943
|
-
|
|
10944
|
-
|
|
10945
|
-
|
|
10946
|
-
|
|
10947
|
-
|
|
10948
|
-
|
|
10949
|
-
|
|
10950
|
-
|
|
10951
|
-
|
|
10952
|
-
|
|
10953
|
-
|
|
10954
|
-
|
|
10955
|
-
|
|
10956
|
-
|
|
10957
|
-
|
|
10958
|
-
|
|
10959
|
-
|
|
10960
|
-
|
|
10961
|
-
.then(r => r.json())
|
|
10962
|
-
|
|
10963
|
-
|
|
10964
|
-
|
|
10965
|
-
$store.confirmingAction.isStopping = false;
|
|
10966
|
-
if ($store.confirmingAction.timeoutId) { clearTimeout($store.confirmingAction.timeoutId); $store.confirmingAction.timeoutId = null; }
|
|
10967
|
-
document.dispatchEvent(new CustomEvent('job-list-refresh'));
|
|
10968
|
-
})
|
|
10969
|
-
.catch(() => { $store.confirmingAction.isStopping = false; });
|
|
10970
|
-
} else {
|
|
10971
|
-
if ($store.confirmingAction.timeoutId) { clearTimeout($store.confirmingAction.timeoutId); $store.confirmingAction.timeoutId = null; }
|
|
10972
|
-
$store.confirmingAction.type = 'job-cancel';
|
|
10973
|
-
$store.confirmingAction.id = '${job.id}';
|
|
10974
|
-
$store.confirmingAction.isStopping = false;
|
|
10975
|
-
$store.confirmingAction.timeoutId = setTimeout(() => {
|
|
10976
|
-
$store.confirmingAction.type = null;
|
|
10977
|
-
$store.confirmingAction.id = null;
|
|
10978
|
-
$store.confirmingAction.isStopping = false;
|
|
10979
|
-
$store.confirmingAction.timeoutId = null;
|
|
10980
|
-
}, 3000);
|
|
10981
|
-
}
|
|
10982
|
-
`,
|
|
10983
|
-
"x-bind:disabled": `$store.confirmingAction.type === 'job-cancel' && $store.confirmingAction.id === '${job.id}' && $store.confirmingAction.isStopping`,
|
|
10984
|
-
children: [
|
|
10985
|
-
/* @__PURE__ */ jsxs(
|
|
10986
|
-
"span",
|
|
10987
|
-
{
|
|
10988
|
-
"x-show": `$store.confirmingAction.type !== 'job-cancel' || $store.confirmingAction.id !== '${job.id}' || $store.confirmingAction.isStopping`,
|
|
10989
|
-
children: [
|
|
11328
|
+
return /* @__PURE__ */ jsx(
|
|
11329
|
+
"div",
|
|
11330
|
+
{
|
|
11331
|
+
id: `job-item-${job.id}`,
|
|
11332
|
+
class: "block p-3 bg-gray-50 dark:bg-gray-700 rounded-lg border border-gray-200 dark:border-gray-600",
|
|
11333
|
+
"data-job-id": job.id,
|
|
11334
|
+
"x-data": "{ jobId: $el.dataset.jobId }",
|
|
11335
|
+
"x-bind:hx-preserve": "$store.confirmingAction.type === 'job-cancel' && $store.confirmingAction.id === jobId",
|
|
11336
|
+
children: /* @__PURE__ */ jsxs("div", { class: "flex items-start justify-between", children: [
|
|
11337
|
+
/* @__PURE__ */ jsxs("div", { class: "flex-1", children: [
|
|
11338
|
+
/* @__PURE__ */ jsxs("p", { class: "text-sm font-medium text-gray-900 dark:text-white", children: [
|
|
11339
|
+
/* @__PURE__ */ jsx("span", { safe: true, children: job.library }),
|
|
11340
|
+
" ",
|
|
11341
|
+
/* @__PURE__ */ jsx(VersionBadge, { version: job.version })
|
|
11342
|
+
] }),
|
|
11343
|
+
/* @__PURE__ */ jsx("div", { class: "text-xs text-gray-500 dark:text-gray-400 mt-1", children: job.startedAt ? /* @__PURE__ */ jsxs("div", { children: [
|
|
11344
|
+
"Last Indexed:",
|
|
11345
|
+
" ",
|
|
11346
|
+
/* @__PURE__ */ jsx("span", { safe: true, children: new Date(job.startedAt).toLocaleString() })
|
|
11347
|
+
] }) : null }),
|
|
11348
|
+
job.progress && job.progress.totalPages > 0 && isActiveJob ? /* @__PURE__ */ jsx("div", { class: "mt-2", children: /* @__PURE__ */ jsx(ProgressBar, { progress: job.progress }) }) : null,
|
|
11349
|
+
job.errorMessage || job.error ? /* @__PURE__ */ jsxs("div", { class: "mt-2 p-2 bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800 rounded text-xs", children: [
|
|
11350
|
+
/* @__PURE__ */ jsx("div", { class: "font-medium text-red-800 dark:text-red-300 mb-1", children: "Error:" }),
|
|
11351
|
+
/* @__PURE__ */ jsx("div", { safe: true, class: "text-red-700 dark:text-red-400", children: job.errorMessage || job.error })
|
|
11352
|
+
] }) : null
|
|
11353
|
+
] }),
|
|
11354
|
+
/* @__PURE__ */ jsxs("div", { class: "flex flex-col items-end gap-2 ml-4", children: [
|
|
11355
|
+
/* @__PURE__ */ jsxs("div", { class: "flex items-center gap-2", children: [
|
|
11356
|
+
job.dbStatus ? /* @__PURE__ */ jsx(StatusBadge, { status: job.dbStatus }) : /* @__PURE__ */ jsx(
|
|
11357
|
+
"span",
|
|
11358
|
+
{
|
|
11359
|
+
class: `px-1.5 py-0.5 text-xs font-medium rounded ${job.status === PipelineJobStatus.COMPLETED ? "bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-300" : job.error ? "bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-300" : "bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-300"}`,
|
|
11360
|
+
children: job.status
|
|
11361
|
+
}
|
|
11362
|
+
),
|
|
11363
|
+
isActiveJob && /* @__PURE__ */ jsxs(
|
|
11364
|
+
"button",
|
|
11365
|
+
{
|
|
11366
|
+
type: "button",
|
|
11367
|
+
class: "font-medium rounded-lg text-xs p-1 text-center inline-flex items-center transition-colors duration-150 ease-in-out border border-gray-300 bg-white text-red-600 hover:bg-red-50 focus:ring-4 focus:outline-none focus:ring-red-100 dark:border-gray-600 dark:bg-gray-800 dark:text-red-400 dark:hover:bg-gray-700 dark:focus:ring-red-900",
|
|
11368
|
+
title: "Stop this job",
|
|
11369
|
+
"x-on:click": "\n if ($store.confirmingAction.type === 'job-cancel' && $store.confirmingAction.id === jobId) {\n $store.confirmingAction.isStopping = true;\n fetch('/web/jobs/' + jobId + '/cancel', {\n method: 'POST',\n headers: { 'Accept': 'application/json' },\n })\n .then(r => r.json())\n .then(() => {\n $store.confirmingAction.type = null;\n $store.confirmingAction.id = null;\n $store.confirmingAction.isStopping = false;\n if ($store.confirmingAction.timeoutId) { clearTimeout($store.confirmingAction.timeoutId); $store.confirmingAction.timeoutId = null; }\n document.dispatchEvent(new CustomEvent('job-list-refresh'));\n })\n .catch(() => { $store.confirmingAction.isStopping = false; });\n } else {\n if ($store.confirmingAction.timeoutId) { clearTimeout($store.confirmingAction.timeoutId); $store.confirmingAction.timeoutId = null; }\n $store.confirmingAction.type = 'job-cancel';\n $store.confirmingAction.id = jobId;\n $store.confirmingAction.isStopping = false;\n $store.confirmingAction.timeoutId = setTimeout(() => {\n $store.confirmingAction.type = null;\n $store.confirmingAction.id = null;\n $store.confirmingAction.isStopping = false;\n $store.confirmingAction.timeoutId = null;\n }, 3000);\n }\n ",
|
|
11370
|
+
"x-bind:disabled": "$store.confirmingAction.type === 'job-cancel' && $store.confirmingAction.id === jobId && $store.confirmingAction.isStopping",
|
|
11371
|
+
children: [
|
|
11372
|
+
/* @__PURE__ */ jsxs("span", { "x-show": "$store.confirmingAction.type !== 'job-cancel' || $store.confirmingAction.id !== jobId || $store.confirmingAction.isStopping", children: [
|
|
10990
11373
|
/* @__PURE__ */ jsx(
|
|
10991
11374
|
"svg",
|
|
10992
11375
|
{
|
|
@@ -10998,37 +11381,31 @@ const JobItem = ({ job }) => {
|
|
|
10998
11381
|
}
|
|
10999
11382
|
),
|
|
11000
11383
|
/* @__PURE__ */ jsx("span", { class: "sr-only", children: "Stop job" })
|
|
11001
|
-
]
|
|
11002
|
-
|
|
11003
|
-
|
|
11004
|
-
|
|
11005
|
-
|
|
11006
|
-
|
|
11007
|
-
|
|
11008
|
-
|
|
11009
|
-
|
|
11010
|
-
|
|
11011
|
-
),
|
|
11012
|
-
/* @__PURE__ */ jsxs(
|
|
11013
|
-
"span",
|
|
11014
|
-
{
|
|
11015
|
-
"x-show": `$store.confirmingAction.type === 'job-cancel' && $store.confirmingAction.id === '${job.id}' && $store.confirmingAction.isStopping`,
|
|
11016
|
-
children: [
|
|
11384
|
+
] }),
|
|
11385
|
+
/* @__PURE__ */ jsx(
|
|
11386
|
+
"span",
|
|
11387
|
+
{
|
|
11388
|
+
"x-show": "$store.confirmingAction.type === 'job-cancel' && $store.confirmingAction.id === jobId && !$store.confirmingAction.isStopping",
|
|
11389
|
+
class: "px-2",
|
|
11390
|
+
children: "Cancel?"
|
|
11391
|
+
}
|
|
11392
|
+
),
|
|
11393
|
+
/* @__PURE__ */ jsxs("span", { "x-show": "$store.confirmingAction.type === 'job-cancel' && $store.confirmingAction.id === jobId && $store.confirmingAction.isStopping", children: [
|
|
11017
11394
|
/* @__PURE__ */ jsx(LoadingSpinner, {}),
|
|
11018
11395
|
/* @__PURE__ */ jsx("span", { class: "sr-only", children: "Stopping..." })
|
|
11019
|
-
]
|
|
11020
|
-
|
|
11021
|
-
|
|
11022
|
-
|
|
11023
|
-
}
|
|
11024
|
-
|
|
11025
|
-
|
|
11026
|
-
|
|
11027
|
-
|
|
11028
|
-
|
|
11029
|
-
)
|
|
11030
|
-
|
|
11031
|
-
|
|
11396
|
+
] })
|
|
11397
|
+
]
|
|
11398
|
+
}
|
|
11399
|
+
)
|
|
11400
|
+
] }),
|
|
11401
|
+
job.error ? (
|
|
11402
|
+
// Keep the error badge for clarity if an error occurred
|
|
11403
|
+
/* @__PURE__ */ jsx("span", { class: "bg-red-100 text-red-800 text-xs font-medium px-1.5 py-0.5 rounded dark:bg-red-900 dark:text-red-300", children: "Error" })
|
|
11404
|
+
) : null
|
|
11405
|
+
] })
|
|
11406
|
+
] })
|
|
11407
|
+
}
|
|
11408
|
+
);
|
|
11032
11409
|
};
|
|
11033
11410
|
const JobList = ({ jobs }) => /* @__PURE__ */ jsx("div", { id: "job-list", class: "space-y-2", children: jobs.length === 0 ? /* @__PURE__ */ jsx("p", { class: "text-center text-gray-500 dark:text-gray-400", children: "No pending jobs." }) : jobs.map((job) => /* @__PURE__ */ jsx(JobItem, { job })) });
|
|
11034
11411
|
function registerJobListRoutes(server, listJobsTool) {
|
|
@@ -11048,7 +11425,7 @@ const Alert = ({ type, title, message }) => {
|
|
|
11048
11425
|
iconSvg = /* @__PURE__ */ jsx(
|
|
11049
11426
|
"svg",
|
|
11050
11427
|
{
|
|
11051
|
-
class: "
|
|
11428
|
+
class: "shrink-0 inline w-4 h-4 me-3",
|
|
11052
11429
|
"aria-hidden": "true",
|
|
11053
11430
|
xmlns: "http://www.w3.org/2000/svg",
|
|
11054
11431
|
fill: "currentColor",
|
|
@@ -11063,7 +11440,7 @@ const Alert = ({ type, title, message }) => {
|
|
|
11063
11440
|
iconSvg = /* @__PURE__ */ jsx(
|
|
11064
11441
|
"svg",
|
|
11065
11442
|
{
|
|
11066
|
-
class: "
|
|
11443
|
+
class: "shrink-0 inline w-4 h-4 me-3",
|
|
11067
11444
|
"aria-hidden": "true",
|
|
11068
11445
|
xmlns: "http://www.w3.org/2000/svg",
|
|
11069
11446
|
fill: "currentColor",
|
|
@@ -11078,7 +11455,7 @@ const Alert = ({ type, title, message }) => {
|
|
|
11078
11455
|
iconSvg = /* @__PURE__ */ jsx(
|
|
11079
11456
|
"svg",
|
|
11080
11457
|
{
|
|
11081
|
-
class: "
|
|
11458
|
+
class: "shrink-0 inline w-4 h-4 me-3",
|
|
11082
11459
|
"aria-hidden": "true",
|
|
11083
11460
|
xmlns: "http://www.w3.org/2000/svg",
|
|
11084
11461
|
fill: "currentColor",
|
|
@@ -11094,7 +11471,7 @@ const Alert = ({ type, title, message }) => {
|
|
|
11094
11471
|
iconSvg = /* @__PURE__ */ jsx(
|
|
11095
11472
|
"svg",
|
|
11096
11473
|
{
|
|
11097
|
-
class: "
|
|
11474
|
+
class: "shrink-0 inline w-4 h-4 me-3",
|
|
11098
11475
|
"aria-hidden": "true",
|
|
11099
11476
|
xmlns: "http://www.w3.org/2000/svg",
|
|
11100
11477
|
fill: "currentColor",
|
|
@@ -11584,18 +11961,7 @@ const ScrapeFormContent = ({
|
|
|
11584
11961
|
]
|
|
11585
11962
|
}
|
|
11586
11963
|
),
|
|
11587
|
-
/* @__PURE__ */ jsx("div", { id: "job-response", class: "mt-2 text-sm" })
|
|
11588
|
-
/* @__PURE__ */ jsx("script", { children: `
|
|
11589
|
-
document.addEventListener('htmx:responseError', function(evt) {
|
|
11590
|
-
// Handle error responses from the form submission
|
|
11591
|
-
if (evt.detail.xhr && evt.detail.xhr.response) {
|
|
11592
|
-
const responseDiv = document.getElementById('job-response');
|
|
11593
|
-
if (responseDiv) {
|
|
11594
|
-
responseDiv.innerHTML = evt.detail.xhr.response;
|
|
11595
|
-
}
|
|
11596
|
-
}
|
|
11597
|
-
});
|
|
11598
|
-
` })
|
|
11964
|
+
/* @__PURE__ */ jsx("div", { id: "job-response", class: "mt-2 text-sm" })
|
|
11599
11965
|
] });
|
|
11600
11966
|
};
|
|
11601
11967
|
const ScrapeForm = ({ defaultExcludePatterns }) => /* @__PURE__ */ jsx("div", { id: "scrape-form-container", children: /* @__PURE__ */ jsx(ScrapeFormContent, { defaultExcludePatterns }) });
|
|
@@ -11821,6 +12187,10 @@ const VersionDetailsRow = ({
|
|
|
11821
12187
|
{
|
|
11822
12188
|
id: rowId,
|
|
11823
12189
|
class: "flex justify-between items-center py-1 border-b border-gray-200 dark:border-gray-600 last:border-b-0",
|
|
12190
|
+
"data-library-name": libraryName,
|
|
12191
|
+
"data-version-param": versionParam,
|
|
12192
|
+
"x-data": "{ library: $el.dataset.libraryName, version: $el.dataset.versionParam, deleteId: $el.dataset.libraryName + ':' + $el.dataset.versionParam }",
|
|
12193
|
+
"x-bind:hx-preserve": "$store.confirmingAction.type === 'version-delete' && $store.confirmingAction.id === deleteId",
|
|
11824
12194
|
children: [
|
|
11825
12195
|
/* @__PURE__ */ jsx(
|
|
11826
12196
|
"span",
|
|
@@ -11853,64 +12223,41 @@ const VersionDetailsRow = ({
|
|
|
11853
12223
|
type: "button",
|
|
11854
12224
|
class: "ml-2 font-medium rounded-lg text-sm p-1 text-center inline-flex items-center transition-colors duration-150 ease-in-out",
|
|
11855
12225
|
title: "Remove this version",
|
|
11856
|
-
"x-
|
|
11857
|
-
"x-bind:
|
|
11858
|
-
"x-
|
|
11859
|
-
"x-on:click": `
|
|
11860
|
-
if ($store.confirmingAction.type === 'version-delete' && $store.confirmingAction.id === '${libraryName}:${versionParam}') {
|
|
11861
|
-
$store.confirmingAction.isDeleting = true;
|
|
11862
|
-
$el.dispatchEvent(new CustomEvent('confirmed-delete', { bubbles: true }));
|
|
11863
|
-
} else {
|
|
11864
|
-
if ($store.confirmingAction.timeoutId) { clearTimeout($store.confirmingAction.timeoutId); $store.confirmingAction.timeoutId = null; }
|
|
11865
|
-
$store.confirmingAction.type = 'version-delete';
|
|
11866
|
-
$store.confirmingAction.id = '${libraryName}:${versionParam}';
|
|
11867
|
-
$store.confirmingAction.isDeleting = false;
|
|
11868
|
-
$store.confirmingAction.timeoutId = setTimeout(() => {
|
|
11869
|
-
$store.confirmingAction.type = null;
|
|
11870
|
-
$store.confirmingAction.id = null;
|
|
11871
|
-
$store.confirmingAction.isDeleting = false;
|
|
11872
|
-
$store.confirmingAction.timeoutId = null;
|
|
11873
|
-
}, 3000);
|
|
11874
|
-
}
|
|
11875
|
-
`,
|
|
12226
|
+
"x-bind:class": `$store.confirmingAction.type === 'version-delete' && $store.confirmingAction.id === deleteId ? '${confirmingStateClasses}' : '${defaultStateClasses}'`,
|
|
12227
|
+
"x-bind:disabled": "$store.confirmingAction.type === 'version-delete' && $store.confirmingAction.id === deleteId && $store.confirmingAction.isDeleting",
|
|
12228
|
+
"x-on:click": "\n if ($store.confirmingAction.type === 'version-delete' && $store.confirmingAction.id === deleteId) {\n $store.confirmingAction.isDeleting = true;\n $el.dispatchEvent(new CustomEvent('confirmed-delete', { bubbles: true }));\n } else {\n if ($store.confirmingAction.timeoutId) { clearTimeout($store.confirmingAction.timeoutId); $store.confirmingAction.timeoutId = null; }\n $store.confirmingAction.type = 'version-delete';\n $store.confirmingAction.id = deleteId;\n $store.confirmingAction.isDeleting = false;\n $store.confirmingAction.timeoutId = setTimeout(() => {\n $store.confirmingAction.type = null;\n $store.confirmingAction.id = null;\n $store.confirmingAction.isDeleting = false;\n $store.confirmingAction.timeoutId = null;\n }, 3000);\n }\n ",
|
|
11876
12229
|
"hx-delete": `/web/libraries/${encodeURIComponent(libraryName)}/versions/${encodeURIComponent(versionParam)}`,
|
|
11877
12230
|
"hx-target": `#${rowId}`,
|
|
11878
12231
|
"hx-swap": "outerHTML",
|
|
11879
12232
|
"hx-trigger": "confirmed-delete",
|
|
11880
12233
|
children: [
|
|
11881
|
-
/* @__PURE__ */ jsxs(
|
|
11882
|
-
|
|
11883
|
-
|
|
11884
|
-
|
|
11885
|
-
|
|
11886
|
-
|
|
11887
|
-
|
|
12234
|
+
/* @__PURE__ */ jsxs("span", { "x-show": "!($store.confirmingAction.type === 'version-delete' && $store.confirmingAction.id === deleteId && $store.confirmingAction.isDeleting)", children: [
|
|
12235
|
+
/* @__PURE__ */ jsx(
|
|
12236
|
+
"svg",
|
|
12237
|
+
{
|
|
12238
|
+
class: "w-4 h-4",
|
|
12239
|
+
"aria-hidden": "true",
|
|
12240
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
12241
|
+
fill: "none",
|
|
12242
|
+
viewBox: "0 0 18 20",
|
|
12243
|
+
children: /* @__PURE__ */ jsx(
|
|
12244
|
+
"path",
|
|
11888
12245
|
{
|
|
11889
|
-
|
|
11890
|
-
"
|
|
11891
|
-
|
|
11892
|
-
|
|
11893
|
-
|
|
11894
|
-
children: /* @__PURE__ */ jsx(
|
|
11895
|
-
"path",
|
|
11896
|
-
{
|
|
11897
|
-
stroke: "currentColor",
|
|
11898
|
-
"stroke-linecap": "round",
|
|
11899
|
-
"stroke-linejoin": "round",
|
|
11900
|
-
"stroke-width": "2",
|
|
11901
|
-
d: "M1 5h16M7 8v8m4-8v8M7 1h4a1 1 0 0 1 1 1v3H6V2a1 1 0 0 1-1-1ZM3 5h12v13a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V5Z"
|
|
11902
|
-
}
|
|
11903
|
-
)
|
|
12246
|
+
stroke: "currentColor",
|
|
12247
|
+
"stroke-linecap": "round",
|
|
12248
|
+
"stroke-linejoin": "round",
|
|
12249
|
+
"stroke-width": "2",
|
|
12250
|
+
d: "M1 5h16M7 8v8m4-8v8M7 1h4a1 1 0 0 1 1 1v3H6V2a1 1 0 0 1-1-1ZM3 5h12v13a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V5Z"
|
|
11904
12251
|
}
|
|
11905
|
-
)
|
|
11906
|
-
|
|
11907
|
-
|
|
11908
|
-
}
|
|
11909
|
-
),
|
|
12252
|
+
)
|
|
12253
|
+
}
|
|
12254
|
+
),
|
|
12255
|
+
/* @__PURE__ */ jsx("span", { class: "sr-only", children: "Remove version" })
|
|
12256
|
+
] }),
|
|
11910
12257
|
/* @__PURE__ */ jsxs(
|
|
11911
12258
|
"span",
|
|
11912
12259
|
{
|
|
11913
|
-
"x-show":
|
|
12260
|
+
"x-show": "$store.confirmingAction.type === 'version-delete' && $store.confirmingAction.id === deleteId && !$store.confirmingAction.isDeleting",
|
|
11914
12261
|
class: "mx-1",
|
|
11915
12262
|
children: [
|
|
11916
12263
|
"Confirm?",
|
|
@@ -11918,16 +12265,10 @@ const VersionDetailsRow = ({
|
|
|
11918
12265
|
]
|
|
11919
12266
|
}
|
|
11920
12267
|
),
|
|
11921
|
-
/* @__PURE__ */ jsxs(
|
|
11922
|
-
|
|
11923
|
-
{
|
|
11924
|
-
|
|
11925
|
-
children: [
|
|
11926
|
-
/* @__PURE__ */ jsx(LoadingSpinner, {}),
|
|
11927
|
-
/* @__PURE__ */ jsx("span", { class: "sr-only", children: "Loading..." })
|
|
11928
|
-
]
|
|
11929
|
-
}
|
|
11930
|
-
)
|
|
12268
|
+
/* @__PURE__ */ jsxs("span", { "x-show": "$store.confirmingAction.type === 'version-delete' && $store.confirmingAction.id === deleteId && $store.confirmingAction.isDeleting", children: [
|
|
12269
|
+
/* @__PURE__ */ jsx(LoadingSpinner, {}),
|
|
12270
|
+
/* @__PURE__ */ jsx("span", { class: "sr-only", children: "Loading..." })
|
|
12271
|
+
] })
|
|
11931
12272
|
]
|
|
11932
12273
|
}
|
|
11933
12274
|
)
|
|
@@ -12135,7 +12476,8 @@ function registerLibraryDetailRoutes(server, listLibrariesTool, searchTool) {
|
|
|
12135
12476
|
} catch (error) {
|
|
12136
12477
|
server.log.error(error, `Failed to search library ${libraryName}`);
|
|
12137
12478
|
reply.type("text/html; charset=utf-8");
|
|
12138
|
-
|
|
12479
|
+
const errorMessage = error instanceof Error ? error.message : "An unexpected error occurred during the search.";
|
|
12480
|
+
return /* @__PURE__ */ jsx(Alert, { type: "error", message: errorMessage });
|
|
12139
12481
|
}
|
|
12140
12482
|
}
|
|
12141
12483
|
);
|
|
@@ -12288,7 +12630,7 @@ class AppServer {
|
|
|
12288
12630
|
try {
|
|
12289
12631
|
if (telemetry.isEnabled()) {
|
|
12290
12632
|
telemetry.setGlobalContext({
|
|
12291
|
-
appVersion: "1.
|
|
12633
|
+
appVersion: "1.29.0",
|
|
12292
12634
|
appPlatform: process.platform,
|
|
12293
12635
|
appNodeVersion: process.version,
|
|
12294
12636
|
appServicesEnabled: this.getActiveServicesList(),
|
|
@@ -12356,6 +12698,9 @@ class AppServer {
|
|
|
12356
12698
|
await cleanupMcpService(this.mcpServer);
|
|
12357
12699
|
}
|
|
12358
12700
|
if (this.wss) {
|
|
12701
|
+
for (const client of this.wss.clients) {
|
|
12702
|
+
client.terminate();
|
|
12703
|
+
}
|
|
12359
12704
|
await new Promise((resolve, reject) => {
|
|
12360
12705
|
this.wss?.close((err) => {
|
|
12361
12706
|
if (err) {
|
|
@@ -12374,6 +12719,9 @@ class AppServer {
|
|
|
12374
12719
|
});
|
|
12375
12720
|
}
|
|
12376
12721
|
await telemetry.shutdown();
|
|
12722
|
+
if (this.server.server) {
|
|
12723
|
+
this.server.server.closeAllConnections();
|
|
12724
|
+
}
|
|
12377
12725
|
await this.server.close();
|
|
12378
12726
|
logger.info("🛑 AppServer stopped");
|
|
12379
12727
|
} catch (error) {
|
|
@@ -15554,7 +15902,7 @@ function createCliProgram() {
|
|
|
15554
15902
|
const commandStartTimes = /* @__PURE__ */ new Map();
|
|
15555
15903
|
let globalEventBus = null;
|
|
15556
15904
|
let globalTelemetryService = null;
|
|
15557
|
-
program.name("docs-mcp-server").description("Unified CLI, MCP Server, and Web Interface for Docs MCP Server.").version("1.
|
|
15905
|
+
program.name("docs-mcp-server").description("Unified CLI, MCP Server, and Web Interface for Docs MCP Server.").version("1.29.0").addOption(
|
|
15558
15906
|
new Option("--verbose", "Enable verbose (debug) logging").conflicts("silent")
|
|
15559
15907
|
).addOption(new Option("--silent", "Disable all logging except errors")).addOption(
|
|
15560
15908
|
new Option("--telemetry", "Enable telemetry collection").env("DOCS_MCP_TELEMETRY").argParser((value) => {
|
|
@@ -15588,7 +15936,7 @@ function createCliProgram() {
|
|
|
15588
15936
|
if (shouldEnableTelemetry()) {
|
|
15589
15937
|
if (telemetry.isEnabled()) {
|
|
15590
15938
|
telemetry.setGlobalContext({
|
|
15591
|
-
appVersion: "1.
|
|
15939
|
+
appVersion: "1.29.0",
|
|
15592
15940
|
appPlatform: process.platform,
|
|
15593
15941
|
appNodeVersion: process.version,
|
|
15594
15942
|
appInterface: "cli",
|