simple-dynamsoft-mcp 7.6.1 → 7.7.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.
@@ -0,0 +1,161 @@
1
+ import { existsSync, readFileSync, readdirSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { resolveSiteLinks } from "./docs-loader.js";
4
+
5
+ // DBR's public API is built on shared Capture Vision (DCV) modules such as
6
+ // CaptureVisionRouter, whose reference pages live only in the DCV docs. DBR's
7
+ // own docs and navigation sidebars link to exactly the DCV pages DBR relies on,
8
+ // so those links are the dependency map: only linked pages are indexed under
9
+ // DBR, never the whole DCV corpus.
10
+
11
+ const DCV_CURRENT_DOCS_PREFIX = "https://www.dynamsoft.com/capture-vision/docs/";
12
+
13
+ // Sidebar include `sidelist-programming/programming-<segment>.html` scopes its
14
+ // links to one DBR platform; other includes apply to the whole edition.
15
+ const SIDEBAR_PLATFORM_BY_SEGMENT = {
16
+ javascript: "web",
17
+ android: "android",
18
+ oc: "ios",
19
+ ios: "ios",
20
+ flutter: "flutter",
21
+ maui: "maui",
22
+ "react-native": "react-native",
23
+ cpp: "cpp",
24
+ dotnet: "dotnet",
25
+ java: "java",
26
+ python: "python",
27
+ nodejs: "nodejs"
28
+ };
29
+
30
+ function normalizeDocUrl(url) {
31
+ // Some DBR links contain `//` inside the path (e.g. `api-reference//core/`).
32
+ let normalized = String(url || "").split("#")[0].split("?")[0].replace(/([^:])\/{2,}/g, "$1/");
33
+ if (!normalized) return "";
34
+ if (normalized.endsWith("/")) return `${normalized}index.html`;
35
+ const lastSegment = normalized.slice(normalized.lastIndexOf("/") + 1);
36
+ if (!lastSegment.includes(".")) normalized = `${normalized}.html`;
37
+ return normalized;
38
+ }
39
+
40
+ function extractDcvLinks(text) {
41
+ const links = new Set();
42
+ const pattern = /https:\/\/www\.dynamsoft\.com\/capture-vision\/docs\/[^\s)"'<>\]`]+/g;
43
+ for (const match of String(text || "").matchAll(pattern)) {
44
+ const normalized = normalizeDocUrl(match[0]);
45
+ if (normalized.startsWith(DCV_CURRENT_DOCS_PREFIX)) links.add(normalized);
46
+ }
47
+ return links;
48
+ }
49
+
50
+ function listIncludeFiles(dir) {
51
+ const files = [];
52
+ if (!existsSync(dir)) return files;
53
+ for (const entry of readdirSync(dir, { withFileTypes: true })) {
54
+ const fullPath = join(dir, entry.name);
55
+ if (entry.isDirectory()) {
56
+ files.push(...listIncludeFiles(fullPath));
57
+ } else if (/\.(html|md)$/i.test(entry.name)) {
58
+ files.push({ path: fullPath, name: entry.name });
59
+ }
60
+ }
61
+ return files;
62
+ }
63
+
64
+ function sidebarPlatform(fileName) {
65
+ const match = String(fileName).match(/^programming-(.+)\.html$/i);
66
+ return match ? SIDEBAR_PLATFORM_BY_SEGMENT[match[1].toLowerCase()] || "" : "";
67
+ }
68
+
69
+ // url -> Set of DBR platforms whose docs link to it ("*" = whole edition).
70
+ function buildDcvLinkMap({ dbrArticles, dbrRoot, siteVars }) {
71
+ const linkMap = new Map();
72
+ const addLinks = (links, platform) => {
73
+ for (const url of links) {
74
+ if (!linkMap.has(url)) linkMap.set(url, new Set());
75
+ linkMap.get(url).add(platform);
76
+ }
77
+ };
78
+
79
+ for (const article of dbrArticles || []) {
80
+ addLinks(extractDcvLinks(article.content), article.platform || "*");
81
+ }
82
+ for (const file of listIncludeFiles(join(dbrRoot || "", "_includes"))) {
83
+ const text = resolveSiteLinks(readFileSync(file.path, "utf8"), siteVars);
84
+ addLinks(extractDcvLinks(text), sidebarPlatform(file.name) || "*");
85
+ }
86
+ return linkMap;
87
+ }
88
+
89
+ function parseVersionParts(version) {
90
+ return String(version || "").split(".").map((part) => Number.parseInt(part, 10));
91
+ }
92
+
93
+ // DBR and DCV ship in lockstep: 11.6.3200 pairs with 3.6.3200. Edition pages
94
+ // must match minor and build; core reference pages are edition-neutral and
95
+ // only need the same minor.
96
+ function isCompatibleSharedVersion(dbrVersion, dcvVersion, { requireBuild }) {
97
+ const [, dbrMinor, dbrBuild] = parseVersionParts(dbrVersion);
98
+ const [, dcvMinor, dcvBuild] = parseVersionParts(dcvVersion);
99
+ if (!Number.isFinite(dbrMinor) || dbrMinor !== dcvMinor) return false;
100
+ if (requireBuild && dbrBuild !== dcvBuild) return false;
101
+ return true;
102
+ }
103
+
104
+ /**
105
+ * Select DCV pages linked from one DBR edition.
106
+ *
107
+ * @param {object} options
108
+ * @param {Array} options.dbrArticles DBR edition articles (with platform).
109
+ * @param {string} options.dbrRoot DBR docs repo root (for `_includes`).
110
+ * @param {Map} options.siteVars DBR repo `{{ site.* }}` variables.
111
+ * @param {string} options.dbrVersion DBR edition version.
112
+ * @param {Array<{articles: Array, version: string, editionSpecific: boolean, slugPrefix: string}>} options.sources
113
+ * @param {Function} [options.onSkip] Called with details when a source is version-incompatible.
114
+ * @returns {Array} Articles with `slugPrefix` (doc:// namespace), `platform`, `platforms`, `sourceVersion`.
115
+ */
116
+ function selectSharedDcvDocs({ dbrArticles, dbrRoot, siteVars, dbrVersion, sources, onSkip }) {
117
+ const linkMap = buildDcvLinkMap({ dbrArticles, dbrRoot, siteVars });
118
+ const editionPlatforms = [...new Set((dbrArticles || []).map((article) => article.platform).filter(Boolean))].sort();
119
+ const selected = [];
120
+
121
+ for (const source of sources) {
122
+ if (!isCompatibleSharedVersion(dbrVersion, source.version, { requireBuild: source.editionSpecific })) {
123
+ onSkip?.({ dbrVersion, dcvVersion: source.version, slugPrefix: source.slugPrefix });
124
+ continue;
125
+ }
126
+ for (const article of source.articles || []) {
127
+ const linkedFrom = linkMap.get(normalizeDocUrl(article.url));
128
+ if (!linkedFrom) continue;
129
+
130
+ let platforms;
131
+ if (source.editionSpecific) {
132
+ // An edition page belongs to one platform (its programming/<lang>/ dir).
133
+ if (!editionPlatforms.includes(article.platform) && editionPlatforms.length > 0) continue;
134
+ platforms = [article.platform];
135
+ } else {
136
+ platforms = linkedFrom.has("*")
137
+ ? editionPlatforms
138
+ : [...linkedFrom].filter((platform) => editionPlatforms.includes(platform)).sort();
139
+ }
140
+ if (platforms.length === 0) continue;
141
+
142
+ selected.push({
143
+ ...article,
144
+ slugPrefix: source.slugPrefix,
145
+ platform: platforms.length === 1 ? platforms[0] : (source.defaultPlatform || platforms[0]),
146
+ platforms,
147
+ sourceVersion: source.version
148
+ });
149
+ }
150
+ }
151
+
152
+ return selected;
153
+ }
154
+
155
+ export {
156
+ buildDcvLinkMap,
157
+ extractDcvLinks,
158
+ isCompatibleSharedVersion,
159
+ normalizeDocUrl,
160
+ selectSharedDcvDocs
161
+ };
@@ -17,6 +17,8 @@ import {
17
17
  } from "./normalizers.js";
18
18
  import { DOCS_CONFIG } from "./resource-index/config.js";
19
19
  import { loadMarkdownDocs } from "./resource-index/docs-loader.js";
20
+ import { selectSharedDcvDocs } from "./resource-index/shared-docs.js";
21
+ import { logEvent } from "../observability/logging.js";
20
22
  import {
21
23
  registryPath,
22
24
  DOC_ROOTS,
@@ -149,40 +151,47 @@ let dcvMobileDocs = [];
149
151
  let dcvServerDocs = [];
150
152
  let dwtDocs = { articles: [] };
151
153
  let ddvDocs = { articles: [] };
154
+ let dbrSharedDocs = { web: [], mobile: [], server: [] };
155
+
156
+ function loadDbrDocs(key, edition, platformResolver) {
157
+ const loaded = loadMarkdownDocs({
158
+ rootDir: DOC_ROOTS[key],
159
+ urlBase: DOCS_CONFIG[key].urlBase,
160
+ excludeDirs: DOCS_CONFIG[key].excludeDirs,
161
+ excludeFiles: DOCS_CONFIG[key].excludeFiles
162
+ });
163
+ return { articles: withEditionScope(loaded.articles, edition, platformResolver), siteVars: loaded.siteVars };
164
+ }
165
+
166
+ function selectDbrSharedDocs(key, edition, dbr, dcvEditionDocs) {
167
+ const dbrVersion = LATEST_VERSIONS.dbr[edition];
168
+ return selectSharedDcvDocs({
169
+ dbrArticles: dbr.articles,
170
+ dbrRoot: DOC_ROOTS[key],
171
+ siteVars: dbr.siteVars,
172
+ dbrVersion,
173
+ sources: [
174
+ { articles: dcvEditionDocs, version: LATEST_VERSIONS.dcv[edition], editionSpecific: true, slugPrefix: "dcv", defaultPlatform: edition },
175
+ { articles: dcvCoreDocs, version: LATEST_VERSIONS.dcv.core, editionSpecific: false, slugPrefix: "dcv-core", defaultPlatform: edition }
176
+ ],
177
+ onSkip: ({ dcvVersion, slugPrefix }) => logEvent("data", "shared_docs_version_mismatch", {
178
+ edition,
179
+ dbr_version: dbrVersion,
180
+ dcv_version: dcvVersion,
181
+ source: slugPrefix
182
+ }, { level: "warn" })
183
+ });
184
+ }
152
185
 
153
186
  function loadDocumentationSets() {
154
- dbrWebDocs = withEditionScope(
155
- loadMarkdownDocs({
156
- rootDir: DOC_ROOTS.dbrWeb,
157
- urlBase: DOCS_CONFIG.dbrWeb.urlBase,
158
- excludeDirs: DOCS_CONFIG.dbrWeb.excludeDirs,
159
- excludeFiles: DOCS_CONFIG.dbrWeb.excludeFiles
160
- }).articles,
161
- "web",
162
- () => "web"
163
- );
187
+ const dbrWeb = loadDbrDocs("dbrWeb", "web", () => "web");
188
+ dbrWebDocs = dbrWeb.articles;
164
189
 
165
- dbrMobileDocs = withEditionScope(
166
- loadMarkdownDocs({
167
- rootDir: DOC_ROOTS.dbrMobile,
168
- urlBase: DOCS_CONFIG.dbrMobile.urlBase,
169
- excludeDirs: DOCS_CONFIG.dbrMobile.excludeDirs,
170
- excludeFiles: DOCS_CONFIG.dbrMobile.excludeFiles
171
- }).articles,
172
- "mobile",
173
- inferDbrMobilePlatform
174
- );
190
+ const dbrMobile = loadDbrDocs("dbrMobile", "mobile", inferDbrMobilePlatform);
191
+ dbrMobileDocs = dbrMobile.articles;
175
192
 
176
- dbrServerDocs = withEditionScope(
177
- loadMarkdownDocs({
178
- rootDir: DOC_ROOTS.dbrServer,
179
- urlBase: DOCS_CONFIG.dbrServer.urlBase,
180
- excludeDirs: DOCS_CONFIG.dbrServer.excludeDirs,
181
- excludeFiles: DOCS_CONFIG.dbrServer.excludeFiles
182
- }).articles,
183
- "server",
184
- inferDbrServerPlatform
185
- );
193
+ const dbrServer = loadDbrDocs("dbrServer", "server", inferDbrServerPlatform);
194
+ dbrServerDocs = dbrServer.articles;
186
195
 
187
196
  dcvCoreDocs = withEditionScope(
188
197
  loadMarkdownDocs({
@@ -252,6 +261,7 @@ function loadDocumentationSets() {
252
261
 
253
262
  dwtDocs = loadMarkdownDocs({
254
263
  rootDir: DOC_ROOTS.dwtArticles,
264
+ siteRoot: DOC_ROOTS.dwt,
255
265
  urlBase: DOCS_CONFIG.dwt.urlBase,
256
266
  includeDirNames: DOCS_CONFIG.dwt.includeDirNames
257
267
  });
@@ -262,6 +272,12 @@ function loadDocumentationSets() {
262
272
  excludeDirs: DOCS_CONFIG.ddv.excludeDirs,
263
273
  excludeFiles: DOCS_CONFIG.ddv.excludeFiles
264
274
  });
275
+
276
+ dbrSharedDocs = {
277
+ web: selectDbrSharedDocs("dbrWeb", "web", dbrWeb, dcvWebDocs),
278
+ mobile: selectDbrSharedDocs("dbrMobile", "mobile", dbrMobile, dcvMobileDocs),
279
+ server: selectDbrSharedDocs("dbrServer", "server", dbrServer, dcvServerDocs)
280
+ };
265
281
  }
266
282
 
267
283
  const dbrServerSdk = registry.sdks["dbr-server"];
@@ -339,6 +355,7 @@ function buildIndexData() {
339
355
  dbrWebDocs,
340
356
  dbrMobileDocs,
341
357
  dbrServerDocs,
358
+ dbrSharedDocs,
342
359
  dwtDocs,
343
360
  ddvDocs,
344
361
  discoverDcvWebSamples,
@@ -380,6 +397,7 @@ function buildResourceIndex() {
380
397
  dbrWebDocs,
381
398
  dbrMobileDocs,
382
399
  dbrServerDocs,
400
+ dbrSharedDocs,
383
401
  dwtDocs,
384
402
  ddvDocs,
385
403
  discoverDcvMobileSamples,
@@ -444,6 +462,8 @@ function editionMatches(normalizedEdition, entryEdition) {
444
462
  function platformMatches(normalizedPlatform, entry) {
445
463
  if (!normalizedPlatform) return true;
446
464
  if (normalizedPlatform === entry.platform) return true;
465
+ // Shared pages (e.g. Capture Vision core reference) serve several platforms.
466
+ if (Array.isArray(entry.platforms) && entry.platforms.includes(normalizedPlatform)) return true;
447
467
  if (normalizedPlatform === "web") return entry.platform === "web";
448
468
  if (isWebFrameworkPlatform(normalizedPlatform)) {
449
469
  const aliases = WEB_FRAMEWORK_TAG_ALIASES[normalizedPlatform] || [normalizedPlatform];
@@ -514,13 +534,12 @@ function buildResourceLookupCandidates(uri) {
514
534
  const parts = String(rest || "").split("/").filter(Boolean);
515
535
  if (parts.length < 5) return candidates;
516
536
 
517
- const head = parts.slice(0, 4);
518
- const slugRaw = parts.slice(4).join("/");
519
- const decodedOnce = safeDecodeURIComponent(slugRaw);
520
- const decodedTwice = safeDecodeURIComponent(decodedOnce);
521
-
522
- for (const slug of [decodedOnce, decodedTwice]) {
523
- const canonical = `${scheme}://${head.join("/")}/${encodeURIComponent(slug)}`;
537
+ // Doc slugs are source paths; accept them with or without per-segment
538
+ // percent-encoding (clients differ in how they echo URIs back).
539
+ const head = parts.slice(0, 4).join("/");
540
+ const slugSegments = parts.slice(4).map((segment) => safeDecodeURIComponent(segment));
541
+ for (const slug of [slugSegments.join("/"), slugSegments.map((segment) => encodeURIComponent(segment)).join("/")]) {
542
+ const canonical = `${scheme}://${head}/${slug}`;
524
543
  if (!candidates.includes(canonical)) {
525
544
  candidates.push(canonical);
526
545
  }
@@ -562,6 +581,7 @@ function getRagSignatureData() {
562
581
  dbrWebDocCount: dbrWebDocs.length,
563
582
  dbrMobileDocCount: dbrMobileDocs.length,
564
583
  dbrServerDocCount: dbrServerDocs.length,
584
+ dbrSharedDocCount: dbrSharedDocs.web.length + dbrSharedDocs.mobile.length + dbrSharedDocs.server.length,
565
585
  dwtDocCount: dwtDocs.articles.length,
566
586
  ddvDocCount: ddvDocs.articles.length,
567
587
  versions: LATEST_VERSIONS,
@@ -34,7 +34,10 @@ export function registerIndexTools({
34
34
  const sampleId = entry.type === "sample" ? getSampleIdFromUri(entry.uri) : "";
35
35
  const sampleHint = sampleId ? ` | sample_id: ${sampleId}` : "";
36
36
  const scoreLabel = formatScoreLabel(entry);
37
- let desc = `${entry.type.toUpperCase()} | ${scopeLabel} | ${versionLabel}${scoreLabel} - ${entry.summary}${sampleHint}`;
37
+ const sharedLabel = entry.type === "doc" && Array.isArray(entry.tags) && entry.tags.includes("shared")
38
+ ? ` | shared Capture Vision API used by DBR (DCV v${entry.sourceVersion || "n/a"})`
39
+ : "";
40
+ let desc = `${entry.type.toUpperCase()} | ${scopeLabel} | ${versionLabel}${sharedLabel}${scoreLabel} - ${entry.summary}${sampleHint}`;
38
41
  if (entry.matchedSnippet) {
39
42
  const snippet = String(entry.matchedSnippet).replace(/\s+/g, " ").slice(0, 220);
40
43
  desc += ` — "${snippet}"`;