@get-technology-inc/jamf-docs-mcp-server 5.5.0 → 5.6.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 +65 -0
- package/dist/core/constants/sources.d.ts.map +1 -0
- package/dist/core/constants/sources.js +73 -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 +14 -0
- package/dist/core/services/cache-key.d.ts.map +1 -1
- package/dist/core/services/cache-key.js +1 -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/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/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
|
@@ -29,6 +29,27 @@ export declare const PAGINATION_CONFIG: {
|
|
|
29
29
|
readonly DEFAULT_PAGE_SIZE: 10;
|
|
30
30
|
readonly MAX_PAGE: 100;
|
|
31
31
|
};
|
|
32
|
+
/**
|
|
33
|
+
* The CSS selectors one documentation source needs to be readable.
|
|
34
|
+
*
|
|
35
|
+
* Extracted as a type because `SELECTORS` below describes learn.jamf.com
|
|
36
|
+
* specifically — its `<article>` markup, its breadcrumb classes, the wrappers
|
|
37
|
+
* Fluid Topics emits — and a second source has different ones. Declaring the
|
|
38
|
+
* shape lets each source carry its own set instead of every source being
|
|
39
|
+
* parsed as though it were Fluid Topics.
|
|
40
|
+
*/
|
|
41
|
+
export interface SelectorSet {
|
|
42
|
+
/** Where the article body lives. */
|
|
43
|
+
readonly CONTENT: string;
|
|
44
|
+
/** The page title. */
|
|
45
|
+
readonly TITLE: string;
|
|
46
|
+
/** Breadcrumb trail anchors. */
|
|
47
|
+
readonly BREADCRUMB: string;
|
|
48
|
+
/** Related-article anchors. */
|
|
49
|
+
readonly RELATED: string;
|
|
50
|
+
/** Everything to strip before converting to Markdown. */
|
|
51
|
+
readonly REMOVE: string;
|
|
52
|
+
}
|
|
32
53
|
export declare const SELECTORS: {
|
|
33
54
|
readonly CONTENT: "article, .article-content, main article, #content";
|
|
34
55
|
readonly TITLE: "h1";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"limits.d.ts","sourceRoot":"","sources":["../../../src/core/constants/limits.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,oBAAY,cAAc;IACxB,QAAQ,aAAa;IACrB,IAAI,SAAS;CACd;AAGD,oBAAY,UAAU;IACpB,IAAI,SAAS;IACb,OAAO,YAAY;CACpB;AAGD,eAAO,MAAM,cAAc;;;;;;;CAOjB,CAAC;AAGX,eAAO,MAAM,YAAY;;;;;;CAMf,CAAC;AAGX,eAAO,MAAM,iBAAiB;;;;CAIpB,CAAC;AAGX,eAAO,MAAM,SAAS;;;;;;
|
|
1
|
+
{"version":3,"file":"limits.d.ts","sourceRoot":"","sources":["../../../src/core/constants/limits.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,oBAAY,cAAc;IACxB,QAAQ,aAAa;IACrB,IAAI,SAAS;CACd;AAGD,oBAAY,UAAU;IACpB,IAAI,SAAS;IACb,OAAO,YAAY;CACpB;AAGD,eAAO,MAAM,cAAc;;;;;;;CAOjB,CAAC;AAGX,eAAO,MAAM,YAAY;;;;;;CAMf,CAAC;AAGX,eAAO,MAAM,iBAAiB;;;;CAIpB,CAAC;AAGX;;;;;;;;GAQG;AACH,MAAM,WAAW,WAAW;IAC1B,oCAAoC;IACpC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,sBAAsB;IACtB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,gCAAgC;IAChC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,+BAA+B;IAC/B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,yDAAyD;IACzD,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAED,eAAO,MAAM,SAAS;;;;;;CAaU,CAAC"}
|
|
@@ -36,7 +36,6 @@ export const PAGINATION_CONFIG = {
|
|
|
36
36
|
DEFAULT_PAGE_SIZE: 10,
|
|
37
37
|
MAX_PAGE: 100
|
|
38
38
|
};
|
|
39
|
-
// HTML selectors for learn.jamf.com (React-based site)
|
|
40
39
|
export const SELECTORS = {
|
|
41
40
|
// Main content - learn.jamf.com uses semantic article tag
|
|
42
41
|
CONTENT: 'article, .article-content, main article, #content',
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"limits.js","sourceRoot":"","sources":["../../../src/core/constants/limits.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,kBAAkB;AAClB,MAAM,CAAN,IAAY,cAGX;AAHD,WAAY,cAAc;IACxB,uCAAqB,CAAA;IACrB,+BAAa,CAAA;AACf,CAAC,EAHW,cAAc,KAAd,cAAc,QAGzB;AAED,6BAA6B;AAC7B,MAAM,CAAN,IAAY,UAGX;AAHD,WAAY,UAAU;IACpB,2BAAa,CAAA;IACb,iCAAmB,CAAA;AACrB,CAAC,EAHW,UAAU,KAAV,UAAU,QAGrB;AAED,iBAAiB;AACjB,MAAM,CAAC,MAAM,cAAc,GAAG;IAC5B,kBAAkB,EAAE,EAAE;IACtB,sBAAsB,EAAE,EAAE;IAC1B,2BAA2B,EAAE,CAAC,EAAQ,wDAAwD;IAC9F,oBAAoB,EAAE,GAAG,EAAa,uCAAuC;IAC7E,kBAAkB,EAAE,MAAM,EAAY,QAAQ;IAC9C,kBAAkB,EAAE,GAAG;CACf,CAAC;AAEX,uCAAuC;AACvC,MAAM,CAAC,MAAM,YAAY,GAAG;IAC1B,kBAAkB,EAAE,IAAI;IACxB,gBAAgB,EAAE,KAAK;IACvB,UAAU,EAAE,GAAG;IACf,eAAe,EAAE,CAAC,EAAG,mBAAmB;IACxC,oBAAoB,EAAE,CAAC,CAAE,wCAAwC;CACzD,CAAC;AAEX,2BAA2B;AAC3B,MAAM,CAAC,MAAM,iBAAiB,GAAG;IAC/B,YAAY,EAAE,CAAC;IACf,iBAAiB,EAAE,EAAE;IACrB,QAAQ,EAAE,GAAG;CACL,CAAC;
|
|
1
|
+
{"version":3,"file":"limits.js","sourceRoot":"","sources":["../../../src/core/constants/limits.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,kBAAkB;AAClB,MAAM,CAAN,IAAY,cAGX;AAHD,WAAY,cAAc;IACxB,uCAAqB,CAAA;IACrB,+BAAa,CAAA;AACf,CAAC,EAHW,cAAc,KAAd,cAAc,QAGzB;AAED,6BAA6B;AAC7B,MAAM,CAAN,IAAY,UAGX;AAHD,WAAY,UAAU;IACpB,2BAAa,CAAA;IACb,iCAAmB,CAAA;AACrB,CAAC,EAHW,UAAU,KAAV,UAAU,QAGrB;AAED,iBAAiB;AACjB,MAAM,CAAC,MAAM,cAAc,GAAG;IAC5B,kBAAkB,EAAE,EAAE;IACtB,sBAAsB,EAAE,EAAE;IAC1B,2BAA2B,EAAE,CAAC,EAAQ,wDAAwD;IAC9F,oBAAoB,EAAE,GAAG,EAAa,uCAAuC;IAC7E,kBAAkB,EAAE,MAAM,EAAY,QAAQ;IAC9C,kBAAkB,EAAE,GAAG;CACf,CAAC;AAEX,uCAAuC;AACvC,MAAM,CAAC,MAAM,YAAY,GAAG;IAC1B,kBAAkB,EAAE,IAAI;IACxB,gBAAgB,EAAE,KAAK;IACvB,UAAU,EAAE,GAAG;IACf,eAAe,EAAE,CAAC,EAAG,mBAAmB;IACxC,oBAAoB,EAAE,CAAC,CAAE,wCAAwC;CACzD,CAAC;AAEX,2BAA2B;AAC3B,MAAM,CAAC,MAAM,iBAAiB,GAAG;IAC/B,YAAY,EAAE,CAAC;IACf,iBAAiB,EAAE,EAAE;IACrB,QAAQ,EAAE,GAAG;CACL,CAAC;AAyBX,MAAM,CAAC,MAAM,SAAS,GAAG;IACvB,0DAA0D;IAC1D,OAAO,EAAE,mDAAmD;IAC5D,KAAK,EAAE,IAAI;IAEX,wCAAwC;IACxC,UAAU,EAAE,yDAAyD;IAErE,kBAAkB;IAClB,OAAO,EAAE,8DAA8D;IAEvE,+CAA+C;IAC/C,MAAM,EAAE,oHAAoH;CAC9F,CAAC"}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Documentation sources outside Fluid Topics.
|
|
3
|
+
*
|
|
4
|
+
* `learn.jamf.com` is a Fluid Topics instance and is addressed through
|
|
5
|
+
* bundles, maps and topics; everything in this file is a plain website that
|
|
6
|
+
* happens to hold Jamf documentation. They need different selectors, a
|
|
7
|
+
* different link-rewrite base, and their own cache namespace, so they are
|
|
8
|
+
* described here rather than being squeezed into {@link JAMF_PRODUCTS}.
|
|
9
|
+
*
|
|
10
|
+
* Adding a source means adding a row here, teaching
|
|
11
|
+
* {@link ALLOWED_HOSTNAMES} nothing (it derives from this table), and nothing
|
|
12
|
+
* else: the article path dispatches on hostname.
|
|
13
|
+
*/
|
|
14
|
+
import { type SelectorSet } from './limits.js';
|
|
15
|
+
export interface StaticDocSource {
|
|
16
|
+
/** Stable id, used as the cache namespace discriminator. */
|
|
17
|
+
readonly id: string;
|
|
18
|
+
/** The single hostname this source serves from. */
|
|
19
|
+
readonly hostname: string;
|
|
20
|
+
/** Origin, without a trailing slash. Root-relative links resolve against it. */
|
|
21
|
+
readonly baseUrl: string;
|
|
22
|
+
/** Human name, for prose and error messages. */
|
|
23
|
+
readonly name: string;
|
|
24
|
+
/** How to read this source's HTML. */
|
|
25
|
+
readonly selectors: SelectorSet;
|
|
26
|
+
/**
|
|
27
|
+
* Appended to every article served from this source.
|
|
28
|
+
*
|
|
29
|
+
* Not decoration. Jamf Concepts describes itself as an innovation lab whose
|
|
30
|
+
* subjects "aren't official products, although some of them might be one
|
|
31
|
+
* day" — presenting that beside learn.jamf.com's product documentation
|
|
32
|
+
* without saying which is which would be the actual error.
|
|
33
|
+
*/
|
|
34
|
+
readonly provenance?: string;
|
|
35
|
+
}
|
|
36
|
+
export declare const STATIC_DOC_SOURCES: {
|
|
37
|
+
readonly 'jamf-concepts': {
|
|
38
|
+
readonly id: "jamf-concepts";
|
|
39
|
+
readonly hostname: "concepts.jamf.com";
|
|
40
|
+
readonly baseUrl: "https://concepts.jamf.com";
|
|
41
|
+
readonly name: "Jamf Concepts";
|
|
42
|
+
readonly selectors: {
|
|
43
|
+
readonly CONTENT: "article, [class*=\"prose\"]";
|
|
44
|
+
readonly TITLE: "h1";
|
|
45
|
+
readonly BREADCRUMB: "[class*=\"breadcrumb\"] a";
|
|
46
|
+
readonly RELATED: "[class*=\"related\"] a";
|
|
47
|
+
readonly REMOVE: string;
|
|
48
|
+
};
|
|
49
|
+
readonly provenance: string;
|
|
50
|
+
};
|
|
51
|
+
};
|
|
52
|
+
export type StaticSourceId = keyof typeof STATIC_DOC_SOURCES;
|
|
53
|
+
/** Every non-Fluid-Topics hostname this server will fetch from. */
|
|
54
|
+
export declare const STATIC_SOURCE_HOSTNAMES: readonly string[];
|
|
55
|
+
/** The source that serves a hostname, or undefined when none does. */
|
|
56
|
+
export declare function staticSourceForHostname(hostname: string): StaticDocSource | undefined;
|
|
57
|
+
/**
|
|
58
|
+
* The source a URL belongs to, or undefined for Fluid Topics and anything else.
|
|
59
|
+
*
|
|
60
|
+
* Returns undefined rather than throwing on an unparseable URL: callers use
|
|
61
|
+
* this to *choose* a path, and the existing validation is what rejects a bad
|
|
62
|
+
* URL with a message about it.
|
|
63
|
+
*/
|
|
64
|
+
export declare function staticSourceForUrl(urlStr: string): StaticDocSource | undefined;
|
|
65
|
+
//# sourceMappingURL=sources.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sources.d.ts","sourceRoot":"","sources":["../../../src/core/constants/sources.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAa,KAAK,WAAW,EAAE,MAAM,aAAa,CAAC;AAE1D,MAAM,WAAW,eAAe;IAC9B,4DAA4D;IAC5D,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,mDAAmD;IACnD,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,gFAAgF;IAChF,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,gDAAgD;IAChD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,sCAAsC;IACtC,QAAQ,CAAC,SAAS,EAAE,WAAW,CAAC;IAChC;;;;;;;OAOG;IACH,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;CAC9B;AAiBD,eAAO,MAAM,kBAAkB;;;;;;;;;;;;;;;CAwBqB,CAAC;AAErD,MAAM,MAAM,cAAc,GAAG,MAAM,OAAO,kBAAkB,CAAC;AAE7D,mEAAmE;AACnE,eAAO,MAAM,uBAAuB,EAAE,SAAS,MAAM,EACa,CAAC;AAEnE,sEAAsE;AACtE,wBAAgB,uBAAuB,CAAC,QAAQ,EAAE,MAAM,GAAG,eAAe,GAAG,SAAS,CAErF;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM,GAAG,eAAe,GAAG,SAAS,CAM9E"}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Documentation sources outside Fluid Topics.
|
|
3
|
+
*
|
|
4
|
+
* `learn.jamf.com` is a Fluid Topics instance and is addressed through
|
|
5
|
+
* bundles, maps and topics; everything in this file is a plain website that
|
|
6
|
+
* happens to hold Jamf documentation. They need different selectors, a
|
|
7
|
+
* different link-rewrite base, and their own cache namespace, so they are
|
|
8
|
+
* described here rather than being squeezed into {@link JAMF_PRODUCTS}.
|
|
9
|
+
*
|
|
10
|
+
* Adding a source means adding a row here, teaching
|
|
11
|
+
* {@link ALLOWED_HOSTNAMES} nothing (it derives from this table), and nothing
|
|
12
|
+
* else: the article path dispatches on hostname.
|
|
13
|
+
*/
|
|
14
|
+
import { SELECTORS } from './limits.js';
|
|
15
|
+
/**
|
|
16
|
+
* Elements to strip that a static marketing-shaped site has and a Fluid
|
|
17
|
+
* Topics fragment does not.
|
|
18
|
+
*
|
|
19
|
+
* `nav`, `header` and `aside` are deliberately absent from
|
|
20
|
+
* {@link SELECTORS}.REMOVE: that set is also used by the glossary path and by
|
|
21
|
+
* every learn.jamf.com article, where removing them would change existing
|
|
22
|
+
* output. Here they are the difference between an article and an article
|
|
23
|
+
* plus the entire site navigation — one Concepts guide measures 23,824
|
|
24
|
+
* characters of text with the nav in and would spend a large part of any
|
|
25
|
+
* token budget on a sidebar.
|
|
26
|
+
*/
|
|
27
|
+
const STATIC_PAGE_REMOVE = `${SELECTORS.REMOVE}, nav, header, aside, [role="navigation"], [class*="sidebar"]`;
|
|
28
|
+
export const STATIC_DOC_SOURCES = {
|
|
29
|
+
'jamf-concepts': {
|
|
30
|
+
id: 'jamf-concepts',
|
|
31
|
+
hostname: 'concepts.jamf.com',
|
|
32
|
+
baseUrl: 'https://concepts.jamf.com',
|
|
33
|
+
name: 'Jamf Concepts',
|
|
34
|
+
selectors: {
|
|
35
|
+
// Every page type — guide, concept, about — wraps its body in exactly
|
|
36
|
+
// one `<article class="prose">`. `main` is deliberately NOT listed:
|
|
37
|
+
// `.html()` takes the first match in *document* order regardless of
|
|
38
|
+
// selector order, and `<main class="flex-1">` opens before the article
|
|
39
|
+
// and encloses the 220px sidebar. Including it pulled the whole guide
|
|
40
|
+
// index in ahead of the content.
|
|
41
|
+
CONTENT: 'article, [class*="prose"]',
|
|
42
|
+
TITLE: 'h1',
|
|
43
|
+
BREADCRUMB: '[class*="breadcrumb"] a',
|
|
44
|
+
RELATED: '[class*="related"] a',
|
|
45
|
+
REMOVE: STATIC_PAGE_REMOVE,
|
|
46
|
+
},
|
|
47
|
+
provenance: 'Source: Jamf Concepts (concepts.jamf.com), Jamf\'s innovation lab. ' +
|
|
48
|
+
'This is exploratory material, not official product documentation — ' +
|
|
49
|
+
'see learn.jamf.com for the supported configuration steps.',
|
|
50
|
+
},
|
|
51
|
+
};
|
|
52
|
+
/** Every non-Fluid-Topics hostname this server will fetch from. */
|
|
53
|
+
export const STATIC_SOURCE_HOSTNAMES = Object.values(STATIC_DOC_SOURCES).map(source => source.hostname);
|
|
54
|
+
/** The source that serves a hostname, or undefined when none does. */
|
|
55
|
+
export function staticSourceForHostname(hostname) {
|
|
56
|
+
return Object.values(STATIC_DOC_SOURCES).find(source => source.hostname === hostname);
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* The source a URL belongs to, or undefined for Fluid Topics and anything else.
|
|
60
|
+
*
|
|
61
|
+
* Returns undefined rather than throwing on an unparseable URL: callers use
|
|
62
|
+
* this to *choose* a path, and the existing validation is what rejects a bad
|
|
63
|
+
* URL with a message about it.
|
|
64
|
+
*/
|
|
65
|
+
export function staticSourceForUrl(urlStr) {
|
|
66
|
+
try {
|
|
67
|
+
return staticSourceForHostname(new URL(urlStr).hostname);
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
return undefined;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
//# sourceMappingURL=sources.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sources.js","sourceRoot":"","sources":["../../../src/core/constants/sources.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAE,SAAS,EAAoB,MAAM,aAAa,CAAC;AAwB1D;;;;;;;;;;;GAWG;AACH,MAAM,kBAAkB,GACtB,GAAG,SAAS,CAAC,MAAM,+DAA+D,CAAC;AAErF,MAAM,CAAC,MAAM,kBAAkB,GAAG;IAChC,eAAe,EAAE;QACf,EAAE,EAAE,eAAe;QACnB,QAAQ,EAAE,mBAAmB;QAC7B,OAAO,EAAE,2BAA2B;QACpC,IAAI,EAAE,eAAe;QACrB,SAAS,EAAE;YACT,sEAAsE;YACtE,oEAAoE;YACpE,oEAAoE;YACpE,uEAAuE;YACvE,sEAAsE;YACtE,iCAAiC;YACjC,OAAO,EAAE,2BAA2B;YACpC,KAAK,EAAE,IAAI;YACX,UAAU,EAAE,yBAAyB;YACrC,OAAO,EAAE,sBAAsB;YAC/B,MAAM,EAAE,kBAAkB;SAC3B;QACD,UAAU,EACR,qEAAqE;YACrE,qEAAqE;YACrE,2DAA2D;KAC9D;CACiD,CAAC;AAIrD,mEAAmE;AACnE,MAAM,CAAC,MAAM,uBAAuB,GAClC,MAAM,CAAC,MAAM,CAAC,kBAAkB,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;AAEnE,sEAAsE;AACtE,MAAM,UAAU,uBAAuB,CAAC,QAAgB;IACtD,OAAO,MAAM,CAAC,MAAM,CAAC,kBAAkB,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,CAAC,QAAQ,KAAK,QAAQ,CAAC,CAAC;AACxF,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,kBAAkB,CAAC,MAAc;IAC/C,IAAI,CAAC;QACH,OAAO,uBAAuB,CAAC,IAAI,GAAG,CAAC,MAAM,CAAC,CAAC,QAAQ,CAAC,CAAC;IAC3D,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAC;IACnB,CAAC;AACH,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"article-service.d.ts","sourceRoot":"","sources":["../../../src/core/services/article-service.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAC3D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAEzD,OAAO,KAAK,EAAE,kBAAkB,EAAE,mBAAmB,EAAmC,MAAM,aAAa,CAAC;AAC5G,OAAO,EAA6B,KAAK,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"article-service.d.ts","sourceRoot":"","sources":["../../../src/core/services/article-service.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAC3D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAEzD,OAAO,KAAK,EAAE,kBAAkB,EAAE,mBAAmB,EAAmC,MAAM,aAAa,CAAC;AAC5G,OAAO,EAA6B,KAAK,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AAWzF,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,wBAAwB,CAAC;AAwCrD;;;GAGG;AACH,MAAM,WAAW,yBAA0B,SAAQ,mBAAmB;IACpE,oFAAoF;IACpF,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,gFAAgF;IAChF,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAC7B;AAED,wBAAsB,kBAAkB,CACtC,KAAK,EAAE,aAAa,EACpB,KAAK,EAAE,MAAM,EACb,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,yBAAyB,GACjC,OAAO,CAAC,kBAAkB,CAAC,CA6F7B;AAID;;;;;;;GAOG;AACH,wBAAsB,sBAAsB,CAC1C,GAAG,EAAE,aAAa,EAClB,KAAK,EAAE,kBAAkB,EACzB,OAAO,EAAE,mBAAmB,GAC3B,OAAO,CAAC,kBAAkB,CAAC,CAsE7B"}
|
|
@@ -11,10 +11,13 @@ import { TOKEN_CONFIG } from '../constants.js';
|
|
|
11
11
|
import { buildDisplayUrl, parseUrl } from './topic-resolver.js';
|
|
12
12
|
import { fetchTopicContent, fetchTopicMetadata } from './ft-client.js';
|
|
13
13
|
import { parseArticle } from './content-parser.js';
|
|
14
|
+
import { staticSourceForUrl } from '../constants/sources.js';
|
|
15
|
+
import { buildArticleView } from './article-view.js';
|
|
16
|
+
import { fetchStaticArticle } from './static-article-service.js';
|
|
14
17
|
import { buildInternalLinkResolver, collectInternalLinkMapIds, fetchTopicAncestors, } from './ft-internal-link.js';
|
|
15
18
|
import { cacheKey } from './cache-key.js';
|
|
16
19
|
import { getMetaValue, bundleStemToDisplayName, FT_META } from '../utils/ft-metadata.js';
|
|
17
|
-
import { extractSections
|
|
20
|
+
import { extractSections } from './tokenizer.js';
|
|
18
21
|
export async function fetchArticleFromFt(cache, mapId, contentId, articleUrl, options) {
|
|
19
22
|
const maxTokens = options.maxTokens ?? TOKEN_CONFIG.DEFAULT_MAX_TOKENS;
|
|
20
23
|
const key = cacheKey('ft-article-v3', { mapId, contentId, articleUrl });
|
|
@@ -97,32 +100,7 @@ export async function fetchArticleFromFt(cache, mapId, contentId, articleUrl, op
|
|
|
97
100
|
contentId,
|
|
98
101
|
sections: allSections,
|
|
99
102
|
};
|
|
100
|
-
|
|
101
|
-
if (options.summaryOnly === true) {
|
|
102
|
-
const summaryResult = extractSummary(parsed.content, title, maxTokens);
|
|
103
|
-
let summaryContent = `## Summary\n\n${summaryResult.summary}\n\n`;
|
|
104
|
-
summaryContent += `## Article Outline (${summaryResult.outline.length} sections)\n\n`;
|
|
105
|
-
for (const section of summaryResult.outline) {
|
|
106
|
-
const indent = ' '.repeat(Math.max(0, section.level - 1));
|
|
107
|
-
summaryContent += `${indent}- ${section.title} (~${section.tokenCount} tokens)\n`;
|
|
108
|
-
}
|
|
109
|
-
summaryContent += `\n*Estimated read time: ${summaryResult.estimatedReadTime} min`
|
|
110
|
-
+ ` (${summaryResult.totalTokens.toLocaleString()} tokens)*\n`;
|
|
111
|
-
return { ...base, content: summaryContent, tokenInfo: summaryResult.tokenInfo };
|
|
112
|
-
}
|
|
113
|
-
// ── Section extraction ──
|
|
114
|
-
if (options.section !== undefined && options.section !== '') {
|
|
115
|
-
const sectionResult = extractSection(parsed.content, options.section, maxTokens);
|
|
116
|
-
if (sectionResult.section !== null) {
|
|
117
|
-
return { ...base, content: sectionResult.content, tokenInfo: sectionResult.tokenInfo };
|
|
118
|
-
}
|
|
119
|
-
const sectionsList = allSections.map(s => `- ${s.title}`).join('\n');
|
|
120
|
-
const notFoundMsg = `*Section "${options.section}" not found.*\n\n**Available sections:**\n${sectionsList}`;
|
|
121
|
-
return { ...base, content: notFoundMsg, tokenInfo: createTokenInfo(notFoundMsg, maxTokens) };
|
|
122
|
-
}
|
|
123
|
-
// ── Full content with truncation ──
|
|
124
|
-
const truncateResult = truncateToTokenLimit(parsed.content, maxTokens, allSections);
|
|
125
|
-
return { ...base, content: truncateResult.content, tokenInfo: truncateResult.tokenInfo };
|
|
103
|
+
return buildArticleView(base, parsed.content, options, maxTokens, allSections);
|
|
126
104
|
}
|
|
127
105
|
// ─── Resolve + fetch (shared by get-article & batch-get-articles) ──
|
|
128
106
|
/**
|
|
@@ -136,6 +114,19 @@ export async function fetchArticleFromFt(cache, mapId, contentId, articleUrl, op
|
|
|
136
114
|
export async function resolveAndFetchArticle(ctx, input, options) {
|
|
137
115
|
const { topicResolver, cache, articleProvider } = ctx;
|
|
138
116
|
const articleUrl = input.url ?? '';
|
|
117
|
+
// Step 0: Non-Fluid-Topics sources, before anything tries to parse the URL
|
|
118
|
+
// as a Fluid Topics one.
|
|
119
|
+
//
|
|
120
|
+
// `topicResolver.parseUrl` only recognises `/{locale}/bundle/…/page/…` and
|
|
121
|
+
// `/r/{locale}/…`, so a concepts.jamf.com URL threw INVALID_URL at step 1 —
|
|
122
|
+
// before `articleProvider.getArticle`, the documented URL fallback, was
|
|
123
|
+
// ever reached. Dispatching on hostname first is what makes a second source
|
|
124
|
+
// reachable at all; for a Fluid Topics URL this is one Set lookup and the
|
|
125
|
+
// flow below is unchanged.
|
|
126
|
+
const staticSource = staticSourceForUrl(articleUrl);
|
|
127
|
+
if (staticSource !== undefined) {
|
|
128
|
+
return await fetchStaticArticle(ctx, staticSource, articleUrl, options);
|
|
129
|
+
}
|
|
139
130
|
// Step 1: Resolve mapId + contentId
|
|
140
131
|
let { mapId, contentId } = input;
|
|
141
132
|
let resolvedLocale;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"article-service.js","sourceRoot":"","sources":["../../../src/core/services/article-service.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAIH,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAE/C,OAAO,EAAE,eAAe,EAAE,QAAQ,EAA2B,MAAM,qBAAqB,CAAC;AACzF,OAAO,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AACvE,OAAO,EAAE,YAAY,EAA6B,MAAM,qBAAqB,CAAC;AAC9E,OAAO,EACL,yBAAyB,EACzB,yBAAyB,EACzB,mBAAmB,GACpB,MAAM,uBAAuB,CAAC;AAE/B,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAC1C,OAAO,EAAE,YAAY,EAAE,uBAAuB,EAAE,OAAO,EAAE,MAAM,yBAAyB,CAAC;AACzF,OAAO,
|
|
1
|
+
{"version":3,"file":"article-service.js","sourceRoot":"","sources":["../../../src/core/services/article-service.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The three shapes an article response can take.
|
|
3
|
+
*
|
|
4
|
+
* Its own module rather than a member of `article-service`: the static-source
|
|
5
|
+
* path needs it too, and importing it from there would make the two services
|
|
6
|
+
* cyclic — `article-service` dispatches into `static-article-service`, which
|
|
7
|
+
* would import back. ESM tolerates that; a reader tracing which module owns
|
|
8
|
+
* what does not.
|
|
9
|
+
*/
|
|
10
|
+
import type { ArticleSection, FetchArticleOptions, FetchArticleResult } from '../types.js';
|
|
11
|
+
/**
|
|
12
|
+
* Turn a parsed article and the caller's options into the view they asked for.
|
|
13
|
+
*
|
|
14
|
+
* Three mutually exclusive shapes — an outline, one named section, or the
|
|
15
|
+
* whole thing truncated to budget — none of which depend on where the HTML
|
|
16
|
+
* came from. Shared so a second source cannot drift from the first on the
|
|
17
|
+
* part callers actually see: the "Available sections" list a missed section
|
|
18
|
+
* falls back to, the read-time footer, the token accounting.
|
|
19
|
+
*/
|
|
20
|
+
export declare function buildArticleView(base: Omit<FetchArticleResult, 'content' | 'tokenInfo'>, content: string, options: Pick<FetchArticleOptions, 'summaryOnly' | 'section'>, maxTokens: number, allSections: ArticleSection[]): FetchArticleResult;
|
|
21
|
+
//# sourceMappingURL=article-view.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"article-view.d.ts","sourceRoot":"","sources":["../../../src/core/services/article-view.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAGH,OAAO,KAAK,EAAE,cAAc,EAAE,mBAAmB,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAE3F;;;;;;;;GAQG;AACH,wBAAgB,gBAAgB,CAC9B,IAAI,EAAE,IAAI,CAAC,kBAAkB,EAAE,SAAS,GAAG,WAAW,CAAC,EACvD,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,IAAI,CAAC,mBAAmB,EAAE,aAAa,GAAG,SAAS,CAAC,EAC7D,SAAS,EAAE,MAAM,EACjB,WAAW,EAAE,cAAc,EAAE,GAC5B,kBAAkB,CA+BpB"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The three shapes an article response can take.
|
|
3
|
+
*
|
|
4
|
+
* Its own module rather than a member of `article-service`: the static-source
|
|
5
|
+
* path needs it too, and importing it from there would make the two services
|
|
6
|
+
* cyclic — `article-service` dispatches into `static-article-service`, which
|
|
7
|
+
* would import back. ESM tolerates that; a reader tracing which module owns
|
|
8
|
+
* what does not.
|
|
9
|
+
*/
|
|
10
|
+
import { createTokenInfo, truncateToTokenLimit, extractSummary, extractSection } from './tokenizer.js';
|
|
11
|
+
/**
|
|
12
|
+
* Turn a parsed article and the caller's options into the view they asked for.
|
|
13
|
+
*
|
|
14
|
+
* Three mutually exclusive shapes — an outline, one named section, or the
|
|
15
|
+
* whole thing truncated to budget — none of which depend on where the HTML
|
|
16
|
+
* came from. Shared so a second source cannot drift from the first on the
|
|
17
|
+
* part callers actually see: the "Available sections" list a missed section
|
|
18
|
+
* falls back to, the read-time footer, the token accounting.
|
|
19
|
+
*/
|
|
20
|
+
export function buildArticleView(base, content, options, maxTokens, allSections) {
|
|
21
|
+
// ── summaryOnly mode ──
|
|
22
|
+
if (options.summaryOnly === true) {
|
|
23
|
+
const summaryResult = extractSummary(content, base.title, maxTokens);
|
|
24
|
+
let summaryContent = `## Summary\n\n${summaryResult.summary}\n\n`;
|
|
25
|
+
summaryContent += `## Article Outline (${summaryResult.outline.length} sections)\n\n`;
|
|
26
|
+
for (const section of summaryResult.outline) {
|
|
27
|
+
const indent = ' '.repeat(Math.max(0, section.level - 1));
|
|
28
|
+
summaryContent += `${indent}- ${section.title} (~${section.tokenCount} tokens)\n`;
|
|
29
|
+
}
|
|
30
|
+
summaryContent += `\n*Estimated read time: ${summaryResult.estimatedReadTime} min`
|
|
31
|
+
+ ` (${summaryResult.totalTokens.toLocaleString()} tokens)*\n`;
|
|
32
|
+
return { ...base, content: summaryContent, tokenInfo: summaryResult.tokenInfo };
|
|
33
|
+
}
|
|
34
|
+
// ── Section extraction ──
|
|
35
|
+
if (options.section !== undefined && options.section !== '') {
|
|
36
|
+
const sectionResult = extractSection(content, options.section, maxTokens);
|
|
37
|
+
if (sectionResult.section !== null) {
|
|
38
|
+
return { ...base, content: sectionResult.content, tokenInfo: sectionResult.tokenInfo };
|
|
39
|
+
}
|
|
40
|
+
const sectionsList = allSections.map(s => `- ${s.title}`).join('\n');
|
|
41
|
+
const notFoundMsg = `*Section "${options.section}" not found.*\n\n**Available sections:**\n${sectionsList}`;
|
|
42
|
+
return { ...base, content: notFoundMsg, tokenInfo: createTokenInfo(notFoundMsg, maxTokens) };
|
|
43
|
+
}
|
|
44
|
+
// ── Full content with truncation ──
|
|
45
|
+
const truncateResult = truncateToTokenLimit(content, maxTokens, allSections);
|
|
46
|
+
return { ...base, content: truncateResult.content, tokenInfo: truncateResult.tokenInfo };
|
|
47
|
+
}
|
|
48
|
+
//# sourceMappingURL=article-view.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"article-view.js","sourceRoot":"","sources":["../../../src/core/services/article-view.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,eAAe,EAAE,oBAAoB,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAGvG;;;;;;;;GAQG;AACH,MAAM,UAAU,gBAAgB,CAC9B,IAAuD,EACvD,OAAe,EACf,OAA6D,EAC7D,SAAiB,EACjB,WAA6B;IAE7B,yBAAyB;IACzB,IAAI,OAAO,CAAC,WAAW,KAAK,IAAI,EAAE,CAAC;QACjC,MAAM,aAAa,GAAG,cAAc,CAAC,OAAO,EAAE,IAAI,CAAC,KAAK,EAAE,SAAS,CAAC,CAAC;QACrE,IAAI,cAAc,GAAG,iBAAiB,aAAa,CAAC,OAAO,MAAM,CAAC;QAClE,cAAc,IAAI,uBAAuB,aAAa,CAAC,OAAO,CAAC,MAAM,gBAAgB,CAAC;QACtF,KAAK,MAAM,OAAO,IAAI,aAAa,CAAC,OAAO,EAAE,CAAC;YAC5C,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,OAAO,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC;YAC3D,cAAc,IAAI,GAAG,MAAM,KAAK,OAAO,CAAC,KAAK,MAAM,OAAO,CAAC,UAAU,YAAY,CAAC;QACpF,CAAC;QACD,cAAc,IAAI,2BAA2B,aAAa,CAAC,iBAAiB,MAAM;cAC9E,KAAK,aAAa,CAAC,WAAW,CAAC,cAAc,EAAE,aAAa,CAAC;QAEjE,OAAO,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,cAAc,EAAE,SAAS,EAAE,aAAa,CAAC,SAAS,EAAE,CAAC;IAClF,CAAC;IAED,2BAA2B;IAC3B,IAAI,OAAO,CAAC,OAAO,KAAK,SAAS,IAAI,OAAO,CAAC,OAAO,KAAK,EAAE,EAAE,CAAC;QAC5D,MAAM,aAAa,GAAG,cAAc,CAAC,OAAO,EAAE,OAAO,CAAC,OAAO,EAAE,SAAS,CAAC,CAAC;QAC1E,IAAI,aAAa,CAAC,OAAO,KAAK,IAAI,EAAE,CAAC;YACnC,OAAO,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,aAAa,CAAC,OAAO,EAAE,SAAS,EAAE,aAAa,CAAC,SAAS,EAAE,CAAC;QACzF,CAAC;QACD,MAAM,YAAY,GAAG,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACrE,MAAM,WAAW,GACf,aAAa,OAAO,CAAC,OAAO,6CAA6C,YAAY,EAAE,CAAC;QAC1F,OAAO,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,eAAe,CAAC,WAAW,EAAE,SAAS,CAAC,EAAE,CAAC;IAC/F,CAAC;IAED,qCAAqC;IACrC,MAAM,cAAc,GAAG,oBAAoB,CAAC,OAAO,EAAE,SAAS,EAAE,WAAW,CAAC,CAAC;IAC7E,OAAO,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,cAAc,CAAC,OAAO,EAAE,SAAS,EAAE,cAAc,CAAC,SAAS,EAAE,CAAC;AAC3F,CAAC"}
|
|
@@ -97,6 +97,20 @@ export interface CacheKeySpaces {
|
|
|
97
97
|
mapId: string;
|
|
98
98
|
contentId: string;
|
|
99
99
|
};
|
|
100
|
+
/**
|
|
101
|
+
* An article from a non-Fluid-Topics source, keyed on the URL it was
|
|
102
|
+
* fetched from.
|
|
103
|
+
*
|
|
104
|
+
* Not folded into `ft-article-v3`: that space is `{mapId, contentId,
|
|
105
|
+
* articleUrl}`, and a static page has neither of the first two. `source`
|
|
106
|
+
* rides along so two sources cannot collide on a path, even though the URL
|
|
107
|
+
* alone already carries the hostname — it keeps the namespace readable on
|
|
108
|
+
* disk, which is the stated reason these keys are not just hashes.
|
|
109
|
+
*/
|
|
110
|
+
'static-article': {
|
|
111
|
+
source: string;
|
|
112
|
+
url: string;
|
|
113
|
+
};
|
|
100
114
|
'maps-registry-v2': null;
|
|
101
115
|
'metadata-products-v2': null;
|
|
102
116
|
'metadata-topics': null;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cache-key.d.ts","sourceRoot":"","sources":["../../../src/core/services/cache-key.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAEtD,OAAO,CAAC,MAAM,eAAe,EAAE,OAAO,MAAM,CAAC;AAE7C,8EAA8E;AAC9E,MAAM,MAAM,QAAQ,GAAG,MAAM,GAAG;IAAE,QAAQ,CAAC,CAAC,eAAe,CAAC,EAAE,IAAI,CAAA;CAAE,CAAC;AAErE,KAAK,cAAc,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,IAAI,CAAC;AAEvD;;;;;;;;;GASG;AACH,MAAM,MAAM,aAAa,GAAG,cAAc,GAAG,SAAS,aAAa,EAAE,CAAC;AAEtE;;;;;GAKG;AACH,MAAM,MAAM,sBAAsB,GAAG,SAAS,CAAC,SAAS,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC,CAAC,EAAE,CAAC;AAEvF;;;;;;;;GAQG;AAMH,MAAM,WAAW,cAAc;IAC7B,WAAW,EAAE;QACX,KAAK,EAAE,MAAM,CAAC;QACd,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;QAC7B,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;QACtB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;QACvB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;QACpB,OAAO,EAAE,sBAAsB,CAAC;KACjC,CAAC;IASF,eAAe,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAA;KAAE,CAAC;IAQ1E,QAAQ,EAAE;QAAE,MAAM,EAAE,QAAQ,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;IACjE,gBAAgB,EAAE;QAAE,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC;IACpC,gBAAgB,EAAE;QAAE,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC;IASpC,cAAc,EAAE;QAAE,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC;IAClC,kBAAkB,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC;IACzD,kBAAkB,EAAE,IAAI,CAAC;IACzB,sBAAsB,EAAE,IAAI,CAAC;IAC7B,iBAAiB,EAAE,IAAI,CAAC;IACxB,+BAA+B,EAAE,IAAI,CAAC;CACvC;AAID,MAAM,MAAM,cAAc,GAAG,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"cache-key.d.ts","sourceRoot":"","sources":["../../../src/core/services/cache-key.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAEtD,OAAO,CAAC,MAAM,eAAe,EAAE,OAAO,MAAM,CAAC;AAE7C,8EAA8E;AAC9E,MAAM,MAAM,QAAQ,GAAG,MAAM,GAAG;IAAE,QAAQ,CAAC,CAAC,eAAe,CAAC,EAAE,IAAI,CAAA;CAAE,CAAC;AAErE,KAAK,cAAc,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,IAAI,CAAC;AAEvD;;;;;;;;;GASG;AACH,MAAM,MAAM,aAAa,GAAG,cAAc,GAAG,SAAS,aAAa,EAAE,CAAC;AAEtE;;;;;GAKG;AACH,MAAM,MAAM,sBAAsB,GAAG,SAAS,CAAC,SAAS,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC,CAAC,EAAE,CAAC;AAEvF;;;;;;;;GAQG;AAMH,MAAM,WAAW,cAAc;IAC7B,WAAW,EAAE;QACX,KAAK,EAAE,MAAM,CAAC;QACd,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;QAC7B,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;QACtB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;QACvB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;QACpB,OAAO,EAAE,sBAAsB,CAAC;KACjC,CAAC;IASF,eAAe,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAA;KAAE,CAAC;IAQ1E,QAAQ,EAAE;QAAE,MAAM,EAAE,QAAQ,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;IACjE,gBAAgB,EAAE;QAAE,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC;IACpC,gBAAgB,EAAE;QAAE,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC;IASpC,cAAc,EAAE;QAAE,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC;IAClC,kBAAkB,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC;IACzD;;;;;;;;;OASG;IACH,gBAAgB,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,CAAC;IAClD,kBAAkB,EAAE,IAAI,CAAC;IACzB,sBAAsB,EAAE,IAAI,CAAC;IAC7B,iBAAiB,EAAE,IAAI,CAAC;IACxB,+BAA+B,EAAE,IAAI,CAAC;CACvC;AAID,MAAM,MAAM,cAAc,GAAG,MAAM,cAAc,CAAC;AAsDlD,qFAAqF;AACrF,eAAO,MAAM,gBAAgB,EAAE,SAAS,cAAc,EACK,CAAC;AAE5D,sFAAsF;AACtF,MAAM,MAAM,YAAY,CAAC,CAAC,SAAS,cAAc,IAC/C,cAAc,CAAC,CAAC,CAAC,SAAS,IAAI,GAAG,EAAE,GAAG,CAAC,KAAK,EAAE,cAAc,CAAC,CAAC,CAAC,CAAC,CAAC;AAqCnE;;;GAGG;AACH,wBAAgB,QAAQ,CAAC,CAAC,SAAS,cAAc,EAC/C,SAAS,EAAE,CAAC,EACZ,GAAG,IAAI,EAAE,YAAY,CAAC,CAAC,CAAC,GACvB,QAAQ,CAMV"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cache-key.js","sourceRoot":"","sources":["../../../src/core/services/cache-key.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;
|
|
1
|
+
{"version":3,"file":"cache-key.js","sourceRoot":"","sources":["../../../src/core/services/cache-key.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AAoIH;;;;;;GAMG;AACH,MAAM,wBAAwB,GAE1B;IACF,WAAW,EAAE,IAAI;IACjB,eAAe,EAAE,IAAI;IACrB,QAAQ,EAAE,IAAI;IACd,gBAAgB,EAAE,IAAI;IACtB,gBAAgB,EAAE,IAAI;IACtB,cAAc,EAAE,IAAI;IACpB,kBAAkB,EAAE,IAAI;IACxB,gBAAgB,EAAE,IAAI;IACtB,kBAAkB,EAAE,IAAI;IACxB,sBAAsB,EAAE,IAAI;IAC5B,iBAAiB,EAAE,IAAI;IACvB,+BAA+B,EAAE,IAAI;CACtC,CAAC;AAEF,qFAAqF;AACrF,MAAM,CAAC,MAAM,gBAAgB,GAC3B,MAAM,CAAC,IAAI,CAAC,wBAAwB,CAAqB,CAAC;AAM5D;;;;GAIG;AACH,SAAS,YAAY,CAAC,KAAuB;IAC3C,MAAM,SAAS,GAAkC,EAAE,CAAC;IACpD,KAAK,MAAM,IAAI,IAAI,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC;QAC7C,MAAM,KAAK,GAAG,KAAK,CAAC,IAAI,CAAC,CAAC;QAC1B,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;YACxB,YAAY,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;YAC1B,SAAS,CAAC,IAAI,CAAC,GAAG,KAAK,CAAC;QAC1B,CAAC;IACH,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC;AAED;;;;;;GAMG;AACH,SAAS,YAAY,CAAC,KAAoB,EAAE,IAAY;IACtD,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;QACzD,MAAM,IAAI,SAAS,CACjB,mBAAmB,IAAI,QAAQ,MAAM,CAAC,KAAK,CAAC,6CAA6C,CAC1F,CAAC;IACJ,CAAC;IACD,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QACzB,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;YAAC,YAAY,CAAC,IAAqB,EAAE,IAAI,CAAC,CAAC;QAAC,CAAC;IAC1E,CAAC;AACH,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,QAAQ,CACtB,SAAY,EACZ,GAAG,IAAqB;IAExB,6EAA6E;IAC7E,yEAAyE;IACzE,MAAM,CAAC,KAAK,CAAC,GAAG,IAAoC,CAAC;IACrD,MAAM,OAAO,GAAG,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,SAAS,CAAC,YAAY,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC;IACrF,OAAO,GAAG,SAAS,GAAG,OAAO,EAAc,CAAC;AAC9C,CAAC"}
|
|
@@ -8,11 +8,24 @@
|
|
|
8
8
|
* - Search snippet cleaning
|
|
9
9
|
*/
|
|
10
10
|
import * as cheerio from 'cheerio';
|
|
11
|
+
import type { SelectorSet } from '../constants/limits.js';
|
|
11
12
|
import { type InternalLinkResolver } from './ft-internal-link.js';
|
|
13
|
+
/** Per-source overrides for {@link cleanHtml}; every field defaults to learn.jamf.com's. */
|
|
14
|
+
export interface CleanHtmlOptions {
|
|
15
|
+
/** Selector set for this source. Defaults to {@link SELECTORS}. */
|
|
16
|
+
selectors?: SelectorSet;
|
|
17
|
+
/** Origin that root-relative hrefs and srcs resolve against. Defaults to {@link DOCS_BASE_URL}. */
|
|
18
|
+
linkBase?: string;
|
|
19
|
+
}
|
|
12
20
|
/**
|
|
13
21
|
* Clean HTML content: remove unwanted elements, fix relative URLs.
|
|
22
|
+
*
|
|
23
|
+
* Both the selector set and the link base are parameters with learn.jamf.com
|
|
24
|
+
* defaults rather than module constants, so a caller reading a different site
|
|
25
|
+
* gets that site's markup rules. Existing single-argument callers — the
|
|
26
|
+
* glossary path among them — are unaffected.
|
|
14
27
|
*/
|
|
15
|
-
export declare function cleanHtml($: cheerio.CheerioAPI): void;
|
|
28
|
+
export declare function cleanHtml($: cheerio.CheerioAPI, options?: CleanHtmlOptions): void;
|
|
16
29
|
/**
|
|
17
30
|
* Turn Fluid Topics' `<span class="ft-internal-link" data-mapid data-tocid>`
|
|
18
31
|
* into a real `<a href>`.
|
|
@@ -45,6 +58,16 @@ export interface ParseArticleOptions {
|
|
|
45
58
|
* `relatedArticles`. See {@link linkInternalSpans}.
|
|
46
59
|
*/
|
|
47
60
|
resolveInternalLink?: InternalLinkResolver | undefined;
|
|
61
|
+
/**
|
|
62
|
+
* Selector set for the source this HTML came from.
|
|
63
|
+
*
|
|
64
|
+
* Optional, defaulting to learn.jamf.com's, because `parseArticle` is a
|
|
65
|
+
* published deep-import (`./core/*` is in the package export map) and
|
|
66
|
+
* making it required would be a breaking change for embedders.
|
|
67
|
+
*/
|
|
68
|
+
selectors?: SelectorSet;
|
|
69
|
+
/** Origin that root-relative hrefs and srcs resolve against. */
|
|
70
|
+
linkBase?: string;
|
|
48
71
|
}
|
|
49
72
|
/**
|
|
50
73
|
* Parse HTML into article structure (title, markdown content, breadcrumb).
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"content-parser.d.ts","sourceRoot":"","sources":["../../../src/core/services/content-parser.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,OAAO,MAAM,SAAS,CAAC;AAGnC,OAAO,EAA0B,KAAK,oBAAoB,EAAE,MAAM,uBAAuB,CAAC;AA+B1F
|
|
1
|
+
{"version":3,"file":"content-parser.d.ts","sourceRoot":"","sources":["../../../src/core/services/content-parser.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,OAAO,MAAM,SAAS,CAAC;AAGnC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AAC1D,OAAO,EAA0B,KAAK,oBAAoB,EAAE,MAAM,uBAAuB,CAAC;AA+B1F,4FAA4F;AAC5F,MAAM,WAAW,gBAAgB;IAC/B,mEAAmE;IACnE,SAAS,CAAC,EAAE,WAAW,CAAC;IACxB,mGAAmG;IACnG,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED;;;;;;;GAOG;AACH,wBAAgB,SAAS,CAAC,CAAC,EAAE,OAAO,CAAC,UAAU,EAAE,OAAO,CAAC,EAAE,gBAAgB,GAAG,IAAI,CAuBjF;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,iBAAiB,CAC/B,CAAC,EAAE,OAAO,CAAC,UAAU,EACrB,OAAO,EAAE,oBAAoB,GAC5B,IAAI,CAgBN;AAID,MAAM,WAAW,oBAAoB;IACnC,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,eAAe,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;CACnD;AAED,MAAM,WAAW,mBAAmB;IAClC,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB;;;;OAIG;IACH,mBAAmB,CAAC,EAAE,oBAAoB,GAAG,SAAS,CAAC;IACvD;;;;;;OAMG;IACH,SAAS,CAAC,EAAE,WAAW,CAAC;IACxB,gEAAgE;IAChE,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED;;GAEG;AACH,wBAAgB,YAAY,CAC1B,IAAI,EAAE,MAAM,EACZ,UAAU,EAAE,MAAM,EAClB,OAAO,CAAC,EAAE,mBAAmB,GAC5B,oBAAoB,CA4EtB;AAUD;;GAEG;AACH,wBAAgB,YAAY,CAC1B,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,MAAM,GAAG,IAAI,GACrB,MAAM,CAwBR;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEnD"}
|
|
@@ -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,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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"}
|
package/dist/core/utils/url.d.ts
CHANGED
|
@@ -5,7 +5,15 @@
|
|
|
5
5
|
* Shared by both the schema layer and the scraper service.
|
|
6
6
|
*/
|
|
7
7
|
import { type LocaleId } from '../constants.js';
|
|
8
|
-
/**
|
|
8
|
+
/**
|
|
9
|
+
* Allowed hostnames for URL validation.
|
|
10
|
+
*
|
|
11
|
+
* The two Fluid Topics hosts plus every registered non-FT source. Derived
|
|
12
|
+
* rather than listed so that adding a source is one row in
|
|
13
|
+
* {@link STATIC_DOC_SOURCES} and cannot be half-done: a hostname that the
|
|
14
|
+
* article path knows how to parse but this set rejects would fail with a
|
|
15
|
+
* "must be from" error that names the wrong problem.
|
|
16
|
+
*/
|
|
9
17
|
export declare const ALLOWED_HOSTNAMES: Set<string>;
|
|
10
18
|
/** Check whether a URL string points to an allowed Jamf documentation hostname with HTTPS. */
|
|
11
19
|
export declare function isAllowedHostname(urlStr: string): boolean;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"url.d.ts","sourceRoot":"","sources":["../../../src/core/utils/url.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAqC,KAAK,QAAQ,EAAE,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"url.d.ts","sourceRoot":"","sources":["../../../src/core/utils/url.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAqC,KAAK,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAGnF;;;;;;;;GAQG;AACH,eAAO,MAAM,iBAAiB,aAI5B,CAAC;AAEH,8FAA8F;AAC9F,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAOzD;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,MAAM,GAAG,QAAQ,CAa7D"}
|
package/dist/core/utils/url.js
CHANGED
|
@@ -5,10 +5,20 @@
|
|
|
5
5
|
* Shared by both the schema layer and the scraper service.
|
|
6
6
|
*/
|
|
7
7
|
import { DEFAULT_LOCALE, SUPPORTED_LOCALES } from '../constants.js';
|
|
8
|
-
|
|
8
|
+
import { STATIC_SOURCE_HOSTNAMES } from '../constants/sources.js';
|
|
9
|
+
/**
|
|
10
|
+
* Allowed hostnames for URL validation.
|
|
11
|
+
*
|
|
12
|
+
* The two Fluid Topics hosts plus every registered non-FT source. Derived
|
|
13
|
+
* rather than listed so that adding a source is one row in
|
|
14
|
+
* {@link STATIC_DOC_SOURCES} and cannot be half-done: a hostname that the
|
|
15
|
+
* article path knows how to parse but this set rejects would fail with a
|
|
16
|
+
* "must be from" error that names the wrong problem.
|
|
17
|
+
*/
|
|
9
18
|
export const ALLOWED_HOSTNAMES = new Set([
|
|
10
19
|
'learn.jamf.com',
|
|
11
20
|
'docs.jamf.com',
|
|
21
|
+
...STATIC_SOURCE_HOSTNAMES,
|
|
12
22
|
]);
|
|
13
23
|
/** Check whether a URL string points to an allowed Jamf documentation hostname with HTTPS. */
|
|
14
24
|
export function isAllowedHostname(urlStr) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"url.js","sourceRoot":"","sources":["../../../src/core/utils/url.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,cAAc,EAAE,iBAAiB,EAAiB,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"url.js","sourceRoot":"","sources":["../../../src/core/utils/url.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,cAAc,EAAE,iBAAiB,EAAiB,MAAM,iBAAiB,CAAC;AACnF,OAAO,EAAE,uBAAuB,EAAE,MAAM,yBAAyB,CAAC;AAElE;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAG,IAAI,GAAG,CAAS;IAC/C,gBAAgB;IAChB,eAAe;IACf,GAAG,uBAAuB;CAC3B,CAAC,CAAC;AAEH,8FAA8F;AAC9F,MAAM,UAAU,iBAAiB,CAAC,MAAc;IAC9C,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,CAAC;QAC5B,OAAO,GAAG,CAAC,QAAQ,KAAK,QAAQ,IAAI,iBAAiB,CAAC,GAAG,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;IAC1E,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,oBAAoB,CAAC,MAAc;IACjD,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,CAAC;QAC5B,MAAM,YAAY,GAAG,GAAG,CAAC,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;QAC7D,+DAA+D;QAC/D,MAAM,SAAS,GAAG,YAAY,CAAC,CAAC,CAAC,CAAC;QAClC,IAAI,SAAS,KAAK,SAAS,IAAI,SAAS,IAAI,iBAAiB,EAAE,CAAC;YAC9D,OAAO,SAAqB,CAAC;QAC/B,CAAC;IACH,CAAC;IAAC,MAAM,CAAC;QACP,4BAA4B;IAC9B,CAAC;IACD,OAAO,cAAc,CAAC;AACxB,CAAC"}
|