@taurgis/bonsai 3.1.0 → 3.2.2
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/README.md +21 -4
- package/agents/agents/salesforce-docs-researcher.agent.md +14 -1
- package/agents/instructions/salesforce-research.instructions.md +2 -1
- package/bin/cli.mjs +9 -17
- package/dist/base-command.d.ts +37 -10
- package/dist/base-command.js +113 -46
- package/dist/commands/config/base.js +11 -2
- package/dist/commands/config/get.js +8 -12
- package/dist/commands/config/index.js +2 -2
- package/dist/commands/config/list.js +10 -16
- package/dist/commands/config/set.d.ts +2 -1
- package/dist/commands/config/set.js +34 -27
- package/dist/commands/config/unset.d.ts +2 -1
- package/dist/commands/config/unset.js +25 -24
- package/dist/commands/fetch.d.ts +7 -15
- package/dist/commands/fetch.js +58 -401
- package/dist/commands/import.d.ts +4 -9
- package/dist/commands/import.js +56 -280
- package/dist/commands/inspect.d.ts +7 -1
- package/dist/commands/inspect.js +103 -52
- package/dist/commands/list.d.ts +13 -1
- package/dist/commands/list.js +79 -32
- package/dist/commands/prune.d.ts +4 -1
- package/dist/commands/prune.js +56 -65
- package/dist/commands/status.d.ts +3 -2
- package/dist/commands/status.js +52 -79
- package/dist/hooks/command-not-found/suggest.d.ts +1 -0
- package/dist/hooks/command-not-found/suggest.js +66 -39
- package/dist/lib/argv.d.ts +24 -3
- package/dist/lib/argv.js +124 -53
- package/dist/lib/batch.d.ts +41 -0
- package/dist/lib/batch.js +39 -0
- package/dist/lib/cache-view.d.ts +21 -0
- package/dist/lib/cache-view.js +30 -0
- package/dist/lib/cli-emit.d.ts +28 -0
- package/dist/lib/cli-emit.js +44 -0
- package/dist/lib/cli-error-policy.d.ts +57 -0
- package/dist/lib/cli-error-policy.js +74 -0
- package/dist/lib/cli-flag-manifest.d.ts +5 -0
- package/dist/lib/cli-flag-manifest.js +8 -0
- package/dist/lib/cli-presentation.d.ts +39 -0
- package/dist/lib/cli-presentation.js +50 -0
- package/dist/lib/cli-result-types.d.ts +88 -0
- package/dist/lib/cli-result-types.js +2 -0
- package/dist/lib/config/index.d.ts +22 -4
- package/dist/lib/config/index.js +59 -4
- package/dist/lib/config/resolve.d.ts +21 -0
- package/dist/lib/config/resolve.js +55 -6
- package/dist/lib/config-persist.d.ts +28 -0
- package/dist/lib/config-persist.js +29 -0
- package/dist/lib/envelope.d.ts +41 -3
- package/dist/lib/envelope.js +147 -4
- package/dist/lib/help-preflight.d.ts +5 -8
- package/dist/lib/help-preflight.js +13 -32
- package/dist/lib/json-meta.d.ts +0 -2
- package/dist/lib/json-meta.js +21 -25
- package/dist/lib/limit-flag.d.ts +11 -1
- package/dist/lib/limit-flag.js +19 -6
- package/dist/lib/parse-error-ux.d.ts +5 -0
- package/dist/lib/parse-error-ux.js +73 -0
- package/dist/lib/prune-flags.d.ts +28 -0
- package/dist/lib/prune-flags.js +74 -0
- package/dist/lib/research/artifact-index.d.ts +11 -2
- package/dist/lib/research/artifact-index.js +7 -4
- package/dist/lib/research/browser.d.ts +41 -2
- package/dist/lib/research/browser.js +139 -50
- package/dist/lib/research/capture.d.ts +24 -2
- package/dist/lib/research/capture.js +24 -4
- package/dist/lib/research/cli-io.d.ts +33 -0
- package/dist/lib/research/cli-io.js +27 -0
- package/dist/lib/research/compress.d.ts +7 -0
- package/dist/lib/research/compress.js +7 -3
- package/dist/lib/research/docs/capabilities.d.ts +15 -0
- package/dist/lib/research/docs/capabilities.js +5 -0
- package/dist/lib/research/docs/clean-dom.d.ts +2 -0
- package/dist/lib/research/docs/clean-dom.js +2 -0
- package/dist/lib/research/docs/code-blocks.d.ts +2 -0
- package/dist/lib/research/docs/code-blocks.js +2 -0
- package/dist/lib/research/docs/detect.d.ts +4 -0
- package/dist/lib/research/docs/detect.js +4 -0
- package/dist/lib/research/docs/machine-readable.d.ts +18 -0
- package/dist/lib/research/docs/machine-readable.js +10 -0
- package/dist/lib/research/docs/markdown-source.d.ts +20 -0
- package/dist/lib/research/docs/markdown-source.js +19 -2
- package/dist/lib/research/docs/quality-gates.d.ts +5 -0
- package/dist/lib/research/docs/quality-gates.js +4 -0
- package/dist/lib/research/docs/rsc.d.ts +9 -2
- package/dist/lib/research/docs/rsc.js +5 -2
- package/dist/lib/research/docs/section-artifacts.d.ts +12 -0
- package/dist/lib/research/docs/section-artifacts.js +12 -0
- package/dist/lib/research/docs/sections.d.ts +18 -0
- package/dist/lib/research/docs/sections.js +17 -1
- package/dist/lib/research/docs/source-map.d.ts +28 -0
- package/dist/lib/research/docs/source-map.js +27 -0
- package/dist/lib/research/docs/validate.d.ts +35 -2
- package/dist/lib/research/docs/validate.js +47 -2
- package/dist/lib/research/extract.d.ts +16 -0
- package/dist/lib/research/extract.js +12 -1
- package/dist/lib/research/fetch-command-service.d.ts +36 -0
- package/dist/lib/research/fetch-command-service.js +310 -0
- package/dist/lib/research/fetch-result.d.ts +114 -0
- package/dist/lib/research/fetch-result.js +212 -0
- package/dist/lib/research/fetcher.d.ts +46 -8
- package/dist/lib/research/fetcher.js +58 -30
- package/dist/lib/research/freshness.d.ts +54 -11
- package/dist/lib/research/freshness.js +91 -31
- package/dist/lib/research/import-command-service.d.ts +56 -0
- package/dist/lib/research/import-command-service.js +381 -0
- package/dist/lib/research/keywords.js +5 -1
- package/dist/lib/research/markdown.d.ts +12 -1
- package/dist/lib/research/markdown.js +13 -5
- package/dist/lib/research/persist-artifact.d.ts +23 -0
- package/dist/lib/research/persist-artifact.js +27 -0
- package/dist/lib/research/prompt-injection.d.ts +8 -0
- package/dist/lib/research/prompt-injection.js +42 -13
- package/dist/lib/research/proxy.d.ts +14 -8
- package/dist/lib/research/proxy.js +14 -10
- package/dist/lib/research/revalidate.d.ts +49 -20
- package/dist/lib/research/revalidate.js +80 -15
- package/dist/lib/research/schema.d.ts +7 -0
- package/dist/lib/research/schema.js +2 -0
- package/dist/lib/research/secret-scan.d.ts +6 -0
- package/dist/lib/research/secret-scan.js +8 -0
- package/dist/lib/research/secure-write.d.ts +11 -1
- package/dist/lib/research/secure-write.js +38 -7
- package/dist/lib/research/storage.d.ts +5 -1
- package/dist/lib/research/storage.js +8 -3
- package/dist/lib/research/summarize.d.ts +15 -0
- package/dist/lib/research/summarize.js +11 -0
- package/dist/lib/research/url.d.ts +9 -6
- package/dist/lib/research/url.js +33 -21
- package/dist/lib/text.d.ts +7 -2
- package/dist/lib/text.js +13 -2
- package/dist/lib/value-flags.d.ts +14 -0
- package/dist/lib/value-flags.js +28 -0
- package/dist/sites/coveo.d.ts +10 -0
- package/dist/sites/coveo.js +10 -0
- package/dist/sites/index.d.ts +16 -0
- package/dist/sites/index.js +16 -4
- package/dist/sites/markdown-twin.d.ts +31 -0
- package/dist/sites/markdown-twin.js +56 -0
- package/dist/sites/salesforce/fetch-page.d.ts +8 -2
- package/dist/sites/salesforce/fetch-page.js +14 -3
- package/dist/sites/salesforce/help-tooling-markdown.d.ts +30 -0
- package/dist/sites/salesforce/help-tooling-markdown.js +74 -0
- package/dist/sites/salesforce/index.d.ts +1 -0
- package/dist/sites/salesforce/index.js +1 -0
- package/dist/sites/salesforce-developer/fetch-page.d.ts +6 -1
- package/dist/sites/salesforce-developer/fetch-page.js +6 -1
- package/dist/sites/salesforce-developer/index.d.ts +1 -0
- package/dist/sites/salesforce-developer/index.js +1 -0
- package/dist/sites/salesforce-developer/route-markdown.d.ts +18 -6
- package/dist/sites/salesforce-developer/route-markdown.js +29 -67
- package/dist/sites/salesforce-doc-fetch.d.ts +39 -5
- package/dist/sites/salesforce-doc-fetch.js +52 -7
- package/dist/sites/salesforce-dom-probe.d.ts +20 -2
- package/dist/sites/salesforce-dom-probe.js +20 -2
- package/dist/sites/types.d.ts +14 -8
- package/dist/sites/types.js +3 -0
- package/oclif.manifest.json +192 -87
- package/package.json +19 -7
- package/agents/agents/official-docs-researcher.agent.md +0 -87
- package/agents/instructions/web-research.instructions.md +0 -44
- package/agents/skills/web-research/SKILL.md +0 -118
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { fetchText } from '../../lib/research/fetcher.js';
|
|
2
|
+
import { probeMarkdownTwin } from '../markdown-twin.js';
|
|
3
|
+
// Salesforce's official "B2C Developer Tooling" project (GA, SalesforceCommerceCloud org) mirrors
|
|
4
|
+
// a curated subset of help.salesforce.com's B2C Commerce articles as static Markdown for its own
|
|
5
|
+
// CLI/MCP `docs` tool: /help/<category>/<slug>.md on its GitHub Pages site. Two categories cover
|
|
6
|
+
// admin- and merchant-facing content. Fetching that twin replaces Help's ~45s shadow-DOM render
|
|
7
|
+
// with one static request — the same win developer.salesforce.com's route.md twin gives us.
|
|
8
|
+
//
|
|
9
|
+
// Unlike the developer.salesforce.com twin, the mirror lives on a DIFFERENT host than the article
|
|
10
|
+
// (help.salesforce.com -> salesforcecommercecloud.github.io), and there is no public index mapping
|
|
11
|
+
// a help.salesforce.com article to its slug/category: the mapping ships inside the b2c-developer-
|
|
12
|
+
// tooling CLI's bundled package, not as a fetchable file. But B2C Commerce Help articles' `id` query
|
|
13
|
+
// param follows a stable `cc.<slug>.htm` pattern that matches the mirror's slug exactly (verified
|
|
14
|
+
// against several live articles spanning both categories), so the slug is derivable from the URL;
|
|
15
|
+
// which of the two categories holds it is not, so both are probed (see probeMarkdownTwin).
|
|
16
|
+
const HELP_HOST = 'help.salesforce.com';
|
|
17
|
+
const TOOLING_HOST = 'salesforcecommercecloud.github.io';
|
|
18
|
+
const TOOLING_HELP_BASE = `https://${TOOLING_HOST}/b2c-developer-tooling/help`;
|
|
19
|
+
// Order doesn't reflect prevalence, just the two categories b2c-developer-tooling publishes.
|
|
20
|
+
const HELP_TOOLING_CATEGORIES = ['help-admin', 'help-merchant'];
|
|
21
|
+
// Every real B2C Commerce Help slug is lowercase word characters/underscores (e.g.
|
|
22
|
+
// b2c_inventory_list_object_import_export); restricting the capture group to that charset (rather
|
|
23
|
+
// than `.+`) keeps a crafted `id` query param from steering the mirror probe at an arbitrary path
|
|
24
|
+
// on the trusted host.
|
|
25
|
+
const HELP_ARTICLE_ID_PATTERN = /^cc\.([\w-]+)\.htm$/i;
|
|
26
|
+
/**
|
|
27
|
+
* Extracts the b2c-developer-tooling slug from a help.salesforce.com article URL, or null when the
|
|
28
|
+
* URL isn't a `cc.<slug>.htm` B2C Commerce Help article (wrong host, missing `id`, or an `id` that
|
|
29
|
+
* doesn't follow that pattern — e.g. numeric knowledge-article ids, or other clouds' Help content).
|
|
30
|
+
*
|
|
31
|
+
* @param url - A help.salesforce.com article URL (or any URL; non-matching returns null).
|
|
32
|
+
* @returns The derived slug (e.g. `b2c_inventory_list_object_import_export`), or null.
|
|
33
|
+
*/
|
|
34
|
+
export function deriveHelpToolingSlug(url) {
|
|
35
|
+
let parsed;
|
|
36
|
+
try {
|
|
37
|
+
parsed = new URL(url);
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
if (parsed.hostname.toLowerCase() !== HELP_HOST)
|
|
43
|
+
return null;
|
|
44
|
+
const id = parsed.searchParams.get('id');
|
|
45
|
+
if (!id)
|
|
46
|
+
return null;
|
|
47
|
+
return HELP_ARTICLE_ID_PATTERN.exec(id)?.[1] ?? null;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Builds the candidate mirror URLs for a slug, one per known category. Category membership isn't
|
|
51
|
+
* derivable from the URL, so callers must probe each until one validates.
|
|
52
|
+
*
|
|
53
|
+
* @param slug - Slug derived by {@link deriveHelpToolingSlug}.
|
|
54
|
+
* @returns Candidate `.md` URLs on the b2c-developer-tooling mirror, one per category.
|
|
55
|
+
*/
|
|
56
|
+
export function candidateHelpToolingUrls(slug) {
|
|
57
|
+
return HELP_TOOLING_CATEGORIES.map((category) => `${TOOLING_HELP_BASE}/${category}/${slug}.md`);
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Probes the b2c-developer-tooling Markdown mirror for a help.salesforce.com article. Returns a
|
|
61
|
+
* complete SiteFetchResult (with route_markdown provenance) for the first category whose twin
|
|
62
|
+
* validates, or null so the caller falls back to the browser capture. Never throws: any
|
|
63
|
+
* network/validation failure just means "no twin in this category," or "no twin at all."
|
|
64
|
+
*
|
|
65
|
+
* @param url - The help.salesforce.com article URL to probe.
|
|
66
|
+
* @param fetcher - HTTP fetcher (injectable for tests; defaults to `fetchText`).
|
|
67
|
+
* @returns SiteFetchResult with route_markdown provenance, or null when no valid twin is found.
|
|
68
|
+
*/
|
|
69
|
+
export async function fetchHelpToolingMarkdown(url, fetcher = fetchText) {
|
|
70
|
+
const slug = deriveHelpToolingSlug(url);
|
|
71
|
+
if (!slug)
|
|
72
|
+
return null;
|
|
73
|
+
return probeMarkdownTwin(candidateHelpToolingUrls(slug), { allowedHost: TOOLING_HOST, fetcher });
|
|
74
|
+
}
|
|
@@ -2,6 +2,7 @@ import { fetchSalesforcePage } from './fetch-page.js';
|
|
|
2
2
|
// Scoped to help.salesforce.com — an LWR Experience site with light-DOM article content.
|
|
3
3
|
// developer.salesforce.com is intentionally NOT routed here: its API-reference docs render
|
|
4
4
|
// inside shadow DOM with tabbed/code content that needs a different extraction strategy.
|
|
5
|
+
/** Site module for help.salesforce.com: forces rendered capture and applies the Help-specific Lightning Web Runtime (LWR) fetcher. */
|
|
5
6
|
export const salesforce = {
|
|
6
7
|
id: 'salesforce',
|
|
7
8
|
name: 'Salesforce Help',
|
|
@@ -3,6 +3,11 @@ import type { SiteFetchResult } from '../types.js';
|
|
|
3
3
|
* Fetches a developer.salesforce.com doc page. Supported articles publish a Markdown twin at the
|
|
4
4
|
* derived `.md` route ("View as Markdown"), which is preferred: one static request instead of a
|
|
5
5
|
* browser render, and it returns the article's source. Pages without a validated twin (API
|
|
6
|
-
* reference, atlas.* books, landing pages) fall back to the shared
|
|
6
|
+
* reference, atlas.* books, landing pages) fall back to the shared Lightning Web Runtime (LWR)
|
|
7
|
+
* shadow-DOM fetcher.
|
|
8
|
+
*
|
|
9
|
+
* @param url - The developer.salesforce.com doc page URL.
|
|
10
|
+
* @returns Extracted Markdown, fetch metadata, and capture provenance (route_markdown or browser_fallback).
|
|
11
|
+
* @throws {Error} When the browser fallback path fails (same modes as `fetchSalesforceDoc`).
|
|
7
12
|
*/
|
|
8
13
|
export declare function fetchDeveloperPage(url: string): Promise<SiteFetchResult>;
|
|
@@ -16,7 +16,12 @@ const CONTENT_SELECTORS = [
|
|
|
16
16
|
* Fetches a developer.salesforce.com doc page. Supported articles publish a Markdown twin at the
|
|
17
17
|
* derived `.md` route ("View as Markdown"), which is preferred: one static request instead of a
|
|
18
18
|
* browser render, and it returns the article's source. Pages without a validated twin (API
|
|
19
|
-
* reference, atlas.* books, landing pages) fall back to the shared
|
|
19
|
+
* reference, atlas.* books, landing pages) fall back to the shared Lightning Web Runtime (LWR)
|
|
20
|
+
* shadow-DOM fetcher.
|
|
21
|
+
*
|
|
22
|
+
* @param url - The developer.salesforce.com doc page URL.
|
|
23
|
+
* @returns Extracted Markdown, fetch metadata, and capture provenance (route_markdown or browser_fallback).
|
|
24
|
+
* @throws {Error} When the browser fallback path fails (same modes as `fetchSalesforceDoc`).
|
|
20
25
|
*/
|
|
21
26
|
export async function fetchDeveloperPage(url) {
|
|
22
27
|
const fromMarkdownRoute = await fetchDeveloperRouteMarkdown(url);
|
|
@@ -2,6 +2,7 @@ import { fetchDeveloperPage } from './fetch-page.js';
|
|
|
2
2
|
// developer.salesforce.com docs (guides + API reference) render client-side inside web
|
|
3
3
|
// components, so content lives in shadow DOM — a fundamentally different extraction strategy
|
|
4
4
|
// than help.salesforce.com (see ../salesforce).
|
|
5
|
+
/** Site module for developer.salesforce.com: forces rendered capture and applies the shadow-DOM Lightning Web Runtime (LWR) fetcher. */
|
|
5
6
|
export const salesforceDeveloper = {
|
|
6
7
|
id: 'salesforce-developer',
|
|
7
8
|
name: 'Salesforce Developer',
|
|
@@ -1,23 +1,35 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { fetchText } from '../../lib/research/fetcher.js';
|
|
2
2
|
import type { SiteFetchResult } from '../types.js';
|
|
3
3
|
/**
|
|
4
4
|
* Derives the candidate `.md` route for a developer.salesforce.com doc URL, or null when the URL
|
|
5
5
|
* can't serve one (wrong host, not under /docs/, or an atlas.* book — verified to answer `.md`
|
|
6
6
|
* with an HTML shell, never Markdown). Query/hash are view state and are dropped.
|
|
7
|
+
*
|
|
8
|
+
* @param url - A developer.salesforce.com doc URL (or any URL; non-matching returns null).
|
|
9
|
+
* @returns The derived `.md` URL, or null when no `.md` route exists for this URL.
|
|
7
10
|
*/
|
|
8
11
|
export declare function deriveMarkdownUrl(url: string): string | null;
|
|
9
|
-
/** Removes unresolvable `::include{…}` directive lines, reporting how many were dropped.
|
|
12
|
+
/** Removes unresolvable `::include{…}` directive lines, reporting how many were dropped.
|
|
13
|
+
*
|
|
14
|
+
* @param md - Markdown body from the `.md` route response.
|
|
15
|
+
* @returns Cleaned body string and a count of removed directive lines.
|
|
16
|
+
*/
|
|
10
17
|
export declare function stripIncludeDirectives(md: string): {
|
|
11
18
|
body: string;
|
|
12
19
|
dropped: number;
|
|
13
20
|
};
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
21
|
+
/**
|
|
22
|
+
* Injectable HTTP fetcher used by `fetchDeveloperRouteMarkdown`. The default is `fetchText`;
|
|
23
|
+
* tests inject a fixture fetcher to avoid real network calls.
|
|
24
|
+
*/
|
|
25
|
+
export type RouteMarkdownFetcher = typeof fetchText;
|
|
18
26
|
/**
|
|
19
27
|
* Probes the derived `.md` route for a developer doc URL. Returns a complete SiteFetchResult
|
|
20
28
|
* (with route_markdown provenance) when the twin validates, or null so the caller falls back to
|
|
21
29
|
* the browser capture. Never throws: any network/validation failure is just "no twin".
|
|
30
|
+
*
|
|
31
|
+
* @param url - The developer.salesforce.com doc page URL to probe.
|
|
32
|
+
* @param fetcher - HTTP fetcher (injectable for tests; defaults to `fetchText`).
|
|
33
|
+
* @returns SiteFetchResult with route_markdown provenance, or null when no valid twin is found.
|
|
22
34
|
*/
|
|
23
35
|
export declare function fetchDeveloperRouteMarkdown(url: string, fetcher?: RouteMarkdownFetcher): Promise<SiteFetchResult | null>;
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { fetchText } from '../../lib/research/fetcher.js';
|
|
2
|
-
import {
|
|
3
|
-
import { extractFromSource } from '../../lib/research/docs/markdown-source.js';
|
|
2
|
+
import { probeMarkdownTwin } from '../markdown-twin.js';
|
|
4
3
|
// developer.salesforce.com now publishes a Markdown twin for supported doc articles (the
|
|
5
4
|
// "View as Markdown" link): /docs/<cloud>/<book>/guide/<article>[.html] also serves
|
|
6
5
|
// <article>.md with Content-Type text/markdown. The URL mapping is deterministic, so we derive
|
|
@@ -9,10 +8,9 @@ import { extractFromSource } from '../../lib/research/docs/markdown-source.js';
|
|
|
9
8
|
//
|
|
10
9
|
// Coverage is rolling out per doc set, so the probe result is only a candidate (T-19/T-24):
|
|
11
10
|
// unsupported articles 404, and atlas.* books answer the .md route with an HTTP 200 HTML SPA
|
|
12
|
-
// shell — which makes the strict content-type + body validation
|
|
13
|
-
// defensive garnish.
|
|
11
|
+
// shell — which makes the strict content-type + body validation in probeMarkdownTwin
|
|
12
|
+
// load-bearing, not defensive garnish.
|
|
14
13
|
const DEVELOPER_HOST = 'developer.salesforce.com';
|
|
15
|
-
const MARKDOWN_CONTENT_TYPE = 'text/markdown';
|
|
16
14
|
// Salesforce's WAF answers 403 unless the User-Agent LEADS with a recognized HTTP-tool token
|
|
17
15
|
// (curl/wget/python-requests pass; bare product names and browser UAs on a non-browser TLS stack
|
|
18
16
|
// are blocked). Lead with the compatibility token — the same convention as browsers' Mozilla/5.0
|
|
@@ -24,6 +22,9 @@ const PROBE_HEADERS = {
|
|
|
24
22
|
* Derives the candidate `.md` route for a developer.salesforce.com doc URL, or null when the URL
|
|
25
23
|
* can't serve one (wrong host, not under /docs/, or an atlas.* book — verified to answer `.md`
|
|
26
24
|
* with an HTML shell, never Markdown). Query/hash are view state and are dropped.
|
|
25
|
+
*
|
|
26
|
+
* @param url - A developer.salesforce.com doc URL (or any URL; non-matching returns null).
|
|
27
|
+
* @returns The derived `.md` URL, or null when no `.md` route exists for this URL.
|
|
27
28
|
*/
|
|
28
29
|
export function deriveMarkdownUrl(url) {
|
|
29
30
|
let parsed;
|
|
@@ -46,7 +47,11 @@ export function deriveMarkdownUrl(url) {
|
|
|
46
47
|
// `::include{src="../../shared/….md"}` directives reference build-time snippet files that are
|
|
47
48
|
// not published on the .md route (they 404), so they'd survive as inert noise in the artifact.
|
|
48
49
|
const INCLUDE_DIRECTIVE = /^\s*::include\{[^}]*\}\s*$/;
|
|
49
|
-
/** Removes unresolvable `::include{…}` directive lines, reporting how many were dropped.
|
|
50
|
+
/** Removes unresolvable `::include{…}` directive lines, reporting how many were dropped.
|
|
51
|
+
*
|
|
52
|
+
* @param md - Markdown body from the `.md` route response.
|
|
53
|
+
* @returns Cleaned body string and a count of removed directive lines.
|
|
54
|
+
*/
|
|
50
55
|
export function stripIncludeDirectives(md) {
|
|
51
56
|
let dropped = 0;
|
|
52
57
|
const body = md
|
|
@@ -68,75 +73,32 @@ export function stripIncludeDirectives(md) {
|
|
|
68
73
|
.join('');
|
|
69
74
|
return { body, dropped };
|
|
70
75
|
}
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
if (mediaType !== MARKDOWN_CONTENT_TYPE)
|
|
80
|
-
return false;
|
|
81
|
-
let finalUrl;
|
|
82
|
-
try {
|
|
83
|
-
finalUrl = new URL(res.finalUrl);
|
|
84
|
-
}
|
|
85
|
-
catch {
|
|
86
|
-
return false;
|
|
87
|
-
}
|
|
88
|
-
// Scheme matters, not just host: a redirect hop downgrading to plain http would let an on-path
|
|
89
|
-
// response be cached as the trusted twin.
|
|
90
|
-
if (finalUrl.protocol !== 'https:' || finalUrl.hostname.toLowerCase() !== DEVELOPER_HOST) {
|
|
91
|
-
return false;
|
|
92
|
-
}
|
|
93
|
-
return validateTextArtifact(res.content).ok;
|
|
76
|
+
function stripIncludesForTwin(raw) {
|
|
77
|
+
const { body, dropped } = stripIncludeDirectives(raw);
|
|
78
|
+
if (dropped === 0)
|
|
79
|
+
return { body };
|
|
80
|
+
return {
|
|
81
|
+
body,
|
|
82
|
+
qualityNote: `${dropped} ::include directive(s) removed (shared snippets are not published on the .md route)`,
|
|
83
|
+
};
|
|
94
84
|
}
|
|
95
|
-
// A real twin is a small static file that answers fast; anything slower should mean "no twin,
|
|
96
|
-
// fall back now" instead of stacking the fetcher's default 10s on top of the browser render.
|
|
97
|
-
const PROBE_TIMEOUT_MS = 4_000;
|
|
98
|
-
// The browser path refuses to cache captures under this floor (MIN_CONTAINER_CHARS); a twin that
|
|
99
|
-
// thin is a rollout stub, and falling back gives the rendered page a chance to do better.
|
|
100
|
-
const MIN_TWIN_CHARS = 100;
|
|
101
85
|
/**
|
|
102
86
|
* Probes the derived `.md` route for a developer doc URL. Returns a complete SiteFetchResult
|
|
103
87
|
* (with route_markdown provenance) when the twin validates, or null so the caller falls back to
|
|
104
88
|
* the browser capture. Never throws: any network/validation failure is just "no twin".
|
|
89
|
+
*
|
|
90
|
+
* @param url - The developer.salesforce.com doc page URL to probe.
|
|
91
|
+
* @param fetcher - HTTP fetcher (injectable for tests; defaults to `fetchText`).
|
|
92
|
+
* @returns SiteFetchResult with route_markdown provenance, or null when no valid twin is found.
|
|
105
93
|
*/
|
|
106
94
|
export async function fetchDeveloperRouteMarkdown(url, fetcher = fetchText) {
|
|
107
95
|
const candidate = deriveMarkdownUrl(url);
|
|
108
96
|
if (!candidate)
|
|
109
97
|
return null;
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
}
|
|
117
|
-
if (!isMarkdownResponse(res))
|
|
118
|
-
return null;
|
|
119
|
-
const { body, dropped } = stripIncludeDirectives(res.content);
|
|
120
|
-
const extraction = extractFromSource(body, res.finalUrl);
|
|
121
|
-
if (extraction.detailedMarkdown.length < MIN_TWIN_CHARS)
|
|
122
|
-
return null;
|
|
123
|
-
if (dropped > 0) {
|
|
124
|
-
extraction.qualityNotes = [
|
|
125
|
-
...(extraction.qualityNotes ?? []),
|
|
126
|
-
`${dropped} ::include directive(s) removed (shared snippets are not published on the .md route)`,
|
|
127
|
-
];
|
|
128
|
-
}
|
|
129
|
-
return {
|
|
130
|
-
fetchResult: {
|
|
131
|
-
contentType: res.contentType,
|
|
132
|
-
etag: res.etag,
|
|
133
|
-
lastModified: res.lastModified,
|
|
134
|
-
finalUrl: res.finalUrl,
|
|
135
|
-
responseSize: res.responseSize,
|
|
136
|
-
content: res.content,
|
|
137
|
-
},
|
|
138
|
-
extraction,
|
|
139
|
-
captureMethod: 'route_markdown',
|
|
140
|
-
sourceDocUrl: res.finalUrl,
|
|
141
|
-
};
|
|
98
|
+
return probeMarkdownTwin([candidate], {
|
|
99
|
+
allowedHost: DEVELOPER_HOST,
|
|
100
|
+
fetcher,
|
|
101
|
+
headers: PROBE_HEADERS,
|
|
102
|
+
transformBody: stripIncludesForTwin,
|
|
103
|
+
});
|
|
142
104
|
}
|
|
@@ -1,25 +1,59 @@
|
|
|
1
1
|
import type { SiteFetchResult } from './types.js';
|
|
2
|
-
/** True when the extracted text is an error/not-found/loading shell rather than real content.
|
|
2
|
+
/** True when the extracted text is an error/not-found/loading shell rather than real content.
|
|
3
|
+
*
|
|
4
|
+
* @param text - Extracted Markdown text to check.
|
|
5
|
+
* @returns True when the text matches a Salesforce error/not-found pattern.
|
|
6
|
+
*/
|
|
3
7
|
export declare function looksLikeSalesforceError(text: string): boolean;
|
|
8
|
+
/**
|
|
9
|
+
* Removes known boilerplate lines from converted Salesforce Markdown: the feedback widget text,
|
|
10
|
+
* "View as Markdown" toolbar labels, and empty icon-anchor lines.
|
|
11
|
+
*
|
|
12
|
+
* @param markdown - Markdown output from the DOM or content API conversion.
|
|
13
|
+
* @returns Markdown with boilerplate lines stripped and excess blank lines collapsed.
|
|
14
|
+
*/
|
|
4
15
|
export declare function stripBoilerplate(markdown: string): string;
|
|
5
|
-
/** Parses the Salesforce private content API JSON payload.
|
|
16
|
+
/** Parses the Salesforce private content API JSON payload.
|
|
17
|
+
*
|
|
18
|
+
* @param raw - Raw JSON string from the `/docs/get_document_content/` network response.
|
|
19
|
+
* @returns Parsed `{ content, title }` object, or null when the body is empty or unparseable.
|
|
20
|
+
*/
|
|
6
21
|
export declare function parseDocsApiPayload(raw: string): {
|
|
7
22
|
content: string;
|
|
8
23
|
title: string;
|
|
9
24
|
} | null;
|
|
10
|
-
/** Keeps whichever Markdown source carries more structural content (DOM vs content API HTML).
|
|
25
|
+
/** Keeps whichever Markdown source carries more structural content (DOM vs content API HTML).
|
|
26
|
+
*
|
|
27
|
+
* @param domMarkdown - Markdown serialized from the shadow-DOM container.
|
|
28
|
+
* @param apiHtml - Raw HTML body from the Salesforce content API.
|
|
29
|
+
* @param domTitle - Page title from the DOM capture.
|
|
30
|
+
* @param apiTitle - Title from the content API response.
|
|
31
|
+
* @returns The richer Markdown, the winning title, and a flag indicating whether the API source won.
|
|
32
|
+
*/
|
|
11
33
|
export declare function preferRicherMarkdown(domMarkdown: string, apiHtml: string, domTitle: string, apiTitle: string): {
|
|
12
34
|
markdown: string;
|
|
13
35
|
title: string;
|
|
14
36
|
usedApi: boolean;
|
|
15
37
|
};
|
|
38
|
+
/**
|
|
39
|
+
* Options for the shared Salesforce Lightning Web Runtime (LWR) shadow-DOM fetcher.
|
|
40
|
+
* `contentSelectors` drive the priority content-container pick; `removeSelectors` strips
|
|
41
|
+
* per-site chrome that survives the shared BASE_REMOVE_SELECTORS list.
|
|
42
|
+
*/
|
|
16
43
|
export interface SalesforceDocOptions {
|
|
17
44
|
allowedHost: string;
|
|
18
45
|
contentSelectors: string[];
|
|
19
46
|
removeSelectors?: string[];
|
|
20
47
|
}
|
|
21
48
|
/**
|
|
22
|
-
* Renders a Salesforce LWR doc page, serializes its shadow-DOM content
|
|
23
|
-
* it to Markdown. Validates the host before any network access.
|
|
49
|
+
* Renders a Salesforce Lightning Web Runtime (LWR) doc page, serializes its shadow-DOM content
|
|
50
|
+
* container, and converts it to Markdown. Validates the host before any network access.
|
|
51
|
+
*
|
|
52
|
+
* @param url - The Salesforce doc page URL to fetch.
|
|
53
|
+
* @param options - Allowed host, priority content selectors, and optional per-site remove selectors.
|
|
54
|
+
* @returns Extracted Markdown, fetch metadata, and browser-capture provenance.
|
|
55
|
+
* @throws {Error} When the URL is invalid, the host is not the allowed host, DNS is unsafe,
|
|
56
|
+
* Chrome/CDP navigation or evaluation fails, the response body exceeds the size limit,
|
|
57
|
+
* or the captured content is too short / looks like an error page.
|
|
24
58
|
*/
|
|
25
59
|
export declare function fetchSalesforceDoc(url: string, { allowedHost, contentSelectors, removeSelectors }: SalesforceDocOptions): Promise<SiteFetchResult>;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { openCdpPage, waitForLoad, ResponseCapture, describeNavigationFailure, } from '../lib/research/browser.js';
|
|
2
2
|
import { checkDnsSafety } from '../lib/research/fetcher.js';
|
|
3
3
|
import { htmlToMarkdown } from '../lib/research/markdown.js';
|
|
4
|
+
import { sanitizePromptInjection } from '../lib/research/prompt-injection.js';
|
|
4
5
|
import { contentMetrics, contentRicherThan } from '../lib/research/regression.js';
|
|
5
6
|
import { SALESFORCE_SHADOW_DOM_HELPERS, buildPriorityContainerPickBlock, pollSalesforceContentReady, } from './salesforce-dom-probe.js';
|
|
6
7
|
// Both help.salesforce.com and developer.salesforce.com are LWR sites that render doc content
|
|
@@ -24,7 +25,11 @@ const ERROR_PATTERNS = [
|
|
|
24
25
|
/we looked high and low/i,
|
|
25
26
|
/sorry to interrupt/i,
|
|
26
27
|
];
|
|
27
|
-
/** True when the extracted text is an error/not-found/loading shell rather than real content.
|
|
28
|
+
/** True when the extracted text is an error/not-found/loading shell rather than real content.
|
|
29
|
+
*
|
|
30
|
+
* @param text - Extracted Markdown text to check.
|
|
31
|
+
* @returns True when the text matches a Salesforce error/not-found pattern.
|
|
32
|
+
*/
|
|
28
33
|
export function looksLikeSalesforceError(text) {
|
|
29
34
|
return ERROR_PATTERNS.some((re) => re.test(text));
|
|
30
35
|
}
|
|
@@ -71,6 +76,13 @@ const MARKDOWN_TOOLBAR_LINE = /^(copy as markdown|view as markdown|copy url to m
|
|
|
71
76
|
// A link with empty anchor text — e.g. an icon-only "home" logo link. It carries no readable
|
|
72
77
|
// content and renders as a bare `[](url)`. (Images `` are intentionally not matched.)
|
|
73
78
|
const EMPTY_LINK_LINE = /^\[\]\([^)]*\)$/;
|
|
79
|
+
/**
|
|
80
|
+
* Removes known boilerplate lines from converted Salesforce Markdown: the feedback widget text,
|
|
81
|
+
* "View as Markdown" toolbar labels, and empty icon-anchor lines.
|
|
82
|
+
*
|
|
83
|
+
* @param markdown - Markdown output from the DOM or content API conversion.
|
|
84
|
+
* @returns Markdown with boilerplate lines stripped and excess blank lines collapsed.
|
|
85
|
+
*/
|
|
74
86
|
export function stripBoilerplate(markdown) {
|
|
75
87
|
return markdown
|
|
76
88
|
.split('\n')
|
|
@@ -82,7 +94,11 @@ export function stripBoilerplate(markdown) {
|
|
|
82
94
|
.trim();
|
|
83
95
|
}
|
|
84
96
|
const DOCS_CONTENT_API = '/docs/get_document_content/';
|
|
85
|
-
/** Parses the Salesforce private content API JSON payload.
|
|
97
|
+
/** Parses the Salesforce private content API JSON payload.
|
|
98
|
+
*
|
|
99
|
+
* @param raw - Raw JSON string from the `/docs/get_document_content/` network response.
|
|
100
|
+
* @returns Parsed `{ content, title }` object, or null when the body is empty or unparseable.
|
|
101
|
+
*/
|
|
86
102
|
export function parseDocsApiPayload(raw) {
|
|
87
103
|
if (!raw?.trim())
|
|
88
104
|
return null;
|
|
@@ -96,7 +112,14 @@ export function parseDocsApiPayload(raw) {
|
|
|
96
112
|
return null;
|
|
97
113
|
}
|
|
98
114
|
}
|
|
99
|
-
/** Keeps whichever Markdown source carries more structural content (DOM vs content API HTML).
|
|
115
|
+
/** Keeps whichever Markdown source carries more structural content (DOM vs content API HTML).
|
|
116
|
+
*
|
|
117
|
+
* @param domMarkdown - Markdown serialized from the shadow-DOM container.
|
|
118
|
+
* @param apiHtml - Raw HTML body from the Salesforce content API.
|
|
119
|
+
* @param domTitle - Page title from the DOM capture.
|
|
120
|
+
* @param apiTitle - Title from the content API response.
|
|
121
|
+
* @returns The richer Markdown, the winning title, and a flag indicating whether the API source won.
|
|
122
|
+
*/
|
|
100
123
|
export function preferRicherMarkdown(domMarkdown, apiHtml, domTitle, apiTitle) {
|
|
101
124
|
const apiMarkdown = stripBoilerplate(htmlToMarkdown(apiHtml));
|
|
102
125
|
if (contentRicherThan(contentMetrics(apiMarkdown), contentMetrics(domMarkdown))) {
|
|
@@ -295,10 +318,22 @@ async function captureSalesforceAttempt(page, url, captureScript, contentSelecto
|
|
|
295
318
|
// as if it were real article content — it's plausible-length HTML that isn't a 404/loading shell — so
|
|
296
319
|
// the fetch would silently "succeed" with Chrome's error page cached as the documentation.
|
|
297
320
|
const nav = await page.client.send('Page.navigate', { url }, page.sessionId);
|
|
321
|
+
// A fast redirect can fail synchronously within Page.navigate's own response (surfacing as
|
|
322
|
+
// nav.errorText below); check the Fetch-domain safety guard first so a blocked navigation (e.g.
|
|
323
|
+
// an SSRF attempt via a private-address redirect) reports its specific reason instead of the
|
|
324
|
+
// opaque net::ERR_BLOCKED_BY_CLIENT that Chrome's own errorText would otherwise give it.
|
|
325
|
+
const blockedNavigation = page.takeBlockedNavigationError();
|
|
326
|
+
if (blockedNavigation)
|
|
327
|
+
throw blockedNavigation;
|
|
298
328
|
if (nav.errorText) {
|
|
299
329
|
throw describeNavigationFailure(nav.errorText, url);
|
|
300
330
|
}
|
|
301
331
|
await waitForLoad(page.client, page.sessionId, TIMEOUT_MS, 0).catch(() => { });
|
|
332
|
+
// A mid-navigation redirect may have been blocked after Page.navigate returned; surface that
|
|
333
|
+
// instead of letting Chrome's resulting error page sail through as captured content.
|
|
334
|
+
const blockedNavigationAfterLoad = page.takeBlockedNavigationError();
|
|
335
|
+
if (blockedNavigationAfterLoad)
|
|
336
|
+
throw blockedNavigationAfterLoad;
|
|
302
337
|
await page.client
|
|
303
338
|
.send('Runtime.evaluate', { expression: ACCEPT_CONSENT_EXPRESSION }, page.sessionId)
|
|
304
339
|
.catch(() => { });
|
|
@@ -327,6 +362,9 @@ async function captureSalesforceAttempt(page, url, captureScript, contentSelecto
|
|
|
327
362
|
return { html, title, detailedMarkdown, usedDocsApi };
|
|
328
363
|
}
|
|
329
364
|
function buildSalesforceFetchResult(url, html, title, detailedMarkdown, qualityNotes) {
|
|
365
|
+
// Same untrusted-HTML → Markdown path as extractHtmlContent: sanitize before the artifact
|
|
366
|
+
// is built so site-module browser captures cannot skip the shared prompt-injection guard.
|
|
367
|
+
const sanitizedMarkdown = sanitizePromptInjection(detailedMarkdown);
|
|
330
368
|
return {
|
|
331
369
|
fetchResult: {
|
|
332
370
|
contentType: 'text/html',
|
|
@@ -338,8 +376,8 @@ function buildSalesforceFetchResult(url, html, title, detailedMarkdown, qualityN
|
|
|
338
376
|
},
|
|
339
377
|
extraction: {
|
|
340
378
|
title: title || url,
|
|
341
|
-
detailedMarkdown,
|
|
342
|
-
confidence: confidenceFor(
|
|
379
|
+
detailedMarkdown: sanitizedMarkdown,
|
|
380
|
+
confidence: confidenceFor(sanitizedMarkdown.length),
|
|
343
381
|
qualityNotes,
|
|
344
382
|
},
|
|
345
383
|
// Rendered capture is this fetcher's only strategy; reporting it lets a refresh overwrite
|
|
@@ -348,8 +386,15 @@ function buildSalesforceFetchResult(url, html, title, detailedMarkdown, qualityN
|
|
|
348
386
|
};
|
|
349
387
|
}
|
|
350
388
|
/**
|
|
351
|
-
* Renders a Salesforce LWR doc page, serializes its shadow-DOM content
|
|
352
|
-
* it to Markdown. Validates the host before any network access.
|
|
389
|
+
* Renders a Salesforce Lightning Web Runtime (LWR) doc page, serializes its shadow-DOM content
|
|
390
|
+
* container, and converts it to Markdown. Validates the host before any network access.
|
|
391
|
+
*
|
|
392
|
+
* @param url - The Salesforce doc page URL to fetch.
|
|
393
|
+
* @param options - Allowed host, priority content selectors, and optional per-site remove selectors.
|
|
394
|
+
* @returns Extracted Markdown, fetch metadata, and browser-capture provenance.
|
|
395
|
+
* @throws {Error} When the URL is invalid, the host is not the allowed host, DNS is unsafe,
|
|
396
|
+
* Chrome/CDP navigation or evaluation fails, the response body exceeds the size limit,
|
|
397
|
+
* or the captured content is too short / looks like an error page.
|
|
353
398
|
*/
|
|
354
399
|
export async function fetchSalesforceDoc(url, { allowedHost, contentSelectors, removeSelectors = [] }) {
|
|
355
400
|
const parsed = parseSalesforceDocUrl(url, allowedHost);
|
|
@@ -1,16 +1,34 @@
|
|
|
1
1
|
import type { CdpPage } from '../lib/research/browser.js';
|
|
2
|
-
/** Shared in-page helpers for traversing Salesforce LWR open shadow roots. */
|
|
2
|
+
/** Shared in-page helpers for traversing Salesforce Lightning Web Runtime (LWR) open shadow roots. */
|
|
3
3
|
export declare const SALESFORCE_SHADOW_DOM_HELPERS = "\nconst deepElements = (root) => {\n const out = [];\n const visit = (node) => {\n if (node.shadowRoot) visit(node.shadowRoot);\n for (const child of Array.from(node.children || [])) {\n out.push(child);\n visit(child);\n }\n };\n visit(root);\n return out;\n};\n\nconst deepText = (root) => {\n let txt = '';\n const visit = (node) => {\n if (node.nodeType === 3) {\n txt += node.textContent || '';\n } else if (node.nodeType === 1 || node.nodeType === 9 || node.nodeType === 11) {\n const tag = node.tagName ? node.tagName.toLowerCase() : '';\n if (tag !== 'style' && tag !== 'script') {\n if (node.shadowRoot) visit(node.shadowRoot);\n for (const child of Array.from(node.childNodes || [])) visit(child);\n }\n }\n };\n visit(root);\n return txt;\n};\n\nconst textLenFor = (el) =>\n Math.max((el.innerText || '').trim().length, deepText(el).trim().length);\n";
|
|
4
4
|
/**
|
|
5
5
|
* In-page block: pick the highest-priority content container that exists on the page.
|
|
6
6
|
* Does not fall through to lower-priority selectors when a higher-priority host is present
|
|
7
7
|
* (e.g. doc-content-layout banner vs doc-amf-reference table).
|
|
8
|
+
*
|
|
9
|
+
* @param selectors - CSS selectors in priority order; the first that matches wins.
|
|
10
|
+
* @param minChars - Minimum text length a matched container must have to be accepted.
|
|
11
|
+
* @param containerVar - Name of the JS variable to assign the result to (default `'container'`).
|
|
12
|
+
* @returns A JavaScript code block string (without the surrounding `(() => {…})()` wrapper).
|
|
8
13
|
*/
|
|
9
14
|
export declare function buildPriorityContainerPickBlock(selectors: string[], minChars: number, containerVar?: string): string;
|
|
10
|
-
/**
|
|
15
|
+
/**
|
|
16
|
+
* Chrome DevTools Protocol (CDP) expression polled until the priority content host reaches
|
|
17
|
+
* `minChars` and the body settles.
|
|
18
|
+
*
|
|
19
|
+
* @param selectors - CSS selectors in priority order matching the site's content containers.
|
|
20
|
+
* @param minChars - Minimum text length before the container is considered ready.
|
|
21
|
+
* @returns A self-invoking JS expression string that evaluates to `{ has: boolean, len: number }`.
|
|
22
|
+
*/
|
|
11
23
|
export declare function buildContentReadyProbeExpression(selectors: string[], minChars: number): string;
|
|
12
24
|
/**
|
|
13
25
|
* Polls until the priority Salesforce content host is ready and body text has settled.
|
|
14
26
|
* Returns without throwing on timeout; the caller captures whatever rendered and judges quality.
|
|
27
|
+
*
|
|
28
|
+
* @param page - Open Chrome DevTools Protocol (CDP) page to evaluate expressions on.
|
|
29
|
+
* @param selectors - CSS selectors in priority order matching the site's content containers.
|
|
30
|
+
* @param minChars - Minimum container text length before polling stops.
|
|
31
|
+
* @param timeoutMs - Maximum wall-clock time (ms) to wait before giving up.
|
|
32
|
+
* @returns Resolves when content is ready, or when `timeoutMs` elapses (no throw on timeout).
|
|
15
33
|
*/
|
|
16
34
|
export declare function pollSalesforceContentReady(page: CdpPage, selectors: string[], minChars: number, timeoutMs: number): Promise<void>;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/** Shared in-page helpers for traversing Salesforce LWR open shadow roots. */
|
|
1
|
+
/** Shared in-page helpers for traversing Salesforce Lightning Web Runtime (LWR) open shadow roots. */
|
|
2
2
|
export const SALESFORCE_SHADOW_DOM_HELPERS = `
|
|
3
3
|
const deepElements = (root) => {
|
|
4
4
|
const out = [];
|
|
@@ -37,6 +37,11 @@ const textLenFor = (el) =>
|
|
|
37
37
|
* In-page block: pick the highest-priority content container that exists on the page.
|
|
38
38
|
* Does not fall through to lower-priority selectors when a higher-priority host is present
|
|
39
39
|
* (e.g. doc-content-layout banner vs doc-amf-reference table).
|
|
40
|
+
*
|
|
41
|
+
* @param selectors - CSS selectors in priority order; the first that matches wins.
|
|
42
|
+
* @param minChars - Minimum text length a matched container must have to be accepted.
|
|
43
|
+
* @param containerVar - Name of the JS variable to assign the result to (default `'container'`).
|
|
44
|
+
* @returns A JavaScript code block string (without the surrounding `(() => {…})()` wrapper).
|
|
40
45
|
*/
|
|
41
46
|
export function buildPriorityContainerPickBlock(selectors, minChars, containerVar = 'container') {
|
|
42
47
|
return `
|
|
@@ -61,7 +66,14 @@ export function buildPriorityContainerPickBlock(selectors, minChars, containerVa
|
|
|
61
66
|
${containerVar} = ${containerVar} || document.body || document.documentElement;
|
|
62
67
|
`;
|
|
63
68
|
}
|
|
64
|
-
/**
|
|
69
|
+
/**
|
|
70
|
+
* Chrome DevTools Protocol (CDP) expression polled until the priority content host reaches
|
|
71
|
+
* `minChars` and the body settles.
|
|
72
|
+
*
|
|
73
|
+
* @param selectors - CSS selectors in priority order matching the site's content containers.
|
|
74
|
+
* @param minChars - Minimum text length before the container is considered ready.
|
|
75
|
+
* @returns A self-invoking JS expression string that evaluates to `{ has: boolean, len: number }`.
|
|
76
|
+
*/
|
|
65
77
|
export function buildContentReadyProbeExpression(selectors, minChars) {
|
|
66
78
|
return `(() => {
|
|
67
79
|
${SALESFORCE_SHADOW_DOM_HELPERS}
|
|
@@ -90,6 +102,12 @@ export function buildContentReadyProbeExpression(selectors, minChars) {
|
|
|
90
102
|
/**
|
|
91
103
|
* Polls until the priority Salesforce content host is ready and body text has settled.
|
|
92
104
|
* Returns without throwing on timeout; the caller captures whatever rendered and judges quality.
|
|
105
|
+
*
|
|
106
|
+
* @param page - Open Chrome DevTools Protocol (CDP) page to evaluate expressions on.
|
|
107
|
+
* @param selectors - CSS selectors in priority order matching the site's content containers.
|
|
108
|
+
* @param minChars - Minimum container text length before polling stops.
|
|
109
|
+
* @param timeoutMs - Maximum wall-clock time (ms) to wait before giving up.
|
|
110
|
+
* @returns Resolves when content is ready, or when `timeoutMs` elapses (no throw on timeout).
|
|
93
111
|
*/
|
|
94
112
|
export async function pollSalesforceContentReady(page, selectors, minChars, timeoutMs) {
|
|
95
113
|
const expression = buildContentReadyProbeExpression(selectors, minChars);
|
package/dist/sites/types.d.ts
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
import type { ExtractionResult } from '../lib/research/extract.js';
|
|
2
|
+
import type { FetchedContent } from '../lib/research/fetcher.js';
|
|
2
3
|
import type { CaptureMethod } from '../lib/research/schema.js';
|
|
4
|
+
/**
|
|
5
|
+
* Result returned by a site module's custom `fetchPage` implementation.
|
|
6
|
+
* Carries the raw fetch metadata, the extracted Markdown, and optional provenance fields
|
|
7
|
+
* (`captureMethod`, `sourceDocUrl`) so the cache pipeline can record how the content was obtained.
|
|
8
|
+
*/
|
|
3
9
|
export interface SiteFetchResult {
|
|
4
|
-
fetchResult:
|
|
5
|
-
contentType: string | null;
|
|
6
|
-
etag: string | null;
|
|
7
|
-
lastModified: string | null;
|
|
8
|
-
finalUrl: string;
|
|
9
|
-
responseSize: number;
|
|
10
|
-
content: string;
|
|
11
|
-
};
|
|
10
|
+
fetchResult: FetchedContent;
|
|
12
11
|
extraction: ExtractionResult;
|
|
13
12
|
captureMethod?: CaptureMethod;
|
|
14
13
|
sourceDocUrl?: string;
|
|
@@ -17,12 +16,19 @@ export interface SiteFetchResult {
|
|
|
17
16
|
* Stamps a site module's reported provenance onto artifact metadata. Shared by the cache-miss
|
|
18
17
|
* fetch and revalidation so the two write paths can't drift. A result without captureMethod (a
|
|
19
18
|
* module predating provenance reporting) leaves the metadata untouched.
|
|
19
|
+
*
|
|
20
|
+
* @param metadata - Artifact metadata fields updated in place.
|
|
21
|
+
* @param siteFetch - Site-module fetch result carrying optional provenance.
|
|
20
22
|
*/
|
|
21
23
|
export declare function applySiteFetchProvenance(metadata: {
|
|
22
24
|
capture_method: CaptureMethod | null;
|
|
23
25
|
source_doc_url: string | null;
|
|
24
26
|
source_urls: string[];
|
|
25
27
|
}, siteFetch: SiteFetchResult): void;
|
|
28
|
+
/** Registered site-specific fetch/extract strategy for a domain family.
|
|
29
|
+
* `defaults.rendered` ORs with the user's `--rendered` flag; a missing or false value is a safe default.
|
|
30
|
+
* When `fetchPage` is absent, callers use the generic fetch/extract pipeline.
|
|
31
|
+
*/
|
|
26
32
|
export interface SiteModule {
|
|
27
33
|
id: string;
|
|
28
34
|
name: string;
|
package/dist/sites/types.js
CHANGED
|
@@ -2,6 +2,9 @@
|
|
|
2
2
|
* Stamps a site module's reported provenance onto artifact metadata. Shared by the cache-miss
|
|
3
3
|
* fetch and revalidation so the two write paths can't drift. A result without captureMethod (a
|
|
4
4
|
* module predating provenance reporting) leaves the metadata untouched.
|
|
5
|
+
*
|
|
6
|
+
* @param metadata - Artifact metadata fields updated in place.
|
|
7
|
+
* @param siteFetch - Site-module fetch result carrying optional provenance.
|
|
5
8
|
*/
|
|
6
9
|
export function applySiteFetchProvenance(metadata, siteFetch) {
|
|
7
10
|
if (!siteFetch.captureMethod)
|