@shoalrun/cli 0.1.0-alpha.11 → 0.1.0-alpha.13
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 +52 -0
- package/README.md +59 -17
- package/agent-docs/.well-known/shoalrun/navigation/examples-tab-50f26fe22670.txt +22 -0
- package/agent-docs/.well-known/shoalrun/navigation/project-updates-50f26fe22670.txt +33 -3
- package/agent-docs/.well-known/shoalrun/navigation/reference-tab-50f26fe22670.txt +22 -0
- package/agent-docs/SNAPSHOT.json +5 -5
- package/agent-docs/api-docs/focused-contracts.md +2 -2
- package/agent-docs/api-docs/generate.md +6 -6
- package/agent-docs/api-docs/generate.sections/add-useful-examples.md +2 -2
- package/agent-docs/api-docs/generate.sections/choose-generated-references-or-mdx.md +5 -5
- package/agent-docs/api-docs/generate.sections/describe-filters-that-do-not-use-their-openapi-parameter-name.md +2 -2
- package/agent-docs/api-docs/generate.sections/generate-many-pages-selectively.md +2 -2
- package/agent-docs/api-docs/generate.sections/link-guides-to-api-references.md +2 -2
- package/agent-docs/api-docs/generate.sections/map-extension-values-to-shoalrun-status.md +2 -2
- package/agent-docs/api-docs/generate.sections/override-generated-reference-order-in-navigation.md +3 -3
- package/agent-docs/api-docs/generate.sections/related-reading.md +2 -2
- package/agent-docs/api-docs/generate.sections/use-a-template-for-editorial-context.md +2 -2
- package/agent-docs/api-docs/support.md +14 -13
- package/agent-docs/author/add-page.md +8 -6
- package/agent-docs/author/agent-overview.md +1 -1
- package/agent-docs/author/agent-workflow.md +1 -1
- package/agent-docs/author/analytics.md +1 -1
- package/agent-docs/author/analytics.sections/adapter-exports-and-consent.md +1 -1
- package/agent-docs/author/analytics.sections/built-in-events.md +1 -1
- package/agent-docs/author/analytics.sections/configure-an-adapter.md +1 -1
- package/agent-docs/author/analytics.sections/custom-interactions.md +1 -1
- package/agent-docs/author/analytics.sections/event-format.md +1 -1
- package/agent-docs/author/analytics.sections/html-markdown-and-agents.md +1 -1
- package/agent-docs/author/analytics.sections/inspect-events-locally.md +1 -1
- package/agent-docs/author/analytics.sections/map-events-to-your-service.md +1 -1
- package/agent-docs/author/analytics.sections/posthog-example.md +1 -1
- package/agent-docs/author/analytics.sections/related-reading.md +1 -1
- package/agent-docs/author/authoring-agent.md +1 -1
- package/agent-docs/author/coding-agent.md +1 -1
- package/agent-docs/author/components/action-link.md +9 -3
- package/agent-docs/author/components/api-embeds.md +11 -3
- package/agent-docs/author/components/api-embeds.sections/apiexample.md +7 -3
- package/agent-docs/author/components/api-embeds.sections/html-markdown-and-agents.md +3 -3
- package/agent-docs/author/components/api-embeds.sections/related-reading.md +3 -3
- package/agent-docs/author/components/badge.md +8 -4
- package/agent-docs/author/components/breadcrumbs.md +1 -1
- package/agent-docs/author/components/callout.md +8 -4
- package/agent-docs/author/components/cards.md +2 -2
- package/agent-docs/author/components/cards.sections/add-a-lucide-icon.md +2 -2
- package/agent-docs/author/components/cards.sections/build-a-collection.md +2 -2
- package/agent-docs/author/components/cards.sections/curate-a-reading-list.md +2 -2
- package/agent-docs/author/components/cards.sections/html-markdown-and-agents.md +2 -2
- package/agent-docs/author/components/cards.sections/link-to-one-document.md +2 -2
- package/agent-docs/author/components/cards.sections/override-card-text.md +2 -2
- package/agent-docs/author/components/cards.sections/related-reading.md +2 -2
- package/agent-docs/author/components/code-blocks.md +8 -4
- package/agent-docs/author/components/collapsible.md +7 -3
- package/agent-docs/author/components/cookbook-chooser.md +7 -3
- package/agent-docs/author/components/custom/react.md +1 -1
- package/agent-docs/author/components/custom/react.sections/html-markdown-and-agents.md +1 -1
- package/agent-docs/author/components/custom/react.sections/interactive-react-example.md +1 -1
- package/agent-docs/author/components/custom/react.sections/registration.md +1 -1
- package/agent-docs/author/components/custom/react.sections/related-reading.md +1 -1
- package/agent-docs/author/components/custom/react.sections/share-the-model-with-the-browser.md +1 -1
- package/agent-docs/author/components/custom/react.sections/where-the-content-lives.md +1 -1
- package/agent-docs/author/components/custom.md +1 -1
- package/agent-docs/author/components/definition-list.md +5 -3
- package/agent-docs/author/components/diagrams/d2-gallery.md +2 -2
- package/agent-docs/author/components/diagrams/d2-gallery.sections/basic-shapes.md +2 -2
- package/agent-docs/author/components/diagrams/d2-gallery.sections/flows-and-structures.md +2 -2
- package/agent-docs/author/components/diagrams/d2-gallery.sections/html-markdown-and-agents.md +2 -2
- package/agent-docs/author/components/diagrams/d2-gallery.sections/layout-engines.md +2 -2
- package/agent-docs/author/components/diagrams/d2-gallery.sections/related-reading.md +2 -2
- package/agent-docs/author/components/diagrams/d2-gallery.sections/sequences-and-grids.md +2 -2
- package/agent-docs/author/components/diagrams/d2-gallery.sections/styles-and-code.md +2 -2
- package/agent-docs/author/components/diagrams/pele-gallery.md +2 -2
- package/agent-docs/author/components/diagrams/pele-gallery.sections/charts-and-planning.md +2 -2
- package/agent-docs/author/components/diagrams/pele-gallery.sections/flowchart-shapes.md +2 -2
- package/agent-docs/author/components/diagrams/pele-gallery.sections/flows-and-behavior.md +2 -2
- package/agent-docs/author/components/diagrams/pele-gallery.sections/html-markdown-and-agents.md +2 -2
- package/agent-docs/author/components/diagrams/pele-gallery.sections/related-reading.md +2 -2
- package/agent-docs/author/components/diagrams/pele-gallery.sections/specialized-diagrams.md +2 -2
- package/agent-docs/author/components/diagrams/pele-gallery.sections/systems-and-structures.md +2 -2
- package/agent-docs/author/components/diagrams.md +13 -5
- package/agent-docs/author/components/doc-link.md +7 -3
- package/agent-docs/author/components/fields.md +6 -4
- package/agent-docs/author/components/file-tree.md +7 -3
- package/agent-docs/author/components/images.md +5 -3
- package/agent-docs/author/components/info-hint.md +7 -3
- package/agent-docs/author/components/kbd.md +5 -3
- package/agent-docs/author/components/math.md +7 -3
- package/agent-docs/author/components/only.md +9 -5
- package/agent-docs/author/components/progress.md +7 -3
- package/agent-docs/author/components/related-reading.md +7 -3
- package/agent-docs/author/components/steps.md +9 -3
- package/agent-docs/author/components/tables.md +7 -3
- package/agent-docs/author/components/tabs.md +7 -3
- package/agent-docs/author/components/timeline.md +10 -4
- package/agent-docs/author/components/tooltips.md +7 -3
- package/agent-docs/author/components/walkthroughs.md +5 -3
- package/agent-docs/author/components/walkthroughs.sections/attributes.md +3 -3
- package/agent-docs/author/components/walkthroughs.sections/build-up-code.md +3 -3
- package/agent-docs/author/components/walkthroughs.sections/choose-a-layout.md +3 -3
- package/agent-docs/author/components/walkthroughs.sections/explain-one-file.md +3 -3
- package/agent-docs/author/components/walkthroughs.sections/html-markdown-and-agents.md +3 -3
- package/agent-docs/author/components/walkthroughs.sections/related-reading.md +3 -3
- package/agent-docs/author/components/walkthroughs.sections/syntax.md +51 -0
- package/agent-docs/author/components/walkthroughs.sections/take-code-from-a-file.md +3 -22
- package/agent-docs/author/components.md +19 -4
- package/agent-docs/author/css.md +13 -10
- package/agent-docs/author/css.sections/borders-fields-and-navigation-tokens.md +3 -3
- package/agent-docs/author/css.sections/core-color-tokens.md +3 -3
- package/agent-docs/author/css.sections/html-markdown-and-agents.md +54 -0
- package/agent-docs/author/css.sections/page-attributes.md +9 -9
- package/agent-docs/author/css.sections/page-transitions.md +3 -3
- package/agent-docs/author/css.sections/responsive-behavior.md +3 -3
- package/agent-docs/author/css.sections/status-code-and-callout-tokens.md +3 -3
- package/agent-docs/author/css.sections/typography-shape-and-width-tokens.md +3 -3
- package/agent-docs/author/custom-metadata.md +2 -2
- package/agent-docs/author/custom-metadata.sections/add-a-custom-page-field.md +2 -2
- package/agent-docs/author/custom-metadata.sections/choose-text-for-each-surface.md +2 -2
- package/agent-docs/author/custom-metadata.sections/preview-changes-to-metadata-providers.md +2 -2
- package/agent-docs/author/custom-metadata.sections/read-metadata-from-a-project-module.md +2 -2
- package/agent-docs/author/custom-metadata.sections/related-reading.md +2 -2
- package/agent-docs/author/design-templates.md +1 -1
- package/agent-docs/author/design-writing-guides.md +1 -1
- package/agent-docs/author/frontmatter-reference.md +34 -27
- package/agent-docs/author/frontmatter-reference.sections/choose-a-publication-and-visibility.md +5 -3
- package/agent-docs/author/frontmatter-reference.sections/control-output-details.md +5 -3
- package/agent-docs/author/frontmatter-reference.sections/global-defaults-and-enforced-values.md +5 -3
- package/agent-docs/author/frontmatter-reference.sections/html-markdown-and-agents.md +33 -0
- package/agent-docs/author/frontmatter-reference.sections/identify-and-route-a-page.md +5 -3
- package/agent-docs/author/frontmatter-reference.sections/point-agents-to-it.md +5 -3
- package/agent-docs/author/frontmatter-reference.sections/project-defined-fields.md +5 -3
- package/agent-docs/author/frontmatter-reference.sections/related-reading.md +5 -3
- package/agent-docs/author/frontmatter-reference.sections/use-the-local-files-while-authoring.md +5 -3
- package/agent-docs/author/frontmatter-reference.sections/when-the-files-update.md +5 -3
- package/agent-docs/author/frontmatter.md +7 -4
- package/agent-docs/author/frontmatter.sections/audience-badges.md +3 -3
- package/agent-docs/author/frontmatter.sections/give-deprecation-guidance.md +3 -3
- package/agent-docs/author/frontmatter.sections/global-defaults-and-enforced-values.md +3 -3
- package/agent-docs/author/frontmatter.sections/optional-native-fields.md +7 -4
- package/agent-docs/author/frontmatter.sections/publication-fields.md +3 -3
- package/agent-docs/author/frontmatter.sections/publishing-policy.md +3 -3
- package/agent-docs/author/frontmatter.sections/related-metadata-tasks.md +3 -3
- package/agent-docs/author/frontmatter.sections/related-reading.md +3 -3
- package/agent-docs/author/frontmatter.sections/required-after-inheritance.md +3 -3
- package/agent-docs/author/frontmatter.sections/seo-fields.md +3 -3
- package/agent-docs/author/frontmatter.sections/validation-rules.md +3 -3
- package/agent-docs/author/markdown-metadata.md +17 -9
- package/agent-docs/author/navigation.md +13 -2
- package/agent-docs/author/navigation.sections/arrange-authored-pages.md +2 -2
- package/agent-docs/author/navigation.sections/fill-an-api-tag-group.md +2 -2
- package/agent-docs/author/navigation.sections/hide-group-page-counts.md +13 -2
- package/agent-docs/author/navigation.sections/html-markdown-and-agents.md +2 -2
- package/agent-docs/author/navigation.sections/local-api-preview.md +2 -2
- package/agent-docs/author/navigation.sections/mix-guides-and-api-references.md +2 -2
- package/agent-docs/author/navigation.sections/optionally-split-a-large-menu-into-files.md +2 -2
- package/agent-docs/author/navigation.sections/reach-navigation-while-reading.md +2 -2
- package/agent-docs/author/navigation.sections/related-reading.md +2 -2
- package/agent-docs/author/navigation.sections/split-navigation-into-tabs.md +2 -2
- package/agent-docs/author/navigation.sections/subject-lifecycle-labels.md +2 -2
- package/agent-docs/author/recipe-drafts.md +16 -16
- package/agent-docs/author/recipe-drafts.sections/1-write-one-reusable-step.md +5 -5
- package/agent-docs/author/recipe-drafts.sections/2-list-the-steps-for-one-complete-goal.md +3 -3
- package/agent-docs/author/recipe-drafts.sections/3-generate-a-draft-and-review-it.md +4 -4
- package/agent-docs/author/recipe-drafts.sections/4-maintain-shoalrun-s-published-recipe-snapshots.md +3 -3
- package/agent-docs/author/recipe-drafts.sections/related-reading.md +1 -1
- package/agent-docs/author/recipe-drafts.sections/start-an-ingredient-from-an-api-example.md +5 -5
- package/agent-docs/author/recipes.md +4 -4
- package/agent-docs/author/recipes.sections/how-agents-use-this.md +1 -1
- package/agent-docs/author/recipes.sections/one-example-from-shoalrun.md +4 -4
- package/agent-docs/author/recipes.sections/related-reading.md +1 -1
- package/agent-docs/author/recipes.sections/try-a-complete-workflow.md +1 -1
- package/agent-docs/author/recipes.sections/use-recipes-for-task-oriented-how-tos.md +1 -1
- package/agent-docs/author/recipes.sections/what-changing-an-ingredient-updates.md +1 -1
- package/agent-docs/author/recipes.sections/what-each-file-does.md +1 -1
- package/agent-docs/author/recipes.sections/what-happens-and-when.md +1 -1
- package/agent-docs/author/related-reading.md +5 -1
- package/agent-docs/author/related-reading.sections/automatic-footers.md +1 -1
- package/agent-docs/author/related-reading.sections/declare-relationships.md +1 -1
- package/agent-docs/author/related-reading.sections/display-related-documents.md +1 -1
- package/agent-docs/author/related-reading.sections/html-markdown-and-agents.md +28 -0
- package/agent-docs/author/related-reading.sections/inspect-output-and-changes.md +1 -1
- package/agent-docs/author/related-reading.sections/reading-paths-in-section-markdown.md +1 -1
- package/agent-docs/author/related-reading.sections/related-reading.md +1 -1
- package/agent-docs/author/related-reading.sections/where-relationship-explanations-come-from.md +1 -1
- package/agent-docs/author/search.md +1 -1
- package/agent-docs/author/search.sections/choose-a-provider.md +1 -1
- package/agent-docs/author/search.sections/cli-and-mcp-ranking-details.md +1 -1
- package/agent-docs/author/search.sections/hosted-semantic-search-limits.md +1 -1
- package/agent-docs/author/search.sections/html-markdown-and-agents.md +1 -1
- package/agent-docs/author/search.sections/lexical-search.md +1 -1
- package/agent-docs/author/search.sections/local-semantic-preview.md +1 -1
- package/agent-docs/author/search.sections/read-a-search-result-with-an-agent.md +1 -1
- package/agent-docs/author/search.sections/related-reading.md +1 -1
- package/agent-docs/author/search.sections/reuse-the-lexical-index.md +1 -1
- package/agent-docs/author/search.sections/turn-search-off.md +1 -1
- package/agent-docs/author/search.sections/use-the-search-box.md +1 -1
- package/agent-docs/author/search.sections/write-a-custom-provider.md +1 -1
- package/agent-docs/author/snippets.md +5 -1
- package/agent-docs/author/taxonomy.md +1 -1
- package/agent-docs/author/templates.md +16 -8
- package/agent-docs/author/templates.sections/built-in-feature-templates.md +3 -3
- package/agent-docs/author/templates.sections/built-in-templates.md +3 -3
- package/agent-docs/author/templates.sections/choose-formatting-enforcement-for-your-project.md +3 -3
- package/agent-docs/author/templates.sections/create-or-edit-a-project-template.md +12 -4
- package/agent-docs/author/templates.sections/keep-the-four-shapes-distinct.md +3 -3
- package/agent-docs/author/templates.sections/project-added-templates.md +3 -3
- package/agent-docs/author/templates.sections/related-reading.md +3 -3
- package/agent-docs/author/templates.sections/scaffold-a-page.md +7 -7
- package/agent-docs/author/templates.sections/short-cli-and-derived-values.md +3 -3
- package/agent-docs/author/templates.sections/what-every-template-gives-you.md +3 -3
- package/agent-docs/author/workspace/commands.md +2 -2
- package/agent-docs/author/workspace.md +8 -2
- package/agent-docs/author/writing-style.md +27 -5
- package/agent-docs/author/writing-style.sections/adapt-this-example.md +3 -3
- package/agent-docs/author/writing-style.sections/choose-components-by-reader-need.md +6 -4
- package/agent-docs/author/writing-style.sections/evidence-and-uncertainty.md +3 -3
- package/agent-docs/author/writing-style.sections/example-audience-and-scope.md +3 -3
- package/agent-docs/author/writing-style.sections/feature-availability.md +36 -0
- package/agent-docs/author/writing-style.sections/ingredients-recipes-and-cookbooks.md +3 -3
- package/agent-docs/author/writing-style.sections/page-purpose-and-structure.md +12 -4
- package/agent-docs/author/writing-style.sections/procedures-and-examples.md +3 -3
- package/agent-docs/author/writing-style.sections/related-reading.md +3 -3
- package/agent-docs/author/writing-style.sections/review-a-draft.md +3 -3
- package/agent-docs/author/writing-style.sections/reviewed-writing-examples.md +3 -3
- package/agent-docs/author/writing-style.sections/voice-and-explanations.md +3 -3
- package/agent-docs/changelog/2026-10-14-agent-readiness.md +2 -2
- package/agent-docs/changelog/2026-10-15-evaluation-builds.md +2 -2
- package/agent-docs/changelog/2026-10-16-navigation-layouts.md +2 -2
- package/agent-docs/changelog/2026-10-17-mcp-hosting.md +2 -2
- package/agent-docs/changelog/2026-10-18-search-and-tooltips.md +2 -2
- package/agent-docs/changelog/2026-10-19-visual-navigation.md +2 -2
- package/agent-docs/changelog/2026-10-25.md +2 -2
- package/agent-docs/changelog/2026-10-26.md +2 -2
- package/agent-docs/changelog/2026-10-27.md +2 -2
- package/agent-docs/changelog/2026-10-28.md +89 -0
- package/agent-docs/changelog/authoring-preview.md +2 -2
- package/agent-docs/changelog/authoring-search.md +3 -3
- package/agent-docs/changelog/authoring-tools.md +2 -2
- package/agent-docs/changelog/working-tree-skill.md +2 -2
- package/agent-docs/changelog.md +11 -3
- package/agent-docs/chat/checks.md +1 -1
- package/agent-docs/chat/checks.sections/answer-ahead-of-time.md +1 -1
- package/agent-docs/chat/checks.sections/answer-for-one-audience.md +1 -1
- package/agent-docs/chat/checks.sections/answer-without-a-model.md +1 -1
- package/agent-docs/chat/checks.sections/follow-up-questions.md +1 -1
- package/agent-docs/chat/checks.sections/how-an-answer-is-checked.md +1 -1
- package/agent-docs/chat/checks.sections/questions-with-several-parts.md +1 -1
- package/agent-docs/chat/checks.sections/related-reading.md +1 -1
- package/agent-docs/chat/checks.sections/reuse-answers.md +1 -1
- package/agent-docs/chat/host.md +1 -1
- package/agent-docs/chat/host.sections/cap-the-daily-spend.md +1 -1
- package/agent-docs/chat/host.sections/keep-a-record-of-questions.md +1 -1
- package/agent-docs/chat/host.sections/related-reading.md +1 -1
- package/agent-docs/chat/passage-index.md +2 -2
- package/agent-docs/chat/passage-index.sections/build-the-index.md +2 -2
- package/agent-docs/chat/passage-index.sections/embed-passages.md +2 -2
- package/agent-docs/chat/passage-index.sections/how-the-default-chunker-splits-a-page.md +2 -2
- package/agent-docs/chat/passage-index.sections/limits.md +2 -2
- package/agent-docs/chat/passage-index.sections/passage-ids-and-links.md +2 -2
- package/agent-docs/chat/passage-index.sections/related-reading.md +2 -2
- package/agent-docs/chat/passage-index.sections/what-a-passage-contains.md +2 -2
- package/agent-docs/chat/prepared-answers.md +1 -1
- package/agent-docs/chat/settings.md +2 -2
- package/agent-docs/chat/settings.sections/choose-the-scorer.md +2 -2
- package/agent-docs/chat/settings.sections/configure-chat.md +2 -2
- package/agent-docs/chat/settings.sections/configure-retrieval.md +2 -2
- package/agent-docs/chat/settings.sections/related-reading.md +2 -2
- package/agent-docs/chat/settings.sections/send-extra-request-fields.md +2 -2
- package/agent-docs/chat/settings.sections/use-more-than-one-model.md +2 -2
- package/agent-docs/chat/settings.sections/when-the-scorer-is-down.md +2 -2
- package/agent-docs/chat/setup.md +1 -1
- package/agent-docs/chat.md +1 -1
- package/agent-docs/cli.md +2 -2
- package/agent-docs/cli.sections/audit-a-project-and-api-sources.md +2 -2
- package/agent-docs/cli.sections/author-pages.md +2 -2
- package/agent-docs/cli.sections/build-and-serve.md +2 -2
- package/agent-docs/cli.sections/inspect-config-and-operations.md +2 -2
- package/agent-docs/cli.sections/npm-scripts-in-this-repository.md +2 -2
- package/agent-docs/cli.sections/related-reading.md +2 -2
- package/agent-docs/cli.sections/search-and-chat.md +2 -2
- package/agent-docs/cli.sections/start-a-project.md +2 -2
- package/agent-docs/cli.sections/working-tree-authoring-tools.md +2 -2
- package/agent-docs/config/apis.md +2 -2
- package/agent-docs/config/content.md +2 -2
- package/agent-docs/config/content.sections/authoring.md +2 -2
- package/agent-docs/config/content.sections/badges.md +2 -2
- package/agent-docs/config/content.sections/callouts.md +2 -2
- package/agent-docs/config/content.sections/cookbooks.md +2 -2
- package/agent-docs/config/content.sections/frontmatter.md +2 -2
- package/agent-docs/config/content.sections/readerfilters.md +2 -2
- package/agent-docs/config/content.sections/related-reading.md +2 -2
- package/agent-docs/config/content.sections/relationships.md +2 -2
- package/agent-docs/config/content.sections/snippets.md +2 -2
- package/agent-docs/config/content.sections/templates.md +2 -2
- package/agent-docs/config/content.sections/variables.md +2 -2
- package/agent-docs/config/examples.md +22 -14
- package/agent-docs/config/examples.sections/feature-showcase.md +21 -13
- package/agent-docs/config/examples.sections/minimal-site.md +15 -10
- package/agent-docs/config/examples.sections/multilingual-help-center.md +15 -10
- package/agent-docs/config/examples.sections/parcel-api-documentation.md +15 -10
- package/agent-docs/config/examples.sections/related-reading.md +15 -10
- package/agent-docs/config/examples.sections/shared-company-preset.md +16 -11
- package/agent-docs/config/integrations.md +2 -2
- package/agent-docs/config/outputs.md +17 -13
- package/agent-docs/config/outputs.sections/exports.md +16 -13
- package/agent-docs/config/outputs.sections/metadata.md +3 -3
- package/agent-docs/config/outputs.sections/related-reading.md +3 -3
- package/agent-docs/config/publishing.md +2 -2
- package/agent-docs/config/publishing.sections/navigation.md +2 -2
- package/agent-docs/config/publishing.sections/redirects.md +2 -2
- package/agent-docs/config/publishing.sections/related-reading.md +2 -2
- package/agent-docs/config/site.md +2 -2
- package/agent-docs/config/site.sections/related-reading.md +2 -2
- package/agent-docs/config/site.sections/theme.md +2 -2
- package/agent-docs/config.md +2 -2
- package/agent-docs/configuration.md +22 -20
- package/agent-docs/configuration.sections/build-several-sites-from-shared-content.md +2 -2
- package/agent-docs/configuration.sections/change-inherited-lists-deliberately.md +5 -5
- package/agent-docs/configuration.sections/extract-a-section-with-include.md +4 -4
- package/agent-docs/configuration.sections/inspect-and-validate-changes.md +6 -6
- package/agent-docs/configuration.sections/related-reading.md +2 -2
- package/agent-docs/configuration.sections/separate-config-inheritance-from-page-metadata.md +2 -2
- package/agent-docs/configuration.sections/share-defaults-through-extends.md +7 -7
- package/agent-docs/configuration.sections/start-with-one-working-file.md +8 -6
- package/agent-docs/configuration.sections/understand-the-three-stages.md +2 -2
- package/agent-docs/configuration.sections/understand-where-paths-point.md +2 -2
- package/agent-docs/connect/mcp.md +1 -1
- package/agent-docs/content-delivery/agents.md +86 -0
- package/agent-docs/content-delivery/api.md +130 -0
- package/agent-docs/content-delivery/client.md +79 -0
- package/agent-docs/content-delivery/http-api/operations/get-content-representation-fa1d2e3f.md +394 -0
- package/agent-docs/content-delivery/http-api/operations/get-content-representation-head-4052375c.md +256 -0
- package/agent-docs/content-delivery/http-api/operations/get-content-resource-849f1c49.md +911 -0
- package/agent-docs/content-delivery/http-api/operations/get-content-resource-head-0a352acd.md +246 -0
- package/agent-docs/content-delivery/http-api/operations/list-content-resources-8e977ec7.md +913 -0
- package/agent-docs/content-delivery/http-api/operations/list-content-resources-head-b67cbad9.md +236 -0
- package/agent-docs/content-delivery/http-api.md +78 -0
- package/agent-docs/content-delivery/manifest.md +79 -0
- package/agent-docs/content-delivery/publish.md +72 -0
- package/agent-docs/content-delivery/render.md +73 -0
- package/agent-docs/content-delivery/rest.md +108 -0
- package/agent-docs/content-delivery/sections.md +110 -0
- package/agent-docs/content-delivery/select.md +100 -0
- package/agent-docs/content-delivery.md +79 -0
- package/agent-docs/contribute/agent-skills.md +1 -1
- package/agent-docs/cookbooks/authoring.md +7 -3
- package/agent-docs/cookbooks/site-workflows.md +8 -4
- package/agent-docs/cookbooks/welcome-email.md +7 -3
- package/agent-docs/documents.json +7707 -6283
- package/agent-docs/example-api/cedar/create-enrollment-7af3e5d2.md +2 -2
- package/agent-docs/example-api/cedar/get-legacy-parcel-6758d051.md +2 -2
- package/agent-docs/example-api/cedar/get-parcel-edee10e6.md +2 -2
- package/agent-docs/example-api/cedar/list-parcels-0345fb5f.md +2 -2
- package/agent-docs/example-api/cedar/parcel-delivered-7d1ba6db.md +2 -2
- package/agent-docs/example-api/cedar/upload-manifest-1af17891.md +2 -2
- package/agent-docs/example-api/cedar.md +2 -2
- package/agent-docs/example-api/create-note.md +2 -2
- package/agent-docs/example-api/generated/get-note-cbedb319.md +2 -2
- package/agent-docs/example-api/write-note.md +2 -2
- package/agent-docs/explore.md +2 -2
- package/agent-docs/glossary.md +3 -7
- package/agent-docs/ingredients/audit-site.md +2 -2
- package/agent-docs/ingredients/build-site.md +2 -2
- package/agent-docs/ingredients/compile-framework.md +2 -2
- package/agent-docs/ingredients/complete-page.md +2 -2
- package/agent-docs/ingredients/inspect-agent-bundle.md +2 -2
- package/agent-docs/ingredients/inspect-build.md +2 -2
- package/agent-docs/ingredients/preview-site.md +2 -2
- package/agent-docs/ingredients/refresh-agent-bundle.md +2 -2
- package/agent-docs/ingredients/scaffold-page.md +2 -2
- package/agent-docs/non-goals.md +2 -2
- package/agent-docs/provenance.json +4 -4
- package/agent-docs/publish/agent-discovery.md +1 -1
- package/agent-docs/publish/agent-friendliness.md +1 -1
- package/agent-docs/publish/agent-friendliness.sections/discover-a-page-with-a-small-context-budget.md +1 -1
- package/agent-docs/publish/agent-friendliness.sections/distinguish-examples-from-a-real-service.md +1 -1
- package/agent-docs/publish/agent-friendliness.sections/evidence-has-a-scope.md +1 -1
- package/agent-docs/publish/agent-friendliness.sections/keep-shared-instructions-complete.md +1 -1
- package/agent-docs/publish/agent-friendliness.sections/related-reading.md +1 -1
- package/agent-docs/publish/agent-friendliness.sections/secret-handshake.md +1 -1
- package/agent-docs/publish/agent-friendliness.sections/try-the-page-controls.md +1 -1
- package/agent-docs/publish/agent-friendliness.sections/use-the-transport-your-client-supports.md +1 -1
- package/agent-docs/publish/agent-friendliness.sections/verify-the-page-and-detect-changes.md +1 -1
- package/agent-docs/publish/agent-friendliness.sections/what-an-agent-reads.md +1 -1
- package/agent-docs/publish/agent-friendliness.sections/what-happens-to-an-unsupported-component.md +1 -1
- package/agent-docs/publish/api-references.md +10 -8
- package/agent-docs/publish/api-references.sections/author-the-reference-page.md +1 -1
- package/agent-docs/publish/api-references.sections/build-and-verify-the-output.md +1 -1
- package/agent-docs/publish/api-references.sections/inspect-the-machine-readable-contract.md +1 -1
- package/agent-docs/publish/api-references.sections/{generate-many-pages-selectively.md → next-steps.md} +28 -2
- package/agent-docs/publish/api-references.sections/preview-a-small-api-reference.md +1 -1
- package/agent-docs/publish/api-references.sections/put-the-spec-in-your-project.md +1 -1
- package/agent-docs/publish/api-references.sections/register-the-operation.md +1 -1
- package/agent-docs/publish/api-references.sections/related-reading.md +1 -1
- package/agent-docs/publish/api-references.sections/review-imported-content-before-publishing.md +1 -1
- package/agent-docs/publish/api-references.sections/what-readers-get.md +1 -1
- package/agent-docs/publish/audit.md +1 -1
- package/agent-docs/publish/audit.sections/add-project-tests.md +1 -1
- package/agent-docs/publish/audit.sections/check-agent-evaluation-reports.md +1 -1
- package/agent-docs/publish/audit.sections/check-before-pushing.md +1 -1
- package/agent-docs/publish/audit.sections/connect-a-documentation-test-suite.md +1 -1
- package/agent-docs/publish/audit.sections/read-the-report.md +1 -1
- package/agent-docs/publish/audit.sections/related-reading.md +1 -1
- package/agent-docs/publish/content-export.md +33 -17
- package/agent-docs/publish/content-export.sections/assets-downloads-and-notices.md +4 -4
- package/agent-docs/publish/content-export.sections/check-the-result.md +3 -3
- package/agent-docs/publish/content-export.sections/export-selected-pages.md +13 -10
- package/agent-docs/publish/content-export.sections/inspect-the-manifest.md +6 -4
- package/agent-docs/publish/content-export.sections/prerequisites.md +4 -4
- package/agent-docs/publish/content-export.sections/related-reading.md +3 -3
- package/agent-docs/publish/content-export.sections/review-exported-api-material.md +4 -4
- package/agent-docs/publish/content-export.sections/try-the-help-drawer-example.md +4 -4
- package/agent-docs/publish/content-export.sections/upgrade-an-existing-integration.md +34 -0
- package/agent-docs/publish/content-export.sections/what-the-representations-retain.md +5 -5
- package/agent-docs/publish/content-negotiation.md +2 -2
- package/agent-docs/publish/content-negotiation.sections/hosting-contract.md +2 -2
- package/agent-docs/publish/content-negotiation.sections/missing-pages.md +2 -2
- package/agent-docs/publish/content-negotiation.sections/related-reading.md +2 -2
- package/agent-docs/publish/content-negotiation.sections/validators-and-manifest-links.md +2 -2
- package/agent-docs/publish/content-negotiation.sections/verify-the-deployed-url.md +2 -2
- package/agent-docs/publish/content-negotiation.sections/what-if-the-host-cannot-negotiate.md +2 -2
- package/agent-docs/publish/content-negotiation.sections/what-shoalrun-builds-and-what-the-host-does.md +2 -2
- package/agent-docs/publish/content-negotiation.sections/why-this-matters.md +2 -2
- package/agent-docs/publish/deployment-check.md +2 -2
- package/agent-docs/publish/evals.md +1 -1
- package/agent-docs/publish/evals.sections/add-the-audit-hook.md +1 -1
- package/agent-docs/publish/evals.sections/check-the-report.md +1 -1
- package/agent-docs/publish/evals.sections/define-a-task-file.md +1 -1
- package/agent-docs/publish/evals.sections/diagnose-a-different-build.md +1 -1
- package/agent-docs/publish/evals.sections/prepare-a-report-for-the-built-site.md +1 -1
- package/agent-docs/publish/evals.sections/related-reading.md +1 -1
- package/agent-docs/publish/evals.sections/run-and-review-the-tasks.md +1 -1
- package/agent-docs/publish/evals.sections/shoalrun-s-own-example.md +1 -1
- package/agent-docs/publish/host-adapters.md +1 -1
- package/agent-docs/publish/manni.md +1 -1
- package/agent-docs/publish/manni.sections/check-the-result.md +1 -1
- package/agent-docs/publish/manni.sections/how-this-site-uses-manni.md +1 -1
- package/agent-docs/publish/manni.sections/optional-custom-policies.md +1 -1
- package/agent-docs/publish/manni.sections/prerequisites.md +1 -1
- package/agent-docs/publish/mcp-analytics.md +1 -1
- package/agent-docs/publish/mcp-analytics.sections/adapter-exports.md +1 -1
- package/agent-docs/publish/mcp-analytics.sections/enable-analytics.md +1 -1
- package/agent-docs/publish/mcp-analytics.sections/event-fields-and-service-mappings.md +1 -1
- package/agent-docs/publish/mcp-analytics.sections/optionally-collect-intent-and-group-calls.md +1 -1
- package/agent-docs/publish/mcp-analytics.sections/optionally-identify-the-client-and-model.md +1 -1
- package/agent-docs/publish/mcp-analytics.sections/related-reading.md +1 -1
- package/agent-docs/publish/mcp-analytics.sections/safeguards-and-limits.md +1 -1
- package/agent-docs/publish/mcp.md +12 -10
- package/agent-docs/publish/mcp.sections/check-budgets-and-errors.md +2 -2
- package/agent-docs/publish/mcp.sections/choose-the-documentation-source.md +7 -7
- package/agent-docs/publish/mcp.sections/configure-the-client.md +5 -5
- package/agent-docs/publish/mcp.sections/generate-a-vercel-endpoint.md +2 -2
- package/agent-docs/publish/mcp.sections/related-reading.md +4 -2
- package/agent-docs/publish/mcp.sections/search-then-read.md +2 -2
- package/agent-docs/publish/mcp.sections/serve-a-public-endpoint-on-node.md +2 -2
- package/agent-docs/publish/mcp.sections/verify-the-connection.md +2 -2
- package/agent-docs/publish/mcp.sections/why-offer-mcp-for-public-docs.md +2 -2
- package/agent-docs/publish/openapi-extension-mappings.md +2 -2
- package/agent-docs/publish/output.md +14 -14
- package/agent-docs/publish/output.sections/api-contracts.md +3 -3
- package/agent-docs/publish/output.sections/code-highlighting.md +3 -3
- package/agent-docs/publish/output.sections/current-limits.md +4 -4
- package/agent-docs/publish/output.sections/incremental-sync.md +3 -3
- package/agent-docs/publish/output.sections/manifest-records-and-sections.md +3 -3
- package/agent-docs/publish/output.sections/navigation-section-indexes.md +3 -3
- package/agent-docs/publish/output.sections/page-formats.md +3 -3
- package/agent-docs/publish/output.sections/published-output-contract.md +3 -3
- package/agent-docs/publish/output.sections/reader-appearance-and-controls.md +3 -3
- package/agent-docs/publish/output.sections/related-reading.md +3 -3
- package/agent-docs/publish/output.sections/response-identity.md +3 -3
- package/agent-docs/publish/output.sections/site-files.md +3 -3
- package/agent-docs/publish/output.sections/validate-an-existing-build-locally.md +5 -5
- package/agent-docs/publish/output.sections/where-files-go.md +11 -11
- package/agent-docs/publish/publications.md +1 -1
- package/agent-docs/publish/revisions.md +1 -1
- package/agent-docs/publish/seo.md +17 -3
- package/agent-docs/publish/seo.sections/related-reading.md +28 -0
- package/agent-docs/publish/seo.sections/set-the-public-address.md +36 -0
- package/agent-docs/publish/seo.sections/what-agents-receive.md +28 -0
- package/agent-docs/publish/seo.sections/what-the-build-adds.md +42 -0
- package/agent-docs/publish/seo.sections/write-the-page-title-and-summary.md +43 -0
- package/agent-docs/publish/single-source-sites.md +1 -1
- package/agent-docs/publish/troubleshooting.md +1 -1
- package/agent-docs/publish/vercel.md +1 -1
- package/agent-docs/publish/vercel.sections/check-the-deployment.md +1 -1
- package/agent-docs/publish/vercel.sections/include-an-mcp-endpoint.md +1 -1
- package/agent-docs/publish/vercel.sections/prepare-your-project.md +1 -1
- package/agent-docs/publish/vercel.sections/related-reading.md +1 -1
- package/agent-docs/publish.md +1 -1
- package/agent-docs/recipes/add-page.md +3 -3
- package/agent-docs/recipes/add-page.sections/audit-the-shoalrun-docs-project.md +2 -2
- package/agent-docs/recipes/add-page.sections/before-you-start.md +2 -3
- package/agent-docs/recipes/add-page.sections/build-the-shoalrun-docs-site.md +2 -2
- package/agent-docs/recipes/add-page.sections/check-agent-search-and-page-lookup.md +2 -2
- package/agent-docs/recipes/add-page.sections/check-the-built-page-artifacts.md +2 -2
- package/agent-docs/recipes/add-page.sections/check-the-outcome.md +2 -2
- package/agent-docs/recipes/add-page.sections/complete-the-guide-and-add-navigation.md +2 -2
- package/agent-docs/recipes/add-page.sections/install-the-site-dependencies.md +2 -2
- package/agent-docs/recipes/add-page.sections/regenerate-the-agent-documentation-bundle.md +3 -2
- package/agent-docs/recipes/add-page.sections/related-reading.md +2 -2
- package/agent-docs/recipes/add-page.sections/scaffold-a-new-guide.md +2 -2
- package/agent-docs/recipes/audit-docs.md +2 -3
- package/agent-docs/recipes/build-docs.md +2 -3
- package/agent-docs/recipes/preview-hybrid-search.md +2 -2
- package/agent-docs/recipes/preview-page.md +2 -3
- package/agent-docs/recipes/queue-email.md +2 -2
- package/agent-docs/recipes/refresh-agent-docs.md +21 -3
- package/agent-docs/recipes/send-welcome-email.md +2 -2
- package/agent-docs/reference/agent-skills.md +2 -2
- package/agent-docs/search-and-chat.md +1 -1
- package/agent-docs/search-index.json +1 -1
- package/agent-docs/start.md +7 -3
- package/agent-docs/start.sections/build-this-site.md +1 -1
- package/agent-docs/start.sections/migrating-from-github-packages.md +35 -0
- package/agent-docs/start.sections/prerequisites.md +1 -1
- package/agent-docs/start.sections/start-your-own-project.md +1 -1
- package/agent-docs/status.md +2 -2
- package/agent-docs/styling/color-modes.md +6 -2
- package/agent-docs/styling/footer.md +10 -8
- package/agent-docs/styling/header.md +5 -3
- package/agent-docs/styling/layouts.md +27 -5
- package/agent-docs/styling/layouts.sections/build-a-landing-page.md +7 -4
- package/agent-docs/styling/layouts.sections/build-a-splash-page.md +7 -4
- package/agent-docs/styling/layouts.sections/control-navigation-tabs.md +7 -4
- package/agent-docs/styling/layouts.sections/customize-the-hero.md +7 -4
- package/agent-docs/styling/layouts.sections/hero.md +8 -5
- package/agent-docs/styling/layouts.sections/hide-the-table-of-contents-or-give-content-more-room.md +54 -0
- package/agent-docs/styling/layouts.sections/html-markdown-and-agents.md +43 -0
- package/agent-docs/styling/layouts.sections/related-reading.md +7 -4
- package/agent-docs/styling/palettes.md +6 -2
- package/agent-docs/styling/share-images.md +6 -2
- package/agent-docs/styling/themes.md +6 -3
- package/agent-docs/styling/try.md +3 -15
- package/dist/api.js +70 -3
- package/dist/authoring-hints.d.ts +3 -0
- package/dist/authoring-hints.js +35 -0
- package/dist/authoring-project.d.ts +1 -1
- package/dist/authoring-project.js +2 -0
- package/dist/authoring-validation.js +2 -0
- package/dist/code-samples.js +22 -11
- package/dist/compiler-config.d.ts +1 -0
- package/dist/compiler-config.js +13 -0
- package/dist/compiler.js +2 -1
- package/dist/component-names.js +1 -1
- package/dist/component-transform.js +7 -1
- package/dist/content-delivery-contract.d.ts +108 -0
- package/dist/content-delivery-contract.js +15 -0
- package/dist/content-delivery-transport.d.ts +3 -0
- package/dist/content-delivery-transport.js +47 -0
- package/dist/content-delivery-validation.d.ts +7 -0
- package/dist/content-delivery-validation.js +122 -0
- package/dist/content-delivery.d.ts +21 -0
- package/dist/content-delivery.js +119 -0
- package/dist/content-export-anchors.d.ts +11 -0
- package/dist/content-export-anchors.js +79 -0
- package/dist/content-export-command.d.ts +4 -2
- package/dist/content-export-command.js +15 -2
- package/dist/content-export-comparison.d.ts +16 -0
- package/dist/content-export-comparison.js +48 -0
- package/dist/content-export-config.d.ts +17 -0
- package/dist/content-export-config.js +47 -3
- package/dist/content-export-render.js +20 -1
- package/dist/content-export.d.ts +5 -1
- package/dist/content-export.js +193 -40
- package/dist/content-mount.d.ts +11 -0
- package/dist/content-mount.js +73 -0
- package/dist/content-rest-client.d.ts +12 -0
- package/dist/content-rest-client.js +162 -0
- package/dist/content-rest-contract.d.ts +27 -0
- package/dist/content-rest-contract.js +9 -0
- package/dist/content-rest-server.d.ts +7 -0
- package/dist/content-rest-server.js +99 -0
- package/dist/content-source.d.ts +3 -1
- package/dist/frontmatter.js +8 -7
- package/dist/html-renderer.js +25 -0
- package/dist/markdown-sections.d.ts +2 -1
- package/dist/markdown-sections.js +8 -18
- package/dist/page-renderer.js +5 -3
- package/dist/publication-plan.js +2 -1
- package/dist/scaffold-placeholders.d.ts +2 -0
- package/dist/scaffold-placeholders.js +19 -0
- package/dist/section-identity.d.ts +24 -0
- package/dist/section-identity.js +137 -0
- package/examples/content-delivery/README.md +87 -0
- package/examples/content-delivery/app.js +91 -0
- package/examples/content-delivery/content/billing.mdx +22 -0
- package/examples/content-delivery/docs.config.yaml +36 -0
- package/examples/content-delivery/index.html +25 -0
- package/examples/content-delivery/preview.mjs +86 -0
- package/examples/content-delivery/snippets/refund-window.mdx +3 -0
- package/examples/help-drawer/README.md +2 -2
- package/examples/help-drawer/app.js +3 -3
- package/package.json +6 -3
- package/schemas/content-export.schema.json +469 -33
- package/schemas/content-rest.openapi.json +1627 -0
- package/skills/shoalrun-author/SKILL.md +2 -0
- package/styles/site.css +4 -1
- package/agent-docs/.well-known/shoalrun/navigation/changelog-tab-50f26fe22670.txt +0 -33
- package/agent-docs/publish/api-references.sections/add-useful-examples.md +0 -36
- package/agent-docs/publish/api-references.sections/describe-filters-that-do-not-use-their-openapi-parameter-name.md +0 -36
- package/agent-docs/publish/api-references.sections/link-guides-to-api-references.md +0 -36
- package/agent-docs/publish/api-references.sections/map-extension-values-to-shoalrun-status.md +0 -36
- package/agent-docs/publish/api-references.sections/publish-several-versions.md +0 -36
- package/agent-docs/publish/api-references.sections/use-a-template-for-editorial-context.md +0 -36
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import { ContentDeliveryError, contentRefKey } from './content-delivery-contract.js';
|
|
2
|
+
import { contentResourceKey } from './content-rest-contract.js';
|
|
3
|
+
import { validContentPublication, validContentRef, validateContentResources, safeContentPath } from './content-delivery-validation.js';
|
|
4
|
+
import { freeze, sha256, readBounded } from './content-delivery-transport.js';
|
|
5
|
+
const digestPattern = /^[a-f0-9]{64}$/;
|
|
6
|
+
const record = (value) => !!value && typeof value === 'object' && !Array.isArray(value);
|
|
7
|
+
const exactKeys = (value, keys) => record(value) && Object.keys(value).length === keys.length && Object.keys(value).every(key => keys.includes(key));
|
|
8
|
+
const samePublication = (left, right) => left.channel === right.channel && left.version === right.version && left.language === right.language;
|
|
9
|
+
const invalid = (message) => { throw new ContentDeliveryError('invalid_manifest', message); };
|
|
10
|
+
/** Preloads a REST inventory and pins all subsequent reads to that revision. */
|
|
11
|
+
export async function openRestContentSource(options) {
|
|
12
|
+
let baseUrl;
|
|
13
|
+
try {
|
|
14
|
+
baseUrl = new URL(options.baseUrl, typeof location === 'undefined' ? undefined : location.href);
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
return invalid('REST base URL must be a valid HTTP(S) URL.');
|
|
18
|
+
}
|
|
19
|
+
if (!['http:', 'https:'].includes(baseUrl.protocol) || baseUrl.username || baseUrl.password || baseUrl.search || baseUrl.hash || !/^\/(?:[A-Za-z0-9_-]+\/?)*$/.test(baseUrl.pathname))
|
|
20
|
+
invalid('Invalid REST base URL.');
|
|
21
|
+
if (typeof location !== 'undefined' && baseUrl.origin !== location.origin)
|
|
22
|
+
invalid('Browser content must be served from the application origin.');
|
|
23
|
+
if (!validContentPublication(options.publication) || options.revision !== undefined && !digestPattern.test(options.revision))
|
|
24
|
+
invalid('Invalid REST publication or revision.');
|
|
25
|
+
const publication = { ...options.publication };
|
|
26
|
+
const requestedRevision = options.revision;
|
|
27
|
+
const fetcher = options.fetch ?? globalThis.fetch;
|
|
28
|
+
const manifestBytes = options.limits?.manifestBytes ?? 5_000_000;
|
|
29
|
+
const resourceBytes = options.limits?.resourceBytes ?? 8_000_000;
|
|
30
|
+
const maxResources = options.limits?.resources ?? 10_000;
|
|
31
|
+
for (const limit of [manifestBytes, resourceBytes, maxResources])
|
|
32
|
+
if (!Number.isSafeInteger(limit) || limit <= 0)
|
|
33
|
+
invalid('Limits must be positive safe integers.');
|
|
34
|
+
const lifetime = new AbortController();
|
|
35
|
+
let disposed = false;
|
|
36
|
+
const openSignal = options.signal ? AbortSignal.any([options.signal, lifetime.signal]) : lifetime.signal;
|
|
37
|
+
const urlFor = (suffix, revision) => {
|
|
38
|
+
const url = new URL(baseUrl.href);
|
|
39
|
+
url.pathname = baseUrl.pathname.replace(/\/$/, '') + '/resources' + suffix;
|
|
40
|
+
for (const key of ['channel', 'version', 'language'])
|
|
41
|
+
url.searchParams.set(key, publication[key]);
|
|
42
|
+
if (revision !== undefined)
|
|
43
|
+
url.searchParams.set('revision', revision);
|
|
44
|
+
return url;
|
|
45
|
+
};
|
|
46
|
+
async function request(url, mediaType, limit, signal) {
|
|
47
|
+
signal.throwIfAborted();
|
|
48
|
+
let response;
|
|
49
|
+
try {
|
|
50
|
+
response = await fetcher(url.href, { signal, redirect: 'error', credentials: 'same-origin', cache: 'no-store', headers: { Accept: mediaType } });
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
signal.throwIfAborted();
|
|
54
|
+
throw new ContentDeliveryError('network_error', 'Cannot fetch REST content.');
|
|
55
|
+
}
|
|
56
|
+
const reject = async (code, message) => {
|
|
57
|
+
// Cancelling releases the body without consuming an arbitrary host error.
|
|
58
|
+
// A custom stream's cancellation promise must not delay the failure.
|
|
59
|
+
void response.body?.cancel().catch(() => { });
|
|
60
|
+
signal.throwIfAborted();
|
|
61
|
+
throw new ContentDeliveryError(code, message);
|
|
62
|
+
};
|
|
63
|
+
if (response.redirected || response.url && response.url !== url.href)
|
|
64
|
+
return reject('invalid_content', 'Redirected content is not accepted.');
|
|
65
|
+
const actualType = response.headers.get('content-type')?.split(';', 1)[0].trim().toLowerCase();
|
|
66
|
+
try {
|
|
67
|
+
if (!response.ok) {
|
|
68
|
+
const codes = { 400: 'invalid_request', 404: 'not_found', 406: 'unsupported_format', 409: 'stale_revision', 405: 'method_not_allowed', 500: 'internal_error' };
|
|
69
|
+
if (!(response.status in codes))
|
|
70
|
+
return reject('network_error', `Content request failed (${response.status}).`);
|
|
71
|
+
if (actualType !== 'application/json')
|
|
72
|
+
return reject('invalid_content', 'REST error must be JSON.');
|
|
73
|
+
const bytes = await readBounded(response, Math.min(manifestBytes, 65_536), signal);
|
|
74
|
+
let envelope;
|
|
75
|
+
try {
|
|
76
|
+
envelope = JSON.parse(new TextDecoder('utf-8', { fatal: true }).decode(bytes));
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
throw new ContentDeliveryError('invalid_content', 'Malformed REST error.');
|
|
80
|
+
}
|
|
81
|
+
if (!exactKeys(envelope, ['error']) || !exactKeys(envelope.error, ['code', 'message']) || codes[response.status] !== envelope.error.code || typeof envelope.error.message !== 'string')
|
|
82
|
+
throw new ContentDeliveryError('invalid_content', 'Malformed REST error.');
|
|
83
|
+
const code = response.status === 404 ? 'not_found' : response.status === 406 ? 'unsupported_format' : response.status === 409 ? 'stale_revision' : 'network_error';
|
|
84
|
+
throw new ContentDeliveryError(code, envelope.error.message);
|
|
85
|
+
}
|
|
86
|
+
if (actualType !== mediaType)
|
|
87
|
+
return reject('invalid_content', 'Unexpected content type.');
|
|
88
|
+
return await readBounded(response, limit, signal);
|
|
89
|
+
}
|
|
90
|
+
catch (error) {
|
|
91
|
+
signal.throwIfAborted();
|
|
92
|
+
if (error instanceof ContentDeliveryError)
|
|
93
|
+
throw error;
|
|
94
|
+
throw new ContentDeliveryError('network_error', 'Content response was interrupted.');
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
let envelope;
|
|
98
|
+
try {
|
|
99
|
+
envelope = JSON.parse(new TextDecoder('utf-8', { fatal: true }).decode(await request(urlFor('', requestedRevision), 'application/json', manifestBytes, openSignal)));
|
|
100
|
+
}
|
|
101
|
+
catch (error) {
|
|
102
|
+
if (error instanceof ContentDeliveryError || openSignal.aborted)
|
|
103
|
+
throw error;
|
|
104
|
+
return invalid('REST inventory is not valid UTF-8 JSON.');
|
|
105
|
+
}
|
|
106
|
+
if (!exactKeys(envelope, ['schemaVersion', 'revision', 'publication', 'resources']) || envelope.schemaVersion !== 1 || !digestPattern.test(envelope.revision ?? '') || !validContentPublication(envelope.publication) || !samePublication(envelope.publication, publication))
|
|
107
|
+
invalid('Invalid REST inventory identity.');
|
|
108
|
+
if (requestedRevision !== undefined && requestedRevision !== envelope.revision)
|
|
109
|
+
throw new ContentDeliveryError('stale_revision', 'REST inventory differs from the requested revision.');
|
|
110
|
+
if (!Array.isArray(envelope.resources) || envelope.resources.length > maxResources)
|
|
111
|
+
invalid('Invalid REST resource inventory.');
|
|
112
|
+
const resources = [];
|
|
113
|
+
const assets = new Set();
|
|
114
|
+
for (const item of envelope.resources) {
|
|
115
|
+
if (!exactKeys(item, ['key', 'resource']) || !record(item.resource) || !validContentRef(item.resource.ref) || item.key !== contentResourceKey(item.resource.ref))
|
|
116
|
+
invalid('Invalid REST resource key.');
|
|
117
|
+
if (!Array.isArray(item.resource.assets) || item.resource.assets.some((asset) => !safeContentPath(asset) || !asset.startsWith('assets/')))
|
|
118
|
+
invalid('Invalid REST resource assets.');
|
|
119
|
+
item.resource.assets.forEach((asset) => assets.add(asset));
|
|
120
|
+
resources.push(item.resource);
|
|
121
|
+
}
|
|
122
|
+
validateContentResources(resources, publication, assets, rep => record(rep) && safeContentPath(rep.path) && Number.isSafeInteger(rep.bytes) && rep.bytes >= 0 && rep.bytes <= resourceBytes && digestPattern.test(rep.digest ?? ''));
|
|
123
|
+
const revision = envelope.revision;
|
|
124
|
+
freeze(resources);
|
|
125
|
+
freeze(publication);
|
|
126
|
+
const byRef = new Map(resources.map(resource => [contentRefKey(resource.ref), resource]));
|
|
127
|
+
openSignal.throwIfAborted();
|
|
128
|
+
const active = () => { if (disposed)
|
|
129
|
+
throw new ContentDeliveryError('disposed', 'Content snapshot has been disposed.'); };
|
|
130
|
+
return Object.freeze({ revision, publication,
|
|
131
|
+
list() { active(); return resources; },
|
|
132
|
+
async get(ref, getOptions) {
|
|
133
|
+
active();
|
|
134
|
+
if (!validContentRef(ref))
|
|
135
|
+
throw new ContentDeliveryError('not_found', 'Invalid content reference.');
|
|
136
|
+
const resource = byRef.get(contentRefKey(ref));
|
|
137
|
+
if (!resource)
|
|
138
|
+
throw new ContentDeliveryError('not_found', 'No matching content in this publication.');
|
|
139
|
+
const format = getOptions?.format;
|
|
140
|
+
if (!['html', 'markdown'].includes(format) || !resource.representations[format])
|
|
141
|
+
throw new ContentDeliveryError('unsupported_format', 'The requested representation is unavailable.');
|
|
142
|
+
const representation = resource.representations[format];
|
|
143
|
+
const signal = getOptions.signal ? AbortSignal.any([lifetime.signal, getOptions.signal]) : lifetime.signal;
|
|
144
|
+
const bytes = await request(urlFor('/' + contentResourceKey(resource.ref) + '/representations/' + format, revision), representation.mediaType, resourceBytes, signal);
|
|
145
|
+
if (bytes.byteLength !== representation.bytes || await sha256(bytes) !== representation.digest)
|
|
146
|
+
throw new ContentDeliveryError('stale_revision', 'Representation differs from the opened revision.');
|
|
147
|
+
signal.throwIfAborted();
|
|
148
|
+
active();
|
|
149
|
+
let content;
|
|
150
|
+
try {
|
|
151
|
+
content = new TextDecoder('utf-8', { fatal: true }).decode(bytes);
|
|
152
|
+
}
|
|
153
|
+
catch {
|
|
154
|
+
throw new ContentDeliveryError('invalid_content', 'Content is not valid UTF-8.');
|
|
155
|
+
}
|
|
156
|
+
return Object.freeze({ resource, representation, format, content, revision });
|
|
157
|
+
},
|
|
158
|
+
dispose() { if (!disposed) {
|
|
159
|
+
disposed = true;
|
|
160
|
+
lifetime.abort(new ContentDeliveryError('disposed', 'Content snapshot has been disposed.'));
|
|
161
|
+
} }, });
|
|
162
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { ContentPublication, ContentRef, ContentResource } from './content-delivery-contract.js';
|
|
2
|
+
/** Public identity only: independent of publication, revision, and export filenames. */
|
|
3
|
+
export declare function contentResourceKey(ref: ContentRef): string;
|
|
4
|
+
export type ContentRestEntry = {
|
|
5
|
+
key: string;
|
|
6
|
+
resource: ContentResource;
|
|
7
|
+
};
|
|
8
|
+
export type ContentRestList = {
|
|
9
|
+
schemaVersion: 1;
|
|
10
|
+
revision: string;
|
|
11
|
+
publication: ContentPublication;
|
|
12
|
+
resources: ContentRestEntry[];
|
|
13
|
+
};
|
|
14
|
+
export type ContentRestResource = {
|
|
15
|
+
schemaVersion: 1;
|
|
16
|
+
revision: string;
|
|
17
|
+
publication: ContentPublication;
|
|
18
|
+
key: string;
|
|
19
|
+
resource: ContentResource;
|
|
20
|
+
};
|
|
21
|
+
export type ContentRestErrorCode = 'invalid_request' | 'not_found' | 'unsupported_format' | 'stale_revision' | 'method_not_allowed' | 'internal_error';
|
|
22
|
+
export type ContentRestError = {
|
|
23
|
+
error: {
|
|
24
|
+
code: ContentRestErrorCode;
|
|
25
|
+
message: string;
|
|
26
|
+
};
|
|
27
|
+
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { contentRefKey } from './content-delivery-contract.js';
|
|
2
|
+
/** Public identity only: independent of publication, revision, and export filenames. */
|
|
3
|
+
export function contentResourceKey(ref) {
|
|
4
|
+
const bytes = new TextEncoder().encode(contentRefKey(ref));
|
|
5
|
+
let binary = '';
|
|
6
|
+
for (const byte of bytes)
|
|
7
|
+
binary += String.fromCharCode(byte);
|
|
8
|
+
return 'k_' + btoa(binary).replaceAll('+', '-').replaceAll('/', '_').replace(/=+$/, '');
|
|
9
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { ContentSnapshot } from './content-delivery-contract.js';
|
|
2
|
+
export type ContentRestHandlerOptions = {
|
|
3
|
+
sources: readonly ContentSnapshot[];
|
|
4
|
+
basePath?: string;
|
|
5
|
+
};
|
|
6
|
+
/** Serve already-approved, pinned snapshots. The caller retains source ownership. */
|
|
7
|
+
export declare function createContentRestHandler(options: ContentRestHandlerOptions): (request: Request) => Promise<Response>;
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { ContentDeliveryError, contentRefKey } from './content-delivery-contract.js';
|
|
2
|
+
import { contentResourceKey } from './content-rest-contract.js';
|
|
3
|
+
const publicationKey = (publication) => JSON.stringify([publication.channel, publication.version, publication.language]);
|
|
4
|
+
const digest = async (bytes) => [...new Uint8Array(await crypto.subtle.digest('SHA-256', bytes))].map(value => value.toString(16).padStart(2, '0')).join('');
|
|
5
|
+
const encoder = new TextEncoder();
|
|
6
|
+
const validPublication = (value) => value && Object.keys(value).sort().join(',') === 'channel,language,version' && Object.values(value).every(part => typeof part === 'string' && part.length > 0 && part.length <= 2048);
|
|
7
|
+
const canonical = (value) => Array.isArray(value) ? value.map(canonical) : value && typeof value === 'object' ? Object.fromEntries(Object.keys(value).sort().map(key => [key, canonical(value[key])])) : value;
|
|
8
|
+
const same = (left, right) => JSON.stringify(canonical(left)) === JSON.stringify(canonical(right));
|
|
9
|
+
/** Serve already-approved, pinned snapshots. The caller retains source ownership. */
|
|
10
|
+
export function createContentRestHandler(options) {
|
|
11
|
+
const basePath = options.basePath ?? '/content/v1';
|
|
12
|
+
if (!/^\/(?:[A-Za-z0-9_-]+\/)*[A-Za-z0-9_-]+$/.test(basePath))
|
|
13
|
+
throw new Error('Content REST basePath must be an absolute path without a trailing slash.');
|
|
14
|
+
const publications = new Map();
|
|
15
|
+
for (const source of options.sources) {
|
|
16
|
+
if (!/^[a-f0-9]{64}$/.test(source.revision) || !validPublication(source.publication))
|
|
17
|
+
throw new Error('Content REST source requires a valid revision and publication.');
|
|
18
|
+
const publication = structuredClone(source.publication);
|
|
19
|
+
const key = publicationKey(publication);
|
|
20
|
+
if (publications.has(key))
|
|
21
|
+
throw new Error('Content REST sources must have distinct publications.');
|
|
22
|
+
const resources = source.list().map(resource => ({ key: contentResourceKey(resource.ref), resource: structuredClone(resource) }));
|
|
23
|
+
if (resources.some(item => !validPublication(item.resource.publication) || publicationKey(item.resource.publication) !== key) || new Set(resources.map(item => item.key)).size !== resources.length)
|
|
24
|
+
throw new Error('Content REST source contains inconsistent publication or duplicate references.');
|
|
25
|
+
resources.sort((left, right) => left.key < right.key ? -1 : left.key > right.key ? 1 : 0);
|
|
26
|
+
publications.set(key, { source, publication, revision: source.revision, resources });
|
|
27
|
+
}
|
|
28
|
+
return async (request) => {
|
|
29
|
+
request.signal.throwIfAborted();
|
|
30
|
+
const head = request.method === 'HEAD';
|
|
31
|
+
const error = (status, code, message, headers = {}) => new Response(head ? null : JSON.stringify({ error: { code, message } }), { status, headers: { 'Content-Type': 'application/json; charset=utf-8', 'Cache-Control': 'no-store', ...headers } });
|
|
32
|
+
if (!['GET', 'HEAD'].includes(request.method))
|
|
33
|
+
return error(405, 'method_not_allowed', 'Use GET or HEAD.', { Allow: 'GET, HEAD' });
|
|
34
|
+
const url = new URL(request.url);
|
|
35
|
+
const prefix = `${basePath}/resources`;
|
|
36
|
+
if (url.pathname !== prefix && !url.pathname.startsWith(`${prefix}/`))
|
|
37
|
+
return error(404, 'not_found', 'Unknown content route.');
|
|
38
|
+
const parts = url.pathname.slice(prefix.length).split('/').slice(1);
|
|
39
|
+
if (parts.length !== 0 && parts.length !== 1 && !(parts.length === 3 && parts[1] === 'representations'))
|
|
40
|
+
return error(404, 'not_found', 'Unknown content route.');
|
|
41
|
+
const allowed = new Set(['channel', 'version', 'language', 'revision']);
|
|
42
|
+
if ([...url.searchParams.keys()].some(key => !allowed.has(key) || url.searchParams.getAll(key).length !== 1))
|
|
43
|
+
return error(400, 'invalid_request', 'Unknown or repeated query parameter.');
|
|
44
|
+
const publication = Object.fromEntries(['channel', 'version', 'language'].map(key => [key, url.searchParams.get(key)]));
|
|
45
|
+
if (Object.values(publication).some(value => !value || value.length > 2048))
|
|
46
|
+
return error(400, 'invalid_request', 'channel, version, and language are required.');
|
|
47
|
+
const revision = url.searchParams.get('revision');
|
|
48
|
+
if (revision !== null && !/^[a-f0-9]{64}$/.test(revision) || parts.length > 0 && revision === null)
|
|
49
|
+
return error(400, 'invalid_request', 'A valid revision is required for resource requests.');
|
|
50
|
+
const selected = publications.get(publicationKey(publication));
|
|
51
|
+
if (!selected)
|
|
52
|
+
return error(404, 'not_found', 'Publication not found.');
|
|
53
|
+
if (revision !== null && revision !== selected.revision)
|
|
54
|
+
return error(409, 'stale_revision', 'The requested revision is not available.');
|
|
55
|
+
const resource = parts.length ? selected.resources.find(item => item.key === parts[0]) : undefined;
|
|
56
|
+
if (parts.length && !resource)
|
|
57
|
+
return error(404, 'not_found', 'Resource not found.');
|
|
58
|
+
try {
|
|
59
|
+
let body;
|
|
60
|
+
let mediaType = 'application/json';
|
|
61
|
+
if (!parts.length)
|
|
62
|
+
body = JSON.stringify({ schemaVersion: 1, revision: selected.revision, publication: selected.publication, resources: selected.resources });
|
|
63
|
+
else if (parts.length === 1)
|
|
64
|
+
body = JSON.stringify({ schemaVersion: 1, revision: selected.revision, publication: selected.publication, ...resource });
|
|
65
|
+
else {
|
|
66
|
+
const format = parts[2];
|
|
67
|
+
if (!['html', 'markdown'].includes(format) || !resource.resource.representations[format])
|
|
68
|
+
return error(406, 'unsupported_format', 'Representation not available.');
|
|
69
|
+
const descriptor = resource.resource.representations[format];
|
|
70
|
+
const result = await selected.source.get(resource.resource.ref, { format, signal: request.signal });
|
|
71
|
+
request.signal.throwIfAborted();
|
|
72
|
+
if (result.revision !== selected.revision)
|
|
73
|
+
return error(409, 'stale_revision', 'The requested revision is no longer available.');
|
|
74
|
+
if (typeof result.content !== 'string' || contentRefKey(result.resource.ref) !== contentRefKey(resource.resource.ref) || result.format !== format || !same(result.resource, resource.resource) || !same(result.representation, descriptor))
|
|
75
|
+
return error(500, 'internal_error', 'Content source returned inconsistent metadata.');
|
|
76
|
+
const bytes = encoder.encode(result.content);
|
|
77
|
+
if (bytes.byteLength !== descriptor.bytes || await digest(bytes) !== descriptor.digest)
|
|
78
|
+
return error(409, 'stale_revision', 'The requested revision bytes are no longer available.');
|
|
79
|
+
body = result.content;
|
|
80
|
+
mediaType = descriptor.mediaType;
|
|
81
|
+
}
|
|
82
|
+
const bytes = encoder.encode(body);
|
|
83
|
+
const etag = `"sha256-${await digest(bytes)}"`;
|
|
84
|
+
request.signal.throwIfAborted();
|
|
85
|
+
const headers = { 'Content-Type': `${mediaType}; charset=utf-8`, 'Content-Length': String(bytes.byteLength), ETag: etag, 'Cache-Control': revision === null ? 'no-cache' : 'public, max-age=31536000, immutable' };
|
|
86
|
+
const condition = request.headers.get('if-none-match');
|
|
87
|
+
const matches = condition?.trim() === '*' || condition?.split(',').some(value => value.trim().replace(/^W\//, '') === etag);
|
|
88
|
+
if (matches)
|
|
89
|
+
return new Response(null, { status: 304, headers });
|
|
90
|
+
return new Response(head ? null : body, { status: 200, headers });
|
|
91
|
+
}
|
|
92
|
+
catch (cause) {
|
|
93
|
+
request.signal.throwIfAborted();
|
|
94
|
+
if (cause instanceof ContentDeliveryError && ['stale_revision', 'not_found'].includes(cause.code))
|
|
95
|
+
return error(409, 'stale_revision', 'The requested revision is no longer available.');
|
|
96
|
+
return error(500, 'internal_error', 'Content source could not serve the request.');
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
}
|
package/dist/content-source.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { AuthoredChange, ResolvedChange } from './authored-change.js';
|
|
2
2
|
import type { SubjectDeprecation, ResolvedSubjectDeprecation } from './subject-deprecation.js';
|
|
3
3
|
import type { ReaderSelection } from './reader-filters.js';
|
|
4
|
+
import type { StandaloneSectionDeclaration } from './section-identity.js';
|
|
4
5
|
export type Variant = {
|
|
5
6
|
channel: string;
|
|
6
7
|
version: string;
|
|
@@ -32,7 +33,7 @@ export type Frontmatter = {
|
|
|
32
33
|
product?: string;
|
|
33
34
|
area?: string | string[];
|
|
34
35
|
subarea?: string | string[];
|
|
35
|
-
standaloneSections?:
|
|
36
|
+
standaloneSections?: StandaloneSectionDeclaration[];
|
|
36
37
|
access?: {
|
|
37
38
|
policy: string;
|
|
38
39
|
};
|
|
@@ -45,6 +46,7 @@ export type Frontmatter = {
|
|
|
45
46
|
layout?: 'docs' | 'wide' | 'landing' | 'splash';
|
|
46
47
|
image?: string;
|
|
47
48
|
showNavigationTabs?: boolean;
|
|
49
|
+
showVerified?: boolean;
|
|
48
50
|
[key: string]: unknown;
|
|
49
51
|
};
|
|
50
52
|
export type Document = {
|
package/dist/frontmatter.js
CHANGED
|
@@ -2,8 +2,9 @@ import { validateAuthoredChange, changeKinds } from './authored-change.js';
|
|
|
2
2
|
import { validateSubjectDeprecation } from './subject-deprecation.js';
|
|
3
3
|
import { validateCardMetadata } from './cards.js';
|
|
4
4
|
import { resolveTaxonomy, validateTaxonomy, taxonomySchema } from './taxonomy.js';
|
|
5
|
+
import { validateStandaloneSectionDeclarations } from './section-identity.js';
|
|
5
6
|
export const nativePublicFields = new Set(['id', 'title', 'type', 'summary', 'description', 'language', 'locale', 'date', 'updated', 'verified', 'releaseVersion', 'product', 'area', 'subarea', 'operations', 'relatedApiReferences', 'audiences', 'readerFilters', 'tags', 'subjectLifecycle', 'subjectDeprecation', 'change', 'lifecycle']);
|
|
6
|
-
export const protectedMetadataRoots = new Set(['standaloneSections', 'access', 'seo', 'markdownMetadata', 'variables', 'tocDepth', 'layout', 'showNavigationTabs', 'image', 'private', 'internal', 'card', 'displayText', 'publications']);
|
|
7
|
+
export const protectedMetadataRoots = new Set(['standaloneSections', 'access', 'seo', 'markdownMetadata', 'variables', 'tocDepth', 'layout', 'showNavigationTabs', 'showVerified', 'image', 'private', 'internal', 'card', 'displayText', 'publications']);
|
|
7
8
|
const fieldTypes = new Set(['string', 'number', 'integer', 'boolean', 'object', 'array']);
|
|
8
9
|
const safePath = (value) => /^[A-Za-z_][A-Za-z0-9_-]*(?:\.[A-Za-z_][A-Za-z0-9_-]*)*$/.test(value);
|
|
9
10
|
const object = (value) => !!value && typeof value === 'object' && !Array.isArray(value);
|
|
@@ -116,10 +117,7 @@ export function validateFrontmatter(frontmatter, config, file, source) {
|
|
|
116
117
|
validateCardMetadata(frontmatter.card, file);
|
|
117
118
|
validateSubjectDeprecation(frontmatter.subjectDeprecation, file);
|
|
118
119
|
validateAuthoredChange(frontmatter, file);
|
|
119
|
-
|
|
120
|
-
if (standalone !== undefined && (!Array.isArray(standalone) || standalone.some(title => typeof title !== 'string' || !title.trim() || title !== title.trim()) || new Set(standalone).size !== standalone.length)) {
|
|
121
|
-
throw new Error(`${file}: standaloneSections must be a unique list of nonempty, trimmed heading titles`);
|
|
122
|
-
}
|
|
120
|
+
validateStandaloneSectionDeclarations(frontmatter.standaloneSections, file);
|
|
123
121
|
const mode = config?.changelogIdentifier ?? 'either';
|
|
124
122
|
if (frontmatter.type === 'changelog-entry' &&
|
|
125
123
|
(mode === 'date' && frontmatter.date === undefined || mode === 'version' && frontmatter.releaseVersion === undefined || mode === 'either' && frontmatter.date === undefined && frontmatter.releaseVersion === undefined))
|
|
@@ -180,6 +178,8 @@ export function validateFrontmatter(frontmatter, config, file, source) {
|
|
|
180
178
|
throw new Error(`${file}: tocDepth must be 0 or an integer from 2 through 6`);
|
|
181
179
|
if (frontmatter.layout !== undefined && !['docs', 'wide', 'landing', 'splash'].includes(frontmatter.layout))
|
|
182
180
|
throw new Error(`${file}: layout must be docs, wide, landing, or splash`);
|
|
181
|
+
if (frontmatter.showVerified !== undefined && typeof frontmatter.showVerified !== 'boolean')
|
|
182
|
+
throw new Error(`${file}: showVerified must be true or false`);
|
|
183
183
|
if (frontmatter.showNavigationTabs !== undefined && typeof frontmatter.showNavigationTabs !== 'boolean')
|
|
184
184
|
throw new Error(`${file}: showNavigationTabs must be true or false`);
|
|
185
185
|
if (frontmatter.image !== undefined && (typeof frontmatter.image !== 'string' || !/\.(?:png|jpe?g)$/i.test(frontmatter.image) || /^[a-z][a-z0-9+.-]*:/i.test(frontmatter.image)))
|
|
@@ -245,8 +245,9 @@ export function frontmatterSchema(config) {
|
|
|
245
245
|
subjectDeprecation: { type: 'object', minProperties: 1, additionalProperties: false, description: 'Page-specific subject retirement dates and stable document IDs; requires deprecated subjectLifecycle, including OpenAPI inference. Dates do not change status automatically.', properties: { deprecatedOn: { type: 'string', format: 'date' }, sunsetOn: { type: 'string', format: 'date' }, replacement: { type: 'string', minLength: 1 }, migration: { type: 'string', minLength: 1 } } },
|
|
246
246
|
subjectLifecycle: { type: 'string', enum: ['deprecated', 'in-development', 'beta', 'experimental'], description: 'Displays a content status badge in the page and navigation.' },
|
|
247
247
|
lifecycle: { type: 'string', minLength: 1, description: 'Editorial lifecycle of the document; validated by an opted-in external vocabulary.' },
|
|
248
|
-
standaloneSections: { type: 'array', items: { type: 'string' }, uniqueItems: true, description: 'Page-specific
|
|
248
|
+
standaloneSections: { type: 'array', items: { oneOf: [{ type: 'string', minLength: 1 }, { type: 'object', additionalProperties: false, required: ['id', 'context'], properties: { id: { type: 'string', pattern: '^[a-z0-9][a-z0-9-]{0,511}$' }, context: { const: 'section' } } }] }, uniqueItems: true, description: 'Page-specific standalone approval: a heading title approves contextual discovery with the page introduction; {id, context: section} approves the root level-two section slice for content export. IDs may be derived or explicitly anchored. Omitted means full-page reads.' },
|
|
249
249
|
tocDepth: { type: 'integer', enum: [0, 2, 3, 4, 5, 6], description: 'Deepest heading shown in the page table of contents; 0 hides it. Defaults to 3.' },
|
|
250
|
+
showVerified: { type: 'boolean', description: 'Show an authored verified date in the HTML page header. Defaults to true. False hides it only in HTML; Markdown and manifest dates remain unchanged. Does not assign a verification date.' },
|
|
250
251
|
showNavigationTabs: { type: 'boolean', description: 'Show the HTML navigation section bar when tabs are configured. Defaults to true for docs and wide layouts, false for landing and splash. Does not change agent discovery or section membership.' },
|
|
251
252
|
image: { type: 'string', pattern: '\\.(?:png|jpe?g)$', description: 'Local .png or .jpg shown in link previews for this page, relative to the page file; replaces the generated share image. Best at 1200×630.' },
|
|
252
253
|
layout: { type: 'string', enum: ['docs', 'wide', 'landing', 'splash'], description: 'HTML page layout: docs (default), wide (no page outline, full content width), landing (no sidebar or outline; a Hero replaces the page header), or splash (full-bleed marketing page built from Hero and Section blocks).' },
|
|
@@ -319,7 +320,7 @@ export function frontmatterReference(config, sectionsOnly = false) {
|
|
|
319
320
|
'Language is the content language and selects a publication. Locale describes formatting conventions in the content and does not select a publication. `frontmatter.locales` can set a project locale for each language; pages may override it.', '',
|
|
320
321
|
'## Categorize, connect, and present the page', '',
|
|
321
322
|
'By default, `product`, `area`, and `subarea` are optional, nonempty string categories. They can come from project defaults and appear in the document manifest and search index. A matching configured reader facet makes each category a page filter; without that facet it remains taxonomy only. `operations` links a guide or reference to stable API operation IDs. On a guide or how-to page, `relatedApiReferences` names API reference page IDs to add cross links in both directions within the same publication. Tags help document collections find the page. Card settings change its preview when another page links to it; they do not change the destination. `subjectLifecycle` displays the status of the documented API or feature; `lifecycle` is available for the document\'s editorial state. `tocDepth` limits the HTML page table of contents; use 0 to hide it. `layout` picks the HTML page layout: `docs`, `wide`, `landing`, or `splash`. `showNavigationTabs` overrides the navigation tab bar for this page; landing and splash pages hide it by default. `image` replaces the generated link-preview image. `date` and `releaseVersion` can identify a changelog entry under the project\'s `frontmatter.changelogIdentifier` rule.', '',
|
|
322
|
-
...table, ...nativeRows(['product', 'area', 'subarea', 'operations', 'relatedApiReferences', 'tags', 'date', 'releaseVersion', 'subjectLifecycle', 'subjectDeprecation', 'change', 'lifecycle', 'card', 'standaloneSections', 'tocDepth', 'layout', 'showNavigationTabs', 'image']), '',
|
|
323
|
+
...table, ...nativeRows(['product', 'area', 'subarea', 'operations', 'relatedApiReferences', 'tags', 'date', 'releaseVersion', 'subjectLifecycle', 'subjectDeprecation', 'change', 'lifecycle', 'card', 'standaloneSections', 'tocDepth', 'layout', 'showNavigationTabs', 'showVerified', 'image']), '',
|
|
323
324
|
'Projects can opt into built-in category enforcement with `frontmatter.taxonomy`. It declares areas and their allowed subareas, optional required fields, and optional explicit-field requirements for authored pages. Its `multiple` list permits arrays for selected category fields; otherwise each field allows one value. Defaults and enforced values still apply; generated API references use resolved categories. Definitions appear in the document manifest and constrain the generated frontmatter schema. Omit taxonomy or set it to false to preserve optional category behavior.', '',
|
|
324
325
|
'## Control output details', '',
|
|
325
326
|
'`seo` changes the browser title or HTML meta description. `displayText` overrides the text chosen for one output surface on this page. `variables` supplies values to registered snippets before they expand into the page body. `markdownMetadata` selects or overrides the YAML preamble of the generated Markdown twin—the machine-readable copy of this page. That generated preamble is separate from the source frontmatter shown above.', '',
|
package/dist/html-renderer.js
CHANGED
|
@@ -15,6 +15,7 @@ export function renderHtmlWithHeadings(tree, highlighter, reservedIds = []) {
|
|
|
15
15
|
const hast = html.runSync(tree);
|
|
16
16
|
const used = new Set(reservedIds);
|
|
17
17
|
const headings = [];
|
|
18
|
+
const aliases = new Map();
|
|
18
19
|
const textOf = (node) => node.type === 'text' ? node.value : (node.children ?? []).map(textOf).join('');
|
|
19
20
|
const checkUrl = (value) => {
|
|
20
21
|
if (typeof value !== 'string')
|
|
@@ -34,6 +35,11 @@ export function renderHtmlWithHeadings(tree, highlighter, reservedIds = []) {
|
|
|
34
35
|
};
|
|
35
36
|
collectIds(hast);
|
|
36
37
|
const visit = (node) => {
|
|
38
|
+
const sectionId = node.properties?.['data-shoalrun-section-id'] ?? node.properties?.dataShoalrunSectionId;
|
|
39
|
+
if (node.properties) {
|
|
40
|
+
delete node.properties['data-shoalrun-section-id'];
|
|
41
|
+
delete node.properties.dataShoalrunSectionId;
|
|
42
|
+
}
|
|
37
43
|
const match = /^h([2-6])$/.exec(node.tagName ?? '');
|
|
38
44
|
if (match && !node.properties?.className?.some((name) => ['tab-fallback-heading', 'walkthrough-step-title'].includes(name))) {
|
|
39
45
|
const title = textOf(node).trim();
|
|
@@ -50,11 +56,30 @@ export function renderHtmlWithHeadings(tree, highlighter, reservedIds = []) {
|
|
|
50
56
|
}
|
|
51
57
|
if (!node.properties?.className?.includes('doc-step-heading-toc-ignore'))
|
|
52
58
|
headings.push({ depth, id, title });
|
|
59
|
+
if (typeof sectionId === 'string' && sectionId !== id)
|
|
60
|
+
aliases.set(node, sectionId);
|
|
53
61
|
}
|
|
54
62
|
}
|
|
55
63
|
node.children?.forEach(visit);
|
|
56
64
|
};
|
|
57
65
|
visit(hast);
|
|
66
|
+
// Preserve existing heading-derived URLs. Explicit identities add aliases,
|
|
67
|
+
// rather than reserving names early and renumbering unrelated headings.
|
|
68
|
+
for (const id of aliases.values()) {
|
|
69
|
+
if (used.has(id))
|
|
70
|
+
throw new Error(`Explicit section ID ${id} conflicts with an existing HTML anchor`);
|
|
71
|
+
used.add(id);
|
|
72
|
+
}
|
|
73
|
+
const addAliases = (node) => {
|
|
74
|
+
if (!node.children)
|
|
75
|
+
return;
|
|
76
|
+
node.children = node.children.flatMap((child) => {
|
|
77
|
+
addAliases(child);
|
|
78
|
+
const id = aliases.get(child);
|
|
79
|
+
return id ? [{ type: 'element', tagName: 'span', properties: { id, ariaHidden: 'true' }, children: [] }, child] : [child];
|
|
80
|
+
});
|
|
81
|
+
};
|
|
82
|
+
addAliases(hast);
|
|
58
83
|
let rendered = String(html.stringify(hast));
|
|
59
84
|
for (const [marker, markup] of math)
|
|
60
85
|
rendered = rendered.replaceAll(marker, markup);
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type StandaloneSectionDeclaration } from './section-identity.js';
|
|
1
2
|
export type MarkdownSection = {
|
|
2
3
|
id: string;
|
|
3
4
|
title: string;
|
|
@@ -12,4 +13,4 @@ export type MarkdownSection = {
|
|
|
12
13
|
markdownTokenEncoding: string;
|
|
13
14
|
content: string;
|
|
14
15
|
};
|
|
15
|
-
export declare function markdownSections(tree: any, markdownUrl: string, parent: string, prefix: string, render: (tree: any) => string,
|
|
16
|
+
export declare function markdownSections(tree: any, markdownUrl: string, parent: string, prefix: string, render: (tree: any) => string, declarations?: StandaloneSectionDeclaration[]): MarkdownSection[];
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { createHash } from 'node:crypto';
|
|
2
2
|
import { markdownSize } from './markdown-size.js';
|
|
3
3
|
import { link, list, paragraph, renderMarkdown, root, text } from './semantic-markdown.js';
|
|
4
|
+
import { validateStandaloneSections } from './section-identity.js';
|
|
4
5
|
const digest = (value) => createHash('sha256').update(value).digest('hex');
|
|
5
6
|
const pathKey = (item) => `${item.type}:${item.id}`;
|
|
6
7
|
// Only direct page children qualify: hoisting a view from a ReaderSection, tab,
|
|
@@ -27,34 +28,23 @@ function missingReadingPath(all, included) {
|
|
|
27
28
|
const textOf = (node) => typeof node.value === 'string' ? node.value : (node.children ?? []).map(textOf).join('');
|
|
28
29
|
// Split the semantic root, never the flattened Markdown: a reader section or tab
|
|
29
30
|
// remains intact even when it contains headings. Nested headings are not resources.
|
|
30
|
-
export function markdownSections(tree, markdownUrl, parent, prefix, render,
|
|
31
|
+
export function markdownSections(tree, markdownUrl, parent, prefix, render, declarations = []) {
|
|
31
32
|
const parentSize = markdownSize(parent);
|
|
32
33
|
const children = tree.children ?? [];
|
|
33
|
-
const
|
|
34
|
+
const identities = validateStandaloneSections(tree, declarations, markdownUrl);
|
|
35
|
+
const starts = identities.map(section => section.start);
|
|
34
36
|
// Check declarations even on short pages: a renamed or ambiguous heading must
|
|
35
37
|
// never silently retain an author's approval. Titles refer to normalized text.
|
|
36
|
-
for (const title of standaloneTitles) {
|
|
37
|
-
if (starts.filter(start => textOf(children[start]).trim() === title).length !== 1) {
|
|
38
|
-
throw new Error(`${markdownUrl}: standaloneSections title "${title}" must match exactly one top-level level-two heading`);
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
38
|
if (parentSize.markdownTokens < 2000)
|
|
42
39
|
return [];
|
|
43
40
|
const preamble = children.slice(0, starts[0] ?? 0);
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
const title = textOf(children[start]).trim();
|
|
47
|
-
const stem = title.normalize('NFKD').toLowerCase().replace(/[\u0300-\u036f]/g, '').replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '').slice(0, 80).replace(/-$/, '') || 'section';
|
|
48
|
-
let id = stem;
|
|
49
|
-
for (let suffix = 2; used.has(id); suffix++)
|
|
50
|
-
id = `${stem}-${suffix}`;
|
|
51
|
-
used.add(id);
|
|
52
|
-
const end = children.findIndex((node, position) => position > start && node.type === 'heading' && node.depth <= 2);
|
|
53
|
-
const nodes = [...preamble, ...children.slice(start, end === -1 ? children.length : end)].filter(node => !['definition', 'footnoteDefinition'].includes(node.type));
|
|
41
|
+
return identities.map(({ start, end, id, title }) => {
|
|
42
|
+
const nodes = [...preamble, ...children.slice(start, end)].filter(node => !['definition', 'footnoteDefinition'].includes(node.type));
|
|
54
43
|
const definitions = children.filter(node => ['definition', 'footnoteDefinition'].includes(node.type));
|
|
55
44
|
const content = prefix + renderMarkdown(root([paragraph(text('Parent page: '), link(markdownUrl, 'Full Markdown page'))])) + '\n'
|
|
56
45
|
+ render(root([...missingReadingPath(children, nodes), ...nodes, ...definitions]));
|
|
57
|
-
|
|
46
|
+
const standalone = declarations.some(item => typeof item === 'string' ? item === title : item.id === id);
|
|
47
|
+
return { id, title, depth: 2, ...(standalone ? { standalone: true } : {}), markdownUrl: `${markdownUrl.slice(0, -3)}.sections/${id}.md`, parentMarkdownUrl: markdownUrl,
|
|
58
48
|
parentDigest: digest(parent), digest: digest(content), ...markdownSize(content), content };
|
|
59
49
|
}).filter(section => section.markdownTokens <= parentSize.markdownTokens / 2 && section.markdownBytes <= parentSize.markdownBytes / 2);
|
|
60
50
|
}
|
package/dist/page-renderer.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { resolveAgentDiscovery } from './agent-discovery.js';
|
|
2
|
-
import { navigationCounts } from './compiler-config.js';
|
|
2
|
+
import { navigationCounts, navigationHeading } from './compiler-config.js';
|
|
3
3
|
import { navigationTab } from './project-navigation.js';
|
|
4
4
|
import { highlightedExample } from './highlight.js';
|
|
5
5
|
import { readerFilterAttribute } from './reader-filters.js';
|
|
@@ -81,8 +81,9 @@ export function buildHtmlPage(doc, tree, markdownUrl, siteTitle, navigation, exa
|
|
|
81
81
|
const date = doc.frontmatter.date ? `<time class="page-date" datetime="${esc(doc.frontmatter.date)}">${esc(doc.frontmatter.date)}</time>` : '';
|
|
82
82
|
const changelogEntry = doc.frontmatter.type === 'changelog-entry';
|
|
83
83
|
const updated = doc.frontmatter.updated && !(changelogEntry && doc.frontmatter.date) ? `<span class="page-date">Updated <time datetime="${esc(doc.frontmatter.updated)}">${esc(doc.frontmatter.updated.slice(0, 10))}</time></span>` : '';
|
|
84
|
+
const verified = doc.frontmatter.verified && doc.frontmatter.showVerified !== false ? `<span class="page-date">Verified <time datetime="${esc(doc.frontmatter.verified)}">${esc(doc.frontmatter.verified)}</time></span>` : '';
|
|
84
85
|
const releaseVersion = doc.frontmatter.releaseVersion ? `<span class="page-version">Version ${esc(doc.frontmatter.releaseVersion)}</span>` : '';
|
|
85
|
-
const pageDates = changelogEntry ? `<div class="page-meta">${date}${updated}${releaseVersion}</div>` : `${date}${updated}${releaseVersion}`;
|
|
86
|
+
const pageDates = changelogEntry ? `<div class="page-meta">${date}${updated}${verified}${releaseVersion}</div>` : `${date}${updated}${verified}${releaseVersion}`;
|
|
86
87
|
const containsCurrent = (item) => item.route === doc.route || !!item.items?.some(containsCurrent);
|
|
87
88
|
const activeTab = navigationTab(navigation, doc.route);
|
|
88
89
|
const analyticsBootstrap = config.analytics ? `<script type="application/json" id="analytics-config">${JSON.stringify({ pageId: doc.frontmatter.id, pageUrl: doc.route, documentType: doc.frontmatter.type, channel: doc.variant.channel, version: doc.variant.version, language: doc.variant.language, ...(activeTab ? { navigationSectionId: activeTab.id } : {}) }).replaceAll('<', '\\u003c')}</script><script type="module" src="/assets/analytics.js"></script>` : '';
|
|
@@ -107,7 +108,8 @@ export function buildHtmlPage(doc, tree, markdownUrl, siteTitle, navigation, exa
|
|
|
107
108
|
return `<li class="nav-entry${deprecated ? ' nav-deprecated' : ''}" data-search="${esc(search)}"${config.readerFilters ? ` data-reader-filters="${esc(readerFilterAttribute(item.readerFilters ?? {}))}"` : ''}><a href="${esc(item.route)}"${item.route === doc.route ? ' aria-current="page"' : ''}>${method}${label}${status}</a></li>`;
|
|
108
109
|
}).join('');
|
|
109
110
|
const readerControls = readerConfig?.controls.sidebar ? readerFilterSlot({ display: 'checks', label: 'Reader view' }, esc) : '';
|
|
110
|
-
const
|
|
111
|
+
const heading = navigationHeading(config.outputs);
|
|
112
|
+
const nav = `<details class="nav-disclosure" open><summary>Menu</summary><div class="nav-body"><a class="region-return" href="#main" data-region-return>Back to article</a><div class="nav-tools">${heading === false ? '' : `<h2>${esc(heading)}</h2>`}${readerControls}<div class="nav-search" hidden><label for="nav-filter">Filter navigation</label><input id="nav-filter" type="search" autocomplete="off"><p class="nav-filter-status" role="status" aria-live="polite"></p></div></div><ul class="nav-tree">${renderNav(sidebarNavigation)}</ul></div></details>`;
|
|
111
113
|
const readerBanner = readerConfig ? '<aside class="reader-outside-view" role="status" hidden></aside>' : '';
|
|
112
114
|
const readerBootstrap = readerConfig ? `<script type="application/json" id="reader-filter-config">${JSON.stringify({ facets: readerConfig.facets, storageKey: `shoalrun-reader-view:${basePath || '/'}`, moduleUrl: readerConfig.module ? sitePath(basePath, '/assets/reader-view-custom.js') : null }).replaceAll('<', '\\u003c')}</script><script type="module" src="/assets/reader-filters.js"></script>` : '';
|
|
113
115
|
const groups = new Map();
|
package/dist/publication-plan.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { hasScaffoldPlaceholders } from './scaffold-placeholders.js';
|
|
1
2
|
import { matchesContentFilter } from './content-filter.js';
|
|
2
3
|
import { readdir } from 'node:fs/promises';
|
|
3
4
|
import path from 'node:path';
|
|
@@ -60,7 +61,7 @@ export async function planAuthoredDocuments({ base, basePath, config, variants,
|
|
|
60
61
|
const marks = pageReaderFilters(frontmatter, readerFilters, file);
|
|
61
62
|
if (!matchesContentFilter(marks, contentFilter))
|
|
62
63
|
continue;
|
|
63
|
-
if (
|
|
64
|
+
if (hasScaffoldPlaceholders(body) || /\[\[TODO:[\s\S]*?\]\]/.test(JSON.stringify(frontmatter)) || frontmatter.summary?.startsWith('Draft guide for '))
|
|
64
65
|
throw new Error(`${file}: unresolved scaffold placeholders cannot be published`);
|
|
65
66
|
for (const variant of variants) {
|
|
66
67
|
if (pagePublications ? !pagePublications.some(item => item.channel === variant.channel && item.version === variant.version) : (frontmatter.channel && frontmatter.channel !== variant.channel || frontmatter.version && frontmatter.version !== variant.version))
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { parse } from './semantic-markdown.js';
|
|
2
|
+
/** Fenced code are teaching examples, not scaffold prose. */
|
|
3
|
+
export function hasScaffoldPlaceholders(body) {
|
|
4
|
+
const placeholder = /\[\[TODO:[\s\S]*?\]\]/;
|
|
5
|
+
if (!placeholder.test(body))
|
|
6
|
+
return false;
|
|
7
|
+
const ranges = [];
|
|
8
|
+
const visit = (node) => {
|
|
9
|
+
if (node.type === 'code' && node.position)
|
|
10
|
+
ranges.push([node.position.start.offset, node.position.end.offset]);
|
|
11
|
+
else
|
|
12
|
+
for (const child of node.children ?? [])
|
|
13
|
+
visit(child);
|
|
14
|
+
};
|
|
15
|
+
visit(parse.parse(body));
|
|
16
|
+
for (const [start, end] of ranges.sort((a, b) => b[0] - a[0]))
|
|
17
|
+
body = body.slice(0, start) + ' '.repeat(end - start) + body.slice(end);
|
|
18
|
+
return placeholder.test(body);
|
|
19
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/** Section identities are independent of a representation's DOM anchors. */
|
|
2
|
+
export type StandaloneSectionDeclaration = string | {
|
|
3
|
+
id: string;
|
|
4
|
+
context: 'section';
|
|
5
|
+
};
|
|
6
|
+
export type SectionIdentity = {
|
|
7
|
+
id: string;
|
|
8
|
+
title: string;
|
|
9
|
+
depth: 2;
|
|
10
|
+
start: number;
|
|
11
|
+
end: number;
|
|
12
|
+
explicit: boolean;
|
|
13
|
+
};
|
|
14
|
+
export declare const validSectionId: (value: unknown) => value is string;
|
|
15
|
+
/** Consume only a direct trailing marker on an authored root H2. Code stays inert. */
|
|
16
|
+
export declare function lowerSectionMarkers(tree: any, label?: string): any;
|
|
17
|
+
export declare function sectionIdentities(tree: any, label?: string): SectionIdentity[];
|
|
18
|
+
export declare function validateStandaloneSectionDeclarations(value: unknown, label?: string): asserts value is StandaloneSectionDeclaration[] | undefined;
|
|
19
|
+
export declare function validateStandaloneSections(tree: any, declarations?: StandaloneSectionDeclaration[], label?: string): SectionIdentity[];
|
|
20
|
+
/** Slice-only delivery is a distinct author approval from contextual discovery. */
|
|
21
|
+
export declare function selectSection(tree: any, id: string, declarations?: StandaloneSectionDeclaration[], label?: string): {
|
|
22
|
+
section: SectionIdentity;
|
|
23
|
+
tree: any;
|
|
24
|
+
};
|