@microsoft/rayfin-docs 1.27.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/LICENSE +21 -0
- package/assets/catalog.json +132 -0
- package/dist/_internal/build-index.d.ts +14 -0
- package/dist/_internal/build-index.js +13 -0
- package/dist/catalog-types.d.ts +52 -0
- package/dist/catalog-types.js +21 -0
- package/dist/catalog.d.ts +85 -0
- package/dist/catalog.js +227 -0
- package/dist/discovery.d.ts +110 -0
- package/dist/discovery.js +402 -0
- package/dist/index.d.ts +91 -0
- package/dist/index.js +422 -0
- package/dist/loader.d.ts +21 -0
- package/dist/loader.js +296 -0
- package/dist/search.d.ts +72 -0
- package/dist/search.js +254 -0
- package/dist/site-cjs/discovery.js +409 -0
- package/dist/site-cjs/package.json +1 -0
- package/dist/site-cjs/types.js +2 -0
- package/dist/types.d.ts +89 -0
- package/dist/types.js +2 -0
- package/package.json +62 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,422 @@
|
|
|
1
|
+
import { createHash } from 'crypto';
|
|
2
|
+
import { mkdirSync, readdirSync, readFileSync, unlinkSync, writeFileSync, } from 'fs';
|
|
3
|
+
import { homedir, tmpdir } from 'os';
|
|
4
|
+
import { basename, dirname, join, resolve } from 'path';
|
|
5
|
+
import { getCatalog } from './catalog.js';
|
|
6
|
+
import { docKindToModule, discoverRayfinDocsPackages, hasExplicitDiscoverySource, } from './discovery.js';
|
|
7
|
+
import { loadDocs, loadDocsFromPackage } from './loader.js';
|
|
8
|
+
import { DOCS_INDEX_SCHEMA_VERSION, DOCS_SEARCH_CONFIG_FINGERPRINT, createDocsIndex, loadDocsIndexFromPrebuilt, searchDocs, serializeDocsIndex, } from './search.js';
|
|
9
|
+
const PREBUILT_INDEX_FILENAME = 'docs.search.json';
|
|
10
|
+
const DISCOVERED_CACHE_PREFIX = 'docs-index-';
|
|
11
|
+
/**
|
|
12
|
+
* Skip reading existing on-disk cache files while still writing a fresh index
|
|
13
|
+
* after rebuild. Used by CLI/MCP `--no-cache` flags.
|
|
14
|
+
*/
|
|
15
|
+
export const DOCS_CACHE_SKIP_READ = { read: false };
|
|
16
|
+
export const DOCS_NO_CACHE_OPTION_DESCRIPTION = 'Bypass any existing on-disk docs index cache and rebuild from installed package docs.';
|
|
17
|
+
/**
|
|
18
|
+
* Default loaded module set when no `modules` option is provided. Mirrors
|
|
19
|
+
* the MCP `start` default and the CLI's builder default. Host docs are
|
|
20
|
+
* .NET reference content (DocFX-generated, ~70% of corpus) and are
|
|
21
|
+
* loaded only when explicitly requested via `--module host` (CLI) or
|
|
22
|
+
* `--host-docs` (MCP).
|
|
23
|
+
*
|
|
24
|
+
* Exported so the CLI command group, the MCP `start` command, and the
|
|
25
|
+
* build-time prebuilt-index emitter all read the same constant - changing
|
|
26
|
+
* the default loaded set requires editing one site.
|
|
27
|
+
*/
|
|
28
|
+
export const DOCS_DEFAULT_MODULES = ['guide', 'ts-sdk'];
|
|
29
|
+
/**
|
|
30
|
+
* Catalog-derived list of packages CLI/MCP probe for the `rayfinDocs`
|
|
31
|
+
* field. Kept lazy so importing the docs library does not read bundled
|
|
32
|
+
* catalog assets until discovery needs the default candidates.
|
|
33
|
+
*
|
|
34
|
+
* External consumers SHOULD NOT import this constant directly; treat it
|
|
35
|
+
* as an internal default that may change shape between versions. The
|
|
36
|
+
* `discover` option on `DocsService` accepts any candidate list — pass
|
|
37
|
+
* your own if you need stable behavior across Rayfin versions.
|
|
38
|
+
*
|
|
39
|
+
* Packages not yet migrated to ship their own docs are silently skipped
|
|
40
|
+
* by `discoverRayfinDocsPackages`; catalog candidates not present in
|
|
41
|
+
* `node_modules` appear in `DiscoveryResult.notInstalled`.
|
|
42
|
+
*
|
|
43
|
+
* **`--module ts-sdk` compatibility:** when a discovered package
|
|
44
|
+
* declares `kind: 'api-reference'`, its docs are tagged with the
|
|
45
|
+
* legacy `module: 'ts-sdk'` so existing CLI/MCP filters keep working
|
|
46
|
+
* across the migration. Individual packages remain addressable through
|
|
47
|
+
* their globally unique doc ids (for example, `rayfin-core:index.md`).
|
|
48
|
+
*/
|
|
49
|
+
let knownRayfinDocsPackages;
|
|
50
|
+
export function getKnownRayfinDocsPackages() {
|
|
51
|
+
return (knownRayfinDocsPackages ??= getCatalog()
|
|
52
|
+
.packages.filter((p) => p.modules.length > 0)
|
|
53
|
+
.map((p) => p.name));
|
|
54
|
+
}
|
|
55
|
+
export { defaultTrust, discoverRayfinDocsPackages, validateRayfinDocsManifest, docKindToModule, } from './discovery.js';
|
|
56
|
+
// Catalog API — folded into rayfin-docs in the post-Phase-5
|
|
57
|
+
// consolidation. The catalog updates with the same trigger as the
|
|
58
|
+
// indexer ("we shipped a new SDK package"), so splitting them across
|
|
59
|
+
// packages just multiplies release coordination.
|
|
60
|
+
export { getCatalog, discoverPackages, mapDiscoverResult, deriveInstallCommand, deriveUpdateCommand, _resetCatalogForTesting, } from './catalog.js';
|
|
61
|
+
function modulesKey(modules) {
|
|
62
|
+
return [...modules].sort().join(',');
|
|
63
|
+
}
|
|
64
|
+
function isPrebuiltDocsIndex(value) {
|
|
65
|
+
return (!!value &&
|
|
66
|
+
typeof value === 'object' &&
|
|
67
|
+
typeof value.schemaVersion === 'number' &&
|
|
68
|
+
typeof value.configFingerprint === 'string' &&
|
|
69
|
+
Array.isArray(value.modules) &&
|
|
70
|
+
Array.isArray(value.entries) &&
|
|
71
|
+
!!value.miniSerialized &&
|
|
72
|
+
typeof value.miniSerialized === 'object');
|
|
73
|
+
}
|
|
74
|
+
function isCompatiblePrebuiltIndex(parsed, modules) {
|
|
75
|
+
return (parsed.schemaVersion === DOCS_INDEX_SCHEMA_VERSION &&
|
|
76
|
+
parsed.configFingerprint === DOCS_SEARCH_CONFIG_FINGERPRINT &&
|
|
77
|
+
modulesKey(parsed.modules) === modulesKey(modules));
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Load the prebuilt search index from disk if present and compatible with
|
|
81
|
+
* the runtime's expected schema/config/module set. Returns `undefined`
|
|
82
|
+
* when the prebuilt is missing, malformed, or stale - callers fall back to
|
|
83
|
+
* source-building.
|
|
84
|
+
*/
|
|
85
|
+
function tryLoadPrebuiltIndex(assetsRoot, modules) {
|
|
86
|
+
const path = join(assetsRoot, PREBUILT_INDEX_FILENAME);
|
|
87
|
+
let parsed;
|
|
88
|
+
try {
|
|
89
|
+
parsed = JSON.parse(readFileSync(path, 'utf8'));
|
|
90
|
+
}
|
|
91
|
+
catch {
|
|
92
|
+
// Missing file (ENOENT) or malformed JSON - fall back to source build.
|
|
93
|
+
return undefined;
|
|
94
|
+
}
|
|
95
|
+
// Shape guard: a partially-written or hand-crafted file might parse as
|
|
96
|
+
// JSON but be missing required fields. The contract of this helper is
|
|
97
|
+
// "return undefined on any problem so the caller can fall back" - if the
|
|
98
|
+
// payload is malformed, returning `parsed` would crash inside
|
|
99
|
+
// `loadDocsIndexFromPrebuilt` instead of falling back cleanly.
|
|
100
|
+
if (!isPrebuiltDocsIndex(parsed)) {
|
|
101
|
+
return undefined;
|
|
102
|
+
}
|
|
103
|
+
return isCompatiblePrebuiltIndex(parsed, modules) ? parsed : undefined;
|
|
104
|
+
}
|
|
105
|
+
function normalizeCacheOptions(cache, defaultEnabled) {
|
|
106
|
+
if (cache === false) {
|
|
107
|
+
return { enabled: false, read: false, write: false };
|
|
108
|
+
}
|
|
109
|
+
if (cache === true || cache === undefined) {
|
|
110
|
+
return {
|
|
111
|
+
enabled: cache === true || defaultEnabled,
|
|
112
|
+
read: true,
|
|
113
|
+
write: true,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
return {
|
|
117
|
+
enabled: true,
|
|
118
|
+
...(cache.dir ? { dir: cache.dir } : {}),
|
|
119
|
+
read: cache.read ?? true,
|
|
120
|
+
write: cache.write ?? true,
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
function defaultDocsCacheBase(useTemp = false) {
|
|
124
|
+
if (useTemp) {
|
|
125
|
+
return join(tmpdir(), 'rayfin', 'cache');
|
|
126
|
+
}
|
|
127
|
+
const home = homedir();
|
|
128
|
+
return home
|
|
129
|
+
? join(home, '.rayfin', 'cache')
|
|
130
|
+
: join(tmpdir(), 'rayfin', 'cache');
|
|
131
|
+
}
|
|
132
|
+
function defaultDocsCacheDir(discoverFrom, useTemp = false) {
|
|
133
|
+
const base = defaultDocsCacheBase(useTemp);
|
|
134
|
+
if (!discoverFrom) {
|
|
135
|
+
return base;
|
|
136
|
+
}
|
|
137
|
+
const projectHash = createHash('sha256')
|
|
138
|
+
.update(resolve(discoverFrom))
|
|
139
|
+
.digest('hex')
|
|
140
|
+
.slice(0, 16);
|
|
141
|
+
return join(base, 'projects', projectHash);
|
|
142
|
+
}
|
|
143
|
+
function discoveredCacheKey(packages, modules) {
|
|
144
|
+
const payload = {
|
|
145
|
+
schemaVersion: DOCS_INDEX_SCHEMA_VERSION,
|
|
146
|
+
configFingerprint: DOCS_SEARCH_CONFIG_FINGERPRINT,
|
|
147
|
+
modules: [...modules].sort(),
|
|
148
|
+
packages: packages
|
|
149
|
+
.map((pkg) => ({
|
|
150
|
+
name: pkg.packageName,
|
|
151
|
+
version: pkg.packageVersion,
|
|
152
|
+
module: pkg.manifest.module,
|
|
153
|
+
kind: pkg.manifest.kind,
|
|
154
|
+
}))
|
|
155
|
+
.sort((a, b) => a.name.localeCompare(b.name)),
|
|
156
|
+
};
|
|
157
|
+
return createHash('sha256').update(JSON.stringify(payload)).digest('hex');
|
|
158
|
+
}
|
|
159
|
+
function discoveredCachePath(packages, modules, cache, discoverFrom) {
|
|
160
|
+
const dir = cache.dir ?? defaultDocsCacheDir(discoverFrom);
|
|
161
|
+
return join(dir, `${DISCOVERED_CACHE_PREFIX}${discoveredCacheKey(packages, modules)}.json`);
|
|
162
|
+
}
|
|
163
|
+
function fallbackDiscoveredCachePath(packages, modules, cache, discoverFrom) {
|
|
164
|
+
if (cache.dir) {
|
|
165
|
+
return undefined;
|
|
166
|
+
}
|
|
167
|
+
return discoveredCachePath(packages, modules, {
|
|
168
|
+
...cache,
|
|
169
|
+
dir: defaultDocsCacheDir(discoverFrom, true),
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
function tryLoadDiscoveredIndexCache(cachePaths, modules, cache) {
|
|
173
|
+
if (!cache.enabled || !cache.read || cachePaths.length === 0) {
|
|
174
|
+
return undefined;
|
|
175
|
+
}
|
|
176
|
+
for (const cachePath of cachePaths) {
|
|
177
|
+
let parsed;
|
|
178
|
+
try {
|
|
179
|
+
parsed = JSON.parse(readFileSync(cachePath, 'utf8'));
|
|
180
|
+
}
|
|
181
|
+
catch {
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
if (!isPrebuiltDocsIndex(parsed)) {
|
|
185
|
+
continue;
|
|
186
|
+
}
|
|
187
|
+
if (isCompatiblePrebuiltIndex(parsed, modules)) {
|
|
188
|
+
return parsed;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
return undefined;
|
|
192
|
+
}
|
|
193
|
+
function normalizeDiscoveryOptions(discover) {
|
|
194
|
+
if (hasExplicitDiscoverySource(discover)) {
|
|
195
|
+
return discover;
|
|
196
|
+
}
|
|
197
|
+
return {
|
|
198
|
+
...discover,
|
|
199
|
+
candidates: getKnownRayfinDocsPackages(),
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
function pruneStaleDiscoveredIndexCaches(currentPath) {
|
|
203
|
+
try {
|
|
204
|
+
const dir = dirname(currentPath);
|
|
205
|
+
const currentName = basename(currentPath);
|
|
206
|
+
for (const entry of readdirSync(dir)) {
|
|
207
|
+
if (entry !== currentName &&
|
|
208
|
+
entry.startsWith(DISCOVERED_CACHE_PREFIX) &&
|
|
209
|
+
entry.endsWith('.json')) {
|
|
210
|
+
unlinkSync(join(dir, entry));
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
catch {
|
|
215
|
+
// Cache cleanup is best-effort; stale files are harmless but bounded.
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
function writeDiscoveredIndexCache(targetPath, fallbackPath, cache, prebuilt) {
|
|
219
|
+
if (!cache.enabled || !cache.write || !targetPath) {
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
try {
|
|
223
|
+
mkdirSync(dirname(targetPath), { recursive: true });
|
|
224
|
+
writeFileSync(targetPath, JSON.stringify(prebuilt));
|
|
225
|
+
pruneStaleDiscoveredIndexCaches(targetPath);
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
catch (err) {
|
|
229
|
+
if (cache.dir) {
|
|
230
|
+
throw err;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
if (!fallbackPath) {
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
try {
|
|
237
|
+
mkdirSync(dirname(fallbackPath), { recursive: true });
|
|
238
|
+
writeFileSync(fallbackPath, JSON.stringify(prebuilt));
|
|
239
|
+
pruneStaleDiscoveredIndexCaches(fallbackPath);
|
|
240
|
+
}
|
|
241
|
+
catch {
|
|
242
|
+
// Cache writes are a performance optimization; docs can still be served.
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
function parseQualifiedSymbol(symbol) {
|
|
246
|
+
const separator = symbol.lastIndexOf('::');
|
|
247
|
+
if (separator <= 0 || separator >= symbol.length - 2) {
|
|
248
|
+
return undefined;
|
|
249
|
+
}
|
|
250
|
+
return {
|
|
251
|
+
owner: symbol.slice(0, separator).toLowerCase(),
|
|
252
|
+
symbol: symbol.slice(separator + 2),
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
export class DocsService {
|
|
256
|
+
entries;
|
|
257
|
+
entriesById;
|
|
258
|
+
symbolToSections;
|
|
259
|
+
searchIndex;
|
|
260
|
+
discoveryReport;
|
|
261
|
+
hydrateFromPrebuilt(prebuilt) {
|
|
262
|
+
this.entries = prebuilt.entries;
|
|
263
|
+
const index = loadDocsIndexFromPrebuilt(prebuilt);
|
|
264
|
+
this.entriesById = index.entriesById;
|
|
265
|
+
this.symbolToSections = index.symbolToSections;
|
|
266
|
+
this.searchIndex = index.mini;
|
|
267
|
+
}
|
|
268
|
+
constructor(options) {
|
|
269
|
+
if (!options.assetsRoot && !options.discover) {
|
|
270
|
+
throw new Error('DocsService requires at least one source: pass `assetsRoot` (legacy bundled corpus), `discover` (per-package discovery), or both. Both modes can compose during the Phase 2/3 migration; per-package entries override bundled ones on collision.');
|
|
271
|
+
}
|
|
272
|
+
if (options.assetsRoot !== undefined && options.assetsRoot.length === 0) {
|
|
273
|
+
throw new Error('`assetsRoot` must be a non-empty path. Pass `undefined` (omit the option) to skip the bundled-corpus source entirely.');
|
|
274
|
+
}
|
|
275
|
+
const modules = options.modules ?? DOCS_DEFAULT_MODULES;
|
|
276
|
+
const cache = normalizeCacheOptions(options.cache, !!options.discover && !options.assetsRoot);
|
|
277
|
+
// Discovery results — empty array when discover wasn't passed OR
|
|
278
|
+
// when discovery returned no packages. We compute this BEFORE
|
|
279
|
+
// committing to the bundled fast path so an empty discovery doesn't
|
|
280
|
+
// sacrifice the prebuilt-index speedup.
|
|
281
|
+
const discoveredEntries = [];
|
|
282
|
+
let discoveredPackages = [];
|
|
283
|
+
let discoveredCacheFile;
|
|
284
|
+
let discoveredFallbackCacheFile;
|
|
285
|
+
if (options.discover) {
|
|
286
|
+
const discoveryOptions = normalizeDiscoveryOptions(options.discover);
|
|
287
|
+
const report = discoverRayfinDocsPackages(discoveryOptions);
|
|
288
|
+
this.discoveryReport = report;
|
|
289
|
+
discoveredPackages = report.discovered.filter((pkg) => modules.includes(docKindToModule(pkg.manifest.kind)));
|
|
290
|
+
discoveredCacheFile =
|
|
291
|
+
!options.assetsRoot && discoveredPackages.length > 0 && cache.enabled
|
|
292
|
+
? discoveredCachePath(discoveredPackages, modules, cache, discoveryOptions.from)
|
|
293
|
+
: undefined;
|
|
294
|
+
discoveredFallbackCacheFile =
|
|
295
|
+
!options.assetsRoot && discoveredPackages.length > 0 && cache.enabled
|
|
296
|
+
? fallbackDiscoveredCachePath(discoveredPackages, modules, cache, discoveryOptions.from)
|
|
297
|
+
: undefined;
|
|
298
|
+
if (!options.assetsRoot) {
|
|
299
|
+
const cached = tryLoadDiscoveredIndexCache([discoveredCacheFile, discoveredFallbackCacheFile].filter((path) => !!path), modules, cache);
|
|
300
|
+
if (cached) {
|
|
301
|
+
this.hydrateFromPrebuilt(cached);
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
for (const pkg of discoveredPackages) {
|
|
306
|
+
discoveredEntries.push(...loadDocsFromPackage(pkg));
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
if (options.assetsRoot) {
|
|
310
|
+
const root = options.assetsRoot;
|
|
311
|
+
const prebuilt = tryLoadPrebuiltIndex(root, modules);
|
|
312
|
+
if (prebuilt && discoveredEntries.length === 0) {
|
|
313
|
+
// Fast path: prebuilt-only mode (no discovery to merge with).
|
|
314
|
+
// Rehydrate index + entries from the prebuilt envelope without
|
|
315
|
+
// re-walking markdown OR rebuilding the MiniSearch index.
|
|
316
|
+
// Critical for CLI cold start — Phase 2 broke this path before
|
|
317
|
+
// by always passing `discover` even when no packages declared
|
|
318
|
+
// rayfinDocs; restored here by deferring the fast-path decision
|
|
319
|
+
// until AFTER discovery has actually produced results.
|
|
320
|
+
this.hydrateFromPrebuilt(prebuilt);
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
323
|
+
const bundled = prebuilt
|
|
324
|
+
? prebuilt.entries
|
|
325
|
+
: loadDocs(root, [...modules]);
|
|
326
|
+
if (discoveredEntries.length === 0) {
|
|
327
|
+
// No discovery overlay — skip the merge map entirely.
|
|
328
|
+
this.entries = bundled;
|
|
329
|
+
}
|
|
330
|
+
else {
|
|
331
|
+
const merged = new Map();
|
|
332
|
+
for (const entry of bundled) {
|
|
333
|
+
merged.set(entry.id, entry);
|
|
334
|
+
}
|
|
335
|
+
for (const entry of discoveredEntries) {
|
|
336
|
+
// Per-package wins on collision: later `set` overwrites the
|
|
337
|
+
// earlier bundled entry with the same id.
|
|
338
|
+
merged.set(entry.id, entry);
|
|
339
|
+
}
|
|
340
|
+
this.entries = [...merged.values()];
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
else {
|
|
344
|
+
// discover-only mode (no bundled corpus).
|
|
345
|
+
this.entries = discoveredEntries;
|
|
346
|
+
}
|
|
347
|
+
const index = createDocsIndex(this.entries);
|
|
348
|
+
this.entriesById = index.entriesById;
|
|
349
|
+
this.symbolToSections = index.symbolToSections;
|
|
350
|
+
this.searchIndex = index.mini;
|
|
351
|
+
if (!options.assetsRoot && discoveredPackages.length > 0) {
|
|
352
|
+
writeDiscoveredIndexCache(discoveredCacheFile, discoveredFallbackCacheFile, cache, serializeDocsIndex(this.entries, [...modules], index));
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
/**
|
|
356
|
+
* Returns a frozen snapshot of the discovery report from
|
|
357
|
+
* construction, if `discover` was passed. `undefined` when no
|
|
358
|
+
* discovery ran (bundled-only mode). Useful for surfacing "package X
|
|
359
|
+
* is in your trust list but not installed" or "manifest invalid;
|
|
360
|
+
* skipped" diagnostics. The returned object is deep-frozen so
|
|
361
|
+
* consumers cannot mutate the service's internal state.
|
|
362
|
+
*/
|
|
363
|
+
getDiscoveryReport() {
|
|
364
|
+
if (!this.discoveryReport)
|
|
365
|
+
return undefined;
|
|
366
|
+
// Deep clone via JSON round-trip then freeze. Discovery results
|
|
367
|
+
// are small structured data — never functions or class instances —
|
|
368
|
+
// so JSON is safe and avoids exposing the internal references.
|
|
369
|
+
const frozen = JSON.parse(JSON.stringify(this.discoveryReport));
|
|
370
|
+
Object.freeze(frozen);
|
|
371
|
+
Object.freeze(frozen.discovered);
|
|
372
|
+
Object.freeze(frozen.notInstalled);
|
|
373
|
+
Object.freeze(frozen.untrustedSkipped);
|
|
374
|
+
Object.freeze(frozen.invalidManifest);
|
|
375
|
+
return frozen;
|
|
376
|
+
}
|
|
377
|
+
listDocs(module) {
|
|
378
|
+
return this.entries
|
|
379
|
+
.filter((entry) => (module ? entry.module === module : true))
|
|
380
|
+
.map((entry) => ({
|
|
381
|
+
id: entry.id,
|
|
382
|
+
module: entry.module,
|
|
383
|
+
path: entry.path,
|
|
384
|
+
title: entry.title,
|
|
385
|
+
...(entry.source ? { source: entry.source } : {}),
|
|
386
|
+
}));
|
|
387
|
+
}
|
|
388
|
+
getDocById(id) {
|
|
389
|
+
return this.entriesById.get(id);
|
|
390
|
+
}
|
|
391
|
+
getDocsByPath(path, module) {
|
|
392
|
+
const normalized = path.replace(/^\//, '');
|
|
393
|
+
return this.entries.filter((entry) => entry.path === normalized && (module ? entry.module === module : true));
|
|
394
|
+
}
|
|
395
|
+
getDocByPath(path, module) {
|
|
396
|
+
const matches = this.getDocsByPath(path, module);
|
|
397
|
+
return matches.length === 1 ? matches[0] : undefined;
|
|
398
|
+
}
|
|
399
|
+
searchDocs(query, module, limit = 10, scope = 'docs') {
|
|
400
|
+
return searchDocs({
|
|
401
|
+
mini: this.searchIndex,
|
|
402
|
+
entriesById: this.entriesById,
|
|
403
|
+
symbolToSections: this.symbolToSections,
|
|
404
|
+
}, query, module, limit, scope);
|
|
405
|
+
}
|
|
406
|
+
getSymbolDocs(symbol, module) {
|
|
407
|
+
const qualified = parseQualifiedSymbol(symbol);
|
|
408
|
+
const key = (qualified?.symbol ?? symbol).toLowerCase();
|
|
409
|
+
const sections = this.symbolToSections.get(key) ?? [];
|
|
410
|
+
return sections.filter((section) => {
|
|
411
|
+
if (module && section.module !== module) {
|
|
412
|
+
return false;
|
|
413
|
+
}
|
|
414
|
+
if (!qualified) {
|
|
415
|
+
return true;
|
|
416
|
+
}
|
|
417
|
+
return (section.source?.packageName.toLowerCase() === qualified.owner ||
|
|
418
|
+
section.source?.module.toLowerCase() === qualified.owner);
|
|
419
|
+
});
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
//# sourceMappingURL=index.js.map
|
package/dist/loader.d.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { DiscoveredPackage } from './discovery.js';
|
|
2
|
+
import type { DocEntry, DocModule } from './types.js';
|
|
3
|
+
export declare function loadDocs(assetsRoot: string, modules?: DocModule[]): DocEntry[];
|
|
4
|
+
/**
|
|
5
|
+
* Walk the docs tree of a discovered package and produce {@link DocEntry}
|
|
6
|
+
* items tagged with the package's source metadata.
|
|
7
|
+
*
|
|
8
|
+
* Per design.md, discovered packages have a flat `<packageRoot>/<dir>/`
|
|
9
|
+
* layout (no `/docs/<module>/` nesting). The manifest's `kind` is
|
|
10
|
+
* mapped to a legacy `DocModule` so existing CLI/MCP filters
|
|
11
|
+
* (`--module ts-sdk`) keep working through the migration.
|
|
12
|
+
*
|
|
13
|
+
* **Symlink safety.** The manifest validator in `discovery.ts` rejects
|
|
14
|
+
* `..` segments in the manifest's `dir` string, but a malicious package
|
|
15
|
+
* could still place a symlink at `<packageRoot>/<dir>` or inside that
|
|
16
|
+
* tree pointing to e.g. `/etc/passwd`. We resolve the package root,
|
|
17
|
+
* docs root, walked directories, and every walked file's realpath, then
|
|
18
|
+
* skip anything that escapes the trusted package/docs boundaries.
|
|
19
|
+
*/
|
|
20
|
+
export declare function loadDocsFromPackage(pkg: DiscoveredPackage): DocEntry[];
|
|
21
|
+
//# sourceMappingURL=loader.d.ts.map
|