@get-technology-inc/jamf-docs-mcp-server 5.6.0 → 5.8.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/sources.d.ts +124 -1
- package/dist/core/constants/sources.d.ts.map +1 -1
- package/dist/core/constants/sources.js +67 -0
- package/dist/core/constants/sources.js.map +1 -1
- package/dist/core/services/cache-key.d.ts +14 -0
- package/dist/core/services/cache-key.d.ts.map +1 -1
- package/dist/core/services/cache-key.js +3 -0
- package/dist/core/services/cache-key.js.map +1 -1
- package/dist/core/services/intercom-service.d.ts +85 -0
- package/dist/core/services/intercom-service.d.ts.map +1 -0
- package/dist/core/services/intercom-service.js +312 -0
- package/dist/core/services/intercom-service.js.map +1 -0
- 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.map +1 -1
- package/dist/core/services/static-article-service.js +29 -0
- package/dist/core/services/static-article-service.js.map +1 -1
- package/dist/core/tools/get-toc.d.ts.map +1 -1
- package/dist/core/tools/get-toc.js +95 -5
- 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 +45 -11
- package/dist/core/tools/list-products.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reading an Intercom Help Center.
|
|
3
|
+
*
|
|
4
|
+
* support.jamf.com is a Next.js app whose every page embeds its own data as
|
|
5
|
+
* JSON in `<script id="__NEXT_DATA__">`. That is the source of truth: the
|
|
6
|
+
* rendered DOM is a view of it, and parsing the JSON avoids guessing at
|
|
7
|
+
* class names that change with any theme update.
|
|
8
|
+
*
|
|
9
|
+
* The content model is a block list, not HTML, so it is rendered to Markdown
|
|
10
|
+
* here rather than going through `content-parser`.
|
|
11
|
+
*/
|
|
12
|
+
import * as cheerio from 'cheerio';
|
|
13
|
+
import { httpGetText } from '../http-client.js';
|
|
14
|
+
import { cacheKey } from './cache-key.js';
|
|
15
|
+
import { PAGINATION_CONFIG, TOKEN_CONFIG } from '../constants.js';
|
|
16
|
+
import { calculatePagination, truncateListByTokens, buildPaginationNote, } from './tokenizer.js';
|
|
17
|
+
// ─── __NEXT_DATA__ ──────────────────────────────────────────────
|
|
18
|
+
/**
|
|
19
|
+
* Pull the embedded page data out of an Intercom Help Center page.
|
|
20
|
+
*
|
|
21
|
+
* The opening tag carries a `nonce` attribute, so a regex anchored on
|
|
22
|
+
* `<script id="__NEXT_DATA__" type="application/json">` misses every page.
|
|
23
|
+
* Matching up to the first `>` is what makes it robust to attribute drift.
|
|
24
|
+
*/
|
|
25
|
+
export function parseNextData(html) {
|
|
26
|
+
const match = /<script id="__NEXT_DATA__"[^>]*>([\s\S]*?)<\/script>/.exec(html);
|
|
27
|
+
if (match?.[1] === undefined) {
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
try {
|
|
31
|
+
return JSON.parse(match[1]);
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
/** `props.pageProps`, or null when the page is not shaped like one. */
|
|
38
|
+
function pageProps(html) {
|
|
39
|
+
const data = parseNextData(html);
|
|
40
|
+
const props = data?.props?.pageProps;
|
|
41
|
+
return typeof props === 'object' && props !== null
|
|
42
|
+
? props
|
|
43
|
+
: null;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* A JSON value read back as a string, or the fallback.
|
|
47
|
+
*
|
|
48
|
+
* `String(value)` on an `unknown` renders an object as "[object Object]" and
|
|
49
|
+
* puts it straight into a title. These payloads come off the wire, so the
|
|
50
|
+
* narrowing has to happen at the boundary rather than being asserted.
|
|
51
|
+
*/
|
|
52
|
+
function asString(value, fallback = '') {
|
|
53
|
+
if (typeof value === 'string') {
|
|
54
|
+
return value;
|
|
55
|
+
}
|
|
56
|
+
if (typeof value === 'number') {
|
|
57
|
+
return String(value);
|
|
58
|
+
}
|
|
59
|
+
return fallback;
|
|
60
|
+
}
|
|
61
|
+
/** Inline HTML inside a block's `text`, flattened to Markdown-safe text. */
|
|
62
|
+
function inlineText(html) {
|
|
63
|
+
const $ = cheerio.load(`<div>${html}</div>`);
|
|
64
|
+
$('a[href]').each((_, el) => {
|
|
65
|
+
const href = $(el).attr('href') ?? '';
|
|
66
|
+
const label = $(el).text();
|
|
67
|
+
if (href !== '' && label !== '') {
|
|
68
|
+
$(el).replaceWith(`[${label}](${href})`);
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
$('code').each((_, el) => { $(el).replaceWith(`\`${$(el).text()}\``); });
|
|
72
|
+
$('strong, b').each((_, el) => { $(el).replaceWith(`**${$(el).text()}**`); });
|
|
73
|
+
$('em, i').each((_, el) => { $(el).replaceWith(`*${$(el).text()}*`); });
|
|
74
|
+
return $('div').text().replace(/\s+/g, ' ').trim();
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Render a nested list.
|
|
78
|
+
*
|
|
79
|
+
* A list item carries its text as a `content` block array — usually a single
|
|
80
|
+
* `paragraph` — not as `text`. The block's own `text` is a pre-rendered
|
|
81
|
+
* string of the whole list ("1. …\n2. …"), which is why reading `item.text`
|
|
82
|
+
* produces empty bullets rather than an obvious error: every item has the
|
|
83
|
+
* field, and it is undefined on all of them.
|
|
84
|
+
*
|
|
85
|
+
* Nested lists arrive as further list blocks inside that same `content`, so
|
|
86
|
+
* they are rendered by recursing through {@link renderBlocks} with the depth
|
|
87
|
+
* carried in the indent.
|
|
88
|
+
*/
|
|
89
|
+
function renderList(items, ordered, depth) {
|
|
90
|
+
const indent = ' '.repeat(depth);
|
|
91
|
+
return items
|
|
92
|
+
.map((item, index) => {
|
|
93
|
+
const bullet = ordered ? `${String(index + 1)}.` : '-';
|
|
94
|
+
const body = item.content !== undefined && item.content.length > 0
|
|
95
|
+
? renderBlocks(item.content, depth + 1).trim()
|
|
96
|
+
: inlineText(item.text ?? '');
|
|
97
|
+
if (body === '') {
|
|
98
|
+
return '';
|
|
99
|
+
}
|
|
100
|
+
const [first = '', ...rest] = body.split('\n');
|
|
101
|
+
const continuation = rest.map(line => `${indent} ${line}`).join('\n');
|
|
102
|
+
return `${indent}${bullet} ${first}\n${continuation === '' ? '' : `${continuation}\n`}`;
|
|
103
|
+
})
|
|
104
|
+
.join('');
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* The blocks that map to one Markdown construct each.
|
|
108
|
+
*
|
|
109
|
+
* Split from {@link renderBlock} so the two nesting types — `callout` and
|
|
110
|
+
* `collapsibleSection`, which recurse — stay legible next to each other
|
|
111
|
+
* rather than at the bottom of one long switch. Returns null for anything it
|
|
112
|
+
* does not handle.
|
|
113
|
+
*/
|
|
114
|
+
function renderSimpleBlock(block, depth) {
|
|
115
|
+
switch (block.type) {
|
|
116
|
+
case 'heading':
|
|
117
|
+
return `## ${inlineText(block.text ?? '')}\n\n`;
|
|
118
|
+
case 'subheading':
|
|
119
|
+
return `### ${inlineText(block.text ?? '')}\n\n`;
|
|
120
|
+
case 'paragraph': {
|
|
121
|
+
const text = inlineText(block.text ?? '');
|
|
122
|
+
return text === '' ? '' : `${text}\n\n`;
|
|
123
|
+
}
|
|
124
|
+
case 'orderedNestedList':
|
|
125
|
+
return `${renderList(block.items ?? [], true, depth)}\n`;
|
|
126
|
+
case 'unorderedNestedList':
|
|
127
|
+
return `${renderList(block.items ?? [], false, depth)}\n`;
|
|
128
|
+
case 'code':
|
|
129
|
+
return `\`\`\`\n${block.text ?? ''}\n\`\`\`\n\n`;
|
|
130
|
+
case 'horizontalRule':
|
|
131
|
+
return '---\n\n';
|
|
132
|
+
case 'image':
|
|
133
|
+
return block.url !== undefined ? `\n\n` : '';
|
|
134
|
+
case undefined:
|
|
135
|
+
default:
|
|
136
|
+
return null;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Render one Intercom block.
|
|
141
|
+
*
|
|
142
|
+
* Ten types occur across the live corpus, not the four the integration notes
|
|
143
|
+
* listed. `callout` and `collapsibleSection` nest their body under `content`
|
|
144
|
+
* rather than `text`, and `subheading`, `unorderedNestedList`,
|
|
145
|
+
* `collapsibleSection`, `image`, `code` and `horizontalRule` would all be
|
|
146
|
+
* dropped by a renderer that only knew the four — silently, since a missing
|
|
147
|
+
* block leaves no trace in the output.
|
|
148
|
+
*/
|
|
149
|
+
export function renderBlock(block, depth = 0) {
|
|
150
|
+
const simple = renderSimpleBlock(block, depth);
|
|
151
|
+
if (simple !== null) {
|
|
152
|
+
return simple;
|
|
153
|
+
}
|
|
154
|
+
switch (block.type) {
|
|
155
|
+
case 'callout':
|
|
156
|
+
// Rendered as a blockquote: a callout is emphasis, and losing it would
|
|
157
|
+
// turn "do not do this" into an ordinary sentence.
|
|
158
|
+
return `${renderBlocks(block.content ?? [])
|
|
159
|
+
.trimEnd()
|
|
160
|
+
.split('\n')
|
|
161
|
+
.map(line => `> ${line}`)
|
|
162
|
+
.join('\n')}\n\n`;
|
|
163
|
+
case 'collapsibleSection':
|
|
164
|
+
return `**${inlineText(block.summary ?? '')}**\n\n${renderBlocks(block.content ?? [])}`;
|
|
165
|
+
case undefined:
|
|
166
|
+
default:
|
|
167
|
+
// Unknown type: keep whatever text it has rather than dropping the
|
|
168
|
+
// block. Intercom adds types over time and silence is the worse
|
|
169
|
+
// failure — a reader cannot tell a missing paragraph from one that was
|
|
170
|
+
// never written.
|
|
171
|
+
return block.text !== undefined ? `${inlineText(block.text)}\n\n` : '';
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
export function renderBlocks(blocks, depth = 0) {
|
|
175
|
+
return blocks.map(block => renderBlock(block, depth)).join('');
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Parse one Help Center article page.
|
|
179
|
+
*
|
|
180
|
+
* `articleContent.markdown` exists on every article and is `null` on every
|
|
181
|
+
* one measured — the body is in `blocks`. Reading the field that is named
|
|
182
|
+
* for what you want is the trap here.
|
|
183
|
+
*/
|
|
184
|
+
export function parseIntercomArticle(html) {
|
|
185
|
+
const props = pageProps(html);
|
|
186
|
+
const article = props?.articleContent;
|
|
187
|
+
if (article === undefined) {
|
|
188
|
+
return null;
|
|
189
|
+
}
|
|
190
|
+
const blocks = (article.blocks ?? []);
|
|
191
|
+
// `breadcrumbs` is a sibling of `articleContent` under pageProps, not a key
|
|
192
|
+
// of it.
|
|
193
|
+
const crumbs = (props?.breadcrumbs ?? []);
|
|
194
|
+
return {
|
|
195
|
+
title: asString(article.title, 'Untitled'),
|
|
196
|
+
content: renderBlocks(blocks).trim(),
|
|
197
|
+
...(typeof article.description === 'string' && article.description !== ''
|
|
198
|
+
? { description: article.description } : {}),
|
|
199
|
+
...(typeof article.lastUpdatedDate === 'string'
|
|
200
|
+
? { lastUpdated: article.lastUpdatedDate.slice(0, 10) } : {}),
|
|
201
|
+
breadcrumb: crumbs
|
|
202
|
+
.map(crumb => crumb.label ?? crumb.name ?? '')
|
|
203
|
+
.filter(label => label !== ''),
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
function slugFromUrl(url) {
|
|
207
|
+
const last = url.replace(/\/$/, '').split('/').pop() ?? '';
|
|
208
|
+
// `/collections/12369024-jamf-pro` → `jamf-pro`. The numeric id is
|
|
209
|
+
// Intercom's and changes if a collection is recreated; the slug is what a
|
|
210
|
+
// reader recognises, so it is what publication ids are built from.
|
|
211
|
+
return last.replace(/^\d+-/, '');
|
|
212
|
+
}
|
|
213
|
+
/** The Help Center's top-level collections for one locale. */
|
|
214
|
+
export async function listIntercomCollections(ctx, source, locale) {
|
|
215
|
+
const key = cacheKey('intercom-collections', { source: source.id, locale });
|
|
216
|
+
const cached = await ctx.cache.get(key);
|
|
217
|
+
if (cached !== null) {
|
|
218
|
+
return cached;
|
|
219
|
+
}
|
|
220
|
+
const html = await httpGetText(`${source.baseUrl}/${locale}/`);
|
|
221
|
+
const props = pageProps(html);
|
|
222
|
+
const home = props?.home;
|
|
223
|
+
const collections = (home?.collections ?? []).map((collection) => {
|
|
224
|
+
const url = asString(collection.url);
|
|
225
|
+
return {
|
|
226
|
+
id: asString(collection.id),
|
|
227
|
+
slug: typeof collection.slug === 'string' && collection.slug !== ''
|
|
228
|
+
? collection.slug
|
|
229
|
+
: slugFromUrl(url),
|
|
230
|
+
name: asString(collection.name),
|
|
231
|
+
description: asString(collection.description),
|
|
232
|
+
url,
|
|
233
|
+
articleCount: typeof collection.articleCount === 'number' ? collection.articleCount : 0,
|
|
234
|
+
};
|
|
235
|
+
});
|
|
236
|
+
await ctx.cache.set(key, collections, ctx.config.cacheTtl.products);
|
|
237
|
+
return collections;
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* The article tree of one collection.
|
|
241
|
+
*
|
|
242
|
+
* A collection page's `__NEXT_DATA__` carries the whole subtree —
|
|
243
|
+
* subcollections and every article summary — so one request answers what
|
|
244
|
+
* crawling 356 article pages would.
|
|
245
|
+
*/
|
|
246
|
+
export async function fetchIntercomCollectionToc(ctx, source, collection) {
|
|
247
|
+
const key = cacheKey('intercom-collection-toc', { source: source.id, collection: collection.id });
|
|
248
|
+
const cached = await ctx.cache.get(key);
|
|
249
|
+
if (cached !== null) {
|
|
250
|
+
return cached;
|
|
251
|
+
}
|
|
252
|
+
const html = await httpGetText(collection.url);
|
|
253
|
+
const props = pageProps(html);
|
|
254
|
+
const raw = props?.collection;
|
|
255
|
+
const toEntries = (summaries) => (summaries ?? []).map(summary => ({
|
|
256
|
+
title: asString(summary.title, 'Untitled'),
|
|
257
|
+
url: asString(summary.url),
|
|
258
|
+
}));
|
|
259
|
+
const entries = [
|
|
260
|
+
// Articles that sit directly in the collection come first: they are the
|
|
261
|
+
// ones with no subcollection to file them under, and dropping them is
|
|
262
|
+
// the easy mistake — Jamf Pro has 11 of them beside 24 subcollections.
|
|
263
|
+
...toEntries(raw?.articleSummaries),
|
|
264
|
+
...(raw?.subcollections ?? []).map((sub) => {
|
|
265
|
+
const children = toEntries(sub.articleSummaries);
|
|
266
|
+
const entry = {
|
|
267
|
+
title: asString(sub.name, 'Untitled'),
|
|
268
|
+
url: asString(sub.url),
|
|
269
|
+
};
|
|
270
|
+
if (children.length > 0) {
|
|
271
|
+
entry.children = children;
|
|
272
|
+
}
|
|
273
|
+
return entry;
|
|
274
|
+
}),
|
|
275
|
+
];
|
|
276
|
+
await ctx.cache.set(key, entries, ctx.config.cacheTtl.products);
|
|
277
|
+
return entries;
|
|
278
|
+
}
|
|
279
|
+
/**
|
|
280
|
+
* A `FetchTocResult` for one Intercom collection.
|
|
281
|
+
*
|
|
282
|
+
* Pagination and truncation match the other TOC paths, so a caller cannot
|
|
283
|
+
* tell from the response shape which kind of source answered.
|
|
284
|
+
*/
|
|
285
|
+
export async function fetchIntercomToc(ctx, source, collection, options = {}) {
|
|
286
|
+
const page = options.page ?? PAGINATION_CONFIG.DEFAULT_PAGE;
|
|
287
|
+
const maxTokens = options.maxTokens ?? TOKEN_CONFIG.DEFAULT_MAX_TOKENS;
|
|
288
|
+
const allToc = await fetchIntercomCollectionToc(ctx, source, collection);
|
|
289
|
+
const count = (entries) => entries.reduce((total, entry) => total + 1 + (entry.children !== undefined ? count(entry.children) : 0), 0);
|
|
290
|
+
const serialise = (entry, depth = 0) => {
|
|
291
|
+
const indent = ' '.repeat(depth);
|
|
292
|
+
const children = entry.children?.map(child => serialise(child, depth + 1)).join('') ?? '';
|
|
293
|
+
return `${indent}- ${entry.title}\n${children}`;
|
|
294
|
+
};
|
|
295
|
+
const calc = calculatePagination(allToc.length, page, PAGINATION_CONFIG.DEFAULT_PAGE_SIZE);
|
|
296
|
+
const { items, tokenCount, truncated } = truncateListByTokens(allToc.slice(calc.startIndex, calc.endIndex), maxTokens, serialise);
|
|
297
|
+
const paginationNote = buildPaginationNote(calc);
|
|
298
|
+
return {
|
|
299
|
+
toc: items,
|
|
300
|
+
pagination: {
|
|
301
|
+
page: calc.page,
|
|
302
|
+
pageSize: calc.pageSize,
|
|
303
|
+
totalPages: calc.totalPages,
|
|
304
|
+
totalItems: count(allToc),
|
|
305
|
+
hasNext: calc.hasNext,
|
|
306
|
+
hasPrev: calc.hasPrev,
|
|
307
|
+
},
|
|
308
|
+
tokenInfo: { tokenCount, truncated, maxTokens },
|
|
309
|
+
...(paginationNote !== undefined ? { paginationNote } : {}),
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
//# sourceMappingURL=intercom-service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"intercom-service.js","sourceRoot":"","sources":["../../../src/core/services/intercom-service.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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"}
|
|
@@ -1 +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;
|
|
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;AAEzD,OAAO,KAAK,EAAE,mBAAmB,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAa3E;;;;;;;;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"}
|