@shoalrun/cli 0.1.0-alpha.18 → 0.1.0-beta.1
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/CHANGELOG.md +44 -0
- package/README.md +63 -28
- package/agent-docs/.well-known/shoalrun/navigation/project-updates-50f26fe22670.txt +7 -3
- package/agent-docs/SNAPSHOT.json +5 -5
- package/agent-docs/about.md +3 -3
- package/agent-docs/author/agent-overview.md +2 -2
- package/agent-docs/author/components/code-blocks.md +46 -1
- package/agent-docs/author/frontmatter-reference.md +1 -0
- package/agent-docs/author/frontmatter-reference.sections/categorize-connect-and-present-the-page.md +1 -0
- package/agent-docs/author/workspace.md +2 -2
- package/agent-docs/author/writing-style.md +9 -3
- package/agent-docs/author/writing-style.sections/adapt-this-example.md +1 -1
- package/agent-docs/author/writing-style.sections/choose-components-by-reader-need.md +2 -2
- package/agent-docs/author/writing-style.sections/evidence-and-uncertainty.md +1 -1
- package/agent-docs/author/writing-style.sections/example-audience-and-scope.md +1 -1
- package/agent-docs/author/writing-style.sections/feature-availability.md +1 -1
- package/agent-docs/author/writing-style.sections/ingredients-recipes-and-cookbooks.md +1 -1
- package/agent-docs/author/writing-style.sections/page-purpose-and-structure.md +2 -2
- package/agent-docs/author/writing-style.sections/procedures-and-examples.md +1 -1
- package/agent-docs/author/writing-style.sections/related-reading.md +1 -1
- package/agent-docs/author/writing-style.sections/release-notes.md +30 -0
- package/agent-docs/author/writing-style.sections/review-a-draft.md +1 -1
- package/agent-docs/author/writing-style.sections/reviewed-writing-examples.md +1 -1
- package/agent-docs/author/writing-style.sections/voice-and-explanations.md +1 -1
- package/agent-docs/changelog/2026-10-16-navigation-layouts.md +1 -1
- package/agent-docs/changelog/branding-security.md +66 -0
- package/agent-docs/changelog/pdf-custom-languages.md +48 -0
- package/agent-docs/changelog.md +17 -1
- package/agent-docs/chat/host.md +20 -2
- package/agent-docs/chat/settings.md +1 -1
- package/agent-docs/chat/settings.sections/configure-chat.md +1 -1
- package/agent-docs/config/examples.md +3 -3
- package/agent-docs/config/examples.sections/feature-showcase.md +3 -3
- package/agent-docs/config/examples.sections/minimal-site.md +2 -2
- package/agent-docs/config/examples.sections/multilingual-help-center.md +2 -2
- package/agent-docs/config/examples.sections/parcel-api-documentation.md +2 -2
- package/agent-docs/config/examples.sections/related-reading.md +2 -2
- package/agent-docs/config/examples.sections/shared-company-preset.md +2 -2
- package/agent-docs/config/outputs.md +33 -12
- package/agent-docs/config/outputs.sections/exports.md +3 -3
- package/agent-docs/config/outputs.sections/metadata.md +3 -3
- package/agent-docs/config/outputs.sections/pdf-output.md +51 -0
- package/agent-docs/config/outputs.sections/related-reading.md +3 -3
- package/agent-docs/configuration.md +2 -2
- package/agent-docs/configuration.sections/build-several-sites-from-shared-content.md +1 -1
- package/agent-docs/configuration.sections/change-inherited-lists-deliberately.md +1 -1
- package/agent-docs/configuration.sections/extract-a-section-with-include.md +1 -1
- package/agent-docs/configuration.sections/inspect-and-validate-changes.md +1 -1
- package/agent-docs/configuration.sections/related-reading.md +1 -1
- package/agent-docs/configuration.sections/separate-config-inheritance-from-page-metadata.md +1 -1
- package/agent-docs/configuration.sections/share-defaults-through-extends.md +1 -1
- package/agent-docs/configuration.sections/start-with-one-working-file.md +2 -2
- package/agent-docs/configuration.sections/understand-the-three-stages.md +1 -1
- package/agent-docs/configuration.sections/understand-where-paths-point.md +1 -1
- package/agent-docs/contribute/agent-skills.md +3 -1
- package/agent-docs/documents.json +623 -270
- package/agent-docs/features.md +4 -1
- package/agent-docs/index.md +2 -2
- package/agent-docs/provenance.json +6 -6
- package/agent-docs/publish/content-export.md +2 -0
- package/agent-docs/publish/content-export.sections/assets-downloads-and-notices.md +2 -0
- package/agent-docs/publish/content-export.sections/check-the-result.md +2 -0
- package/agent-docs/publish/content-export.sections/inspect-the-manifest.md +2 -0
- package/agent-docs/publish/content-export.sections/prerequisites.md +2 -0
- package/agent-docs/publish/content-export.sections/related-reading.md +2 -0
- package/agent-docs/publish/content-export.sections/review-exported-api-material.md +2 -0
- package/agent-docs/publish/content-export.sections/try-the-help-drawer-example.md +2 -0
- package/agent-docs/publish/content-export.sections/what-the-representations-retain.md +2 -0
- package/agent-docs/publish/pdf.md +184 -0
- package/agent-docs/publish/pdf.sections/apply-print-styling.md +65 -0
- package/agent-docs/publish/pdf.sections/check-the-result.md +33 -0
- package/agent-docs/publish/pdf.sections/export-a-page-or-publication.md +61 -0
- package/agent-docs/publish/pdf.sections/html-markdown-and-agents.md +31 -0
- package/agent-docs/publish/pdf.sections/offer-browser-page-printing.md +49 -0
- package/agent-docs/publish/pdf.sections/offer-direct-downloads-through-a-node-server.md +46 -0
- package/agent-docs/publish/pdf.sections/prerequisites.md +37 -0
- package/agent-docs/publish/pdf.sections/publish-an-optional-manual-download.md +39 -0
- package/agent-docs/publish.md +2 -0
- package/agent-docs/reference/agent-skills.md +7 -3
- package/agent-docs/search-index.json +1 -1
- package/agent-docs/start.md +4 -4
- package/agent-docs/start.sections/add-the-page-to-navigation.md +2 -2
- package/agent-docs/start.sections/build-and-check-both-outputs.md +2 -2
- package/agent-docs/start.sections/build-this-site.md +2 -2
- package/agent-docs/start.sections/check-writing-style.md +2 -2
- package/agent-docs/start.sections/create-a-page.md +2 -2
- package/agent-docs/start.sections/make-it-yours.md +2 -2
- package/agent-docs/start.sections/migrating-from-github-packages.md +2 -2
- package/agent-docs/start.sections/prerequisites.md +2 -2
- package/agent-docs/start.sections/related-reading.md +2 -2
- package/agent-docs/start.sections/start-your-own-project.md +4 -4
- package/agent-docs/status.md +7 -5
- package/agent-docs/what-is-shoalrun.md +2 -2
- package/assets/ask-widget.js +1 -1
- package/assets/copy-markdown.js +1 -1
- package/assets/print-view.js +165 -0
- package/assets/print.js +63 -0
- package/dist/artifact-bundle.d.ts +1 -0
- package/dist/artifact-bundle.js +2 -0
- package/dist/ask-function.js +5 -2
- package/dist/authoring-snapshot.js +1 -1
- package/dist/browser-print-assets.d.ts +9 -0
- package/dist/browser-print-assets.js +45 -0
- package/dist/chat-config.d.ts +2 -0
- package/dist/chat-config.js +10 -2
- package/dist/cli.js +2 -2
- package/dist/compiler.d.ts +4 -2
- package/dist/compiler.js +29 -4
- package/dist/content-export-command.d.ts +77 -1
- package/dist/content-export-command.js +5 -2
- package/dist/content-export-config.d.ts +4 -1
- package/dist/content-export-config.js +10 -5
- package/dist/content-export-render.d.ts +9 -3
- package/dist/content-export-render.js +11 -7
- package/dist/content-export.d.ts +126 -0
- package/dist/content-export.js +267 -124
- package/dist/content-source.d.ts +1 -0
- package/dist/frontmatter.js +5 -2
- package/dist/highlight-languages.d.ts +9 -0
- package/dist/highlight-languages.js +52 -0
- package/dist/highlight.d.ts +4 -1
- package/dist/highlight.js +20 -5
- package/dist/output-commit.d.ts +1 -1
- package/dist/output-commit.js +4 -2
- package/dist/page-renderer.js +6 -1
- package/dist/pdf-button.d.ts +22 -0
- package/dist/pdf-button.js +55 -0
- package/dist/pdf-config.d.ts +28 -0
- package/dist/pdf-config.js +43 -0
- package/dist/pdf-http.d.ts +36 -0
- package/dist/pdf-http.js +132 -0
- package/dist/pdf-project.d.ts +6 -0
- package/dist/pdf-project.js +31 -0
- package/dist/pdf-renderer.d.ts +45 -0
- package/dist/pdf-renderer.js +208 -0
- package/dist/pdf-site.d.ts +37 -0
- package/dist/pdf-site.js +65 -0
- package/dist/pdf-stylesheet.d.ts +1 -0
- package/dist/pdf-stylesheet.js +57 -0
- package/dist/representation-tree.js +1 -1
- package/dist/server.d.ts +2 -0
- package/dist/server.js +35 -8
- package/dist/turnstile-policy.d.ts +2 -0
- package/dist/turnstile-policy.js +23 -0
- package/dist/turnstile.d.ts +7 -0
- package/dist/turnstile.js +29 -2
- package/dist/vercel-function.js +1 -1
- package/dist/vercel.js +6 -0
- package/examples/custom-language/README.md +32 -0
- package/examples/custom-language/content/start.mdx +28 -0
- package/examples/custom-language/docs.config.yaml +18 -0
- package/examples/custom-language/grammars/custompy.tmLanguage.json +7 -0
- package/examples/pdf/README.md +27 -0
- package/examples/pdf/content/operations.mdx +26 -0
- package/examples/pdf/content/start.mdx +28 -0
- package/examples/pdf/docs.config.yaml +29 -0
- package/examples/pdf/print.css +9 -0
- package/package.json +4 -2
- package/skills/shoalrun-author/SKILL.md +2 -0
- package/skills/shoalrun-author/references/pdf-export.md +15 -0
- package/skills/shoalrun-brand/SKILL.md +54 -0
- package/skills/shoalrun-pdf/SKILL.md +49 -0
- package/skills/shoalrun-pdf/assets/print.css +49 -0
- package/skills/shoalrun-pdf/references/print-design.md +75 -0
- package/styles/site.css +3 -1
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { parse } from 'parse5';
|
|
3
|
+
import { loadConfig } from './config.js';
|
|
4
|
+
import { projectPdfSettings } from './pdf-project.js';
|
|
5
|
+
import { preparePdfDocument } from './pdf-renderer.js';
|
|
6
|
+
import { sitePath } from './site-path.js';
|
|
7
|
+
/** One shared print theme for the entire build; no page content is materialized. */
|
|
8
|
+
export async function browserPrintAssets(configFile, options) {
|
|
9
|
+
const loaded = await loadConfig(configFile, { profile: options.profile });
|
|
10
|
+
const { config, files } = projectPdfSettings(loaded, configFile, options.style);
|
|
11
|
+
const prepared = await preparePdfDocument({
|
|
12
|
+
documents: [{ id: 'print-theme', title: '', html: '' }], title: '', assets: new Map(), publicBase: '/',
|
|
13
|
+
config: { ...config, cover: false, toc: false }, configFile,
|
|
14
|
+
assetRoots: files.map(file => path.dirname(file))
|
|
15
|
+
});
|
|
16
|
+
let css = '';
|
|
17
|
+
const findStyle = (node) => { if (node.tagName === 'style')
|
|
18
|
+
css += (node.childNodes ?? []).map((child) => child.value ?? '').join(''); for (const child of node.childNodes ?? [])
|
|
19
|
+
findStyle(child); };
|
|
20
|
+
findStyle(parse(prepared.html));
|
|
21
|
+
const artifacts = new Map();
|
|
22
|
+
let logo;
|
|
23
|
+
for (const [original, asset] of prepared.assets) {
|
|
24
|
+
const route = sitePath(options.basePath, `/assets/print/${path.posix.basename(original)}`);
|
|
25
|
+
artifacts.set(route, asset.bytes);
|
|
26
|
+
css = css.replaceAll(original, route);
|
|
27
|
+
if (original.startsWith('/__pdf_logo/'))
|
|
28
|
+
logo = route;
|
|
29
|
+
}
|
|
30
|
+
const margins = { top: '18mm', right: '18mm', bottom: '18mm', left: '18mm', ...config.margins };
|
|
31
|
+
css = `@page{size:${config.size ?? 'A4'};margin:${margins.top} ${margins.right} ${margins.bottom} ${margins.left}}\n` + css;
|
|
32
|
+
css += '\n.pdf-print-tools{display:flex;gap:10px;align-items:center;padding:12px 0;margin-bottom:24px;font:14px Arial,sans-serif}.pdf-print-tools button{font:inherit;padding:8px 12px;border:1px solid #89998e;border-radius:4px;cursor:pointer}.pdf-print-tools .pdf-print-note{margin-left:auto;color:#53665e;font-size:12px}.pdf-chapter .page-head h1{margin-bottom:8pt}.pdf-chapter .page-head .summary{font-size:10pt;line-height:1.5;margin:0 0 8pt}.pdf-chapter .page-head .pdf-page-meta{font-size:8pt;margin:5pt 0 12pt}.pdf-page-meta{color:var(--pdf-muted);font-size:9pt;margin-bottom:14pt}.pdf-chapter .eyebrow{color:var(--pdf-muted);font-size:9pt}.pdf-chapter .page-dates{display:flex;flex-wrap:wrap;gap:4pt 10pt;font-size:9pt;color:var(--pdf-muted)}.pdf-chapter .doc-callout{border-left:3pt solid var(--pdf-accent);padding:10pt 14pt;background:#f1f4ee;margin:14pt 0}.pdf-chapter details{display:block}.pdf-reader-label{font-weight:bold;margin-top:14pt}@media screen{html{background:#dfe5de}body{max-width:210mm;margin:24px auto;padding:18mm;background:white;box-shadow:0 8px 40px #173c3520}}@media print{.pdf-print-tools{display:none!important}}';
|
|
33
|
+
// A manual theme may start every chapter on a new page. When no cover or
|
|
34
|
+
// contents precede this chapter, that break can produce a blank first sheet.
|
|
35
|
+
css += '\n.pdf-print-tools + .pdf-chapter{break-before:auto}';
|
|
36
|
+
const stylesheet = sitePath(options.basePath, '/assets/print/theme.css');
|
|
37
|
+
artifacts.set(stylesheet, css);
|
|
38
|
+
const settings = {
|
|
39
|
+
schemaVersion: 1, stylesheet, ...(logo ? { logo } : {}), cover: config.cover ?? false,
|
|
40
|
+
toc: config.toc ?? false, subtitle: config.subtitle ?? '', header: config.header ?? '', footer: config.footer ?? '',
|
|
41
|
+
fonts: (config.fonts ?? []).map(({ family, weight, style }) => ({ family, weight, style }))
|
|
42
|
+
};
|
|
43
|
+
artifacts.set(sitePath(options.basePath, '/assets/print/settings.json'), JSON.stringify(settings));
|
|
44
|
+
return { artifacts, dependencies: new Set([...loaded.dependencies, ...files]) };
|
|
45
|
+
}
|
package/dist/chat-config.d.ts
CHANGED
package/dist/chat-config.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { reservedBodyFields } from './chat.js';
|
|
2
2
|
import { validateReaderSelection } from './reader-filters.js';
|
|
3
|
+
import { turnstileHostnames } from './turnstile-policy.js';
|
|
3
4
|
export const escalateReasons = ['unsupported-claim', 'unsupported-code', 'uncited'];
|
|
4
5
|
export const defaultLogNotice = 'Questions are recorded, with personal details removed, to improve these docs.';
|
|
5
6
|
const mapping = (value) => !!value && typeof value === 'object' && !Array.isArray(value);
|
|
@@ -305,9 +306,9 @@ export function validateChat(value, readerFilters) {
|
|
|
305
306
|
if (!mapping(challenge))
|
|
306
307
|
throw new Error('chat.challenge must be a mapping with provider, siteKey, and secretEnv');
|
|
307
308
|
for (const key of Object.keys(challenge)) {
|
|
308
|
-
if (['provider', 'siteKey', 'secretEnv', 'when', 'freeQuestions', 'busyShare'].includes(key))
|
|
309
|
+
if (['provider', 'siteKey', 'secretEnv', 'hostnames', 'action', 'when', 'freeQuestions', 'busyShare'].includes(key))
|
|
309
310
|
continue;
|
|
310
|
-
throw new Error(/key|secret|token|password/i.test(key) ? `chat.challenge.${key} is not a setting. The config never holds a secret; name the environment variable that does with secretEnv` : `chat.challenge has an unknown setting ${key}; it accepts provider, siteKey, secretEnv, when, freeQuestions, and busyShare`);
|
|
311
|
+
throw new Error(/key|secret|token|password/i.test(key) ? `chat.challenge.${key} is not a setting. The config never holds a secret; name the environment variable that does with secretEnv` : `chat.challenge has an unknown setting ${key}; it accepts provider, siteKey, secretEnv, hostnames, action, when, freeQuestions, and busyShare`);
|
|
311
312
|
}
|
|
312
313
|
if (challenge.provider !== 'turnstile')
|
|
313
314
|
throw new Error('chat.challenge.provider must be turnstile');
|
|
@@ -322,6 +323,13 @@ export function validateChat(value, readerFilters) {
|
|
|
322
323
|
if (challenge.busyShare !== undefined && (typeof challenge.busyShare !== 'number' || !(challenge.busyShare > 0 && challenge.busyShare <= 1)))
|
|
323
324
|
throw new Error('chat.challenge.busyShare must be a number above 0 and at most 1');
|
|
324
325
|
result.challenge = { provider: 'turnstile', siteKey, secretEnv, when: challenge.when ?? 'adaptive', freeQuestions: challenge.freeQuestions === undefined ? 3 : integer(challenge.freeQuestions, 'chat.challenge.freeQuestions', 0, 1000), busyShare: challenge.busyShare ?? 0.5 };
|
|
326
|
+
if (challenge.hostnames !== undefined)
|
|
327
|
+
result.challenge.hostnames = turnstileHostnames(challenge.hostnames, 'chat.challenge.hostnames');
|
|
328
|
+
if (challenge.action !== undefined) {
|
|
329
|
+
if (typeof challenge.action !== 'string' || !/^[A-Za-z0-9_-]{1,32}$/.test(challenge.action))
|
|
330
|
+
throw new Error('chat.challenge.action must contain 1 to 32 letters, numbers, underscores, or hyphens');
|
|
331
|
+
result.challenge.action = challenge.action;
|
|
332
|
+
}
|
|
325
333
|
}
|
|
326
334
|
if (value.budget !== undefined) {
|
|
327
335
|
if (!mapping(value.budget))
|
package/dist/cli.js
CHANGED
|
@@ -40,7 +40,7 @@ import { buildChunkIndex, embeddingInput } from './index-build.js';
|
|
|
40
40
|
import { createProvider, embedChunkIndex, readEmbeddings } from './embeddings.js';
|
|
41
41
|
import { embeddingProviderConfig, queryProviderConfig, validateRetrieval } from './retrieval-config.js';
|
|
42
42
|
import { defaultLogNotice, validateChat } from './chat-config.js';
|
|
43
|
-
import { createSession,
|
|
43
|
+
import { createSession, turnstilePreviewVerifier } from './turnstile.js';
|
|
44
44
|
import { adaptiveChallenge } from './budget.js';
|
|
45
45
|
import { validateReaderFilters } from './reader-filters.js';
|
|
46
46
|
import { askQuestion } from './ask.js';
|
|
@@ -269,7 +269,7 @@ try {
|
|
|
269
269
|
if (publicPreview) {
|
|
270
270
|
chatConfigForPreview = chatConfig;
|
|
271
271
|
publicSettings = { siteKey: '1x00000000000000000000AA', ...(chatConfig?.challenge?.when === 'always' ? { prewarm: true } : {}), notice: chatConfig?.log?.notice ?? defaultLogNotice, ...(chatConfig?.panel ? { panel: chatConfig.panel } : {}) };
|
|
272
|
-
const verify =
|
|
272
|
+
const verify = turnstilePreviewVerifier();
|
|
273
273
|
challenge = ({ token, ip }) => verify({ token, ip });
|
|
274
274
|
}
|
|
275
275
|
const handler = createAskHandler({ hosts, exposeErrors: true, ...(publicPreview ? { response: 'public', challenge, session: createSession({ secret: '1x0000000000000000000000000000000AA' }), ...(chatConfigForPreview?.challenge?.when === 'always' || !setup.budget ? {} : { challengeNeeded: adaptiveChallenge(setup.budget, { freeQuestions: chatConfigForPreview?.challenge?.freeQuestions, busyShare: chatConfigForPreview?.challenge?.busyShare }) }) } : {}), ...(log ? { record: event => log.write(event) } : {}), ...(setup.budget ? { budget: setup.budget } : {}), answer: (question, history, context) => askQuestion({ question, history, ...setup.options, modelAllowed: context.modelAllowed, signal: context.signal, ...(publicPreview ? { requireCitations: true } : {}) }) });
|
package/dist/compiler.d.ts
CHANGED
|
@@ -6,13 +6,14 @@ export declare function checkThemeContrast(configFile: string): Promise<{
|
|
|
6
6
|
modes: Mode[];
|
|
7
7
|
palette: string;
|
|
8
8
|
}>;
|
|
9
|
-
|
|
9
|
+
type CompileOptions = {
|
|
10
10
|
apiReview?: 'report';
|
|
11
11
|
sourceOverrides?: ReadonlyMap<string, string>;
|
|
12
12
|
authoringFiles?: ReadonlySet<string>;
|
|
13
13
|
profile?: string;
|
|
14
14
|
onDependency?: (file: string) => void;
|
|
15
|
-
}
|
|
15
|
+
};
|
|
16
|
+
export declare function compile(configFile: string, destination?: string, provenance?: BuildProvenance, options?: CompileOptions): Promise<{
|
|
16
17
|
authoringSources?: {
|
|
17
18
|
id: string;
|
|
18
19
|
file: string;
|
|
@@ -240,3 +241,4 @@ export declare function compile(configFile: string, destination?: string, proven
|
|
|
240
241
|
schemaVersion: number;
|
|
241
242
|
configDigest: string;
|
|
242
243
|
}>;
|
|
244
|
+
export {};
|
package/dist/compiler.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { buildPdfSiteArtifacts } from './pdf-site.js';
|
|
2
|
+
import { resolvePdfButton, validatePdfButtonStyle } from './pdf-button.js';
|
|
1
3
|
import { documentKey, publicationKey, samePublication } from './publication-identity.js';
|
|
2
4
|
import { validateHeader, validateFooter } from './site-identity.js';
|
|
3
5
|
import { profileSelection } from './deployment-profile.js';
|
|
@@ -102,6 +104,15 @@ export async function checkThemeContrast(configFile) {
|
|
|
102
104
|
return { modes, palette: palette.name };
|
|
103
105
|
}
|
|
104
106
|
export async function compile(configFile, destination, provenance = {}, options = {}) {
|
|
107
|
+
let dispose;
|
|
108
|
+
try {
|
|
109
|
+
return await compileProject(configFile, destination, provenance, options, release => { dispose = release; });
|
|
110
|
+
}
|
|
111
|
+
finally {
|
|
112
|
+
dispose?.();
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
async function compileProject(configFile, destination, provenance, options, ownHighlighter) {
|
|
105
116
|
const loaded = await loadConfig(configFile, { profile: options.profile, onDependency: options.onDependency });
|
|
106
117
|
const componentPlugins = await loadComponentPlugins(loaded);
|
|
107
118
|
const provenanceRecord = buildInfo(loaded.digest, provenance);
|
|
@@ -118,9 +129,15 @@ export async function compile(configFile, destination, provenance = {}, options
|
|
|
118
129
|
const calloutTypes = resolveCalloutTypes(config.callouts, configFile);
|
|
119
130
|
const badgeVariants = resolveBadgeVariants(config.badges, configFile);
|
|
120
131
|
const htmlOptions = configField(config.outputs, 'html');
|
|
132
|
+
const pdfButton = resolvePdfButton(config.outputs);
|
|
133
|
+
validatePdfButtonStyle(pdfButton, config.exports);
|
|
121
134
|
const highlighting = configField(htmlOptions, 'highlighting');
|
|
122
135
|
const highlightingOrigin = configField(highlighting, 'module') === undefined ? configFile : explainSegments(loaded, ['outputs', 'html', 'highlighting', 'module']).source.file;
|
|
123
|
-
const highlighter = await createCodeHighlighter(highlighting, highlightingOrigin
|
|
136
|
+
const highlighter = await createCodeHighlighter(highlighting, highlightingOrigin, {
|
|
137
|
+
grammarOrigin: index => explainSegments(loaded, ['outputs', 'html', 'highlighting', 'languages', String(index), 'grammar']).source.file,
|
|
138
|
+
onDependency: options.onDependency
|
|
139
|
+
});
|
|
140
|
+
ownHighlighter(highlighter.dispose);
|
|
124
141
|
const base = path.dirname(path.resolve(configFile));
|
|
125
142
|
if (config.site.customCss !== undefined && (typeof config.site.customCss !== 'string' || !config.site.customCss.endsWith('.css')))
|
|
126
143
|
throw new Error('site.customCss must be a local .css path');
|
|
@@ -313,7 +330,7 @@ export async function compile(configFile, destination, provenance = {}, options
|
|
|
313
330
|
const cardUses = new Map();
|
|
314
331
|
const publishedRelationships = [];
|
|
315
332
|
const styleFiles = ['site.css', 'tokens.css', 'header.css'].map(name => path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../styles', name));
|
|
316
|
-
const scriptFiles = [...(analyticsModule ? ['analytics.js'] : []), 'code-samples.js', 'cookbook-chooser.js', 'example-panel.js', 'mode.js', 'copy-markdown.js', 'components.js', 'navigation.js', 'reader.js', ...(readerFilters ? ['reader-filters.js'] : []), 'search-highlight.js', 'search.js', 'table-filter.js', 'tabs.js', 'tooltips.js', 'file-tree.js', 'image-viewer.js', 'walkthrough.js'].map(name => path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../assets', name));
|
|
333
|
+
const scriptFiles = [...(analyticsModule ? ['analytics.js'] : []), 'code-samples.js', 'cookbook-chooser.js', 'example-panel.js', 'mode.js', ...(pdfButton?.mode === 'print' ? ['print.js', 'print-view.js'] : []), 'copy-markdown.js', 'components.js', 'navigation.js', 'reader.js', ...(readerFilters ? ['reader-filters.js'] : []), 'search-highlight.js', 'search.js', 'table-filter.js', 'tabs.js', 'tooltips.js', 'file-tree.js', 'image-viewer.js', 'walkthrough.js'].map(name => path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../assets', name));
|
|
317
334
|
const documentRoutes = new Set([...docs.values()].map(doc => doc.route));
|
|
318
335
|
const { apiPublications, focusedContracts, navigationEntries, navigationTagOrder, rendered, snippetUses, apiEmbedUses, operations } = await materializePages({
|
|
319
336
|
variables: isConfigMapping(config.variables) ? config.variables : {}, frontmatterConfig: config.frontmatter,
|
|
@@ -379,7 +396,7 @@ export async function compile(configFile, destination, provenance = {}, options
|
|
|
379
396
|
const resolvedNavigation = renderPublicationPages({
|
|
380
397
|
freshness,
|
|
381
398
|
rendered, docs, relationships, knownOperations, cardUses, publishedRelationships, cookbooks, documentRoutes,
|
|
382
|
-
basePath, siteOrigin, colorMode, config, highlighter, editionLabel, customStyleFile, themePreset, palette,
|
|
399
|
+
basePath, siteOrigin, colorMode, config: options.authoringFiles ? { ...config, outputs: { ...config.outputs, html: { ...htmlOptions, pdf: false } } } : config, highlighter, editionLabel, customStyleFile, themePreset, palette,
|
|
383
400
|
themeStyleFile, generatedPaletteCss, generatedBadgeCss, themeSwitcherData, generatedFontsCss, fontsPreload: fonts.preload,
|
|
384
401
|
shareFor, faviconRoute, logoRoute, modeToggle, header, footer, search, readerFilters, variants, externalDocuments,
|
|
385
402
|
externalSiteLinks, navigationSpec, navigationEntries, navigationTagOrder
|
|
@@ -442,7 +459,14 @@ export async function compile(configFile, destination, provenance = {}, options
|
|
|
442
459
|
] };
|
|
443
460
|
}
|
|
444
461
|
const apiPublicationReview = await reviewApiPublication(configFile, apiPlan, rendered, apiEmbedUses, focusedContracts, apiSources, sourceForOperation, options.apiReview);
|
|
445
|
-
|
|
462
|
+
if (pdfButton?.mode === 'endpoint') {
|
|
463
|
+
const endpoint = publicPath(pdfButton.endpoint);
|
|
464
|
+
const mcpEndpoint = configField(config.mcp, 'path') ?? '/mcp';
|
|
465
|
+
if (rendered.some(({ doc }) => doc.route === endpoint || endpoint.startsWith(`${doc.route}/`)) || redirects.some(rule => rule.from === endpoint) || endpoint === publicPath(String(mcpEndpoint)))
|
|
466
|
+
throw new Error(`PDF endpoint shadows an existing route: ${endpoint}`);
|
|
467
|
+
}
|
|
468
|
+
const sitePdfs = options.authoringFiles ? { artifacts: new Map(), dependencies: new Set() } : await buildPdfSiteArtifacts(configFile, { outputs: config.outputs, exports: config.exports, documents: rendered.map(page => page.doc), knownDocumentIds, basePath, profile: options.profile });
|
|
469
|
+
const manifestDependencies = [...new Set([...sitePdfs.dependencies, ...loaded.dependencies, ...componentPlugins.dependencies, ...externalManifestFiles, ...api.dependencies, ...(metadataProvider ? [metadataProvider.module] : []), ...metadataDependencies, ...(highlighter.dependency ? [highlighter.dependency] : []), ...(highlighter.dependencies ?? []), ...styleFiles, ...(themeStyleFile ? [themeStyleFile] : []), ...palette.files, ...(search.module ? [search.module] : []), ...previewFiles, ...(customStyleFile ? [customStyleFile] : []), ...(customReaderModule ? [customReaderModule] : []), ...(analyticsModule ? [analyticsModule] : []), ...(mcpAnalyticsModule ? [mcpAnalyticsModule] : []), ...(shareImages.template ? [shareImages.template] : []), ...shareImages.fonts, ...(faviconFile ? [faviconFile] : []), ...heroImages.values(), ...walkthroughSources, ...fontFiles, ...fonts.faces.map(face => face.file), ...scriptFiles, iconLicenseFile, ...[...snippets.values()].map(s => s.file), ...[...docs.values()].map(d => d.file)])].sort();
|
|
446
470
|
const llmsDiscovery = resolveLlmsDiscovery(configField(config.outputs, 'llms'));
|
|
447
471
|
const { manifest, headers, sitemaps } = buildPublicationManifest({
|
|
448
472
|
configPublications: config.publications,
|
|
@@ -454,6 +478,7 @@ export async function compile(configFile, destination, provenance = {}, options
|
|
|
454
478
|
});
|
|
455
479
|
const artifactConfig = options.authoringFiles ? { ...config, mcp: false } : config;
|
|
456
480
|
const { artifacts, buildId } = await assembleArtifacts({
|
|
481
|
+
extraArtifacts: sitePdfs.artifacts,
|
|
457
482
|
base, basePath, publicPath, canonical, siteOrigin, manifest, provenanceRecord, rendered, sitemaps,
|
|
458
483
|
privateDependencies: new Set([...(loaded.deployment?.file ? [loaded.deployment.file] : []), ...(loaded.deployment?.assetFiles ?? [])]),
|
|
459
484
|
diagramAssets, heroImages, focusedContracts, styleFiles, themeStyleFile, generatedPaletteCss,
|
|
@@ -1,5 +1,81 @@
|
|
|
1
|
-
export declare const exportHelp = "Usage: shoalrun export [config] --out DIRECTORY [--target NAME]\n [--format markdown|html|both] [--document ID]...\n [--channel C --version V --language L [--product P]] [--public-base /help/] [--review]\n [--profile FILE] [--previous-manifest FILE] [--fail-on-removed]\n\nExport selected pages, sections, and named snippets as Markdown and passive HTML.\nConfigure typed resources in an export target; --document selects whole pages.\nNavigation, facets, and a prior website build are not required.\n--review writes an API-review candidate; status needs-review exits 2 until pinned.\n--previous-manifest compares public references with an earlier reviewed export.\n--fail-on-removed requires that baseline and refuses to replace output if references disappear.\n";
|
|
1
|
+
export declare const exportHelp = "Usage: shoalrun export [config] --out DIRECTORY [--target NAME]\n [--format markdown|html|both|pdf] [--document ID]...\n [--channel C --version V --language L [--product P]] [--public-base /help/] [--review]\n [--profile FILE] [--previous-manifest FILE] [--fail-on-removed]\n\nExport selected pages, sections, and named snippets as Markdown and passive HTML.\nConfigure typed resources in an export target; --document selects whole pages.\nNavigation, facets, and a prior website build are not required.\n--format pdf exports one publication, or the selected whole pages, as a styled PDF.\nPDF uses outputs.pdf defaults and target pdf overrides; it follows navigation order\nand appends eligible unlisted pages. PDF defaults to the site's contentFilter.\n--review writes an API-review candidate; status needs-review exits 2 until pinned.\n--previous-manifest compares public references with an earlier reviewed export.\n--fail-on-removed requires that baseline and refuses to replace output if references disappear.\n";
|
|
2
2
|
export declare function runExportCommand(args: string[]): Promise<{
|
|
3
|
+
digest: string;
|
|
4
|
+
preparedPdf: import("./content-export.js").PdfExportPreparation;
|
|
5
|
+
status: string;
|
|
6
|
+
output: string;
|
|
7
|
+
publication: import("./content-source.js").Variant;
|
|
8
|
+
formats: import("./content-export-config.js").ExportFormat[];
|
|
9
|
+
profileSelection: {
|
|
10
|
+
schemaVersion: number;
|
|
11
|
+
profileDigest: string;
|
|
12
|
+
features: string[];
|
|
13
|
+
provenance: Record<string, string[]>;
|
|
14
|
+
decisions: {
|
|
15
|
+
kind: "page" | "section";
|
|
16
|
+
id: string;
|
|
17
|
+
included: boolean;
|
|
18
|
+
requires?: import("./deployment-profile.js").FeatureRequirements;
|
|
19
|
+
}[];
|
|
20
|
+
} | undefined;
|
|
21
|
+
resources: number;
|
|
22
|
+
documents: number;
|
|
23
|
+
documentIds: string[];
|
|
24
|
+
comparison: {
|
|
25
|
+
status: "no-baseline";
|
|
26
|
+
added: never[];
|
|
27
|
+
removed: never[];
|
|
28
|
+
diagnostics: never[];
|
|
29
|
+
};
|
|
30
|
+
assets: number;
|
|
31
|
+
apiReview: {
|
|
32
|
+
apiId: string;
|
|
33
|
+
digest: string;
|
|
34
|
+
}[];
|
|
35
|
+
diagnostics: {
|
|
36
|
+
code: string;
|
|
37
|
+
documentId?: string;
|
|
38
|
+
message: string;
|
|
39
|
+
}[];
|
|
40
|
+
} | {
|
|
41
|
+
digest: string;
|
|
42
|
+
pdf: string;
|
|
43
|
+
status: string;
|
|
44
|
+
output: string;
|
|
45
|
+
publication: import("./content-source.js").Variant;
|
|
46
|
+
formats: import("./content-export-config.js").ExportFormat[];
|
|
47
|
+
profileSelection: {
|
|
48
|
+
schemaVersion: number;
|
|
49
|
+
profileDigest: string;
|
|
50
|
+
features: string[];
|
|
51
|
+
provenance: Record<string, string[]>;
|
|
52
|
+
decisions: {
|
|
53
|
+
kind: "page" | "section";
|
|
54
|
+
id: string;
|
|
55
|
+
included: boolean;
|
|
56
|
+
requires?: import("./deployment-profile.js").FeatureRequirements;
|
|
57
|
+
}[];
|
|
58
|
+
} | undefined;
|
|
59
|
+
resources: number;
|
|
60
|
+
documents: number;
|
|
61
|
+
documentIds: string[];
|
|
62
|
+
comparison: {
|
|
63
|
+
status: "no-baseline";
|
|
64
|
+
added: never[];
|
|
65
|
+
removed: never[];
|
|
66
|
+
diagnostics: never[];
|
|
67
|
+
};
|
|
68
|
+
assets: number;
|
|
69
|
+
apiReview: {
|
|
70
|
+
apiId: string;
|
|
71
|
+
digest: string;
|
|
72
|
+
}[];
|
|
73
|
+
diagnostics: {
|
|
74
|
+
code: string;
|
|
75
|
+
documentId?: string;
|
|
76
|
+
message: string;
|
|
77
|
+
}[];
|
|
78
|
+
} | {
|
|
3
79
|
status: string;
|
|
4
80
|
output: string;
|
|
5
81
|
publication: import("./content-source.js").Variant;
|
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
import { exportContent } from './content-export.js';
|
|
2
2
|
export const exportHelp = `Usage: shoalrun export [config] --out DIRECTORY [--target NAME]
|
|
3
|
-
[--format markdown|html|both] [--document ID]...
|
|
3
|
+
[--format markdown|html|both|pdf] [--document ID]...
|
|
4
4
|
[--channel C --version V --language L [--product P]] [--public-base /help/] [--review]
|
|
5
5
|
[--profile FILE] [--previous-manifest FILE] [--fail-on-removed]
|
|
6
6
|
|
|
7
7
|
Export selected pages, sections, and named snippets as Markdown and passive HTML.
|
|
8
8
|
Configure typed resources in an export target; --document selects whole pages.
|
|
9
9
|
Navigation, facets, and a prior website build are not required.
|
|
10
|
+
--format pdf exports one publication, or the selected whole pages, as a styled PDF.
|
|
11
|
+
PDF uses outputs.pdf defaults and target pdf overrides; it follows navigation order
|
|
12
|
+
and appends eligible unlisted pages. PDF defaults to the site's contentFilter.
|
|
10
13
|
--review writes an API-review candidate; status needs-review exits 2 until pinned.
|
|
11
14
|
--previous-manifest compares public references with an earlier reviewed export.
|
|
12
15
|
--fail-on-removed requires that baseline and refuses to replace output if references disappear.
|
|
@@ -51,7 +54,7 @@ export async function runExportCommand(args) {
|
|
|
51
54
|
if (!output)
|
|
52
55
|
throw new Error(exportHelp);
|
|
53
56
|
const format = values.get('--format');
|
|
54
|
-
if (format && !['html', 'markdown', 'both'].includes(format))
|
|
57
|
+
if (format && !['html', 'markdown', 'both', 'pdf'].includes(format))
|
|
55
58
|
throw new Error(exportHelp);
|
|
56
59
|
if (format)
|
|
57
60
|
options.formats = format === 'both' ? ['markdown', 'html'] : [format];
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { type ReaderSelection } from './reader-filters.js';
|
|
2
2
|
import type { Variant } from './content.js';
|
|
3
|
-
|
|
3
|
+
import { type PdfConfig } from './pdf-config.js';
|
|
4
|
+
export type ExportFormat = 'markdown' | 'html' | 'pdf';
|
|
5
|
+
export type PortableExportFormat = Exclude<ExportFormat, 'pdf'>;
|
|
4
6
|
export type ExportResourceSelector = {
|
|
5
7
|
kind: 'page';
|
|
6
8
|
documentId: string;
|
|
@@ -26,6 +28,7 @@ export type ExportTarget = {
|
|
|
26
28
|
assetRoots: string[];
|
|
27
29
|
apiReview: Record<string, string>;
|
|
28
30
|
notices: string[];
|
|
31
|
+
pdf?: PdfConfig;
|
|
29
32
|
};
|
|
30
33
|
export declare function validateExportResources(value: unknown): ExportResourceSelector[];
|
|
31
34
|
export declare function exportPublicBase(value: unknown): string;
|
|
@@ -2,6 +2,7 @@ import { isConfigMapping } from './compiler-config.js';
|
|
|
2
2
|
import { validateContentFilter } from './content-filter.js';
|
|
3
3
|
import { validateReaderFilters } from './reader-filters.js';
|
|
4
4
|
import { validSectionId } from './section-identity.js';
|
|
5
|
+
import { validatePdfConfig } from './pdf-config.js';
|
|
5
6
|
const nonempty = (value) => typeof value === 'string' && value.trim() === value && value.length > 0;
|
|
6
7
|
const snippetId = (value) => nonempty(value) && /^[A-Za-z_][A-Za-z0-9_.-]*$/.test(value)
|
|
7
8
|
&& value.split('.').every(part => /^[A-Za-z_][A-Za-z0-9_-]*$/.test(part) && !['__proto__', 'prototype', 'constructor'].includes(part));
|
|
@@ -50,13 +51,17 @@ export function exportPublicBase(value) {
|
|
|
50
51
|
return value;
|
|
51
52
|
}
|
|
52
53
|
export function validateExportTarget(value, readerFilters) {
|
|
53
|
-
if (!isConfigMapping(value) || Object.keys(value).some(key => !['formats', 'documents', 'resources', 'publication', 'contentFilter', 'publicBase', 'assetRoots', 'apiReview', 'notices'].includes(key)))
|
|
54
|
-
throw new Error('Export target accepts formats, documents or resources, publication, contentFilter, publicBase, assetRoots, apiReview, and
|
|
54
|
+
if (!isConfigMapping(value) || Object.keys(value).some(key => !['formats', 'documents', 'resources', 'publication', 'contentFilter', 'publicBase', 'assetRoots', 'apiReview', 'notices', 'pdf'].includes(key)))
|
|
55
|
+
throw new Error('Export target accepts formats, documents or resources, publication, contentFilter, publicBase, assetRoots, apiReview, notices, and pdf');
|
|
55
56
|
if (value.documents !== undefined && value.resources !== undefined)
|
|
56
57
|
throw new Error('Export documents and resources are mutually exclusive');
|
|
57
58
|
const formats = value.formats ?? ['markdown', 'html'];
|
|
58
|
-
if (!Array.isArray(formats) || !formats.length || formats.some(format => !['markdown', 'html'].includes(format)) || new Set(formats).size !== formats.length)
|
|
59
|
-
throw new Error('Export formats must be a nonempty unique list of markdown
|
|
59
|
+
if (!Array.isArray(formats) || !formats.length || formats.some(format => !['markdown', 'html', 'pdf'].includes(format)) || new Set(formats).size !== formats.length)
|
|
60
|
+
throw new Error('Export formats must be a nonempty unique list of markdown, html, or pdf');
|
|
61
|
+
if (formats.includes('pdf') && formats.length !== 1)
|
|
62
|
+
throw new Error('PDF exports use their own bundle; select pdf alone, or export HTML/Markdown separately');
|
|
63
|
+
if (formats.includes('pdf') && value.resources !== undefined)
|
|
64
|
+
throw new Error('PDF exports select whole pages with documents; section and snippet resources are not supported');
|
|
60
65
|
const strings = (raw, field) => {
|
|
61
66
|
if (raw === undefined)
|
|
62
67
|
return undefined;
|
|
@@ -76,7 +81,7 @@ export function validateExportTarget(value, readerFilters) {
|
|
|
76
81
|
throw new Error('Export apiReview must map API IDs to lowercase SHA-256 digests');
|
|
77
82
|
return { formats: [...formats].sort(), ...(value.documents !== undefined ? { documents: strings(value.documents, 'documents') } : {}), ...(value.resources !== undefined ? { resources: validateExportResources(value.resources) } : {}), ...(publication ? { publication } : {}),
|
|
78
83
|
...(value.contentFilter !== undefined ? { contentFilter: validateContentFilter(value.contentFilter, validateReaderFilters(readerFilters)) } : {}),
|
|
79
|
-
publicBase: exportPublicBase(value.publicBase ?? '/'), assetRoots: strings(value.assetRoots, 'assetRoots') ?? [], apiReview: (value.apiReview ?? {}), notices: strings(value.notices, 'notices') ?? [] };
|
|
84
|
+
publicBase: exportPublicBase(value.publicBase ?? '/'), assetRoots: strings(value.assetRoots, 'assetRoots') ?? [], apiReview: (value.apiReview ?? {}), notices: strings(value.notices, 'notices') ?? [], ...(value.pdf !== undefined ? { pdf: validatePdfConfig(value.pdf) } : {}) };
|
|
80
85
|
}
|
|
81
86
|
export function validateExports(value, readerFilters) {
|
|
82
87
|
if (value === undefined)
|
|
@@ -1,7 +1,13 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { PortableExportFormat } from './content-export-config.js';
|
|
2
|
+
import type { CodeHighlighter } from './highlight.js';
|
|
2
3
|
/** Lower interactions while representation-specific source blocks still exist. */
|
|
3
|
-
export declare function portableTree(tree: any, format:
|
|
4
|
-
|
|
4
|
+
export declare function portableTree(tree: any, format: PortableExportFormat, routes: Set<string>, basePath: string, options?: {
|
|
5
|
+
print?: boolean;
|
|
6
|
+
}): any;
|
|
7
|
+
export declare function passiveHtml(tree: any, prefix: string, checkAnchors?: boolean, options?: {
|
|
8
|
+
print?: boolean;
|
|
9
|
+
highlighter?: CodeHighlighter;
|
|
10
|
+
}): {
|
|
5
11
|
html: string;
|
|
6
12
|
anchors: Array<{
|
|
7
13
|
id: string;
|
|
@@ -4,7 +4,7 @@ import { renderHtmlWithHeadings } from './html-renderer.js';
|
|
|
4
4
|
import { heading, link, paragraph, root, text, renderMarkdown } from './content.js';
|
|
5
5
|
import { walkthroughMarkdown } from './walkthrough.js';
|
|
6
6
|
/** Lower interactions while representation-specific source blocks still exist. */
|
|
7
|
-
export function portableTree(tree, format, routes, basePath) {
|
|
7
|
+
export function portableTree(tree, format, routes, basePath, options = {}) {
|
|
8
8
|
// Provenance is internal export metadata. Keep it when component lowering
|
|
9
9
|
// synthesizes headings so a slice can retain its full-source identities.
|
|
10
10
|
const inherit = (node, key) => {
|
|
@@ -37,7 +37,7 @@ export function portableTree(tree, format, routes, basePath) {
|
|
|
37
37
|
return [heading(3, node.title), ...node.children.flatMap(walk)];
|
|
38
38
|
if (['docTooltip', 'docInfoHint'].includes(node.type))
|
|
39
39
|
return [node.inline ? text(`${node.label} (${node.definition})`) : paragraph(text(`${node.label}: ${node.definition}`))];
|
|
40
|
-
if (node.type === 'docMath')
|
|
40
|
+
if (node.type === 'docMath' && !options.print)
|
|
41
41
|
return [node.display ? { type: 'code', lang: 'latex', value: node.expression } : { type: 'inlineCode', value: node.expression }];
|
|
42
42
|
if (node.type === 'docDiagram')
|
|
43
43
|
return [paragraph({ type: 'image', url: node.url, alt: node.title }), paragraph(text(`${node.title}: ${node.summary}`)), ...(format === 'markdown' ? [{ type: 'code', lang: node.engine, value: node.source }] : [])];
|
|
@@ -78,7 +78,8 @@ export function portableTree(tree, format, routes, basePath) {
|
|
|
78
78
|
const data = node.data ? { ...node.data } : undefined;
|
|
79
79
|
if (data) {
|
|
80
80
|
delete data.hChildren;
|
|
81
|
-
|
|
81
|
+
if (!options.print)
|
|
82
|
+
delete data.mathMarkup;
|
|
82
83
|
}
|
|
83
84
|
return { ...node, ...(data ? { data } : {}), ...(node.children ? { children: node.children.filter((child) => child.type !== 'definition').map(clean) } : {}) };
|
|
84
85
|
};
|
|
@@ -86,8 +87,10 @@ export function portableTree(tree, format, routes, basePath) {
|
|
|
86
87
|
}
|
|
87
88
|
const tags = new Set('a abbr article b blockquote br caption code col colgroup dd del details div dl dt em figcaption figure h1 h2 h3 h4 h5 h6 hr i img input kbd li ol p pre progress s section small span strong sub summary sup table tbody td th thead time tr ul'.split(' '));
|
|
88
89
|
const attrs = new Set(['id', 'href', 'src', 'alt', 'title', 'colspan', 'rowspan', 'start', 'reversed', 'value', 'max', 'datetime', 'scope', 'align', 'type', 'checked', 'disabled', 'aria-label', 'aria-labelledby', 'aria-describedby', 'aria-hidden', 'role', 'tabindex', 'data-footnote-ref', 'data-footnote-backref', 'data-export-link']);
|
|
89
|
-
|
|
90
|
-
|
|
90
|
+
const mathTags = new Set('math semantics annotation mrow mi mn mo mtext mspace ms mfrac msqrt mroot mstyle merror mpadded mphantom mfenced menclose msub msup msubsup munder mover munderover mmultiscripts mprescripts none mtable mtr mtd mlabeledtr'.split(' '));
|
|
91
|
+
const mathAttrs = new Set('xmlns display mathvariant encoding stretchy fence separator accent accentunder form lspace rspace minsize maxsize symmetric largeop movablelimits scriptlevel displaystyle linethickness notation columnalign columnspacing rowalign rowspacing columnspan rowspan width height depth voffset'.split(' '));
|
|
92
|
+
export function passiveHtml(tree, prefix, checkAnchors = true, options = {}) {
|
|
93
|
+
const rendered = renderHtmlWithHeadings(tree, options.highlighter);
|
|
91
94
|
const fragment = parseFragment(rendered.html);
|
|
92
95
|
const anchors = [];
|
|
93
96
|
const ids = new Set();
|
|
@@ -95,7 +98,7 @@ export function passiveHtml(tree, prefix, checkAnchors = true) {
|
|
|
95
98
|
if (node.nodeName === '#text')
|
|
96
99
|
return;
|
|
97
100
|
if (node.tagName) {
|
|
98
|
-
if (!tags.has(node.tagName))
|
|
101
|
+
if (!tags.has(node.tagName) && !(options.print && mathTags.has(node.tagName)))
|
|
99
102
|
throw new Error(`Unsupported passive HTML element <${node.tagName}>`);
|
|
100
103
|
const original = node.attrs ?? [];
|
|
101
104
|
for (const attr of original) {
|
|
@@ -107,7 +110,8 @@ export function passiveHtml(tree, prefix, checkAnchors = true) {
|
|
|
107
110
|
throw new Error(`Unsafe exported URL: ${attr.value}`);
|
|
108
111
|
}
|
|
109
112
|
}
|
|
110
|
-
node.attrs = original.filter((attr) => attrs.has(attr.name))
|
|
113
|
+
node.attrs = original.filter((attr) => attrs.has(attr.name) || options.print && (attr.name === 'class' || mathTags.has(node.tagName) && mathAttrs.has(attr.name)
|
|
114
|
+
|| attr.name === 'style' && /^(?:(?:color|background-color):(?:#[a-f\d]{3,8}|var\(--code-(?:foreground|token-[a-z-]+)\));?)+$/i.test(attr.value)));
|
|
111
115
|
const id = node.attrs.find((attr) => attr.name === 'id');
|
|
112
116
|
if (id) {
|
|
113
117
|
if (ids.has(id.value))
|
package/dist/content-export.d.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { type ExportFormat } from './content-export-config.js';
|
|
2
2
|
import { type Variant } from './content.js';
|
|
3
|
+
import { type PdfRenderInput } from './pdf-renderer.js';
|
|
4
|
+
import type { ContentLink } from './content-delivery-contract.js';
|
|
3
5
|
export type ContentExportOptions = {
|
|
4
6
|
profile?: string;
|
|
5
7
|
target?: string;
|
|
@@ -10,9 +12,132 @@ export type ContentExportOptions = {
|
|
|
10
12
|
review?: boolean;
|
|
11
13
|
previousManifest?: string;
|
|
12
14
|
failOnRemoved?: boolean;
|
|
15
|
+
preparePdfOnly?: boolean;
|
|
16
|
+
pdfLinks?: 'internal' | 'canonical';
|
|
17
|
+
pdfStyleTarget?: string;
|
|
18
|
+
};
|
|
19
|
+
export type PdfExportPreparation = {
|
|
20
|
+
input: PdfRenderInput;
|
|
21
|
+
manifest: {
|
|
22
|
+
schemaVersion: number;
|
|
23
|
+
kind: string;
|
|
24
|
+
publication: Variant;
|
|
25
|
+
title: string;
|
|
26
|
+
documents: Array<{
|
|
27
|
+
id: string;
|
|
28
|
+
title: string;
|
|
29
|
+
contentDigest: string;
|
|
30
|
+
anchors: Representation['anchors'];
|
|
31
|
+
links: ExportLink[];
|
|
32
|
+
}>;
|
|
33
|
+
review: {
|
|
34
|
+
status: string;
|
|
35
|
+
apis: Array<{
|
|
36
|
+
apiId: string;
|
|
37
|
+
digest: string;
|
|
38
|
+
}>;
|
|
39
|
+
};
|
|
40
|
+
diagnostics: Array<{
|
|
41
|
+
code: string;
|
|
42
|
+
documentId?: string;
|
|
43
|
+
message: string;
|
|
44
|
+
}>;
|
|
45
|
+
};
|
|
46
|
+
inputs: string[];
|
|
47
|
+
};
|
|
48
|
+
/** Prepare passive, scoped print inputs without launching a browser or writing output. */
|
|
49
|
+
export declare function preparePdfExport(configFile: string, options?: Omit<ContentExportOptions, 'formats' | 'preparePdfOnly'>): Promise<PdfExportPreparation>;
|
|
50
|
+
type ExportLink = ContentLink;
|
|
51
|
+
type Representation = {
|
|
52
|
+
path: string;
|
|
53
|
+
mediaType: string;
|
|
54
|
+
bytes: number;
|
|
55
|
+
digest: string;
|
|
56
|
+
anchors: Array<{
|
|
57
|
+
id: string;
|
|
58
|
+
exportedId: string;
|
|
59
|
+
title?: string;
|
|
60
|
+
}>;
|
|
61
|
+
links: ExportLink[];
|
|
13
62
|
};
|
|
14
63
|
/** Export selected source content; never invoke the website shell/compiler. */
|
|
15
64
|
export declare function exportContent(configFile: string, destination: string, options?: ContentExportOptions): Promise<{
|
|
65
|
+
digest: string;
|
|
66
|
+
preparedPdf: PdfExportPreparation;
|
|
67
|
+
status: string;
|
|
68
|
+
output: string;
|
|
69
|
+
publication: Variant;
|
|
70
|
+
formats: ExportFormat[];
|
|
71
|
+
profileSelection: {
|
|
72
|
+
schemaVersion: number;
|
|
73
|
+
profileDigest: string;
|
|
74
|
+
features: string[];
|
|
75
|
+
provenance: Record<string, string[]>;
|
|
76
|
+
decisions: {
|
|
77
|
+
kind: "page" | "section";
|
|
78
|
+
id: string;
|
|
79
|
+
included: boolean;
|
|
80
|
+
requires?: import("./deployment-profile.js").FeatureRequirements;
|
|
81
|
+
}[];
|
|
82
|
+
} | undefined;
|
|
83
|
+
resources: number;
|
|
84
|
+
documents: number;
|
|
85
|
+
documentIds: string[];
|
|
86
|
+
comparison: {
|
|
87
|
+
status: "no-baseline";
|
|
88
|
+
added: never[];
|
|
89
|
+
removed: never[];
|
|
90
|
+
diagnostics: never[];
|
|
91
|
+
};
|
|
92
|
+
assets: number;
|
|
93
|
+
apiReview: {
|
|
94
|
+
apiId: string;
|
|
95
|
+
digest: string;
|
|
96
|
+
}[];
|
|
97
|
+
diagnostics: {
|
|
98
|
+
code: string;
|
|
99
|
+
documentId?: string;
|
|
100
|
+
message: string;
|
|
101
|
+
}[];
|
|
102
|
+
} | {
|
|
103
|
+
digest: string;
|
|
104
|
+
pdf: string;
|
|
105
|
+
status: string;
|
|
106
|
+
output: string;
|
|
107
|
+
publication: Variant;
|
|
108
|
+
formats: ExportFormat[];
|
|
109
|
+
profileSelection: {
|
|
110
|
+
schemaVersion: number;
|
|
111
|
+
profileDigest: string;
|
|
112
|
+
features: string[];
|
|
113
|
+
provenance: Record<string, string[]>;
|
|
114
|
+
decisions: {
|
|
115
|
+
kind: "page" | "section";
|
|
116
|
+
id: string;
|
|
117
|
+
included: boolean;
|
|
118
|
+
requires?: import("./deployment-profile.js").FeatureRequirements;
|
|
119
|
+
}[];
|
|
120
|
+
} | undefined;
|
|
121
|
+
resources: number;
|
|
122
|
+
documents: number;
|
|
123
|
+
documentIds: string[];
|
|
124
|
+
comparison: {
|
|
125
|
+
status: "no-baseline";
|
|
126
|
+
added: never[];
|
|
127
|
+
removed: never[];
|
|
128
|
+
diagnostics: never[];
|
|
129
|
+
};
|
|
130
|
+
assets: number;
|
|
131
|
+
apiReview: {
|
|
132
|
+
apiId: string;
|
|
133
|
+
digest: string;
|
|
134
|
+
}[];
|
|
135
|
+
diagnostics: {
|
|
136
|
+
code: string;
|
|
137
|
+
documentId?: string;
|
|
138
|
+
message: string;
|
|
139
|
+
}[];
|
|
140
|
+
} | {
|
|
16
141
|
status: string;
|
|
17
142
|
output: string;
|
|
18
143
|
publication: Variant;
|
|
@@ -45,3 +170,4 @@ export declare function exportContent(configFile: string, destination: string, o
|
|
|
45
170
|
message: string;
|
|
46
171
|
}[];
|
|
47
172
|
}>;
|
|
173
|
+
export {};
|