@shoalrun/cli 0.1.0-alpha.19 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +30 -0
- package/CODE-REFERENCES.md +137 -0
- package/README.md +19 -6
- package/adapters/python/requirements.txt +1 -0
- package/adapters/python/shoalrun_reference.py +234 -0
- 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/api-docs/code-reference-settings.md +160 -0
- package/agent-docs/api-docs/code-reference-settings.sections/catalog-settings.md +50 -0
- package/agent-docs/api-docs/code-reference-settings.sections/complete-reference-page.md +46 -0
- package/agent-docs/api-docs/code-reference-settings.sections/converter-snapshot.md +58 -0
- package/agent-docs/api-docs/code-reference-settings.sections/descriptions-type-links-and-source-links.md +40 -0
- package/agent-docs/api-docs/code-reference-settings.sections/html-markdown-and-agents.md +38 -0
- package/agent-docs/api-docs/code-reference-settings.sections/metadata-and-selection.md +38 -0
- package/agent-docs/api-docs/code-reference-settings.sections/publication-sources.md +54 -0
- package/agent-docs/api-docs/code-reference-settings.sections/python-adapter-limits.md +38 -0
- package/agent-docs/api-docs/code-reference-settings.sections/related-reading.md +32 -0
- package/agent-docs/api-docs/code-reference-settings.sections/symbol-fields.md +56 -0
- package/agent-docs/api-docs/code-references.md +143 -0
- 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/code-api-references.md +44 -0
- package/agent-docs/changelog/pdf-custom-languages.md +48 -0
- package/agent-docs/changelog.md +17 -1
- package/agent-docs/cli.md +11 -1
- package/agent-docs/cli.sections/audit-a-project-and-api-sources.md +1 -1
- package/agent-docs/cli.sections/author-pages.md +1 -1
- package/agent-docs/cli.sections/build-and-serve.md +1 -1
- package/agent-docs/cli.sections/inspect-config-and-operations.md +1 -1
- package/agent-docs/cli.sections/npm-scripts-in-this-repository.md +1 -1
- package/agent-docs/cli.sections/related-reading.md +1 -1
- package/agent-docs/cli.sections/search-and-chat.md +1 -1
- package/agent-docs/cli.sections/start-a-project.md +1 -1
- package/agent-docs/cli.sections/validate-code-reference-snapshots.md +41 -0
- package/agent-docs/cli.sections/working-tree-authoring-tools.md +1 -1
- package/agent-docs/config/apis.md +5 -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/config.md +2 -2
- 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 +1009 -356
- package/agent-docs/features.md +6 -2
- package/agent-docs/index.md +2 -2
- package/agent-docs/provenance.json +6 -6
- package/agent-docs/publish/api-references.md +3 -1
- package/agent-docs/publish/api-references.sections/author-the-reference-page.md +3 -1
- package/agent-docs/publish/api-references.sections/build-and-verify-the-output.md +3 -1
- package/agent-docs/publish/api-references.sections/inspect-the-machine-readable-contract.md +3 -1
- package/agent-docs/publish/api-references.sections/next-steps.md +3 -1
- package/agent-docs/publish/api-references.sections/preview-a-small-api-reference.md +3 -1
- package/agent-docs/publish/api-references.sections/put-the-spec-in-your-project.md +3 -1
- package/agent-docs/publish/api-references.sections/register-the-operation.md +3 -1
- package/agent-docs/publish/api-references.sections/related-reading.md +3 -1
- package/agent-docs/publish/api-references.sections/review-imported-content-before-publishing.md +3 -1
- package/agent-docs/publish/api-references.sections/what-readers-get.md +3 -1
- 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 +9 -5
- package/agent-docs/styling/themes.md +7 -3
- package/agent-docs/what-is-shoalrun.md +2 -2
- 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 +2 -0
- package/dist/artifact-bundle.js +13 -3
- 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/cli-discovery.d.ts +1 -1
- package/dist/cli-discovery.js +1 -0
- package/dist/cli.js +7 -0
- package/dist/code-reference-model.d.ts +85 -0
- package/dist/code-reference-model.js +73 -0
- package/dist/code-references.d.ts +23 -0
- package/dist/code-references.js +371 -0
- package/dist/compiler-config.d.ts +1 -0
- package/dist/compiler.d.ts +4 -2
- package/dist/compiler.js +33 -7
- package/dist/config.js +1 -1
- 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 +2 -0
- package/dist/content-workspace.d.ts +1 -0
- package/dist/content-workspace.js +2 -0
- package/dist/fonts.d.ts +9 -0
- package/dist/fonts.js +58 -3
- 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-materialization.js +3 -2
- package/dist/page-renderer.d.ts +1 -0
- package/dist/page-renderer.js +8 -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/publication-render.d.ts +2 -1
- package/dist/publication-render.js +2 -2
- package/dist/representation-tree.js +5 -1
- package/dist/server.d.ts +2 -0
- package/dist/server.js +35 -8
- package/dist/vercel.js +6 -0
- package/examples/code-references/content/start.md +53 -0
- package/examples/code-references/docs.config.yaml +262 -0
- package/examples/code-references/python/orchard/__init__.py +11 -0
- package/examples/code-references/python/orchard/client.py +212 -0
- package/examples/code-references/python/orchard/errors.py +31 -0
- package/examples/code-references/python/orchard/models.py +69 -0
- package/examples/code-references/python/orchard/pagination.py +39 -0
- package/examples/code-references/reference.json +1244 -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 +11 -2
- package/schemas/code-reference.schema.json +306 -0
- package/skills/shoalrun-author/SKILL.md +6 -0
- package/skills/shoalrun-author/references/code-api-references.md +15 -0
- package/skills/shoalrun-author/references/pdf-export.md +15 -0
- package/skills/shoalrun-brand/SKILL.md +2 -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 +64 -3
|
@@ -0,0 +1,371 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { createHash } from 'node:crypto';
|
|
3
|
+
import { z } from 'zod';
|
|
4
|
+
import { renderHtmlWithHeadings } from './html-renderer.js';
|
|
5
|
+
import { readCodeReference } from './code-reference-model.js';
|
|
6
|
+
import { explainSegments } from './config.js';
|
|
7
|
+
import { resolvePublication } from './compiler-config.js';
|
|
8
|
+
import { resolveFrontmatter, validateFrontmatter, applyFormattingLocale } from './frontmatter.js';
|
|
9
|
+
import { matchesFeatures, validateFeatureClassification } from './deployment-profile.js';
|
|
10
|
+
import { pageReaderFilters } from './reader-filters.js';
|
|
11
|
+
import { matchesContentFilter } from './content-filter.js';
|
|
12
|
+
import { documentKey } from './publication-identity.js';
|
|
13
|
+
import { routeFor, markdownRoute } from './content-source.js';
|
|
14
|
+
import { heading, paragraph, text, parsePlainMarkdown, root } from './semantic-markdown.js';
|
|
15
|
+
const mapping = z.record(z.string(), z.unknown());
|
|
16
|
+
const source = z.object({ kind: z.literal('file'), path: z.string().min(1) }).strict();
|
|
17
|
+
const override = z.object({ documentId: z.string().min(1).optional(), slug: z.string().startsWith('/').optional(), metadata: mapping.optional() }).strict();
|
|
18
|
+
const catalogSchema = z.object({
|
|
19
|
+
source: source.optional(),
|
|
20
|
+
sourceLink: z.object({ baseUrl: z.string().url().refine(value => { const url = new URL(value); return url.protocol === 'https:' && !url.username && !url.password && !url.search && !url.hash; }, 'Source links need an HTTPS base URL without credentials, query or fragment') }).strict().optional(),
|
|
21
|
+
publications: z.array(z.object({ product: z.string().optional(), channel: z.string(), version: z.string(), language: z.string().optional(), source }).strict()).min(1).optional(),
|
|
22
|
+
layout: z.enum(['symbols', 'classes', 'modules']).default('symbols'),
|
|
23
|
+
singlePage: z.union([z.literal(false), z.object({ documentId: z.string().min(1).optional(), slug: z.string().startsWith('/').optional(), title: z.string().min(1).default('Complete API reference'), summary: z.string().min(1).default('Classes, functions, and fields in this publication.'), metadata: mapping.optional() }).strict()]).optional(),
|
|
24
|
+
pathPrefix: z.string().regex(/^\/[a-zA-Z0-9_/-]+$/).default('/reference/code'),
|
|
25
|
+
metadata: mapping.optional(),
|
|
26
|
+
include: z.array(z.string()).optional(), exclude: z.array(z.string()).optional(),
|
|
27
|
+
symbols: z.record(z.string(), override).optional()
|
|
28
|
+
}).strict().refine(value => !!value.source !== !!value.publications, 'Declare source or publications');
|
|
29
|
+
const hash = (value) => createHash('sha256').update(value).digest('hex').slice(0, 16);
|
|
30
|
+
export function codeDocumentId(catalog, symbol) { return `reference.${catalog}.${/^[A-Za-z0-9_.-]+$/.test(symbol) ? symbol : hash(symbol)}`; }
|
|
31
|
+
export async function materializeCodeReferences(input) {
|
|
32
|
+
const { config, loaded, plan, base, basePath, variants, language, readerFilters, contentFilter, dependencies } = input;
|
|
33
|
+
if (config.codeReferences === undefined)
|
|
34
|
+
return;
|
|
35
|
+
const catalogs = z.record(z.string().regex(/^[a-z][a-z0-9-]*$/), catalogSchema).parse(config.codeReferences);
|
|
36
|
+
const owners = new Map([...plan.knownDocumentIds].map(id => [id, 'authored']));
|
|
37
|
+
for (const [catalogId, catalog] of Object.entries(catalogs)) {
|
|
38
|
+
const bindings = catalog.publications ?? [{ ...resolvePublication(config.publications, config.publications.defaults ?? {}, language), source: catalog.source }];
|
|
39
|
+
const seenPublications = new Set();
|
|
40
|
+
for (const [index, binding] of bindings.entries()) {
|
|
41
|
+
const variant = resolvePublication(config.publications, binding, language);
|
|
42
|
+
const identity = documentKey(catalogId, variant);
|
|
43
|
+
if (seenPublications.has(identity))
|
|
44
|
+
throw new Error(`Duplicate code reference publication ${identity}`);
|
|
45
|
+
seenPublications.add(identity);
|
|
46
|
+
const segments = ['codeReferences', catalogId, ...(catalog.publications ? ['publications', String(index)] : []), 'source', 'path'];
|
|
47
|
+
const owner = explainSegments(loaded, segments).source.file;
|
|
48
|
+
if (path.isAbsolute(binding.source.path) || /^[a-z][a-z0-9+.-]*:/i.test(binding.source.path))
|
|
49
|
+
throw new Error('Code reference sources need local relative paths');
|
|
50
|
+
const file = path.resolve(path.dirname(owner), binding.source.path);
|
|
51
|
+
dependencies.add(file);
|
|
52
|
+
const snapshot = await readCodeReference(file, base);
|
|
53
|
+
const symbols = new Map(snapshot.symbols.map(symbol => [symbol.id, symbol]));
|
|
54
|
+
for (const name of [...catalog.include ?? [], ...catalog.exclude ?? [], ...Object.keys(catalog.symbols ?? {})])
|
|
55
|
+
if (!symbols.has(name))
|
|
56
|
+
throw new Error(`Code reference ${catalogId}: unknown selected symbol ${name}`);
|
|
57
|
+
const visibility = new Map();
|
|
58
|
+
for (const symbol of snapshot.symbols) {
|
|
59
|
+
const chain = [];
|
|
60
|
+
let ancestor = symbol;
|
|
61
|
+
while (ancestor && !visibility.has(ancestor.id)) {
|
|
62
|
+
chain.push(ancestor);
|
|
63
|
+
ancestor = ancestor.parent ? symbols.get(ancestor.parent) : undefined;
|
|
64
|
+
}
|
|
65
|
+
let visible = ancestor ? visibility.get(ancestor.id) : true;
|
|
66
|
+
for (const entry of chain.reverse()) {
|
|
67
|
+
visible = visible && entry.visibility === 'public';
|
|
68
|
+
visibility.set(entry.id, visible);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
for (const symbol of snapshot.symbols) {
|
|
72
|
+
const selected = visibility.get(symbol.id) && (!catalog.include || catalog.include.includes(symbol.id)) && !catalog.exclude?.includes(symbol.id);
|
|
73
|
+
const setting = catalog.symbols?.[symbol.id];
|
|
74
|
+
const inherited = { ...catalog.metadata };
|
|
75
|
+
if (setting?.metadata && (Object.hasOwn(setting.metadata, 'shared') || Object.hasOwn(setting.metadata, 'requires'))) {
|
|
76
|
+
delete inherited.shared;
|
|
77
|
+
delete inherited.requires;
|
|
78
|
+
}
|
|
79
|
+
const metadata = { ...inherited, ...setting?.metadata };
|
|
80
|
+
const id = setting?.documentId ?? codeDocumentId(catalogId, symbol.id);
|
|
81
|
+
const ownerId = `${catalogId}:${symbol.id}`;
|
|
82
|
+
if (owners.has(id) && owners.get(id) !== ownerId)
|
|
83
|
+
throw new Error(`Duplicate code reference document ID ${id}`);
|
|
84
|
+
owners.set(id, ownerId);
|
|
85
|
+
plan.knownDocumentIds.add(id);
|
|
86
|
+
const existing = plan.knownDocumentTypes.get(id);
|
|
87
|
+
if (existing && existing !== 'code-reference')
|
|
88
|
+
throw new Error(`Duplicate code reference document ID ${id}`);
|
|
89
|
+
plan.knownDocumentTypes.set(id, 'code-reference');
|
|
90
|
+
const pageSource = {
|
|
91
|
+
...metadata, id, title: symbol.name, summary: symbol.summary ?? `${symbol.kind}: ${symbol.id}`,
|
|
92
|
+
type: 'code-reference', slug: setting?.slug ?? `${catalog.pathPrefix}/${symbol.id.toLowerCase().replace(/[^a-z0-9_-]+/g, '-')}-${hash(symbol.id)}`,
|
|
93
|
+
...variant
|
|
94
|
+
};
|
|
95
|
+
const frontmatter = resolveFrontmatter(pageSource, config.frontmatter);
|
|
96
|
+
frontmatter.access ??= { policy: 'public' };
|
|
97
|
+
if (catalog.layout !== 'symbols')
|
|
98
|
+
frontmatter.tocDepth ??= 2;
|
|
99
|
+
for (const [field, expected] of Object.entries({ id, type: 'code-reference', ...variant }))
|
|
100
|
+
if (frontmatter[field] !== expected)
|
|
101
|
+
throw new Error(`Code reference ${id}: enforced ${field} conflicts with its catalog`);
|
|
102
|
+
applyFormattingLocale(frontmatter, config.frontmatter, pageSource);
|
|
103
|
+
validateFrontmatter(frontmatter, config.frontmatter, file, pageSource);
|
|
104
|
+
const classification = loaded.deployment?.context.classificationPublications ?? variants;
|
|
105
|
+
if (selected && frontmatter.access?.policy === 'public' && classification.some(item => documentKey(catalogId, item) === identity))
|
|
106
|
+
validateFeatureClassification(metadata, loaded.deployment?.context, `${file}: ${symbol.id}`);
|
|
107
|
+
if (!selected || !matchesFeatures(frontmatter.requires, loaded.deployment?.context, `${file}: ${symbol.id}`) || !variants.some(item => documentKey(catalogId, item) === identity))
|
|
108
|
+
continue;
|
|
109
|
+
if (frontmatter.access?.policy !== 'public')
|
|
110
|
+
continue;
|
|
111
|
+
const marks = pageReaderFilters(frontmatter, readerFilters, file);
|
|
112
|
+
if (!matchesContentFilter(marks, contentFilter))
|
|
113
|
+
continue;
|
|
114
|
+
const key = documentKey(id, variant);
|
|
115
|
+
if (plan.docs.has(key))
|
|
116
|
+
throw new Error(`Duplicate code reference document ${id}`);
|
|
117
|
+
const route = routeFor(frontmatter, variant, config);
|
|
118
|
+
const collision = plan.routeOwners.get(route.toLowerCase());
|
|
119
|
+
if (collision && !input.allowRouteCollisions)
|
|
120
|
+
throw new Error(`Code reference route collision ${route}`);
|
|
121
|
+
plan.routeOwners.set(route.toLowerCase(), key);
|
|
122
|
+
plan.docs.set(key, { file, frontmatter, variant, route, markdownRoute: markdownRoute(route, basePath), readerFilters: marks, featureContext: loaded.deployment?.context, body: '', tree: null, codeReference: { catalog: catalogId, programmingLanguage: snapshot.programmingLanguage, symbol, layout: catalog.layout, ...(catalog.sourceLink && symbol.source ? { sourceUrl: catalog.sourceLink.baseUrl.replace(/\/$/, '') + '/' + symbol.source.path.split('/').map(encodeURIComponent).join('/') + (symbol.source.line ? `#L${symbol.source.line}` : '') } : {}) } });
|
|
123
|
+
}
|
|
124
|
+
if (catalog.singlePage) {
|
|
125
|
+
const single = catalog.singlePage;
|
|
126
|
+
const id = single.documentId ?? `reference.${catalogId}.complete`;
|
|
127
|
+
const ownerId = `${catalogId}:complete-page`;
|
|
128
|
+
if (owners.has(id) && owners.get(id) !== ownerId)
|
|
129
|
+
throw new Error(`Duplicate code reference document ID ${id}`);
|
|
130
|
+
owners.set(id, ownerId);
|
|
131
|
+
plan.knownDocumentIds.add(id);
|
|
132
|
+
plan.knownDocumentTypes.set(id, 'code-reference');
|
|
133
|
+
const inherited = { ...catalog.metadata };
|
|
134
|
+
if (single.metadata && (Object.hasOwn(single.metadata, 'shared') || Object.hasOwn(single.metadata, 'requires'))) {
|
|
135
|
+
delete inherited.shared;
|
|
136
|
+
delete inherited.requires;
|
|
137
|
+
}
|
|
138
|
+
const metadata = { ...inherited, ...single.metadata };
|
|
139
|
+
const source = { ...metadata, id, title: single.title, summary: single.summary, type: 'code-reference', slug: single.slug ?? `${catalog.pathPrefix}/all`, ...variant };
|
|
140
|
+
const frontmatter = resolveFrontmatter(source, config.frontmatter);
|
|
141
|
+
frontmatter.access ??= { policy: 'public' };
|
|
142
|
+
for (const [field, expected] of Object.entries({ id, type: 'code-reference', ...variant }))
|
|
143
|
+
if (frontmatter[field] !== expected)
|
|
144
|
+
throw new Error(`Code reference ${id}: enforced ${field} conflicts with its catalog`);
|
|
145
|
+
applyFormattingLocale(frontmatter, config.frontmatter, source);
|
|
146
|
+
validateFrontmatter(frontmatter, config.frontmatter, file, source);
|
|
147
|
+
frontmatter.tocDepth ??= 2;
|
|
148
|
+
const active = variants.some(item => documentKey(catalogId, item) === identity);
|
|
149
|
+
const classification = loaded.deployment?.context.classificationPublications ?? variants;
|
|
150
|
+
if (frontmatter.access?.policy === 'public' && classification.some(item => documentKey(catalogId, item) === identity))
|
|
151
|
+
validateFeatureClassification(metadata, loaded.deployment?.context, `${file}: ${id}`);
|
|
152
|
+
const granted = matchesFeatures(frontmatter.requires, loaded.deployment?.context, `${file}: ${id}`);
|
|
153
|
+
const marks = pageReaderFilters(frontmatter, readerFilters, file);
|
|
154
|
+
const hasContent = [...plan.docs.values()].some(doc => doc.codeReference?.catalog === catalogId && documentKey(catalogId, doc.variant) === identity);
|
|
155
|
+
if (active && granted && hasContent && frontmatter.access.policy === 'public' && matchesContentFilter(marks, contentFilter)) {
|
|
156
|
+
const key = documentKey(id, variant);
|
|
157
|
+
if (plan.docs.has(key))
|
|
158
|
+
throw new Error(`Duplicate code reference document ${id}`);
|
|
159
|
+
const route = routeFor(frontmatter, variant, config);
|
|
160
|
+
if (plan.routeOwners.has(route.toLowerCase()) && !input.allowRouteCollisions)
|
|
161
|
+
throw new Error(`Code reference route collision ${route}`);
|
|
162
|
+
plan.routeOwners.set(route.toLowerCase(), key);
|
|
163
|
+
plan.docs.set(key, { file, frontmatter, variant, route, markdownRoute: markdownRoute(route, basePath), readerFilters: marks, featureContext: loaded.deployment?.context, body: '', tree: null,
|
|
164
|
+
codeReference: { catalog: catalogId, programmingLanguage: snapshot.programmingLanguage, complete: true, symbol: { id, name: single.title, kind: 'module', visibility: 'public' } } });
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
/** Imported prose is inert Markdown; it never enters MDX or project directive expansion. */
|
|
171
|
+
export function renderCodeReference(reference, doc, documents) {
|
|
172
|
+
const eligible = new Map();
|
|
173
|
+
for (const candidate of documents.values())
|
|
174
|
+
if (candidate.codeReference?.catalog === reference.catalog && !candidate.codeReference.complete && documentKey('scope', candidate.variant) === documentKey('scope', doc.variant))
|
|
175
|
+
eligible.set(candidate.codeReference.symbol.id, candidate);
|
|
176
|
+
const owner = (page) => {
|
|
177
|
+
let parent = page.codeReference.symbol.parent;
|
|
178
|
+
while (parent) {
|
|
179
|
+
const candidate = eligible.get(parent);
|
|
180
|
+
if (!candidate)
|
|
181
|
+
return;
|
|
182
|
+
const kind = candidate.codeReference.symbol.kind;
|
|
183
|
+
if (['module', 'namespace'].includes(kind) || reference.layout === 'classes' && ['class', 'interface', 'enum', 'type'].includes(kind))
|
|
184
|
+
return parent;
|
|
185
|
+
parent = candidate.codeReference.symbol.parent;
|
|
186
|
+
}
|
|
187
|
+
};
|
|
188
|
+
const embedded = [...eligible.values()].filter(page => reference.complete || reference.layout !== undefined && reference.layout !== 'symbols' && owner(page) === reference.symbol.id)
|
|
189
|
+
.sort((a, b) => a.codeReference.symbol.id < b.codeReference.symbol.id ? -1 : 1);
|
|
190
|
+
const blocks = reference.complete ? [] : renderCodeSymbol(reference, doc, eligible, false).children;
|
|
191
|
+
const headings = new Map();
|
|
192
|
+
for (const page of embedded) {
|
|
193
|
+
const entry = heading(2, page.codeReference.symbol.id);
|
|
194
|
+
headings.set(entry, page);
|
|
195
|
+
const content = renderCodeSymbol(page.codeReference, page, eligible, true).children;
|
|
196
|
+
const deepen = (node) => { if (node.type === 'heading')
|
|
197
|
+
node.depth = Math.min(6, node.depth + 1); for (const child of node.children ?? [])
|
|
198
|
+
deepen(child); };
|
|
199
|
+
content.forEach(deepen);
|
|
200
|
+
blocks.push(entry, ...content);
|
|
201
|
+
}
|
|
202
|
+
const tree = root(blocks);
|
|
203
|
+
if (embedded.length) {
|
|
204
|
+
// Use the renderer's actual heading allocation, including duplicate headings.
|
|
205
|
+
const reserved = ['main', 'docs-navigation', 'docs-sections', 'documentation-menu', 'page-outline', 'nav-filter', 'examples-title', 'api-view-select'];
|
|
206
|
+
const rendered = renderHtmlWithHeadings(tree, undefined, reserved).headings;
|
|
207
|
+
const targets = new Map();
|
|
208
|
+
let index = 0;
|
|
209
|
+
const collect = (node) => {
|
|
210
|
+
if (node.type === 'heading' && node.depth >= 2 && node.depth <= 6) {
|
|
211
|
+
const target = headings.get(node);
|
|
212
|
+
if (target)
|
|
213
|
+
targets.set(target.frontmatter.id, rendered[index].id);
|
|
214
|
+
index++;
|
|
215
|
+
}
|
|
216
|
+
for (const child of node.children ?? [])
|
|
217
|
+
collect(child);
|
|
218
|
+
};
|
|
219
|
+
collect(tree);
|
|
220
|
+
const relink = (node) => {
|
|
221
|
+
const anchor = targets.get(node.exportDocumentId);
|
|
222
|
+
if (node.type === 'link' && anchor) {
|
|
223
|
+
node.url = `${doc.route}#${anchor}`;
|
|
224
|
+
node.exportDocumentId = doc.frontmatter.id;
|
|
225
|
+
}
|
|
226
|
+
for (const child of node.children ?? [])
|
|
227
|
+
relink(child);
|
|
228
|
+
};
|
|
229
|
+
relink(tree);
|
|
230
|
+
}
|
|
231
|
+
return tree;
|
|
232
|
+
}
|
|
233
|
+
function renderCodeSymbol(reference, doc, eligible, embedded) {
|
|
234
|
+
const prose = (value = '') => {
|
|
235
|
+
const parsed = parsePlainMarkdown(value);
|
|
236
|
+
const definitions = new Map();
|
|
237
|
+
const collect = (node) => { if (node.type === 'definition')
|
|
238
|
+
definitions.set(node.identifier.toLowerCase(), node); for (const child of node.children ?? [])
|
|
239
|
+
collect(child); };
|
|
240
|
+
parsed.forEach(collect);
|
|
241
|
+
const clean = (node) => {
|
|
242
|
+
if (node.type === 'definition')
|
|
243
|
+
return [];
|
|
244
|
+
if (node.type === 'linkReference') {
|
|
245
|
+
const definition = definitions.get(node.identifier.toLowerCase());
|
|
246
|
+
if (!definition)
|
|
247
|
+
return (node.children ?? []).flatMap(clean);
|
|
248
|
+
node = { type: 'link', url: definition.url, title: definition.title, children: node.children };
|
|
249
|
+
}
|
|
250
|
+
if (node.type === 'image' || node.type === 'imageReference')
|
|
251
|
+
throw new Error(`${doc.file}: code reference prose images need explicit asset support`);
|
|
252
|
+
if (typeof node.url === 'string') {
|
|
253
|
+
if (node.url.startsWith('symbol:')) {
|
|
254
|
+
const target = eligible.get(node.url.slice(7));
|
|
255
|
+
if (!target)
|
|
256
|
+
return (node.children ?? []).flatMap(clean);
|
|
257
|
+
node = { ...node, url: target.route, exportDocumentId: target.frontmatter.id };
|
|
258
|
+
}
|
|
259
|
+
else {
|
|
260
|
+
const url = new URL(node.url, 'https://code-reference.invalid');
|
|
261
|
+
if (!['https:', 'mailto:'].includes(url.protocol) || url.username || url.password || url.origin === 'https://code-reference.invalid')
|
|
262
|
+
throw new Error(`${doc.file}: unsupported code reference prose URL ${node.url}`);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
return [{ ...node, ...(node.children ? { children: node.children.flatMap(clean) } : {}) }];
|
|
266
|
+
};
|
|
267
|
+
return parsed.flatMap(clean);
|
|
268
|
+
};
|
|
269
|
+
const symbol = reference.symbol;
|
|
270
|
+
const inlineCode = (value) => ({ type: 'inlineCode', value });
|
|
271
|
+
const code = (value, signature = false) => ({ type: 'code', lang: reference.programmingLanguage, value,
|
|
272
|
+
...(signature ? { data: { hProperties: { className: [`language-${reference.programmingLanguage}`, 'code-reference-signature'] } } } : {}) });
|
|
273
|
+
const pageLink = (page, label = page.frontmatter.title) => ({ type: 'link', url: page.route, exportDocumentId: page.frontmatter.id, children: [inlineCode(label)] });
|
|
274
|
+
// References are explicit converter data; never guess an ambiguous short name.
|
|
275
|
+
const typeNodes = (value, context = symbol) => {
|
|
276
|
+
const tokens = Object.keys(context.typeReferences ?? {}).sort((a, b) => b.length - a.length);
|
|
277
|
+
if (!tokens.length)
|
|
278
|
+
return [inlineCode(value)];
|
|
279
|
+
const output = [];
|
|
280
|
+
let plain = '';
|
|
281
|
+
for (let offset = 0; offset < value.length;) {
|
|
282
|
+
const match = tokens.find(token => value.startsWith(token, offset) && !/[\p{L}\p{N}_.$]/u.test(value[offset - 1] ?? '') && !/[\p{L}\p{N}_.$]/u.test(value[offset + token.length] ?? ''));
|
|
283
|
+
const page = match ? eligible.get(context.typeReferences[match]) : undefined;
|
|
284
|
+
if (match && page) {
|
|
285
|
+
if (plain)
|
|
286
|
+
output.push(inlineCode(plain));
|
|
287
|
+
plain = '';
|
|
288
|
+
output.push(pageLink(page, match));
|
|
289
|
+
offset += match.length;
|
|
290
|
+
}
|
|
291
|
+
else {
|
|
292
|
+
plain += value[offset];
|
|
293
|
+
offset++;
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
if (plain)
|
|
297
|
+
output.push(inlineCode(plain));
|
|
298
|
+
return output;
|
|
299
|
+
};
|
|
300
|
+
const table = (headers, rows, label) => ({ type: 'table', align: headers.map(() => null),
|
|
301
|
+
data: { referenceTableLabel: label, hProperties: { className: ['code-reference-table'] } }, children: [headers.map(value => [text(value)]), ...rows].map(cells => ({ type: 'tableRow', children: cells.map(children => ({ type: 'tableCell', children })) })) });
|
|
302
|
+
const summary = (value) => {
|
|
303
|
+
const nodes = prose(value);
|
|
304
|
+
const paragraph = nodes.find(node => node.type === 'paragraph');
|
|
305
|
+
return paragraph?.children ?? [text('—')];
|
|
306
|
+
};
|
|
307
|
+
const description = prose(symbol.description);
|
|
308
|
+
// Hide the repeated summary only in the website shell; exports retain it.
|
|
309
|
+
if (description[0]?.type === 'paragraph') {
|
|
310
|
+
const plain = (node) => node.value ?? (node.children ?? []).map(plain).join('');
|
|
311
|
+
if (!embedded && plain(description[0]).trim() === doc.frontmatter.summary.trim())
|
|
312
|
+
description[0] = { ...description[0], data: { hProperties: { className: ['code-reference-summary'] } } };
|
|
313
|
+
}
|
|
314
|
+
const identity = paragraph({ type: 'strong', children: [text(symbol.kind.charAt(0).toUpperCase() + symbol.kind.slice(1))] }, text(' '), inlineCode(symbol.id), ...(reference.sourceUrl ? [text(' · '), { type: 'link', url: reference.sourceUrl, children: [text('View source')] }] : []));
|
|
315
|
+
identity.data = { hProperties: { className: ['code-reference-identity'] } };
|
|
316
|
+
const blocks = [identity, ...(symbol.signatures?.length ? [code(symbol.signatures.join('\n\n'), true)] : []), ...description];
|
|
317
|
+
if (symbol.constructorSignature)
|
|
318
|
+
blocks.push(heading(2, 'Constructor'), code(symbol.constructorSignature, true));
|
|
319
|
+
if (symbol.bases?.length)
|
|
320
|
+
blocks.push(paragraph({ type: 'strong', children: [text('Bases: ')] }, ...symbol.bases.flatMap((base, i) => [...(i ? [text(', ')] : []), ...typeNodes(base)])));
|
|
321
|
+
if (symbol.annotation)
|
|
322
|
+
blocks.push(paragraph({ type: 'strong', children: [text('Type: ')] }, ...typeNodes(symbol.annotation)));
|
|
323
|
+
if (symbol.required !== undefined || symbol.default !== undefined)
|
|
324
|
+
blocks.push(paragraph(...(symbol.required === undefined ? [] : [text(symbol.required ? 'Required' : 'Optional')]), ...(symbol.default === undefined ? [] : [text(symbol.required === undefined ? 'Default: ' : ' · Default: '), inlineCode(symbol.default)])));
|
|
325
|
+
const details = (title, items) => {
|
|
326
|
+
if (!items.length)
|
|
327
|
+
return;
|
|
328
|
+
blocks.push(heading(2, title));
|
|
329
|
+
blocks.push(table(title === 'Parameters' ? ['Parameter', 'Type / default', 'Description'] : ['Exception', 'Description'], items.map(item => {
|
|
330
|
+
const kind = [...(item.type ? typeNodes(item.type) : [text('—')]), ...(item.default === undefined ? [] : [text(' = '), inlineCode(item.default)])];
|
|
331
|
+
return title === 'Parameters' ? [[inlineCode(item.name)], kind, summary(item.description)] : [typeNodes(item.name), summary(item.description)];
|
|
332
|
+
}), title));
|
|
333
|
+
// Preserve richer Markdown without forcing block content into a table cell.
|
|
334
|
+
for (const item of items) {
|
|
335
|
+
const content = prose(item.description);
|
|
336
|
+
if (content.length > 1 || content.some(node => node.type !== 'paragraph'))
|
|
337
|
+
blocks.push(heading(3, item.name), ...content);
|
|
338
|
+
}
|
|
339
|
+
};
|
|
340
|
+
details('Parameters', symbol.parameters ?? []);
|
|
341
|
+
for (const [title, result] of [['Returns', symbol.returns], ['Yields', symbol.yields]])
|
|
342
|
+
if (result) {
|
|
343
|
+
blocks.push(heading(2, title));
|
|
344
|
+
if (result.type)
|
|
345
|
+
blocks.push(paragraph(...typeNodes(result.type)));
|
|
346
|
+
blocks.push(...prose(result.description));
|
|
347
|
+
}
|
|
348
|
+
details('Raises', symbol.raises ?? []);
|
|
349
|
+
if (symbol.examples?.length) {
|
|
350
|
+
blocks.push(heading(2, 'Examples'));
|
|
351
|
+
for (const example of symbol.examples)
|
|
352
|
+
blocks.push(...(example.title ? [heading(3, example.title)] : []), code(example.code));
|
|
353
|
+
}
|
|
354
|
+
const members = [...eligible.values()].filter(item => item.codeReference?.symbol.parent === symbol.id).sort((a, b) => a.frontmatter.title < b.frontmatter.title ? -1 : a.frontmatter.title > b.frontmatter.title ? 1 : 0);
|
|
355
|
+
const fields = members.filter(page => ['attribute', 'property', 'constant'].includes(page.codeReference.symbol.kind));
|
|
356
|
+
const callables = members.filter(page => ['function', 'method', 'constructor'].includes(page.codeReference.symbol.kind));
|
|
357
|
+
const types = members.filter(page => !fields.includes(page) && !callables.includes(page));
|
|
358
|
+
for (const [title, pages] of [['Fields', fields], [['module', 'namespace'].includes(symbol.kind) ? 'Functions' : 'Methods', callables], ['Members', types]])
|
|
359
|
+
if (pages.length) {
|
|
360
|
+
blocks.push(heading(2, title), table(title === 'Fields' ? ['Name', 'Type', 'Required / default', 'Description'] : ['Name', 'Kind', 'Description'], pages.map(page => {
|
|
361
|
+
const member = page.codeReference.symbol;
|
|
362
|
+
const declaration = member.signatures?.[0];
|
|
363
|
+
const annotation = declaration?.startsWith(member.name + ': ') ? declaration.slice(member.name.length + 2) : undefined;
|
|
364
|
+
return title === 'Fields' ? [[pageLink(page)], typeNodes(member.annotation ?? annotation ?? member.kind, member), [...(member.required === undefined ? [] : [text(member.required ? 'Required' : 'Optional')]), ...(member.default === undefined ? [] : [text(member.required === undefined ? '' : ' · '), inlineCode(member.default)]), ...(member.required === undefined && member.default === undefined ? [text('—')] : [])], summary(member.summary ?? member.description)] : [[pageLink(page)], [inlineCode(member.kind)], summary(member.summary ?? member.description)];
|
|
365
|
+
}), title));
|
|
366
|
+
}
|
|
367
|
+
const related = (symbol.seeAlso ?? []).flatMap(id => eligible.has(id) ? [eligible.get(id)] : []);
|
|
368
|
+
if (related.length)
|
|
369
|
+
blocks.push(heading(2, 'See also'), { type: 'list', ordered: false, children: related.map(page => ({ type: 'listItem', children: [paragraph(pageLink(page))] })) });
|
|
370
|
+
return root(blocks);
|
|
371
|
+
}
|
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';
|
|
@@ -16,7 +18,7 @@ import { validateAuthoringGuidance } from './authoring-guidance.js';
|
|
|
16
18
|
import { loadFreshness } from './freshness.js';
|
|
17
19
|
import { resolveAgentDiscovery } from './agent-discovery.js';
|
|
18
20
|
import { iconLicenseFile } from './icons.js';
|
|
19
|
-
import { bundledFontLicenses, bundledFonts, fontsCss, resolveFonts } from './fonts.js';
|
|
21
|
+
import { bundledFontLicenses, bundledFonts, fontsCss, resolveFonts, prepareFontAssets, customFontPreloads } from './fonts.js';
|
|
20
22
|
import { bundledShareFonts, resolveShareImages, shareRoute } from './share-images.js';
|
|
21
23
|
import { buildInfo } from './build-info.js';
|
|
22
24
|
import { validateRelationships } from './cards.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');
|
|
@@ -158,6 +175,7 @@ export async function compile(configFile, destination, provenance = {}, options
|
|
|
158
175
|
for (const face of fonts.faces)
|
|
159
176
|
if (fonts.faces.some(other => other.route === face.route && other.file !== face.file))
|
|
160
177
|
throw new Error(`theme.fonts: two font files share the name ${path.basename(face.file)}; rename one`);
|
|
178
|
+
const projectFontAssets = await prepareFontAssets(fonts);
|
|
161
179
|
const generatedFontsCss = fontsCss(fonts);
|
|
162
180
|
// A temporary preview aid: a floating shape and palette switcher on every page. Remove it before launch.
|
|
163
181
|
// `demo` ships it hidden until a page is opened with ?theme-switcher.
|
|
@@ -313,7 +331,7 @@ export async function compile(configFile, destination, provenance = {}, options
|
|
|
313
331
|
const cardUses = new Map();
|
|
314
332
|
const publishedRelationships = [];
|
|
315
333
|
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));
|
|
334
|
+
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
335
|
const documentRoutes = new Set([...docs.values()].map(doc => doc.route));
|
|
318
336
|
const { apiPublications, focusedContracts, navigationEntries, navigationTagOrder, rendered, snippetUses, apiEmbedUses, operations } = await materializePages({
|
|
319
337
|
variables: isConfigMapping(config.variables) ? config.variables : {}, frontmatterConfig: config.frontmatter,
|
|
@@ -379,8 +397,8 @@ export async function compile(configFile, destination, provenance = {}, options
|
|
|
379
397
|
const resolvedNavigation = renderPublicationPages({
|
|
380
398
|
freshness,
|
|
381
399
|
rendered, docs, relationships, knownOperations, cardUses, publishedRelationships, cookbooks, documentRoutes,
|
|
382
|
-
basePath, siteOrigin, colorMode, config, highlighter, editionLabel, customStyleFile, themePreset, palette,
|
|
383
|
-
themeStyleFile, generatedPaletteCss, generatedBadgeCss, themeSwitcherData, generatedFontsCss, fontsPreload: fonts.preload,
|
|
400
|
+
basePath, siteOrigin, colorMode, config: options.authoringFiles ? { ...config, outputs: { ...config.outputs, html: { ...htmlOptions, pdf: false } } } : config, highlighter, editionLabel, customStyleFile, themePreset, palette,
|
|
401
|
+
themeStyleFile, generatedPaletteCss, generatedBadgeCss, themeSwitcherData, generatedFontsCss, fontsPreload: fonts.preload, customFontPreloads: customFontPreloads(fonts),
|
|
384
402
|
shareFor, faviconRoute, logoRoute, modeToggle, header, footer, search, readerFilters, variants, externalDocuments,
|
|
385
403
|
externalSiteLinks, navigationSpec, navigationEntries, navigationTagOrder
|
|
386
404
|
});
|
|
@@ -442,7 +460,14 @@ export async function compile(configFile, destination, provenance = {}, options
|
|
|
442
460
|
] };
|
|
443
461
|
}
|
|
444
462
|
const apiPublicationReview = await reviewApiPublication(configFile, apiPlan, rendered, apiEmbedUses, focusedContracts, apiSources, sourceForOperation, options.apiReview);
|
|
445
|
-
|
|
463
|
+
if (pdfButton?.mode === 'endpoint') {
|
|
464
|
+
const endpoint = publicPath(pdfButton.endpoint);
|
|
465
|
+
const mcpEndpoint = configField(config.mcp, 'path') ?? '/mcp';
|
|
466
|
+
if (rendered.some(({ doc }) => doc.route === endpoint || endpoint.startsWith(`${doc.route}/`)) || redirects.some(rule => rule.from === endpoint) || endpoint === publicPath(String(mcpEndpoint)))
|
|
467
|
+
throw new Error(`PDF endpoint shadows an existing route: ${endpoint}`);
|
|
468
|
+
}
|
|
469
|
+
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 });
|
|
470
|
+
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
471
|
const llmsDiscovery = resolveLlmsDiscovery(configField(config.outputs, 'llms'));
|
|
447
472
|
const { manifest, headers, sitemaps } = buildPublicationManifest({
|
|
448
473
|
configPublications: config.publications,
|
|
@@ -454,12 +479,13 @@ export async function compile(configFile, destination, provenance = {}, options
|
|
|
454
479
|
});
|
|
455
480
|
const artifactConfig = options.authoringFiles ? { ...config, mcp: false } : config;
|
|
456
481
|
const { artifacts, buildId } = await assembleArtifacts({
|
|
482
|
+
extraArtifacts: sitePdfs.artifacts,
|
|
457
483
|
base, basePath, publicPath, canonical, siteOrigin, manifest, provenanceRecord, rendered, sitemaps,
|
|
458
484
|
privateDependencies: new Set([...(loaded.deployment?.file ? [loaded.deployment.file] : []), ...(loaded.deployment?.assetFiles ?? [])]),
|
|
459
485
|
diagramAssets, heroImages, focusedContracts, styleFiles, themeStyleFile, generatedPaletteCss,
|
|
460
486
|
generatedBadgeCss, search, synonyms, config: artifactConfig, builtInSearchProvider, customReaderModule, analyticsModule, mcpAnalyticsModule,
|
|
461
487
|
previewFiles, assetDirectory, themeSwitcher, themeDirectory, palette, colorMode, generatedFontsCss,
|
|
462
|
-
fontFiles, fonts, shareImages, shareFor, themePreset, shareFontFiles, shareCache, faviconFile,
|
|
488
|
+
fontFiles, projectFontAssets, fonts, shareImages, shareFor, themePreset, shareFontFiles, shareCache, faviconFile,
|
|
463
489
|
faviconRoute, customStyleFile, scriptFiles, componentAssets: loaded.deployment?.context.grants ? componentPlugins.assets.filter(asset => usedComponentAssets.has(asset.route)) : componentPlugins.assets, redirects, apiPublications, api, catalog, headers,
|
|
464
490
|
documentRoutes, readerFilters, apiSources
|
|
465
491
|
});
|
package/dist/config.js
CHANGED
|
@@ -9,7 +9,7 @@ import { parseYaml } from './yaml-value.js';
|
|
|
9
9
|
import { validateSearch } from './search-config.js';
|
|
10
10
|
import { resolveTaxonomy } from './taxonomy.js';
|
|
11
11
|
import { validateCookbooks } from './cookbook-chooser.js';
|
|
12
|
-
const allowed = new Set(['profiles', 'exports', 'schemaVersion', 'site', 'navigation', 'redirects', 'apis', 'relationships', 'variables', 'snippets', 'callouts', 'badges', 'publications', 'outputs', 'templates', 'theme', 'frontmatter', 'metadata', 'audit', 'readerFilters', 'retrieval', 'chat', 'authoring', 'search', 'cookbooks', 'analytics', 'mcp', 'components']);
|
|
12
|
+
const allowed = new Set(['profiles', 'exports', 'schemaVersion', 'site', 'navigation', 'redirects', 'apis', 'codeReferences', 'relationships', 'variables', 'snippets', 'callouts', 'badges', 'publications', 'outputs', 'templates', 'theme', 'frontmatter', 'metadata', 'audit', 'readerFilters', 'retrieval', 'chat', 'authoring', 'search', 'cookbooks', 'analytics', 'mcp', 'components']);
|
|
13
13
|
const object = (v) => !!v && typeof v === 'object' && !Array.isArray(v);
|
|
14
14
|
const treeObject = (v) => object(v) && !('kind' in v && 'items' in v);
|
|
15
15
|
export class ConfigError extends Error {
|
|
@@ -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];
|