@shoalrun/cli 0.1.0-beta.1 → 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.
Files changed (119) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/CODE-REFERENCES.md +137 -0
  3. package/README.md +8 -5
  4. package/adapters/python/requirements.txt +1 -0
  5. package/adapters/python/shoalrun_reference.py +234 -0
  6. package/agent-docs/.well-known/shoalrun/navigation/project-updates-50f26fe22670.txt +4 -2
  7. package/agent-docs/SNAPSHOT.json +5 -5
  8. package/agent-docs/about.md +3 -3
  9. package/agent-docs/api-docs/code-reference-settings.md +160 -0
  10. package/agent-docs/api-docs/code-reference-settings.sections/catalog-settings.md +50 -0
  11. package/agent-docs/api-docs/code-reference-settings.sections/complete-reference-page.md +46 -0
  12. package/agent-docs/api-docs/code-reference-settings.sections/converter-snapshot.md +58 -0
  13. package/agent-docs/api-docs/code-reference-settings.sections/descriptions-type-links-and-source-links.md +40 -0
  14. package/agent-docs/api-docs/code-reference-settings.sections/html-markdown-and-agents.md +38 -0
  15. package/agent-docs/api-docs/code-reference-settings.sections/metadata-and-selection.md +38 -0
  16. package/agent-docs/api-docs/code-reference-settings.sections/publication-sources.md +54 -0
  17. package/agent-docs/api-docs/code-reference-settings.sections/python-adapter-limits.md +38 -0
  18. package/agent-docs/api-docs/code-reference-settings.sections/related-reading.md +32 -0
  19. package/agent-docs/api-docs/code-reference-settings.sections/symbol-fields.md +56 -0
  20. package/agent-docs/api-docs/code-references.md +143 -0
  21. package/agent-docs/author/agent-overview.md +2 -2
  22. package/agent-docs/changelog/code-api-references.md +44 -0
  23. package/agent-docs/changelog.md +9 -1
  24. package/agent-docs/cli.md +11 -1
  25. package/agent-docs/cli.sections/audit-a-project-and-api-sources.md +1 -1
  26. package/agent-docs/cli.sections/author-pages.md +1 -1
  27. package/agent-docs/cli.sections/build-and-serve.md +1 -1
  28. package/agent-docs/cli.sections/inspect-config-and-operations.md +1 -1
  29. package/agent-docs/cli.sections/npm-scripts-in-this-repository.md +1 -1
  30. package/agent-docs/cli.sections/related-reading.md +1 -1
  31. package/agent-docs/cli.sections/search-and-chat.md +1 -1
  32. package/agent-docs/cli.sections/start-a-project.md +1 -1
  33. package/agent-docs/cli.sections/validate-code-reference-snapshots.md +41 -0
  34. package/agent-docs/cli.sections/working-tree-authoring-tools.md +1 -1
  35. package/agent-docs/config/apis.md +5 -1
  36. package/agent-docs/config/examples.md +3 -3
  37. package/agent-docs/config/examples.sections/feature-showcase.md +3 -3
  38. package/agent-docs/config/examples.sections/minimal-site.md +2 -2
  39. package/agent-docs/config/examples.sections/multilingual-help-center.md +2 -2
  40. package/agent-docs/config/examples.sections/parcel-api-documentation.md +2 -2
  41. package/agent-docs/config/examples.sections/related-reading.md +2 -2
  42. package/agent-docs/config/examples.sections/shared-company-preset.md +2 -2
  43. package/agent-docs/config.md +2 -2
  44. package/agent-docs/configuration.md +2 -2
  45. package/agent-docs/configuration.sections/build-several-sites-from-shared-content.md +1 -1
  46. package/agent-docs/configuration.sections/change-inherited-lists-deliberately.md +1 -1
  47. package/agent-docs/configuration.sections/extract-a-section-with-include.md +1 -1
  48. package/agent-docs/configuration.sections/inspect-and-validate-changes.md +1 -1
  49. package/agent-docs/configuration.sections/related-reading.md +1 -1
  50. package/agent-docs/configuration.sections/separate-config-inheritance-from-page-metadata.md +1 -1
  51. package/agent-docs/configuration.sections/share-defaults-through-extends.md +1 -1
  52. package/agent-docs/configuration.sections/start-with-one-working-file.md +2 -2
  53. package/agent-docs/configuration.sections/understand-the-three-stages.md +1 -1
  54. package/agent-docs/configuration.sections/understand-where-paths-point.md +1 -1
  55. package/agent-docs/documents.json +609 -215
  56. package/agent-docs/features.md +3 -2
  57. package/agent-docs/provenance.json +6 -6
  58. package/agent-docs/publish/api-references.md +3 -1
  59. package/agent-docs/publish/api-references.sections/author-the-reference-page.md +3 -1
  60. package/agent-docs/publish/api-references.sections/build-and-verify-the-output.md +3 -1
  61. package/agent-docs/publish/api-references.sections/inspect-the-machine-readable-contract.md +3 -1
  62. package/agent-docs/publish/api-references.sections/next-steps.md +3 -1
  63. package/agent-docs/publish/api-references.sections/preview-a-small-api-reference.md +3 -1
  64. package/agent-docs/publish/api-references.sections/put-the-spec-in-your-project.md +3 -1
  65. package/agent-docs/publish/api-references.sections/register-the-operation.md +3 -1
  66. package/agent-docs/publish/api-references.sections/related-reading.md +3 -1
  67. package/agent-docs/publish/api-references.sections/review-imported-content-before-publishing.md +3 -1
  68. package/agent-docs/publish/api-references.sections/what-readers-get.md +3 -1
  69. package/agent-docs/search-index.json +1 -1
  70. package/agent-docs/start.md +3 -3
  71. package/agent-docs/start.sections/add-the-page-to-navigation.md +2 -2
  72. package/agent-docs/start.sections/build-and-check-both-outputs.md +2 -2
  73. package/agent-docs/start.sections/build-this-site.md +2 -2
  74. package/agent-docs/start.sections/check-writing-style.md +2 -2
  75. package/agent-docs/start.sections/create-a-page.md +2 -2
  76. package/agent-docs/start.sections/make-it-yours.md +2 -2
  77. package/agent-docs/start.sections/migrating-from-github-packages.md +2 -2
  78. package/agent-docs/start.sections/prerequisites.md +2 -2
  79. package/agent-docs/start.sections/related-reading.md +2 -2
  80. package/agent-docs/start.sections/start-your-own-project.md +3 -3
  81. package/agent-docs/status.md +6 -4
  82. package/agent-docs/styling/themes.md +7 -3
  83. package/agent-docs/what-is-shoalrun.md +2 -2
  84. package/dist/artifact-bundle.d.ts +1 -0
  85. package/dist/artifact-bundle.js +11 -3
  86. package/dist/cli-discovery.d.ts +1 -1
  87. package/dist/cli-discovery.js +1 -0
  88. package/dist/cli.js +7 -0
  89. package/dist/code-reference-model.d.ts +85 -0
  90. package/dist/code-reference-model.js +73 -0
  91. package/dist/code-references.d.ts +23 -0
  92. package/dist/code-references.js +371 -0
  93. package/dist/compiler-config.d.ts +1 -0
  94. package/dist/compiler.js +4 -3
  95. package/dist/config.js +1 -1
  96. package/dist/content-source.d.ts +1 -0
  97. package/dist/content-workspace.d.ts +1 -0
  98. package/dist/content-workspace.js +2 -0
  99. package/dist/fonts.d.ts +9 -0
  100. package/dist/fonts.js +58 -3
  101. package/dist/page-materialization.js +3 -2
  102. package/dist/page-renderer.d.ts +1 -0
  103. package/dist/page-renderer.js +3 -1
  104. package/dist/publication-render.d.ts +2 -1
  105. package/dist/publication-render.js +2 -2
  106. package/dist/representation-tree.js +4 -0
  107. package/examples/code-references/content/start.md +53 -0
  108. package/examples/code-references/docs.config.yaml +262 -0
  109. package/examples/code-references/python/orchard/__init__.py +11 -0
  110. package/examples/code-references/python/orchard/client.py +212 -0
  111. package/examples/code-references/python/orchard/errors.py +31 -0
  112. package/examples/code-references/python/orchard/models.py +69 -0
  113. package/examples/code-references/python/orchard/pagination.py +39 -0
  114. package/examples/code-references/reference.json +1244 -0
  115. package/package.json +8 -1
  116. package/schemas/code-reference.schema.json +306 -0
  117. package/skills/shoalrun-author/SKILL.md +4 -0
  118. package/skills/shoalrun-author/references/code-api-references.md +15 -0
  119. package/styles/site.css +61 -2
@@ -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
+ }
@@ -13,6 +13,7 @@ export interface ResolvedCompilerConfig extends ConfigMapping {
13
13
  navigation?: unknown;
14
14
  redirects?: unknown;
15
15
  apis?: unknown;
16
+ codeReferences?: unknown;
16
17
  relationships?: unknown;
17
18
  variables?: unknown;
18
19
  snippets?: unknown;
package/dist/compiler.js CHANGED
@@ -18,7 +18,7 @@ import { validateAuthoringGuidance } from './authoring-guidance.js';
18
18
  import { loadFreshness } from './freshness.js';
19
19
  import { resolveAgentDiscovery } from './agent-discovery.js';
20
20
  import { iconLicenseFile } from './icons.js';
21
- import { bundledFontLicenses, bundledFonts, fontsCss, resolveFonts } from './fonts.js';
21
+ import { bundledFontLicenses, bundledFonts, fontsCss, resolveFonts, prepareFontAssets, customFontPreloads } from './fonts.js';
22
22
  import { bundledShareFonts, resolveShareImages, shareRoute } from './share-images.js';
23
23
  import { buildInfo } from './build-info.js';
24
24
  import { validateRelationships } from './cards.js';
@@ -175,6 +175,7 @@ async function compileProject(configFile, destination, provenance, options, ownH
175
175
  for (const face of fonts.faces)
176
176
  if (fonts.faces.some(other => other.route === face.route && other.file !== face.file))
177
177
  throw new Error(`theme.fonts: two font files share the name ${path.basename(face.file)}; rename one`);
178
+ const projectFontAssets = await prepareFontAssets(fonts);
178
179
  const generatedFontsCss = fontsCss(fonts);
179
180
  // A temporary preview aid: a floating shape and palette switcher on every page. Remove it before launch.
180
181
  // `demo` ships it hidden until a page is opened with ?theme-switcher.
@@ -397,7 +398,7 @@ async function compileProject(configFile, destination, provenance, options, ownH
397
398
  freshness,
398
399
  rendered, docs, relationships, knownOperations, cardUses, publishedRelationships, cookbooks, documentRoutes,
399
400
  basePath, siteOrigin, colorMode, config: options.authoringFiles ? { ...config, outputs: { ...config.outputs, html: { ...htmlOptions, pdf: false } } } : config, highlighter, editionLabel, customStyleFile, themePreset, palette,
400
- themeStyleFile, generatedPaletteCss, generatedBadgeCss, themeSwitcherData, generatedFontsCss, fontsPreload: fonts.preload,
401
+ themeStyleFile, generatedPaletteCss, generatedBadgeCss, themeSwitcherData, generatedFontsCss, fontsPreload: fonts.preload, customFontPreloads: customFontPreloads(fonts),
401
402
  shareFor, faviconRoute, logoRoute, modeToggle, header, footer, search, readerFilters, variants, externalDocuments,
402
403
  externalSiteLinks, navigationSpec, navigationEntries, navigationTagOrder
403
404
  });
@@ -484,7 +485,7 @@ async function compileProject(configFile, destination, provenance, options, ownH
484
485
  diagramAssets, heroImages, focusedContracts, styleFiles, themeStyleFile, generatedPaletteCss,
485
486
  generatedBadgeCss, search, synonyms, config: artifactConfig, builtInSearchProvider, customReaderModule, analyticsModule, mcpAnalyticsModule,
486
487
  previewFiles, assetDirectory, themeSwitcher, themeDirectory, palette, colorMode, generatedFontsCss,
487
- fontFiles, fonts, shareImages, shareFor, themePreset, shareFontFiles, shareCache, faviconFile,
488
+ fontFiles, projectFontAssets, fonts, shareImages, shareFor, themePreset, shareFontFiles, shareCache, faviconFile,
488
489
  faviconRoute, customStyleFile, scriptFiles, componentAssets: loaded.deployment?.context.grants ? componentPlugins.assets.filter(asset => usedComponentAssets.has(asset.route)) : componentPlugins.assets, redirects, apiPublications, api, catalog, headers,
489
490
  documentRoutes, readerFilters, apiSources
490
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 {
@@ -57,6 +57,7 @@ export type Frontmatter = {
57
57
  [key: string]: unknown;
58
58
  };
59
59
  export type Document = {
60
+ codeReference?: import('./code-reference-model.js').CodeReference;
60
61
  featureContext?: import('./deployment-profile.js').FeatureContext;
61
62
  change?: ResolvedChange;
62
63
  subjectDeprecation?: ResolvedSubjectDeprecation;
@@ -12,6 +12,7 @@ export declare function prepareContentWorkspace({ base, basePath, config, loaded
12
12
  snippets?: unknown;
13
13
  variables?: unknown;
14
14
  apis?: unknown;
15
+ codeReferences?: unknown;
15
16
  };
16
17
  loaded: Awaited<ReturnType<typeof loadConfig>>;
17
18
  variants: Variant[];
@@ -4,6 +4,7 @@ import path from 'node:path';
4
4
  import { readFile } from 'node:fs/promises';
5
5
  import { parse } from './content.js';
6
6
  import { planAuthoredDocuments } from './publication-plan.js';
7
+ import { materializeCodeReferences } from './code-references.js';
7
8
  import { materializeApiReferences } from './api-publication-plan.js';
8
9
  import { explainSegments } from './config.js';
9
10
  import { validateSnippetConfig } from './snippets.js';
@@ -13,6 +14,7 @@ export async function prepareContentWorkspace({ base, basePath, config, loaded,
13
14
  const { docs, knownDocumentIds, knownDocumentTypes, authoredOperationIds, routeOwners, contentDir } = await planAuthoredDocuments({
14
15
  base, basePath, config, variants, language, readerFilters, contentFilter, featureContext: loaded.deployment?.context, metadataProvider, metadataDependencies, sourceOverrides, authoringFiles, allowRouteCollisions
15
16
  });
17
+ await materializeCodeReferences({ config, loaded, plan: { docs, knownDocumentIds, knownDocumentTypes, authoredOperationIds, routeOwners, contentDir }, base, basePath, variants, language, readerFilters, contentFilter, dependencies: metadataDependencies, allowRouteCollisions });
16
18
  if (config.snippets !== undefined && (!config.snippets || typeof config.snippets !== 'object' || Array.isArray(config.snippets)))
17
19
  throw new Error('snippets must be a mapping');
18
20
  const snippets = new Map();
package/dist/fonts.d.ts CHANGED
@@ -6,12 +6,18 @@ export type FontFace = {
6
6
  style: 'normal' | 'italic';
7
7
  weight: string;
8
8
  };
9
+ export type FontPreload = {
10
+ href: string;
11
+ type: string;
12
+ };
9
13
  export type SiteFonts = {
10
14
  bundled: boolean;
11
15
  faces: FontFace[];
12
16
  tokens: Partial<Record<FontRole, string>>;
13
17
  preload?: string;
18
+ preloadFaces?: FontFace[];
14
19
  };
20
+ export declare const fontMediaType: (file: string) => string | undefined;
15
21
  export declare const bundledFonts: readonly [{
16
22
  readonly family: "Inter";
17
23
  readonly file: "inter.woff2";
@@ -51,4 +57,7 @@ export declare const bundledFonts: readonly [{
51
57
  }];
52
58
  export declare const bundledFontLicenses: string[];
53
59
  export declare function resolveFonts(value: unknown, preset: string, declaringFile: string): SiteFonts;
60
+ /** Pin project font URLs to the same bytes that will be emitted by this build. */
61
+ export declare function prepareFontAssets(fonts: SiteFonts): Promise<Map<string, Buffer>>;
62
+ export declare function customFontPreloads(fonts: SiteFonts): FontPreload[];
54
63
  export declare function fontsCss(fonts: SiteFonts): string | undefined;
package/dist/fonts.js CHANGED
@@ -1,4 +1,7 @@
1
1
  import path from 'node:path';
2
+ import { readFile } from 'node:fs/promises';
3
+ import { createHash } from 'node:crypto';
4
+ export const fontMediaType = (file) => ({ '.woff2': 'font/woff2', '.woff': 'font/woff', '.ttf': 'font/ttf', '.otf': 'font/otf' }[path.extname(file).toLowerCase()]);
2
5
  // Latin variable fonts from Fontsource, all under the SIL Open Font License. Readers download only the faces a page uses.
3
6
  export const bundledFonts = [
4
7
  { family: 'Inter', file: 'inter.woff2', style: 'normal' },
@@ -22,7 +25,7 @@ const fallbackFaces = [
22
25
  { family: 'Jost', src: localSans, size: '95.84%', ascent: '111.65%', descent: '39.13%' }
23
26
  ];
24
27
  export const bundledFontLicenses = ['inter', 'figtree', 'source-serif-4', 'jetbrains-mono', 'jost'].map(name => `${name}-LICENSE.txt`);
25
- // The body font each preset preloads so the first paint uses it.
28
+ // Begin fetching the preset's body font before the browser discovers its CSS rule.
26
29
  const presetBodyFont = { shoal: 'inter.woff2', reef: 'figtree.woff2', ledger: 'source-serif-4.woff2', vice: 'figtree.woff2' };
27
30
  const fallbacks = {
28
31
  body: 'ui-sans-serif, system-ui, sans-serif',
@@ -45,6 +48,8 @@ export function resolveFonts(value, preset, declaringFile) {
45
48
  if (unknown.length)
46
49
  throw new Error(`theme.fonts accepts only body, headings, and code; remove ${unknown.join(', ')}`);
47
50
  const faces = [];
51
+ const preloadFaces = [];
52
+ const families = {};
48
53
  const tokens = {};
49
54
  for (const role of roles) {
50
55
  const entry = value[role];
@@ -56,6 +61,7 @@ export function resolveFonts(value, preset, declaringFile) {
56
61
  if (typeof spec.family !== 'string' || !familyName.test(spec.family))
57
62
  throw new Error(`theme.fonts.${role}.family must be a font name using letters, numbers, spaces, dots, dashes, or underscores`);
58
63
  const files = spec.files ?? [];
64
+ families[role] = spec.family;
59
65
  if (!Array.isArray(files))
60
66
  throw new Error(`theme.fonts.${role}.files must be a list`);
61
67
  for (const [index, item] of files.entries()) {
@@ -74,11 +80,60 @@ export function resolveFonts(value, preset, declaringFile) {
74
80
  const file = path.resolve(path.dirname(declaringFile), face.file);
75
81
  if (!/^[\w.-]+$/.test(path.basename(file)))
76
82
  throw new Error(`${where} file name may use only letters, numbers, dots, dashes, and underscores`);
77
- faces.push({ family: spec.family, file, route: `/assets/fonts/project/${path.basename(file)}`, style, weight });
83
+ const registration = { family: spec.family, file, route: `/assets/fonts/project/${path.basename(file)}`, style, weight };
84
+ faces.push(registration);
78
85
  }
79
86
  tokens[role] = `"${spec.family}", ${fallbacks[role]}`;
80
87
  }
81
- return { bundled: true, faces, tokens, preload: tokens.body ? undefined : presetBodyFont[preset] };
88
+ // CSS resolves style before weight and prefers the last equivalent registration.
89
+ const registrations = [...bundledFonts.map(font => ({ family: font.family, file: font.file, route: `/assets/fonts/${font.file}`, style: font.style, weight: '100 900' })), ...faces];
90
+ for (const role of ['body', 'headings']) {
91
+ if (!families[role])
92
+ continue;
93
+ const target = role === 'body' ? 400 : preset === 'vice' ? 800 : 700;
94
+ const style = role === 'headings' && preset === 'vice' ? 'italic' : 'normal';
95
+ const family = registrations.filter(face => face.family === families[role]);
96
+ const exactStyle = family.filter(face => face.style === style);
97
+ const rank = (face) => {
98
+ const [min, max = min] = face.weight.split(' ').map(Number);
99
+ if (min <= target && max >= target)
100
+ return [0, 0];
101
+ if (target >= 400 && target <= 500) {
102
+ if (min > target && min <= 500)
103
+ return [1, min - target];
104
+ return max < target ? [2, target - max] : [3, min - target];
105
+ }
106
+ if (target < 400)
107
+ return max < target ? [1, target - max] : [2, min - target];
108
+ return min > target ? [1, min - target] : [2, target - max];
109
+ };
110
+ const candidates = (exactStyle.length ? exactStyle : family).map((face, index) => ({ face, index, rank: rank(face) }));
111
+ candidates.sort((a, b) => a.rank[0] - b.rank[0] || a.rank[1] - b.rank[1] || b.index - a.index);
112
+ if (candidates[0])
113
+ preloadFaces.push(candidates[0].face);
114
+ }
115
+ return { bundled: true, faces, tokens, preload: tokens.body ? undefined : presetBodyFont[preset], preloadFaces };
116
+ }
117
+ /** Pin project font URLs to the same bytes that will be emitted by this build. */
118
+ export async function prepareFontAssets(fonts) {
119
+ const files = new Map();
120
+ const assets = new Map();
121
+ for (const face of fonts.faces) {
122
+ let bytes = files.get(face.file);
123
+ if (!bytes) {
124
+ bytes = await readFile(face.file);
125
+ files.set(face.file, bytes);
126
+ }
127
+ const extension = path.extname(face.file).toLowerCase();
128
+ const name = path.basename(face.file, path.extname(face.file));
129
+ const hash = createHash('sha256').update(bytes).digest('hex');
130
+ face.route = `/assets/fonts/project/${name}-${hash}${extension}`;
131
+ assets.set(face.route, bytes);
132
+ }
133
+ return assets;
134
+ }
135
+ export function customFontPreloads(fonts) {
136
+ return [...new Map((fonts.preloadFaces ?? []).map(face => [face.route, { href: face.route, type: fontMediaType(face.file) }])).values()];
82
137
  }
83
138
  const face = (family, url, style, weight, format) => `@font-face{font-family:"${family}";src:url("${url}") format("${format}");font-style:${style};font-weight:${weight};font-display:swap}`;
84
139
  export function fontsCss(fonts) {
@@ -1,3 +1,4 @@
1
+ import { renderCodeReference } from './code-references.js';
1
2
  import { documentKey, publicationKey } from './publication-identity.js';
2
3
  import { createHash } from 'node:crypto';
3
4
  import path from 'node:path';
@@ -28,8 +29,8 @@ export async function materializePages({ variables, frontmatterConfig, publicati
28
29
  for (const other of linkDocuments.values())
29
30
  if (keyFor(other.frontmatter.id, doc.variant) === keyFor(other.frontmatter.id, other.variant))
30
31
  local.set(other.frontmatter.id, other);
31
- const parsed = parse.parse(doc.body);
32
- const tree = await resolveApiEmbedTree(transformMdx(parsed, doc, local, snippets, variables, snippetUses, calloutTypes, base, readerFilters, frontmatterConfig, publications, badgeVariants, componentPlugins, linkDocuments), api, operationId => sourceForOperation(operationId, doc.variant), apiId => {
32
+ const parsed = doc.codeReference ? null : parse.parse(doc.body);
33
+ const tree = doc.codeReference ? renderCodeReference(doc.codeReference, doc, linkDocuments) : await resolveApiEmbedTree(transformMdx(parsed, doc, local, snippets, variables, snippetUses, calloutTypes, base, readerFilters, frontmatterConfig, publications, badgeVariants, componentPlugins, linkDocuments), api, operationId => sourceForOperation(operationId, doc.variant), apiId => {
33
34
  if (doc.featureContext?.grants)
34
35
  throw new Error('Standalone API schema embeds require an explicitly scoped source and are not supported in profiled builds; select an operation instead');
35
36
  const definition = apiPlan.apiDefinitions[apiId];
@@ -36,6 +36,7 @@ export type PageShell = {
36
36
  themeSwitcher?: string;
37
37
  fontsCss?: boolean;
38
38
  preloadFont?: string;
39
+ preloadFonts?: import('./fonts.js').FontPreload[];
39
40
  masthead?: string;
40
41
  share?: {
41
42
  route: string;