@get-technology-inc/jamf-docs-mcp-server 5.5.0 → 5.7.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/dist/core/constants/limits.d.ts +21 -0
- package/dist/core/constants/limits.d.ts.map +1 -1
- package/dist/core/constants/limits.js +0 -1
- package/dist/core/constants/limits.js.map +1 -1
- package/dist/core/constants/sources.d.ts +122 -0
- package/dist/core/constants/sources.d.ts.map +1 -0
- package/dist/core/constants/sources.js +93 -0
- package/dist/core/constants/sources.js.map +1 -0
- package/dist/core/services/article-service.d.ts.map +1 -1
- package/dist/core/services/article-service.js +18 -27
- package/dist/core/services/article-service.js.map +1 -1
- package/dist/core/services/article-view.d.ts +21 -0
- package/dist/core/services/article-view.d.ts.map +1 -0
- package/dist/core/services/article-view.js +48 -0
- package/dist/core/services/article-view.js.map +1 -0
- package/dist/core/services/cache-key.d.ts +18 -0
- package/dist/core/services/cache-key.d.ts.map +1 -1
- package/dist/core/services/cache-key.js +2 -0
- package/dist/core/services/cache-key.js.map +1 -1
- package/dist/core/services/content-parser.d.ts +24 -1
- package/dist/core/services/content-parser.d.ts.map +1 -1
- package/dist/core/services/content-parser.js +24 -10
- package/dist/core/services/content-parser.js.map +1 -1
- package/dist/core/services/sitemap-service.d.ts +60 -0
- package/dist/core/services/sitemap-service.d.ts.map +1 -0
- package/dist/core/services/sitemap-service.js +201 -0
- package/dist/core/services/sitemap-service.js.map +1 -0
- package/dist/core/services/static-article-service.d.ts +38 -0
- package/dist/core/services/static-article-service.d.ts.map +1 -0
- package/dist/core/services/static-article-service.js +121 -0
- package/dist/core/services/static-article-service.js.map +1 -0
- package/dist/core/tools/get-toc.d.ts.map +1 -1
- package/dist/core/tools/get-toc.js +32 -4
- package/dist/core/tools/get-toc.js.map +1 -1
- package/dist/core/tools/list-products.d.ts.map +1 -1
- package/dist/core/tools/list-products.js +21 -10
- package/dist/core/tools/list-products.js.map +1 -1
- package/dist/core/utils/url.d.ts +9 -1
- package/dist/core/utils/url.d.ts.map +1 -1
- package/dist/core/utils/url.js +11 -1
- package/dist/core/utils/url.js.map +1 -1
- package/package.json +1 -1
|
@@ -32,22 +32,32 @@ turndown.addRule('stripScripts', {
|
|
|
32
32
|
filter: ['script', 'style', 'noscript'],
|
|
33
33
|
replacement: () => '',
|
|
34
34
|
});
|
|
35
|
-
// ─── HTML cleaning ──────────────────────────────────────────────
|
|
36
35
|
/**
|
|
37
36
|
* Clean HTML content: remove unwanted elements, fix relative URLs.
|
|
37
|
+
*
|
|
38
|
+
* Both the selector set and the link base are parameters with learn.jamf.com
|
|
39
|
+
* defaults rather than module constants, so a caller reading a different site
|
|
40
|
+
* gets that site's markup rules. Existing single-argument callers — the
|
|
41
|
+
* glossary path among them — are unaffected.
|
|
38
42
|
*/
|
|
39
|
-
export function cleanHtml(
|
|
40
|
-
|
|
43
|
+
export function cleanHtml($, options) {
|
|
44
|
+
const selectors = options?.selectors ?? SELECTORS;
|
|
45
|
+
// Root-relative links resolve against the site they came from. Hard-coding
|
|
46
|
+
// learn.jamf.com here is what would point every internal link and image of
|
|
47
|
+
// a second source at the wrong host — silently, since both produce a valid
|
|
48
|
+
// absolute URL.
|
|
49
|
+
const linkBase = options?.linkBase ?? DOCS_BASE_URL;
|
|
50
|
+
$(selectors.REMOVE).remove();
|
|
41
51
|
$('a[href^="/"]').each((_, el) => {
|
|
42
52
|
const href = $(el).attr('href');
|
|
43
53
|
if (href !== undefined && href !== '') {
|
|
44
|
-
$(el).attr('href', `${
|
|
54
|
+
$(el).attr('href', `${linkBase}${href}`);
|
|
45
55
|
}
|
|
46
56
|
});
|
|
47
57
|
$('img[src^="/"]').each((_, el) => {
|
|
48
58
|
const src = $(el).attr('src');
|
|
49
59
|
if (src !== undefined && src !== '') {
|
|
50
|
-
$(el).attr('src', `${
|
|
60
|
+
$(el).attr('src', `${linkBase}${src}`);
|
|
51
61
|
}
|
|
52
62
|
});
|
|
53
63
|
}
|
|
@@ -85,7 +95,11 @@ export function linkInternalSpans($, resolve) {
|
|
|
85
95
|
*/
|
|
86
96
|
export function parseArticle(html, displayUrl, options) {
|
|
87
97
|
const $ = cheerio.load(html);
|
|
88
|
-
|
|
98
|
+
const selectors = options?.selectors ?? SELECTORS;
|
|
99
|
+
cleanHtml($, {
|
|
100
|
+
selectors,
|
|
101
|
+
...(options?.linkBase !== undefined ? { linkBase: options.linkBase } : {}),
|
|
102
|
+
});
|
|
89
103
|
// Before anything reads anchors: internal links are spans until this runs.
|
|
90
104
|
const resolveInternalLink = options?.resolveInternalLink;
|
|
91
105
|
if (resolveInternalLink !== undefined) {
|
|
@@ -99,7 +113,7 @@ export function parseArticle(html, displayUrl, options) {
|
|
|
99
113
|
// Checked before body wrappers because <article> is semantically broader
|
|
100
114
|
// and should take priority when both exist as siblings.
|
|
101
115
|
if (contentHtml === '') {
|
|
102
|
-
contentHtml = $(
|
|
116
|
+
contentHtml = $(selectors.CONTENT).html() ?? '';
|
|
103
117
|
}
|
|
104
118
|
// 3. Common FT body wrappers (taskbody, conbody, refbody, etc.)
|
|
105
119
|
if (contentHtml === '') {
|
|
@@ -113,19 +127,19 @@ export function parseArticle(html, displayUrl, options) {
|
|
|
113
127
|
if (contentHtml === '') {
|
|
114
128
|
contentHtml = html;
|
|
115
129
|
}
|
|
116
|
-
const extractedTitle = $(
|
|
130
|
+
const extractedTitle = $(selectors.TITLE).first().text().trim();
|
|
117
131
|
const title = extractedTitle !== '' ? extractedTitle : 'Untitled';
|
|
118
132
|
// Convert to Markdown and strip Turndown anchor artifacts from headings
|
|
119
133
|
const content = turndown.turndown(contentHtml)
|
|
120
134
|
.replace(/^(#{1,6}\s+)\[([^\]]*)\]\(#[^)]*\)/gm, '$1$2');
|
|
121
135
|
// Extract breadcrumb
|
|
122
|
-
const breadcrumb = $(
|
|
136
|
+
const breadcrumb = $(selectors.BREADCRUMB)
|
|
123
137
|
.map((_, el) => $(el).text().trim())
|
|
124
138
|
.get()
|
|
125
139
|
.filter(Boolean);
|
|
126
140
|
// Extract related articles
|
|
127
141
|
const relatedArticles = options?.includeRelated === true
|
|
128
|
-
? $(
|
|
142
|
+
? $(selectors.RELATED).map((_, el) => {
|
|
129
143
|
const rawHref = $(el).attr('href') ?? '';
|
|
130
144
|
if (rawHref === '' || rawHref.startsWith('#')) {
|
|
131
145
|
return { title: '', url: '' };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"content-parser.js","sourceRoot":"","sources":["../../../src/core/services/content-parser.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,OAAO,MAAM,SAAS,CAAC;AACnC,OAAO,eAAe,MAAM,UAAU,CAAC;AACvC,OAAO,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"content-parser.js","sourceRoot":"","sources":["../../../src/core/services/content-parser.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Table of contents for a static documentation source, built from its sitemap.
|
|
3
|
+
*
|
|
4
|
+
* A Fluid Topics publication ships a real TOC endpoint. A static site does
|
|
5
|
+
* not — but concepts.jamf.com publishes a sitemap whose paths already encode
|
|
6
|
+
* the hierarchy (`{locale}/guides/{category}/{article}`), so the tree can be
|
|
7
|
+
* derived from 990 URLs in one request instead of crawling 99 pages per
|
|
8
|
+
* locale and parsing each one's navigation.
|
|
9
|
+
*/
|
|
10
|
+
import type { StaticDocSource, StaticSection } from '../constants/sources.js';
|
|
11
|
+
import type { ServerContext } from '../types/context.js';
|
|
12
|
+
import type { FetchTocOptions, FetchTocResult, TocEntry } from '../types.js';
|
|
13
|
+
/** One `<url>` of a sitemap, reduced to what a TOC needs. */
|
|
14
|
+
export interface SitemapEntry {
|
|
15
|
+
/** Canonical absolute URL. */
|
|
16
|
+
url: string;
|
|
17
|
+
/** Path segments after the origin, e.g. `['en', 'guides', 'ai-governance']`. */
|
|
18
|
+
segments: string[];
|
|
19
|
+
/** `<lastmod>`, when present. */
|
|
20
|
+
lastModified?: string;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Extract every `<loc>` from a sitemap, with its `<lastmod>` when it has one.
|
|
24
|
+
*
|
|
25
|
+
* Deliberately a scan for `<url>` blocks rather than an XML parse: the
|
|
26
|
+
* document is a flat list, cheerio would have to be told to treat it as XML,
|
|
27
|
+
* and a sitemap that fails to parse should still yield the entries it does
|
|
28
|
+
* have.
|
|
29
|
+
*/
|
|
30
|
+
export declare function parseSitemap(xml: string): SitemapEntry[];
|
|
31
|
+
/** Fetch and cache a source's sitemap. */
|
|
32
|
+
export declare function loadSitemap(ctx: ServerContext, source: StaticDocSource): Promise<SitemapEntry[]>;
|
|
33
|
+
/**
|
|
34
|
+
* Turn a slug into a heading: `ai-governance` → `AI Governance`.
|
|
35
|
+
*
|
|
36
|
+
* Checked against the fourteen real titles concepts.jamf.com's own guides
|
|
37
|
+
* index renders, which is the only place the site publishes them without a
|
|
38
|
+
* per-page request: eight of nine match exactly. The ninth is
|
|
39
|
+
* `infrastructure-as-code`, which the site titles "Infrastructure As Code"
|
|
40
|
+
* and this produces as "Infrastructure as Code" — standard title case
|
|
41
|
+
* lowercases "as", and matching one page's capitalisation is not worth a
|
|
42
|
+
* special case.
|
|
43
|
+
*/
|
|
44
|
+
export declare function titleFromSlug(slug: string): string;
|
|
45
|
+
/**
|
|
46
|
+
* Build a TOC for one section of a static source, in one locale.
|
|
47
|
+
*
|
|
48
|
+
* @param locale the source's own locale code, e.g. `en` — not `en-US`
|
|
49
|
+
*/
|
|
50
|
+
export declare function buildStaticToc(ctx: ServerContext, source: StaticDocSource, section: StaticSection, locale: string): Promise<TocEntry[]>;
|
|
51
|
+
/**
|
|
52
|
+
* A `FetchTocResult` for a static source's section.
|
|
53
|
+
*
|
|
54
|
+
* Pagination and token truncation are the same operations the Fluid Topics
|
|
55
|
+
* path performs, applied to entries that came from a sitemap instead of a
|
|
56
|
+
* map: a caller paging through a Concepts TOC must not get a different shape
|
|
57
|
+
* from one paging through Jamf Pro's.
|
|
58
|
+
*/
|
|
59
|
+
export declare function fetchStaticToc(ctx: ServerContext, source: StaticDocSource, section: StaticSection, sourceLocale: string, options?: FetchTocOptions): Promise<FetchTocResult>;
|
|
60
|
+
//# sourceMappingURL=sitemap-service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sitemap-service.d.ts","sourceRoot":"","sources":["../../../src/core/services/sitemap-service.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAKH,OAAO,KAAK,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AAC9E,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAQ7E,6DAA6D;AAC7D,MAAM,WAAW,YAAY;IAC3B,8BAA8B;IAC9B,GAAG,EAAE,MAAM,CAAC;IACZ,gFAAgF;IAChF,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,iCAAiC;IACjC,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,YAAY,EAAE,CAmBxD;AAED,0CAA0C;AAC1C,wBAAsB,WAAW,CAC/B,GAAG,EAAE,aAAa,EAClB,MAAM,EAAE,eAAe,GACtB,OAAO,CAAC,YAAY,EAAE,CAAC,CASzB;AA4BD;;;;;;;;;;GAUG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAUlD;AAsBD;;;;GAIG;AACH,wBAAsB,cAAc,CAClC,GAAG,EAAE,aAAa,EAClB,MAAM,EAAE,eAAe,EACvB,OAAO,EAAE,aAAa,EACtB,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,QAAQ,EAAE,CAAC,CAwBrB;AAED;;;;;;;GAOG;AACH,wBAAsB,cAAc,CAClC,GAAG,EAAE,aAAa,EAClB,MAAM,EAAE,eAAe,EACvB,OAAO,EAAE,aAAa,EACtB,YAAY,EAAE,MAAM,EACpB,OAAO,GAAE,eAAoB,GAC5B,OAAO,CAAC,cAAc,CAAC,CAgCzB"}
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Table of contents for a static documentation source, built from its sitemap.
|
|
3
|
+
*
|
|
4
|
+
* A Fluid Topics publication ships a real TOC endpoint. A static site does
|
|
5
|
+
* not — but concepts.jamf.com publishes a sitemap whose paths already encode
|
|
6
|
+
* the hierarchy (`{locale}/guides/{category}/{article}`), so the tree can be
|
|
7
|
+
* derived from 990 URLs in one request instead of crawling 99 pages per
|
|
8
|
+
* locale and parsing each one's navigation.
|
|
9
|
+
*/
|
|
10
|
+
import { httpGetText } from '../http-client.js';
|
|
11
|
+
import { cacheKey } from './cache-key.js';
|
|
12
|
+
import { canonicalStaticUrl } from './static-article-service.js';
|
|
13
|
+
import { PAGINATION_CONFIG, TOKEN_CONFIG } from '../constants.js';
|
|
14
|
+
import { calculatePagination, truncateListByTokens, buildPaginationNote, } from './tokenizer.js';
|
|
15
|
+
/**
|
|
16
|
+
* Extract every `<loc>` from a sitemap, with its `<lastmod>` when it has one.
|
|
17
|
+
*
|
|
18
|
+
* Deliberately a scan for `<url>` blocks rather than an XML parse: the
|
|
19
|
+
* document is a flat list, cheerio would have to be told to treat it as XML,
|
|
20
|
+
* and a sitemap that fails to parse should still yield the entries it does
|
|
21
|
+
* have.
|
|
22
|
+
*/
|
|
23
|
+
export function parseSitemap(xml) {
|
|
24
|
+
const out = [];
|
|
25
|
+
for (const block of xml.match(/<url\b[\s\S]*?<\/url>/g) ?? []) {
|
|
26
|
+
const loc = /<loc>\s*([^<\s]+)\s*<\/loc>/.exec(block)?.[1];
|
|
27
|
+
if (loc === undefined) {
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
const lastmod = /<lastmod>\s*([^<\s]+)\s*<\/lastmod>/.exec(block)?.[1];
|
|
31
|
+
let segments;
|
|
32
|
+
try {
|
|
33
|
+
segments = new URL(loc).pathname.split('/').filter(Boolean);
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
out.push({
|
|
39
|
+
url: canonicalStaticUrl(loc),
|
|
40
|
+
segments,
|
|
41
|
+
...(lastmod !== undefined ? { lastModified: lastmod } : {}),
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
return out;
|
|
45
|
+
}
|
|
46
|
+
/** Fetch and cache a source's sitemap. */
|
|
47
|
+
export async function loadSitemap(ctx, source) {
|
|
48
|
+
const key = cacheKey('static-sitemap', { source: source.id });
|
|
49
|
+
const cached = await ctx.cache.get(key);
|
|
50
|
+
if (cached !== null) {
|
|
51
|
+
return cached;
|
|
52
|
+
}
|
|
53
|
+
const xml = await httpGetText(`${source.baseUrl}/sitemap.xml`);
|
|
54
|
+
const entries = parseSitemap(xml);
|
|
55
|
+
await ctx.cache.set(key, entries, ctx.config.cacheTtl.products);
|
|
56
|
+
return entries;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Terms whose casing a naive capitalise gets wrong.
|
|
60
|
+
*
|
|
61
|
+
* The sitemap gives slugs, not titles, and fetching 99 pages per locale to
|
|
62
|
+
* read each `og:title` is not worth one heading apiece. Word-by-word
|
|
63
|
+
* capitalisation produces "Ai Governance", "Byod" and "Ios", which read as
|
|
64
|
+
* mistakes; this table is what the site's own index page shows those as.
|
|
65
|
+
* Keyed lowercase.
|
|
66
|
+
*/
|
|
67
|
+
const TITLE_CASE_TERMS = {
|
|
68
|
+
ai: 'AI', api: 'API', byod: 'BYOD', ddm: 'DDM', it: 'IT', mdm: 'MDM',
|
|
69
|
+
pki: 'PKI', ldap: 'LDAP', scep: 'SCEP', ztna: 'ZTNA', sso: 'SSO',
|
|
70
|
+
vpn: 'VPN', mfa: 'MFA', dns: 'DNS', ip: 'IP', tls: 'TLS', url: 'URL',
|
|
71
|
+
json: 'JSON', xml: 'XML', sdk: 'SDK', cli: 'CLI', ui: 'UI', ux: 'UX',
|
|
72
|
+
id: 'ID', edr: 'EDR', xdr: 'XDR', siem: 'SIEM', saas: 'SaaS',
|
|
73
|
+
macos: 'macOS', ios: 'iOS', ipados: 'iPadOS', tvos: 'tvOS',
|
|
74
|
+
watchos: 'watchOS', visionos: 'visionOS', jamf: 'Jamf', apple: 'Apple',
|
|
75
|
+
aws: 'AWS', okta: 'Okta', entra: 'Entra', jss: 'JSS',
|
|
76
|
+
};
|
|
77
|
+
/** Words that stay lowercase unless they open the title. */
|
|
78
|
+
const TITLE_MINOR_WORDS = new Set([
|
|
79
|
+
'a', 'an', 'and', 'as', 'at', 'but', 'by', 'for', 'in', 'of', 'on', 'or',
|
|
80
|
+
'the', 'to', 'via', 'with',
|
|
81
|
+
]);
|
|
82
|
+
/**
|
|
83
|
+
* Turn a slug into a heading: `ai-governance` → `AI Governance`.
|
|
84
|
+
*
|
|
85
|
+
* Checked against the fourteen real titles concepts.jamf.com's own guides
|
|
86
|
+
* index renders, which is the only place the site publishes them without a
|
|
87
|
+
* per-page request: eight of nine match exactly. The ninth is
|
|
88
|
+
* `infrastructure-as-code`, which the site titles "Infrastructure As Code"
|
|
89
|
+
* and this produces as "Infrastructure as Code" — standard title case
|
|
90
|
+
* lowercases "as", and matching one page's capitalisation is not worth a
|
|
91
|
+
* special case.
|
|
92
|
+
*/
|
|
93
|
+
export function titleFromSlug(slug) {
|
|
94
|
+
const words = slug.split('-').filter(Boolean);
|
|
95
|
+
return words
|
|
96
|
+
.map((word, index) => {
|
|
97
|
+
const known = TITLE_CASE_TERMS[word.toLowerCase()];
|
|
98
|
+
if (known !== undefined) {
|
|
99
|
+
return known;
|
|
100
|
+
}
|
|
101
|
+
if (index > 0 && TITLE_MINOR_WORDS.has(word.toLowerCase())) {
|
|
102
|
+
return word.toLowerCase();
|
|
103
|
+
}
|
|
104
|
+
return word.charAt(0).toUpperCase() + word.slice(1);
|
|
105
|
+
})
|
|
106
|
+
.join(' ');
|
|
107
|
+
}
|
|
108
|
+
function toTocEntries(nodes, titles) {
|
|
109
|
+
return [...nodes]
|
|
110
|
+
.sort((a, b) => a.slug.localeCompare(b.slug))
|
|
111
|
+
.map(node => {
|
|
112
|
+
const children = toTocEntries(node.children.values(), titles);
|
|
113
|
+
const entry = {
|
|
114
|
+
title: titles.get(node.url ?? '') ?? titleFromSlug(node.slug),
|
|
115
|
+
url: node.url ?? '',
|
|
116
|
+
};
|
|
117
|
+
if (children.length > 0) {
|
|
118
|
+
entry.children = children;
|
|
119
|
+
}
|
|
120
|
+
return entry;
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Build a TOC for one section of a static source, in one locale.
|
|
125
|
+
*
|
|
126
|
+
* @param locale the source's own locale code, e.g. `en` — not `en-US`
|
|
127
|
+
*/
|
|
128
|
+
export async function buildStaticToc(ctx, source, section, locale) {
|
|
129
|
+
const entries = await loadSitemap(ctx, source);
|
|
130
|
+
const root = new Map();
|
|
131
|
+
for (const entry of entries) {
|
|
132
|
+
const [entryLocale, entrySection, ...rest] = entry.segments;
|
|
133
|
+
if (entryLocale !== locale || entrySection !== section.path) {
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
// The section's own index page is the container, not a child of itself.
|
|
137
|
+
if (rest.length === 0) {
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
140
|
+
let level = root;
|
|
141
|
+
let node;
|
|
142
|
+
for (const slug of rest) {
|
|
143
|
+
node = level.get(slug);
|
|
144
|
+
if (node === undefined) {
|
|
145
|
+
node = { slug, children: new Map() };
|
|
146
|
+
level.set(slug, node);
|
|
147
|
+
}
|
|
148
|
+
level = node.children;
|
|
149
|
+
}
|
|
150
|
+
if (node !== undefined) {
|
|
151
|
+
node.url = entry.url;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
return toTocEntries(root.values(), new Map());
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* A `FetchTocResult` for a static source's section.
|
|
158
|
+
*
|
|
159
|
+
* Pagination and token truncation are the same operations the Fluid Topics
|
|
160
|
+
* path performs, applied to entries that came from a sitemap instead of a
|
|
161
|
+
* map: a caller paging through a Concepts TOC must not get a different shape
|
|
162
|
+
* from one paging through Jamf Pro's.
|
|
163
|
+
*/
|
|
164
|
+
export async function fetchStaticToc(ctx, source, section, sourceLocale, options = {}) {
|
|
165
|
+
const page = options.page ?? PAGINATION_CONFIG.DEFAULT_PAGE;
|
|
166
|
+
const maxTokens = options.maxTokens ?? TOKEN_CONFIG.DEFAULT_MAX_TOKENS;
|
|
167
|
+
const allToc = await buildStaticToc(ctx, source, section, sourceLocale);
|
|
168
|
+
const totalItems = countTocEntries(allToc);
|
|
169
|
+
const paginationCalc = calculatePagination(allToc.length, page, PAGINATION_CONFIG.DEFAULT_PAGE_SIZE);
|
|
170
|
+
const paginated = allToc.slice(paginationCalc.startIndex, paginationCalc.endIndex);
|
|
171
|
+
const { items, tokenCount, truncated } = truncateListByTokens(paginated, maxTokens, tocEntryToString);
|
|
172
|
+
const paginationNote = buildPaginationNote(paginationCalc);
|
|
173
|
+
return {
|
|
174
|
+
toc: items,
|
|
175
|
+
pagination: {
|
|
176
|
+
page: paginationCalc.page,
|
|
177
|
+
pageSize: paginationCalc.pageSize,
|
|
178
|
+
totalPages: paginationCalc.totalPages,
|
|
179
|
+
totalItems,
|
|
180
|
+
hasNext: paginationCalc.hasNext,
|
|
181
|
+
hasPrev: paginationCalc.hasPrev,
|
|
182
|
+
},
|
|
183
|
+
tokenInfo: { tokenCount, truncated, maxTokens },
|
|
184
|
+
// The locale that answered is the one asked for: unlike Fluid Topics,
|
|
185
|
+
// where a family may exist in en-US only, a static section either
|
|
186
|
+
// publishes the locale or `resolveTocSource` refused before reaching here.
|
|
187
|
+
resolvedLocale: sourceLocale,
|
|
188
|
+
...(paginationNote !== undefined ? { paginationNote } : {}),
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
/** Total entries including nested children. Mirrors toc-service's own count. */
|
|
192
|
+
function countTocEntries(entries) {
|
|
193
|
+
return entries.reduce((count, entry) => count + 1 + (entry.children !== undefined ? countTocEntries(entry.children) : 0), 0);
|
|
194
|
+
}
|
|
195
|
+
/** Serialise one entry for token estimation. Mirrors toc-service's. */
|
|
196
|
+
function tocEntryToString(entry, depth = 0) {
|
|
197
|
+
const indent = ' '.repeat(depth);
|
|
198
|
+
const childrenStr = entry.children?.map(c => tocEntryToString(c, depth + 1)).join('') ?? '';
|
|
199
|
+
return `${indent}- ${entry.title}\n${childrenStr}`;
|
|
200
|
+
}
|
|
201
|
+
//# sourceMappingURL=sitemap-service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sitemap-service.js","sourceRoot":"","sources":["../../../src/core/services/sitemap-service.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Article retrieval for documentation sources outside Fluid Topics.
|
|
3
|
+
*
|
|
4
|
+
* The Fluid Topics path addresses a topic by `mapId` + `contentId` and gets
|
|
5
|
+
* an HTML fragment back. A static site has neither: the address is the URL,
|
|
6
|
+
* and the response is a whole page whose navigation has to be stripped before
|
|
7
|
+
* anything else. Everything downstream of that — sections, summaries,
|
|
8
|
+
* truncation — is shared with the FT path through
|
|
9
|
+
* {@link buildArticleView}, so the two cannot drift on what a caller sees.
|
|
10
|
+
*/
|
|
11
|
+
import type { StaticDocSource } from '../constants/sources.js';
|
|
12
|
+
import type { ServerContext } from '../types/context.js';
|
|
13
|
+
import type { FetchArticleOptions, FetchArticleResult } from '../types.js';
|
|
14
|
+
/**
|
|
15
|
+
* Canonicalise a URL for a static site.
|
|
16
|
+
*
|
|
17
|
+
* concepts.jamf.com's sitemap emits paths without a trailing slash while the
|
|
18
|
+
* site itself redirects to the slashed form — all 990 entries, so fetching
|
|
19
|
+
* them as listed is 990 redirects. Adding the slash up front is one line here
|
|
20
|
+
* and saves a round trip per article. Paths that look like a file (`.html`,
|
|
21
|
+
* `.json`) are left alone, as is anything with a query or fragment.
|
|
22
|
+
*/
|
|
23
|
+
export declare function canonicalStaticUrl(urlStr: string): string;
|
|
24
|
+
/**
|
|
25
|
+
* The page's own title, preferring Open Graph over `<title>`.
|
|
26
|
+
*
|
|
27
|
+
* `<title>` on these sites carries a site-name suffix ("API Utility | Jamf
|
|
28
|
+
* Concepts"); `og:title` is the same string without it, so it is tried first
|
|
29
|
+
* and the suffix trimmed only as a fallback.
|
|
30
|
+
*/
|
|
31
|
+
export declare function extractDocumentTitle(html: string): string | undefined;
|
|
32
|
+
/**
|
|
33
|
+
* Fetch and parse one article from a static documentation source.
|
|
34
|
+
*
|
|
35
|
+
* @param source the registry row for the hostname in `url`
|
|
36
|
+
*/
|
|
37
|
+
export declare function fetchStaticArticle(ctx: ServerContext, source: StaticDocSource, url: string, options?: FetchArticleOptions): Promise<FetchArticleResult>;
|
|
38
|
+
//# sourceMappingURL=static-article-service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"static-article-service.d.ts","sourceRoot":"","sources":["../../../src/core/services/static-article-service.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAQH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAC/D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,KAAK,EAAE,mBAAmB,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAU3E;;;;;;;;GAQG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAWzD;AAED;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CASrE;AAYD;;;;GAIG;AACH,wBAAsB,kBAAkB,CACtC,GAAG,EAAE,aAAa,EAClB,MAAM,EAAE,eAAe,EACvB,GAAG,EAAE,MAAM,EACX,OAAO,GAAE,mBAAwB,GAChC,OAAO,CAAC,kBAAkB,CAAC,CA2D7B"}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Article retrieval for documentation sources outside Fluid Topics.
|
|
3
|
+
*
|
|
4
|
+
* The Fluid Topics path addresses a topic by `mapId` + `contentId` and gets
|
|
5
|
+
* an HTML fragment back. A static site has neither: the address is the URL,
|
|
6
|
+
* and the response is a whole page whose navigation has to be stripped before
|
|
7
|
+
* anything else. Everything downstream of that — sections, summaries,
|
|
8
|
+
* truncation — is shared with the FT path through
|
|
9
|
+
* {@link buildArticleView}, so the two cannot drift on what a caller sees.
|
|
10
|
+
*/
|
|
11
|
+
import { httpGetText } from '../http-client.js';
|
|
12
|
+
import { parseArticle } from './content-parser.js';
|
|
13
|
+
import { buildArticleView } from './article-view.js';
|
|
14
|
+
import { extractSections } from './tokenizer.js';
|
|
15
|
+
import { cacheKey } from './cache-key.js';
|
|
16
|
+
import { TOKEN_CONFIG } from '../constants.js';
|
|
17
|
+
/**
|
|
18
|
+
* Canonicalise a URL for a static site.
|
|
19
|
+
*
|
|
20
|
+
* concepts.jamf.com's sitemap emits paths without a trailing slash while the
|
|
21
|
+
* site itself redirects to the slashed form — all 990 entries, so fetching
|
|
22
|
+
* them as listed is 990 redirects. Adding the slash up front is one line here
|
|
23
|
+
* and saves a round trip per article. Paths that look like a file (`.html`,
|
|
24
|
+
* `.json`) are left alone, as is anything with a query or fragment.
|
|
25
|
+
*/
|
|
26
|
+
export function canonicalStaticUrl(urlStr) {
|
|
27
|
+
try {
|
|
28
|
+
const url = new URL(urlStr);
|
|
29
|
+
const last = url.pathname.split('/').pop() ?? '';
|
|
30
|
+
if (!url.pathname.endsWith('/') && !last.includes('.')) {
|
|
31
|
+
url.pathname = `${url.pathname}/`;
|
|
32
|
+
}
|
|
33
|
+
return url.toString();
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
return urlStr;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* The page's own title, preferring Open Graph over `<title>`.
|
|
41
|
+
*
|
|
42
|
+
* `<title>` on these sites carries a site-name suffix ("API Utility | Jamf
|
|
43
|
+
* Concepts"); `og:title` is the same string without it, so it is tried first
|
|
44
|
+
* and the suffix trimmed only as a fallback.
|
|
45
|
+
*/
|
|
46
|
+
export function extractDocumentTitle(html) {
|
|
47
|
+
const og = /<meta[^>]+property=["']og:title["'][^>]+content=["']([^"']+)["']/i.exec(html)
|
|
48
|
+
?? /<meta[^>]+content=["']([^"']+)["'][^>]+property=["']og:title["']/i.exec(html);
|
|
49
|
+
if (og?.[1] !== undefined && og[1].trim() !== '') {
|
|
50
|
+
return decodeEntities(og[1].trim());
|
|
51
|
+
}
|
|
52
|
+
const title = /<title[^>]*>([^<]*)<\/title>/i.exec(html);
|
|
53
|
+
if (title?.[1] === undefined) {
|
|
54
|
+
return undefined;
|
|
55
|
+
}
|
|
56
|
+
const trimmed = title[1].split('|')[0]?.trim() ?? '';
|
|
57
|
+
return trimmed !== '' ? decodeEntities(trimmed) : undefined;
|
|
58
|
+
}
|
|
59
|
+
/** The handful of entities that survive into a title attribute. */
|
|
60
|
+
function decodeEntities(text) {
|
|
61
|
+
return text
|
|
62
|
+
.replace(/&/g, '&')
|
|
63
|
+
.replace(/</g, '<')
|
|
64
|
+
.replace(/>/g, '>')
|
|
65
|
+
.replace(/"/g, '"')
|
|
66
|
+
.replace(/&#(?:39|x27);/g, "'");
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Fetch and parse one article from a static documentation source.
|
|
70
|
+
*
|
|
71
|
+
* @param source the registry row for the hostname in `url`
|
|
72
|
+
*/
|
|
73
|
+
export async function fetchStaticArticle(ctx, source, url, options = {}) {
|
|
74
|
+
const maxTokens = options.maxTokens ?? TOKEN_CONFIG.DEFAULT_MAX_TOKENS;
|
|
75
|
+
const displayUrl = canonicalStaticUrl(url);
|
|
76
|
+
const key = cacheKey('static-article', { source: source.id, url: displayUrl });
|
|
77
|
+
let cached = await ctx.cache.get(key);
|
|
78
|
+
if (cached === null) {
|
|
79
|
+
const html = await httpGetText(displayUrl);
|
|
80
|
+
const documentTitle = extractDocumentTitle(html);
|
|
81
|
+
const parsed = parseArticle(html, displayUrl, {
|
|
82
|
+
// The source's own markup rules. Parsing a static page with Fluid
|
|
83
|
+
// Topics' selectors finds no content wrapper and falls through to
|
|
84
|
+
// <body>, which is the whole site chrome.
|
|
85
|
+
selectors: source.selectors,
|
|
86
|
+
// Root-relative links belong to this source, not learn.jamf.com.
|
|
87
|
+
linkBase: source.baseUrl,
|
|
88
|
+
...(options.includeRelated !== undefined ? { includeRelated: options.includeRelated } : {}),
|
|
89
|
+
});
|
|
90
|
+
// `parseArticle` reads the first surviving <h1>. On a static site the
|
|
91
|
+
// page heading often lives in a hero section outside the content wrapper,
|
|
92
|
+
// and stripping the chrome takes it with it — concepts.jamf.com's tool
|
|
93
|
+
// pages put their only <h1> there, so every one of the 37 came back
|
|
94
|
+
// "Untitled". The document's own title is the reliable answer when the
|
|
95
|
+
// body has none of its own; Fluid Topics never needs this because its
|
|
96
|
+
// titles arrive as metadata.
|
|
97
|
+
const title = parsed.title !== 'Untitled' ? parsed.title : documentTitle ?? parsed.title;
|
|
98
|
+
cached = { title, parsed, displayUrl };
|
|
99
|
+
await ctx.cache.set(key, cached, ctx.config.cacheTtl.article);
|
|
100
|
+
}
|
|
101
|
+
const { title, parsed } = cached;
|
|
102
|
+
// Provenance is part of the content, not metadata: it has to survive
|
|
103
|
+
// section extraction and truncation, both of which slice `content`, and a
|
|
104
|
+
// reader who asked for one section still needs to know what they are
|
|
105
|
+
// reading.
|
|
106
|
+
const content = source.provenance !== undefined
|
|
107
|
+
? `${parsed.content}\n\n---\n\n*${source.provenance}*\n`
|
|
108
|
+
: parsed.content;
|
|
109
|
+
const allSections = extractSections(content);
|
|
110
|
+
return buildArticleView({
|
|
111
|
+
title,
|
|
112
|
+
url: cached.displayUrl,
|
|
113
|
+
product: source.name,
|
|
114
|
+
breadcrumb: parsed.breadcrumb.length > 0 ? parsed.breadcrumb : undefined,
|
|
115
|
+
relatedArticles: options.includeRelated === true && parsed.relatedArticles.length > 0
|
|
116
|
+
? parsed.relatedArticles
|
|
117
|
+
: undefined,
|
|
118
|
+
sections: allSections,
|
|
119
|
+
}, content, options, maxTokens, allSections);
|
|
120
|
+
}
|
|
121
|
+
//# sourceMappingURL=static-article-service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"static-article-service.js","sourceRoot":"","sources":["../../../src/core/services/static-article-service.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"get-toc.d.ts","sourceRoot":"","sources":["../../../src/core/tools/get-toc.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,8BAA8B,CAAC;AAC9D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"get-toc.d.ts","sourceRoot":"","sources":["../../../src/core/tools/get-toc.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,8BAA8B,CAAC;AAC9D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAobzD,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,SAAS,EAAE,GAAG,EAAE,aAAa,GAAG,IAAI,CAuJ9E"}
|
|
@@ -6,8 +6,10 @@ import { appToolMeta } from '../apps/index.js';
|
|
|
6
6
|
import { GetTocInputSchema } from '../schemas/index.js';
|
|
7
7
|
import { reportProgress } from '../utils/progress.js';
|
|
8
8
|
import { TocOutputSchema } from '../schemas/output.js';
|
|
9
|
-
import { ResponseFormat, OutputMode, JAMF_PRODUCTS, PRODUCT_ID_LIST, TOKEN_CONFIG, PAGINATION_CONFIG } from '../constants.js';
|
|
9
|
+
import { ResponseFormat, OutputMode, JAMF_PRODUCTS, PRODUCT_ID_LIST, TOKEN_CONFIG, PAGINATION_CONFIG, DEFAULT_LOCALE } from '../constants.js';
|
|
10
10
|
import { fetchTableOfContents } from '../services/toc-service.js';
|
|
11
|
+
import { fetchStaticToc } from '../services/sitemap-service.js';
|
|
12
|
+
import { staticSectionById, } from '../constants/sources.js';
|
|
11
13
|
import { getAvailableVersions } from '../services/metadata.js';
|
|
12
14
|
import { sanitizeMarkdownText, sanitizeMarkdownUrl, getSafeErrorMessage } from '../utils/sanitize.js';
|
|
13
15
|
/**
|
|
@@ -291,6 +293,26 @@ async function resolveTocSource(ctx, params, version) {
|
|
|
291
293
|
};
|
|
292
294
|
}
|
|
293
295
|
const publication = params.publication ?? '';
|
|
296
|
+
// Static sources first: their sections are publications too, and they are
|
|
297
|
+
// not in the Fluid Topics maps registry, so asking it would report them as
|
|
298
|
+
// unknown and suggest something else.
|
|
299
|
+
const staticRow = staticSectionById(publication);
|
|
300
|
+
if (staticRow !== undefined) {
|
|
301
|
+
const locale = params.language ?? DEFAULT_LOCALE;
|
|
302
|
+
const sourceLocale = staticRow.source.locales[locale];
|
|
303
|
+
if (sourceLocale === undefined) {
|
|
304
|
+
return {
|
|
305
|
+
error: `${staticRow.source.name} does not publish in ${locale}. ` +
|
|
306
|
+
`Available: ${Object.keys(staticRow.source.locales).join(', ')}.`,
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
return {
|
|
310
|
+
source: publication,
|
|
311
|
+
sourceLabel: staticRow.section.title,
|
|
312
|
+
availableVersions: [],
|
|
313
|
+
staticSection: { ...staticRow, sourceLocale },
|
|
314
|
+
};
|
|
315
|
+
}
|
|
294
316
|
// Not an enum: there are 97 families and the set is upstream's to change,
|
|
295
317
|
// so the check is a registry lookup and a miss carries suggestions rather
|
|
296
318
|
// than a wall of every id.
|
|
@@ -358,11 +380,17 @@ export function registerGetTocTool(server, ctx) {
|
|
|
358
380
|
}
|
|
359
381
|
}
|
|
360
382
|
await reportProgress(extra, { progress: 0, total: 4, message: 'Fetching TOC...' });
|
|
361
|
-
const
|
|
383
|
+
const tocOptions = {
|
|
362
384
|
...(params.page !== undefined && { page: params.page }),
|
|
363
385
|
maxTokens: params.maxTokens ?? TOKEN_CONFIG.DEFAULT_MAX_TOKENS,
|
|
364
|
-
locale: params.language
|
|
365
|
-
}
|
|
386
|
+
locale: params.language,
|
|
387
|
+
};
|
|
388
|
+
// A static source has no map and no TOC endpoint; its tree comes from
|
|
389
|
+
// the sitemap. Pagination and truncation are shared so both kinds of
|
|
390
|
+
// TOC behave the same once the entries exist.
|
|
391
|
+
const tocResult = resolved.staticSection !== undefined
|
|
392
|
+
? await fetchStaticToc(ctx, resolved.staticSection.source, resolved.staticSection.section, resolved.staticSection.sourceLocale, tocOptions)
|
|
393
|
+
: await fetchTableOfContents(ctx, source, version, tocOptions);
|
|
366
394
|
await reportProgress(extra, { progress: 1, total: 4, message: 'Processing entries...' });
|
|
367
395
|
const { toc, pagination, tokenInfo, paginationNote, mapId, resolvedLocale } = tocResult;
|
|
368
396
|
// Build response
|