blume 2.0.1 → 2.0.3
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 +147 -0
- package/dist/cli/{chunk-qwsrynx5.js → chunk-1d7ve1dm.js} +38 -19
- package/dist/cli/{chunk-qwsrynx5.js.map → chunk-1d7ve1dm.js.map} +4 -4
- package/dist/cli/{chunk-s6jhgk0q.js → chunk-2eytanqx.js} +2 -2
- package/dist/cli/{chunk-kdp5q7ke.js → chunk-2hsdwb9n.js} +18 -19
- package/dist/cli/{chunk-kdp5q7ke.js.map → chunk-2hsdwb9n.js.map} +2 -2
- package/dist/cli/{chunk-hdpx1tax.js → chunk-2z928egk.js} +5 -5
- package/dist/cli/{chunk-jwyddg7y.js → chunk-35d4wj9f.js} +30 -24
- package/dist/cli/chunk-35d4wj9f.js.map +15 -0
- package/dist/cli/{chunk-ah61y8py.js → chunk-364znk6q.js} +2 -2
- package/dist/cli/{chunk-jts8mvcz.js → chunk-3em5wd2y.js} +25 -6
- package/dist/cli/{chunk-jts8mvcz.js.map → chunk-3em5wd2y.js.map} +3 -3
- package/dist/cli/{chunk-s1p84fyh.js → chunk-5f86nr5m.js} +63 -22
- package/dist/cli/chunk-5f86nr5m.js.map +11 -0
- package/dist/cli/{chunk-2hn4b8z7.js → chunk-5m5nmvyq.js} +170 -76
- package/dist/cli/chunk-5m5nmvyq.js.map +13 -0
- package/dist/cli/{chunk-epjnccmv.js → chunk-5xvm6tfj.js} +18 -15
- package/dist/cli/chunk-5xvm6tfj.js.map +10 -0
- package/dist/cli/{chunk-fz5wtpmh.js → chunk-6dsbexzp.js} +18 -15
- package/dist/cli/chunk-6dsbexzp.js.map +10 -0
- package/dist/cli/{chunk-vtk4a6dg.js → chunk-8ktnccpt.js} +1 -1
- package/dist/cli/{chunk-6hsn950k.js → chunk-9t7a85s3.js} +178 -48
- package/dist/cli/chunk-9t7a85s3.js.map +10 -0
- package/dist/cli/{chunk-fxypxtvm.js → chunk-a58773jm.js} +2 -2
- package/dist/cli/{chunk-mb2919y2.js → chunk-acanzt5p.js} +17 -6
- package/dist/cli/chunk-acanzt5p.js.map +10 -0
- package/dist/cli/{chunk-27g6wdth.js → chunk-akbpwfxc.js} +90 -26
- package/dist/cli/chunk-akbpwfxc.js.map +10 -0
- package/dist/cli/{chunk-wm7js3j9.js → chunk-b07cmahc.js} +2 -2
- package/dist/cli/{chunk-wgm7m9qk.js → chunk-c8chx29p.js} +179 -51
- package/dist/cli/chunk-c8chx29p.js.map +36 -0
- package/dist/cli/{chunk-qs4q5p4e.js → chunk-crgn1q09.js} +32 -14
- package/dist/cli/chunk-crgn1q09.js.map +10 -0
- package/dist/cli/chunk-e04dxsz1.js +39 -0
- package/dist/cli/chunk-e04dxsz1.js.map +10 -0
- package/dist/cli/{chunk-yt5n7ppj.js → chunk-ey84smr6.js} +17 -7
- package/dist/cli/chunk-ey84smr6.js.map +10 -0
- package/dist/cli/{chunk-f2z5v128.js → chunk-g4hq16wv.js} +14 -15
- package/dist/cli/{chunk-f2z5v128.js.map → chunk-g4hq16wv.js.map} +2 -2
- package/dist/cli/{chunk-fa25z98p.js → chunk-ga0pf4aj.js} +9 -8
- package/dist/cli/chunk-ga0pf4aj.js.map +11 -0
- package/dist/cli/{chunk-kpf8rrjc.js → chunk-hm3vjy5s.js} +109 -58
- package/dist/cli/chunk-hm3vjy5s.js.map +19 -0
- package/dist/cli/{chunk-fs23ddbb.js → chunk-j85vccga.js} +625 -750
- package/dist/cli/chunk-j85vccga.js.map +36 -0
- package/dist/cli/{chunk-ch6g3ar0.js → chunk-p3v96n38.js} +6 -6
- package/dist/cli/{chunk-ch6g3ar0.js.map → chunk-p3v96n38.js.map} +3 -3
- package/dist/cli/{chunk-q5163e60.js → chunk-p73c0m7w.js} +21 -19
- package/dist/cli/chunk-p73c0m7w.js.map +11 -0
- package/dist/cli/{chunk-dh8cwk36.js → chunk-pehfxfta.js} +24 -9
- package/dist/cli/chunk-pehfxfta.js.map +10 -0
- package/dist/cli/{chunk-qkqwkpte.js → chunk-pv29h0wf.js} +3688 -1313
- package/dist/cli/chunk-pv29h0wf.js.map +190 -0
- package/dist/cli/{chunk-6vm74dry.js → chunk-q58y5e6a.js} +10 -10
- package/dist/cli/{chunk-6vm74dry.js.map → chunk-q58y5e6a.js.map} +3 -3
- package/dist/cli/{chunk-zxcczpyx.js → chunk-r20tn01b.js} +1 -1
- package/dist/cli/{chunk-m3vmjgmq.js → chunk-r9rcc4w7.js} +16 -8
- package/dist/cli/chunk-r9rcc4w7.js.map +10 -0
- package/dist/cli/{chunk-5shv93fd.js → chunk-tkacnehg.js} +2 -2
- package/dist/cli/{chunk-zxh4d9vy.js → chunk-tzmab476.js} +4 -4
- package/dist/cli/{chunk-yw7dm696.js → chunk-vg9r4eb9.js} +24 -11
- package/dist/cli/chunk-vg9r4eb9.js.map +14 -0
- package/dist/cli/{chunk-79jhk4py.js → chunk-wrr3j9w9.js} +260 -132
- package/dist/cli/chunk-wrr3j9w9.js.map +35 -0
- package/dist/cli/{chunk-6crbhc3x.js → chunk-yfyb25rh.js} +54 -27
- package/dist/cli/chunk-yfyb25rh.js.map +14 -0
- package/dist/cli/index.js +162 -35
- package/dist/cli/index.js.map +4 -4
- package/dist/types/ai/agent-surface.d.ts +32 -0
- package/dist/types/ai/api-catalog.d.ts +7 -1
- package/dist/types/ai/ask-context.d.ts +7 -0
- package/dist/types/ai/component-markdown.d.ts +4 -4
- package/dist/types/ai/link-headers.d.ts +8 -1
- package/dist/types/ai/openapi-components.d.ts +5 -2
- package/dist/types/ai/relative-links.d.ts +11 -4
- package/dist/types/ai/skills.d.ts +4 -1
- package/dist/types/ai/static-expression.d.ts +28 -0
- package/dist/types/ai/tar.d.ts +1 -3
- package/dist/types/analytics/databuddy.d.ts +43 -0
- package/dist/types/analytics/index.d.ts +4 -0
- package/dist/types/analytics/one-dollar-stats.d.ts +48 -0
- package/dist/types/analytics/schema.d.ts +28 -0
- package/dist/types/astro/integration.d.ts +3 -2
- package/dist/types/cli/env.d.ts +5 -0
- package/dist/types/cli/init/scaffold.d.ts +19 -3
- package/dist/types/cli/init/starter-spec.d.ts +11 -0
- package/dist/types/core/base-path.d.ts +24 -1
- package/dist/types/core/config-input.d.ts +13 -11
- package/dist/types/core/config.d.ts +2 -2
- package/dist/types/core/directive-diagnostics.d.ts +12 -0
- package/dist/types/core/graph.d.ts +2 -0
- package/dist/types/core/heading-markers.d.ts +5 -7
- package/dist/types/core/i18n-ui.d.ts +31 -0
- package/dist/types/core/i18n.d.ts +9 -1
- package/dist/types/core/last-modified.d.ts +10 -0
- package/dist/types/core/links.d.ts +3 -1
- package/dist/types/core/load-module.d.ts +10 -0
- package/dist/types/core/locale-links.d.ts +12 -2
- package/dist/types/core/meta.d.ts +13 -1
- package/dist/types/core/nav-diagnostics.d.ts +10 -0
- package/dist/types/core/navigation.d.ts +38 -0
- package/dist/types/core/ordering-prefix.d.ts +4 -0
- package/dist/types/core/safe-links.d.ts +3 -1
- package/dist/types/core/schema.d.ts +59 -13
- package/dist/types/core/sources/github-releases.d.ts +5 -0
- package/dist/types/core/sources/lower.d.ts +38 -13
- package/dist/types/core/sources/normalize.d.ts +23 -2
- package/dist/types/core/sources/remote.d.ts +11 -1
- package/dist/types/core/sources/resolve.d.ts +12 -0
- package/dist/types/core/sources/types.d.ts +31 -0
- package/dist/types/core/sources/watch.d.ts +12 -5
- package/dist/types/core/standard-schema.d.ts +5 -0
- package/dist/types/core/types.d.ts +9 -0
- package/dist/types/deploy/adapters/node.d.ts +5 -2
- package/dist/types/deploy/adapters/types.d.ts +7 -0
- package/dist/types/deploy/artifacts.d.ts +6 -4
- package/dist/types/deploy/cloudflare-negotiation.d.ts +3 -2
- package/dist/types/deploy/headers.d.ts +36 -7
- package/dist/types/deploy/node-headers.d.ts +43 -8
- package/dist/types/deploy/platforms/netlify.d.ts +27 -2
- package/dist/types/deploy/platforms/node.d.ts +6 -5
- package/dist/types/deploy/platforms/types.d.ts +15 -0
- package/dist/types/deploy/platforms/vercel.d.ts +3 -2
- package/dist/types/deploy/redirects.d.ts +31 -15
- package/dist/types/deploy/vercel-negotiation.d.ts +3 -2
- package/dist/types/markdown/directives.d.ts +62 -0
- package/dist/types/markdown/features.d.ts +21 -0
- package/dist/types/markdown/mdast.d.ts +63 -0
- package/dist/types/openapi/asyncapi.d.ts +4 -2
- package/dist/types/openapi/model.d.ts +20 -6
- package/dist/types/search/sync/algolia.d.ts +3 -1
- package/docs/01-quickstart.mdx +3 -2
- package/docs/02-deployment.mdx +20 -9
- package/docs/08-faq.mdx +10 -3
- package/docs/advanced/changelog.mdx +1 -1
- package/docs/advanced/custom-pages.mdx +7 -5
- package/docs/cli/audit.mdx +19 -3
- package/docs/cli/doctor.mdx +2 -2
- package/docs/cli/evals.mdx +4 -4
- package/docs/cli/index.mdx +4 -1
- package/docs/cli/translate.mdx +4 -4
- package/docs/cli/version.mdx +1 -1
- package/docs/configuration/analytics.mdx +42 -2
- package/docs/configuration/assistant.mdx +1 -1
- package/docs/configuration/customization.mdx +6 -3
- package/docs/configuration/index.mdx +5 -3
- package/docs/configuration/search.mdx +2 -2
- package/docs/content/components.mdx +2 -2
- package/docs/content/frontmatter.mdx +5 -1
- package/docs/content/i18n.mdx +3 -1
- package/docs/content/index.mdx +8 -4
- package/docs/content/islands.mdx +1 -1
- package/docs/content/meta.mdx +7 -3
- package/docs/content/navigation.mdx +29 -4
- package/docs/content/sources.mdx +18 -16
- package/docs/content/syntax.mdx +14 -0
- package/docs/content/versioning.mdx +2 -1
- package/docs/discoverability/agent-discovery.mdx +22 -8
- package/docs/discoverability/index.mdx +4 -3
- package/docs/discoverability/llms-txt.mdx +2 -5
- package/docs/discoverability/markdown.mdx +6 -4
- package/docs/discoverability/mcp.mdx +4 -0
- package/docs/discoverability/metadata.mdx +3 -2
- package/docs/discoverability/open-graph.mdx +8 -4
- package/docs/discoverability/rss.mdx +4 -2
- package/docs/references/asyncapi.mdx +2 -2
- package/docs/references/graphql.mdx +2 -2
- package/docs/references/openapi.mdx +6 -4
- package/package.json +1 -1
- package/skills/blume-migrate/SKILL.md +6 -6
- package/skills/blume-migrate/references/docusaurus.md +6 -6
- package/skills/blume-migrate/references/fumadocs.md +1 -1
- package/skills/blume-migrate/references/mintlify.md +3 -3
- package/skills/blume-migrate/references/monorepo.md +1 -1
- package/skills/blume-migrate/references/nextra.md +2 -2
- package/skills/blume-migrate/references/starlight.md +5 -5
- package/skills/blume-migrate/scripts/mintlify-codemod.mjs +9 -4
- package/skills/blume-update-docs/references/audit-checklist.md +1 -1
- package/src/ai/agent-readability.ts +2 -2
- package/src/ai/agent-surface.ts +56 -0
- package/src/ai/ai-catalog.ts +6 -2
- package/src/ai/api/handlers.ts +2 -2
- package/src/ai/api/spec.ts +17 -4
- package/src/ai/api-catalog.ts +13 -3
- package/src/ai/ask-context.ts +14 -2
- package/src/ai/ask-data.ts +26 -12
- package/src/ai/changelog-markdown.ts +2 -2
- package/src/ai/component-markdown.ts +43 -37
- package/src/ai/link-headers.ts +22 -8
- package/src/ai/llms.ts +22 -10
- package/src/ai/markdown.ts +23 -11
- package/src/ai/mcp/discovery.ts +2 -2
- package/src/ai/mcp/query.ts +2 -2
- package/src/ai/mcp/server.ts +80 -9
- package/src/ai/openapi-components.ts +22 -6
- package/src/ai/relative-links.ts +99 -15
- package/src/ai/serializers.ts +2 -1
- package/src/ai/skills.ts +14 -1
- package/src/ai/static-expression.ts +416 -0
- package/src/ai/tar.ts +139 -12
- package/src/ai/visibility.ts +45 -14
- package/src/analytics/databuddy.ts +67 -0
- package/src/analytics/head.ts +8 -0
- package/src/analytics/index.ts +7 -0
- package/src/analytics/one-dollar-stats.ts +86 -0
- package/src/analytics/posthog.ts +21 -3
- package/src/analytics/schema.ts +4 -0
- package/src/astro/generate.ts +72 -28
- package/src/astro/include-hmr.ts +31 -12
- package/src/astro/include-refresh.ts +19 -8
- package/src/astro/integration.ts +108 -14
- package/src/astro/runtime-modules.ts +28 -13
- package/src/astro/templates.ts +139 -57
- package/src/audit/catalog.ts +2 -2
- package/src/audit/checks/assets.ts +25 -5
- package/src/audit/checks/content.ts +20 -2
- package/src/audit/checks/i18n.ts +46 -8
- package/src/audit/checks/indexability.ts +39 -19
- package/src/audit/checks/links.ts +22 -1
- package/src/audit/checks/llms.ts +6 -3
- package/src/audit/checks/network.ts +3 -1
- package/src/audit/checks/og-image.ts +10 -0
- package/src/audit/checks/robots.ts +6 -1
- package/src/audit/checks/sitemap.ts +56 -31
- package/src/audit/checks/social.ts +21 -2
- package/src/audit/crawl.ts +88 -15
- package/src/audit/graph.ts +3 -1
- package/src/audit/report.ts +54 -17
- package/src/audit/run.ts +13 -9
- package/src/audit/snapshot.ts +5 -0
- package/src/audit/types.ts +18 -0
- package/src/audit/url.ts +37 -6
- package/src/cli/args.ts +32 -0
- package/src/cli/build-failure.ts +50 -0
- package/src/cli/commands/audit.ts +11 -1
- package/src/cli/commands/build.ts +19 -5
- package/src/cli/commands/check.ts +2 -0
- package/src/cli/commands/doctor.ts +3 -1
- package/src/cli/commands/eval.ts +20 -18
- package/src/cli/commands/init.ts +15 -5
- package/src/cli/commands/preview.ts +15 -0
- package/src/cli/commands/sync.ts +2 -0
- package/src/cli/commands/translate.ts +18 -18
- package/src/cli/commands/upgrade.ts +11 -0
- package/src/cli/commands/validate.ts +3 -1
- package/src/cli/dev-lock.ts +157 -37
- package/src/cli/eject-scripts.ts +32 -7
- package/src/cli/env.ts +12 -1
- package/src/cli/init/scaffold.ts +117 -11
- package/src/cli/init/starter-spec.ts +235 -0
- package/src/cli/report-format.ts +11 -6
- package/src/components/colors.ts +19 -0
- package/src/components/content/AccordionItem.astro +26 -22
- package/src/components/content/Badge.astro +7 -12
- package/src/components/content/Card.astro +2 -2
- package/src/components/content/Component.astro +11 -5
- package/src/components/content/Expandable.astro +5 -1
- package/src/components/content/Frame.astro +2 -7
- package/src/components/content/GithubInfo.astro +12 -2
- package/src/components/content/Prompt.astro +2 -7
- package/src/components/content/Tab.astro +0 -1
- package/src/components/content/Tabs.astro +58 -5
- package/src/components/content/Tile.astro +1 -1
- package/src/components/content/Tooltip.astro +69 -7
- package/src/components/content/Tree.astro +7 -2
- package/src/components/content/TypeTable.astro +10 -5
- package/src/components/content/Update.astro +8 -2
- package/src/components/content/auto-type-table.ts +4 -1
- package/src/components/content/badge-color.ts +20 -0
- package/src/components/content/base-href.ts +14 -22
- package/src/components/content/inline-markdown.ts +27 -7
- package/src/components/copy-feedback.ts +35 -8
- package/src/components/islands/assistant.tsx +37 -6
- package/src/components/islands/base-path.ts +47 -10
- package/src/components/islands/hooks.ts +42 -21
- package/src/components/islands/webmcp.ts +16 -10
- package/src/components/layout/Banner.astro +23 -4
- package/src/components/layout/Breadcrumbs.astro +5 -2
- package/src/components/layout/DiscoveryLinks.astro +9 -5
- package/src/components/layout/Header.astro +23 -10
- package/src/components/layout/LanguageSwitcher.astro +2 -2
- package/src/components/layout/Logo.astro +5 -0
- package/src/components/layout/NavSelector.astro +8 -3
- package/src/components/layout/NavTabMenu.astro +133 -0
- package/src/components/layout/NavTree.astro +31 -12
- package/src/components/layout/NavTreeCache.astro +5 -2
- package/src/components/layout/NavTreeScript.astro +45 -5
- package/src/components/layout/PageActions.astro +4 -3
- package/src/components/layout/PageLayout.astro +28 -13
- package/src/components/layout/Pagination.astro +3 -3
- package/src/components/layout/ReferenceLayout.astro +4 -1
- package/src/components/layout/RootLayout.astro +25 -12
- package/src/components/layout/Search.astro +38 -14
- package/src/components/layout/VersionBanner.astro +2 -2
- package/src/components/layout/analytics-client.ts +14 -0
- package/src/components/layout/toc-active.ts +41 -0
- package/src/components/layout/toc-element.ts +8 -14
- package/src/components/openapi/ApiTagOperations.astro +2 -2
- package/src/components/openapi/AsyncApiOperation.astro +7 -4
- package/src/components/openapi/GraphqlChip.astro +2 -2
- package/src/components/openapi/GraphqlType.astro +11 -3
- package/src/components/openapi/MessageComposer.astro +1 -1
- package/src/components/openapi/Operation.astro +23 -5
- package/src/components/openapi/PanelTabs.astro +4 -1
- package/src/components/openapi/Playground.astro +8 -2
- package/src/components/openapi/RequestPanel.astro +13 -6
- package/src/components/openapi/SchemaProperty.astro +11 -33
- package/src/components/openapi/SchemaTable.astro +34 -52
- package/src/components/openapi/async.ts +38 -6
- package/src/components/openapi/helpers.ts +100 -12
- package/src/components/openapi/message-composer.ts +14 -1
- package/src/components/openapi/message-model.ts +12 -2
- package/src/components/openapi/message.ts +21 -3
- package/src/components/openapi/operation-model.ts +119 -17
- package/src/components/openapi/panel.ts +31 -5
- package/src/components/openapi/param-style.ts +181 -0
- package/src/components/openapi/playground-client.ts +110 -23
- package/src/components/openapi/playground-schema.ts +25 -6
- package/src/components/openapi/request.ts +192 -26
- package/src/components/openapi/schema-tree.ts +209 -0
- package/src/components/openapi/snippets.ts +113 -19
- package/src/components/openapi/validate-json.ts +1 -1
- package/src/components/openapi/ws-client.ts +18 -2
- package/src/core/base-path.ts +69 -8
- package/src/core/config-input.ts +13 -11
- package/src/core/config.ts +18 -4
- package/src/core/diagnostics.ts +219 -30
- package/src/core/directive-diagnostics.ts +99 -0
- package/src/core/frontmatter.ts +21 -18
- package/src/core/graph.ts +120 -11
- package/src/core/heading-markers.ts +5 -18
- package/src/core/i18n-ui.ts +39 -2
- package/src/core/i18n.ts +44 -5
- package/src/core/last-modified.ts +25 -3
- package/src/core/links.ts +7 -4
- package/src/core/load-module.ts +20 -0
- package/src/core/locale-links.ts +22 -18
- package/src/core/manifest.ts +3 -2
- package/src/core/meta.ts +89 -12
- package/src/core/nav-diagnostics.ts +56 -1
- package/src/core/navigation.ts +272 -83
- package/src/core/ordering-prefix.ts +27 -0
- package/src/core/project-graph.ts +50 -6
- package/src/core/safe-href.ts +53 -1
- package/src/core/safe-links.ts +11 -2
- package/src/core/schema.ts +94 -18
- package/src/core/sources/assets.ts +83 -41
- package/src/core/sources/contentful-rich-text.ts +28 -20
- package/src/core/sources/contentful.ts +25 -12
- package/src/core/sources/filesystem.ts +25 -3
- package/src/core/sources/github-releases.ts +131 -11
- package/src/core/sources/lexical.ts +30 -20
- package/src/core/sources/lower.ts +276 -56
- package/src/core/sources/mdx-remote.ts +52 -16
- package/src/core/sources/normalize.ts +456 -119
- package/src/core/sources/notion.ts +77 -34
- package/src/core/sources/obsidian.ts +40 -9
- package/src/core/sources/payload.ts +1 -0
- package/src/core/sources/portable-text.ts +85 -49
- package/src/core/sources/remote.ts +18 -2
- package/src/core/sources/resolve.ts +52 -33
- package/src/core/sources/sanity.ts +1 -0
- package/src/core/sources/strapi-blocks.ts +28 -19
- package/src/core/sources/strapi.ts +1 -0
- package/src/core/sources/types.ts +31 -0
- package/src/core/sources/watch.ts +20 -7
- package/src/core/standard-schema.ts +10 -6
- package/src/core/types.ts +9 -0
- package/src/core/ui-packs/ar.ts +13 -0
- package/src/core/ui-packs/bg.ts +13 -0
- package/src/core/ui-packs/bn.ts +13 -0
- package/src/core/ui-packs/ca.ts +13 -0
- package/src/core/ui-packs/cs.ts +13 -0
- package/src/core/ui-packs/da.ts +13 -0
- package/src/core/ui-packs/de.ts +13 -0
- package/src/core/ui-packs/el.ts +13 -0
- package/src/core/ui-packs/es.ts +13 -0
- package/src/core/ui-packs/fa.ts +13 -0
- package/src/core/ui-packs/fi.ts +13 -0
- package/src/core/ui-packs/fr.ts +13 -0
- package/src/core/ui-packs/he.ts +13 -0
- package/src/core/ui-packs/hi.ts +13 -0
- package/src/core/ui-packs/hr.ts +13 -0
- package/src/core/ui-packs/hu.ts +13 -0
- package/src/core/ui-packs/id.ts +13 -0
- package/src/core/ui-packs/it.ts +13 -0
- package/src/core/ui-packs/ja.ts +13 -0
- package/src/core/ui-packs/ko.ts +13 -0
- package/src/core/ui-packs/nl.ts +13 -0
- package/src/core/ui-packs/no.ts +13 -0
- package/src/core/ui-packs/pl.ts +13 -0
- package/src/core/ui-packs/pt-br.ts +13 -0
- package/src/core/ui-packs/pt.ts +13 -0
- package/src/core/ui-packs/ro.ts +13 -0
- package/src/core/ui-packs/ru.ts +13 -0
- package/src/core/ui-packs/sk.ts +13 -0
- package/src/core/ui-packs/sr.ts +13 -0
- package/src/core/ui-packs/sv.ts +13 -0
- package/src/core/ui-packs/th.ts +13 -0
- package/src/core/ui-packs/tr.ts +13 -0
- package/src/core/ui-packs/uk.ts +13 -0
- package/src/core/ui-packs/vi.ts +13 -0
- package/src/core/ui-packs/zh-tw.ts +13 -0
- package/src/core/ui-packs/zh.ts +13 -0
- package/src/core/version-cut.ts +69 -17
- package/src/core/versions.ts +4 -1
- package/src/deploy/adapters/node.ts +5 -2
- package/src/deploy/adapters/registry.ts +2 -1
- package/src/deploy/adapters/types.ts +13 -1
- package/src/deploy/artifacts.ts +52 -11
- package/src/deploy/cloudflare-negotiation.ts +27 -16
- package/src/deploy/headers.ts +75 -55
- package/src/deploy/node-headers.ts +148 -27
- package/src/deploy/platforms/cloudflare.ts +13 -3
- package/src/deploy/platforms/netlify.ts +83 -5
- package/src/deploy/platforms/node.ts +8 -5
- package/src/deploy/platforms/static.ts +2 -0
- package/src/deploy/platforms/types.ts +15 -0
- package/src/deploy/platforms/vercel.ts +10 -3
- package/src/deploy/redirects.ts +68 -21
- package/src/deploy/robots.ts +2 -2
- package/src/deploy/rss.ts +4 -3
- package/src/deploy/sitemap.ts +7 -5
- package/src/deploy/vercel-negotiation.ts +35 -3
- package/src/eval/agents.ts +10 -2
- package/src/eval/run.ts +25 -0
- package/src/markdown/base-links.ts +74 -29
- package/src/markdown/directives.ts +242 -36
- package/src/markdown/features.ts +17 -0
- package/src/markdown/index.ts +13 -9
- package/src/markdown/mdast.ts +5 -2
- package/src/markdown/relative-links.ts +15 -27
- package/src/markdown/route-snapshot.ts +37 -0
- package/src/og/card.ts +149 -9
- package/src/og/derive.ts +156 -5
- package/src/og/index.ts +1 -0
- package/src/openapi/asyncapi.ts +4 -2
- package/src/openapi/graphql-build.ts +28 -2
- package/src/openapi/model.ts +99 -27
- package/src/openapi/proxy.ts +63 -10
- package/src/openapi/render-mdx.ts +51 -3
- package/src/registry/eject.ts +297 -43
- package/src/search/adapters/version-scope.ts +30 -0
- package/src/search/documents.ts +89 -27
- package/src/search/popular.ts +2 -1
- package/src/search/sync/algolia.ts +36 -2
- package/src/seo/jsonld.ts +7 -3
- package/src/sources/registry.ts +5 -0
- package/src/theme/entry.ts +11 -4
- package/src/translate/agents.ts +6 -1
- package/src/translate/ledger.ts +26 -3
- package/src/translate/meta.ts +76 -24
- package/src/translate/run.ts +14 -8
- package/src/translate/validate.ts +14 -2
- package/src/translate/work-list.ts +91 -19
- package/src/upgrade/upgrade.ts +36 -4
- package/dist/cli/chunk-27g6wdth.js.map +0 -10
- package/dist/cli/chunk-2hn4b8z7.js.map +0 -12
- package/dist/cli/chunk-6crbhc3x.js.map +0 -14
- package/dist/cli/chunk-6hsn950k.js.map +0 -10
- package/dist/cli/chunk-79jhk4py.js.map +0 -35
- package/dist/cli/chunk-82bbrxdn.js +0 -51
- package/dist/cli/chunk-82bbrxdn.js.map +0 -10
- package/dist/cli/chunk-abh8yjkn.js +0 -31
- package/dist/cli/chunk-abh8yjkn.js.map +0 -10
- package/dist/cli/chunk-ce574jw2.js +0 -23
- package/dist/cli/chunk-ce574jw2.js.map +0 -10
- package/dist/cli/chunk-dh8cwk36.js.map +0 -10
- package/dist/cli/chunk-epjnccmv.js.map +0 -10
- package/dist/cli/chunk-fa25z98p.js.map +0 -11
- package/dist/cli/chunk-fs23ddbb.js.map +0 -35
- package/dist/cli/chunk-fz5wtpmh.js.map +0 -10
- package/dist/cli/chunk-jwyddg7y.js.map +0 -15
- package/dist/cli/chunk-kpf8rrjc.js.map +0 -19
- package/dist/cli/chunk-m3vmjgmq.js.map +0 -10
- package/dist/cli/chunk-mb2919y2.js.map +0 -10
- package/dist/cli/chunk-q5163e60.js.map +0 -11
- package/dist/cli/chunk-qkqwkpte.js.map +0 -182
- package/dist/cli/chunk-qs4q5p4e.js.map +0 -10
- package/dist/cli/chunk-s1p84fyh.js.map +0 -10
- package/dist/cli/chunk-wgm7m9qk.js.map +0 -36
- package/dist/cli/chunk-yt5n7ppj.js.map +0 -10
- package/dist/cli/chunk-yw7dm696.js.map +0 -14
- /package/dist/cli/{chunk-s6jhgk0q.js.map → chunk-2eytanqx.js.map} +0 -0
- /package/dist/cli/{chunk-hdpx1tax.js.map → chunk-2z928egk.js.map} +0 -0
- /package/dist/cli/{chunk-ah61y8py.js.map → chunk-364znk6q.js.map} +0 -0
- /package/dist/cli/{chunk-vtk4a6dg.js.map → chunk-8ktnccpt.js.map} +0 -0
- /package/dist/cli/{chunk-fxypxtvm.js.map → chunk-a58773jm.js.map} +0 -0
- /package/dist/cli/{chunk-wm7js3j9.js.map → chunk-b07cmahc.js.map} +0 -0
- /package/dist/cli/{chunk-zxcczpyx.js.map → chunk-r20tn01b.js.map} +0 -0
- /package/dist/cli/{chunk-5shv93fd.js.map → chunk-tkacnehg.js.map} +0 -0
- /package/dist/cli/{chunk-zxh4d9vy.js.map → chunk-tzmab476.js.map} +0 -0
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/core/manifest.ts", "../src/core/project-graph.ts", "../src/core/directive-diagnostics.ts", "../src/markdown/directives.ts", "../src/theme/icon-kind.ts", "../src/theme/icons.ts", "../src/core/nav-diagnostics.ts", "../src/core/graph.ts", "../src/core/last-modified.ts", "../src/core/meta.ts", "../src/core/sources/resolve.ts", "../src/core/sources/json.ts", "../src/core/sources/contentful-rich-text.ts", "../src/core/sources/remote.ts", "../src/core/sources/contentful.ts", "../src/core/sources/filesystem.ts", "../src/core/sources/github-releases.ts", "../src/core/sources/mdx-remote.ts", "../src/core/sources/notion.ts", "../src/core/sources/assets.ts", "../src/core/sources/obsidian.ts", "../src/core/sources/lexical.ts", "../src/core/sources/payload.ts", "../src/core/sources/sanity.ts", "../src/core/sources/portable-text.ts", "../src/core/sources/strapi-blocks.ts", "../src/core/sources/strapi.ts"],
|
|
4
|
+
"sourcesContent": [
|
|
5
|
+
"import { mountBasePath } from \"./base-path.ts\";\nimport { localizeRoute, resolveFallbackLocale } from \"./i18n.ts\";\nimport type { ResolvedConfig } from \"./schema.ts\";\nimport type {\n BlumeManifest,\n ContentGraph,\n PageRecord,\n ProjectContext,\n RouteAlternate,\n RouteManifestEntry,\n VersionAlternate,\n} from \"./types.ts\";\nimport { getBlumeVersion } from \"./version.ts\";\n\n/**\n * Key for the same logical page across versions within one locale: the\n * version- and locale-agnostic route plus the locale code. NUL never appears\n * in either part, so the join is unambiguous.\n */\nconst versionAlternateKey = (versionKey: string, locale: string): string =>\n `${versionKey}\\u0000${locale}`;\n\n/**\n * Get-or-create the shared alternate list for a (versionKey, locale) pair.\n * Lists are attached to route entries by reference and mutated as fallback\n * routes materialize, then sorted once at the end — every holder sees the\n * final list.\n */\nconst versionAlternatesFor = (\n byKey: Map<string, VersionAlternate[]>,\n versionKey: string,\n locale: string\n): VersionAlternate[] => {\n const key = versionAlternateKey(versionKey, locale);\n const existing = byKey.get(key);\n if (existing) {\n return existing;\n }\n const list: VersionAlternate[] = [];\n byKey.set(key, list);\n return list;\n};\n\n/** The current manifest schema version. */\nexport const MANIFEST_VERSION = 1;\n\n/**\n * Whether a page may be indexed on its own merits — not author-excluded and not\n * hidden (unless hidden pages are opted in). This is independent of whether the\n * site search provider is enabled, so features like the MCP server can index\n * docs even when on-page search is off.\n */\nexport const contentIndexable = (\n page: PageRecord,\n config: ResolvedConfig\n): boolean =>\n !page.meta.search.exclude &&\n (!page.meta.sidebar.hidden || config.search.indexing.includeHiddenPages);\n\n/**\n * Fallback materialization: render the fallback locale's content at the\n * localized URL for any translation a non-default locale is missing, so static\n * output is fully prerendered (render-fallback, no client redirect). Fallback\n * routes are not indexed and carry no `hreflang` of their own.\n */\n/**\n * The description a page's `<head>` emits — `seo.description` over the front\n * matter `description` — mirrored onto its route so the OG card's subtitle\n * matches the page's `og:description`.\n */\nconst routeDescription = (page: PageRecord): string | undefined =>\n page.meta.seo.description ?? page.description;\n\nconst buildFallbackRoutes = (\n graph: ContentGraph,\n i18n: NonNullable<ResolvedConfig[\"i18n\"]>,\n alternatesByKey: Map<string, RouteAlternate[]>,\n basePath: string,\n versionAlternatesByKey: Map<string, VersionAlternate[]> | undefined\n): RouteManifestEntry[] => {\n const fallback = resolveFallbackLocale(i18n);\n if (!fallback) {\n return [];\n }\n // A monolingual page (a GitHub release) publishes in one language only, so it\n // gets no copy at every other locale's URL: those would be duplicates of the\n // same text under a translated chrome.\n const fallbackPages = new Map(\n graph.pages.flatMap((page) =>\n page.locale === fallback && !page.monolingual\n ? [[page.translationKey, page] as const]\n : []\n )\n );\n const routes: RouteManifestEntry[] = [];\n for (const { code } of i18n.locales) {\n if (code === fallback) {\n continue;\n }\n const present = new Set(\n graph.pages.flatMap((page) =>\n page.locale === code ? [page.translationKey] : []\n )\n );\n for (const [key, source] of fallbackPages) {\n if (present.has(key)) {\n continue;\n }\n const path = mountBasePath(basePath, localizeRoute(key, code, i18n));\n // A fallback route is a real prerendered page, so it registers as a\n // version alternate too — the switcher on a sibling version's page\n // lands here instead of bouncing to the version root. Its own path is\n // recorded (not the fallback source's), keeping the target in-locale.\n const versionAlternates: VersionAlternate[] = versionAlternatesByKey\n ? versionAlternatesFor(versionAlternatesByKey, source.versionKey, code)\n : [];\n if (versionAlternatesByKey) {\n versionAlternates.push({ path, version: source.version });\n }\n routes.push({\n alternates: alternatesByKey.get(key) ?? [],\n collection: source.collection ?? \"docs\",\n contentType: source.contentType,\n description: routeDescription(source),\n draft: source.meta.draft,\n editUrl: source.editUrl,\n entryId: source.entryId ?? source.source.ref,\n fallback: true,\n hidden: source.meta.sidebar.hidden,\n id: source.id,\n indexable: false,\n lastModified: source.lastModified,\n locale: code,\n monolingual: source.monolingual,\n path,\n source: source.source,\n sourcePath: source.sourcePath,\n title: source.title,\n version: source.version,\n versionAlternates,\n });\n }\n }\n return routes;\n};\n\n/** Build the runtime manifest that bridges core and the generated Astro app. */\nexport const buildManifest = (options: {\n context: ProjectContext;\n config: ResolvedConfig;\n graph: ContentGraph;\n}): BlumeManifest => {\n const { context, config, graph } = options;\n const searchEnabled = config.search.provider.mode !== \"none\";\n const { i18n } = config;\n\n // Real translations per logical page, for `hreflang` and the switcher. Built\n // only under i18n; a single-locale page has no alternates.\n const alternatesByKey = new Map<string, RouteAlternate[]>();\n if (i18n) {\n for (const page of graph.pages) {\n const list = alternatesByKey.get(page.translationKey) ?? [];\n list.push({ locale: page.locale, path: page.route });\n alternatesByKey.set(page.translationKey, list);\n }\n }\n\n // The same logical page across versions, within each locale — for the\n // version switcher and the canonical-to-latest lookup. Built only under\n // versioning; lists are shared by reference and finalized (fallback routes\n // appended, then sorted) before the manifest is returned.\n const versionAlternatesByKey = config.versions\n ? new Map<string, VersionAlternate[]>()\n : undefined;\n if (versionAlternatesByKey) {\n for (const page of graph.pages) {\n versionAlternatesFor(\n versionAlternatesByKey,\n page.versionKey,\n page.locale\n ).push({ path: page.route, version: page.version });\n }\n }\n\n const routes: RouteManifestEntry[] = graph.pages.map((page) => ({\n aiExclude: page.meta.ai.exclude,\n alternates: alternatesByKey.get(page.translationKey) ?? [],\n collection: page.collection ?? \"docs\",\n contentType: page.contentType,\n description: routeDescription(page),\n draft: page.meta.draft,\n editUrl: page.editUrl,\n entryId: page.entryId ?? page.source.ref,\n hidden: page.meta.sidebar.hidden,\n id: page.id,\n indexable: searchEnabled && contentIndexable(page, config),\n lastModified: page.lastModified,\n locale: page.locale,\n monolingual: page.monolingual,\n path: page.route,\n source: page.source,\n sourcePath: page.sourcePath,\n title: page.title,\n version: page.version,\n versionAlternates: versionAlternatesByKey\n ? versionAlternatesFor(\n versionAlternatesByKey,\n page.versionKey,\n page.locale\n )\n : [],\n }));\n\n if (i18n) {\n routes.push(\n ...buildFallbackRoutes(\n graph,\n i18n,\n alternatesByKey,\n config.basePath,\n versionAlternatesByKey\n )\n );\n }\n\n // Alternate lists read current-first, then archived versions in configured\n // (switcher) order. Sorted once here — every route holding a list by\n // reference sees the final ordering.\n if (versionAlternatesByKey && config.versions) {\n const rank = new Map<string, number>(\n config.versions.archived.map((version, index) => [version.id, index + 1])\n );\n for (const list of versionAlternatesByKey.values()) {\n list.sort(\n (a, b) => (rank.get(a.version) ?? 0) - (rank.get(b.version) ?? 0)\n );\n }\n }\n\n routes.sort((a, b) => a.path.localeCompare(b.path));\n\n return {\n blumeVersion: getBlumeVersion(),\n contentRoot: context.contentRoot,\n output: config.deployment.options.output,\n projectRoot: context.root,\n routes,\n version: MANIFEST_VERSION,\n };\n};\n",
|
|
6
|
+
"import { isAbsolute, relative } from \"pathe\";\n\nimport { CHANGELOG_INDEX_ROUTE, hasChangelogIndex } from \"./changelog-index.ts\";\nimport { loadConfig } from \"./config.ts\";\nimport { customStaticRoutes, discoverPages } from \"./custom-pages.ts\";\nimport { unknownDirectiveDiagnostics } from \"./directive-diagnostics.ts\";\nimport { buildContentGraph } from \"./graph.ts\";\nimport { i18nDiagnostics } from \"./i18n.ts\";\nimport { expandIncludes, hasIncludeStatements } from \"./includes.ts\";\nimport {\n gitLastModifiedTimes,\n gitRepositoryRoot,\n lastModifiedShallowWarning,\n realPath,\n resolveLastModifiedConfig,\n} from \"./last-modified.ts\";\nimport { buildManifest } from \"./manifest.ts\";\nimport { discoverFolderMeta } from \"./meta.ts\";\nimport type { FolderMetaSource } from \"./meta.ts\";\nimport { resolveProjectContext } from \"./project.ts\";\nimport type { ResolvedConfig } from \"./schema.ts\";\nimport { normalizeEntry, strippedLineOffset } from \"./sources/normalize.ts\";\nimport { resolveDocsCollection, resolveSources } from \"./sources/resolve.ts\";\nimport type { ContentSource, SourceLoadResult } from \"./sources/types.ts\";\nimport type {\n BlumeManifest,\n ContentGraph,\n Diagnostic,\n ExampleLookup,\n PageRecord,\n ProjectContext,\n} from \"./types.ts\";\nimport { versionsDiagnostics } from \"./versions.ts\";\n\n/** Build mode: drafts are kept in `dev` and dropped in `build`. */\nexport type BuildMode = \"dev\" | \"build\";\n\n/** CLI-supplied overrides applied over the loaded config (see `scanProject`). */\nexport interface ConfigOverrides {\n /** Override `content.root` (`blume dev --content-dir`). */\n contentRoot?: string;\n}\n\n/**\n * Whether `path` sits at or under `dir`. A relative result that is itself\n * absolute means a different drive root on Windows, which is outside too.\n */\nexport const isWithin = (dir: string, path: string): boolean => {\n const rel = relative(dir, path);\n return !(rel.startsWith(\"..\") || isAbsolute(rel));\n};\n\n/** Apply CLI config overrides onto a resolved config (returns a new object). */\nconst applyConfigOverrides = (\n config: ResolvedConfig,\n overrides?: ConfigOverrides\n): ResolvedConfig => {\n if (!overrides) {\n return config;\n }\n // `--content-dir` re-roots every filesystem source: with one (the\n // zero-config case) it is exactly the old `content.root` override, and\n // several must share a root anyway (see `resolveDocsCollection`).\n const { contentRoot } = overrides;\n const sources = contentRoot\n ? config.content.sources.map((source) =>\n source.kind === \"filesystem\"\n ? { ...source, options: { ...source.options, root: contentRoot } }\n : source\n )\n : config.content.sources;\n return {\n ...config,\n content: { ...config.content, sources },\n };\n};\n\n/** Everything Blume knows about a project after a full scan. */\nexport interface BlumeProject {\n mode: BuildMode;\n context: ProjectContext;\n config: ResolvedConfig;\n graph: ContentGraph;\n manifest: BlumeManifest;\n diagnostics: Diagnostic[];\n /** Entries excluded from the graph because their frontmatter failed validation. */\n droppedPages: number;\n /** The instantiated content sources, for lazy entry reads (search/AI/raw). */\n sources: ContentSource[];\n /**\n * Whether the config file set `theme.fonts` itself (see\n * {@link ConfigLoadResult.themeFontsConfigured}); gates OG font derivation.\n */\n themeFontsConfigured: boolean;\n /**\n * Discovered `examples/` sources keyed by `<Component path>`, attached by the\n * runtime/eject layer after {@link scanProject} (example discovery is an Astro\n * concern, so core doesn't run it). Undefined until then; the agent-facing\n * Markdown downleveler reads it to turn `<Component path=\"…\" />` into the\n * example's source. Empty when the project has no examples.\n */\n examples?: ExampleLookup;\n}\n\n/**\n * Guard the invariant that ties a filesystem page to the `docs` collection:\n * Astro ids each collection entry by its path relative to the collection base,\n * so `getEntry(\"docs\", entryId)` only resolves when that entry id equals\n * `relative(base, file)`. A filesystem source ids entries relative to its own\n * root; when that root can't be the collection base (e.g. a second filesystem\n * source rooted elsewhere), the ids diverge and every one of that source's pages\n * would 404 in dev (a static build silently masks it). Emit a hard error naming\n * the mismatch so it can't ship, instead of a silent runtime failure. One\n * diagnostic per file (locale duplicates share a source path).\n */\nconst entryIdDiagnostics = (\n pages: PageRecord[],\n collectionBase: string\n): Diagnostic[] => {\n const diagnostics: Diagnostic[] = [];\n const seen = new Set<string>();\n for (const page of pages) {\n // Only filesystem entries render through the base-rooted `docs` collection;\n // staged sources carry their own id and collection.\n if (page.collection || !page.sourcePath || seen.has(page.sourcePath)) {\n continue;\n }\n seen.add(page.sourcePath);\n const expected = relative(collectionBase, page.sourcePath)\n .split(\"\\\\\")\n .join(\"/\");\n const entryId = page.entryId ?? page.source.ref;\n if (expected !== entryId) {\n diagnostics.push({\n code: \"BLUME_ENTRY_ID_MISMATCH\",\n file: page.sourcePath,\n message: `Content source \"${page.source.name}\" is rooted outside the docs collection base, so ${page.route} resolves entry id \"${entryId}\" but the collection would generate \"${expected}\" — the page would 404 at runtime.`,\n severity: \"error\",\n suggestion:\n \"Use a single filesystem() source (the docs collection roots at it), or give every filesystem() source the same root and partition them with include globs — a root at a subdirectory of the first source's root still mismatches.\",\n });\n }\n }\n return diagnostics;\n};\n\n/**\n * Funnel every loaded source's entries through the shared `normalizeEntry`,\n * collecting pages, diagnostics, and the count of entries dropped outright —\n * an entry that yields no pages but did yield diagnostics was rejected for\n * invalid frontmatter, and callers surface that count so a build with missing\n * pages can't read as clean.\n */\nconst normalizeLoadedEntries = (\n loaded: ({ source: ContentSource } & SourceLoadResult)[],\n config: ResolvedConfig\n) => {\n // Only thread `frontmatter.extend` / `content.types` through when a project\n // opts in, so the known-key split in `normalizeEntry` stays off the default\n // path.\n const frontmatterExtend =\n Object.keys(config.frontmatter.extend).length > 0\n ? config.frontmatter.extend\n : undefined;\n const declaredTypes = Object.entries(config.content.types).filter(\n ([, type]) => Object.keys(type.frontmatter).length > 0\n );\n const typeFrontmatter =\n declaredTypes.length > 0\n ? Object.fromEntries(\n declaredTypes.map(([name, type]) => [name, type.frontmatter])\n )\n : undefined;\n\n const pages: PageRecord[] = [];\n const allDiagnostics: Diagnostic[] = [];\n let droppedPages = 0;\n for (const { source, entries, diagnostics } of loaded) {\n allDiagnostics.push(...diagnostics);\n for (const entry of entries) {\n const normalized = normalizeEntry(entry, {\n basePath: config.basePath,\n defaultType: config.content.defaultType,\n frontmatterExtend,\n i18n: config.i18n,\n source: {\n monolingual: source.monolingual,\n name: source.name,\n orderedNames: source.orderedNames,\n prefix: source.prefix,\n staged: source.staged,\n },\n typeFrontmatter,\n versions: config.versions,\n });\n if (normalized.pages.length === 0 && normalized.diagnostics.length > 0) {\n droppedPages += 1;\n }\n pages.push(...normalized.pages);\n allDiagnostics.push(...normalized.diagnostics);\n if (normalized.pages.length > 0) {\n allDiagnostics.push(...unknownDirectiveDiagnostics(entry, source.name));\n }\n }\n }\n return { diagnostics: allDiagnostics, droppedPages, pages };\n};\n\n/**\n * The local sources' on-disk roots a `git log` pathspec can cover: those\n * inside the repository at `gitRoot`. A root outside it would fail the log\n * outright and can never yield dates. Compared by real path, since git spells\n * its toplevel with every symlink resolved (`/private/tmp/site` for a project\n * opened as `/tmp/site`); the roots themselves keep the project's spelling.\n */\nconst gitContentRoots = (\n sources: readonly ContentSource[],\n gitRoot: string | null\n): string[] => {\n if (gitRoot === null) {\n return [];\n }\n const top = realPath(gitRoot);\n return sources\n .flatMap((source) => (source.contentRoot ? [source.contentRoot] : []))\n .filter((dir) => isWithin(top, realPath(dir)));\n};\n\n/**\n * Run the full core pipeline for a project root: load config, resolve paths,\n * discover content and folder meta, build the graph, and assemble the manifest.\n * Collects all diagnostics without throwing on content-level problems so\n * callers can decide how strict to be.\n */\n/** Narrows the logo config's image shorthand from its object form. */\nconst isLogoShorthand = (\n logo: NonNullable<ResolvedConfig[\"logo\"]>\n): logo is string => typeof logo === \"string\";\n\n/** The configured brand link: `logo.href`, else the site root. */\nconst logoHref = (logo: ResolvedConfig[\"logo\"]): string =>\n logo === undefined || isLogoShorthand(logo) ? \"/\" : (logo.href ?? \"/\");\n\n/** Narrows the banner config's text shorthand from its object form. */\nconst isBannerShorthand = (\n banner: NonNullable<ResolvedConfig[\"banner\"]>\n): banner is string => typeof banner === \"string\";\n\n/** The configured banner link target, when the banner has a link. */\nconst bannerLinkHref = (\n banner: ResolvedConfig[\"banner\"]\n): string | undefined =>\n banner === undefined || isBannerShorthand(banner)\n ? undefined\n : banner.link?.href;\n\nexport const scanProject = async (\n root: string,\n options: {\n devServerUrl?: string;\n mode?: BuildMode;\n preview?: boolean;\n refresh?: boolean;\n /** CLI overrides applied over the loaded config (e.g. `--output`). */\n overrides?: ConfigOverrides;\n /** Relocate the generated runtime (e.g. `.blume-verify` for isolation). */\n runtimeDir?: string;\n } = {}\n): Promise<BlumeProject> => {\n const mode = options.mode ?? \"dev\";\n const preview = options.preview ?? false;\n const configResult = await loadConfig(root, {\n devServerUrl: options.devServerUrl,\n });\n const config = applyConfigOverrides(configResult.config, options.overrides);\n const context = resolveProjectContext(root, config, {\n runtimeDir: options.runtimeDir,\n });\n\n // Each source validates itself (e.g. the filesystem source checks its root\n // exists), replacing the single hard `contentRoot` check.\n const sources = resolveSources(config, context, {\n mode,\n preview,\n refresh: options.refresh,\n });\n for (const source of sources) {\n source.validate?.();\n }\n\n // Folder meta is discovered per filesystem source, under each source's own\n // root and keyed by its route prefix, so a prefixed/root-differing source's\n // `meta.ts` still lines up with its (prefixed) sidebar group path.\n const metaSources: FolderMetaSource[] = sources.flatMap((source) =>\n source.staged || !source.contentRoot\n ? []\n : [\n {\n exclude: source.exclude,\n include: source.include,\n prefix: source.prefix,\n root: source.contentRoot,\n },\n ]\n );\n\n // Run every source's `load()` in parallel, then funnel each entry through the\n // shared `normalizeEntry` so route mapping is identical regardless of origin.\n // Under the `dir` parser, non-default locales are top-level directories whose\n // meta keys must carry the locale in front of the source prefix (see\n // `discoverFolderMeta`).\n const localeDirs =\n config.i18n && config.i18n.parser === \"dir\"\n ? config.i18n.locales.flatMap((locale) =>\n locale.code === config.i18n?.defaultLocale ? [] : [locale.code]\n )\n : undefined;\n\n const [loaded, folderMeta] = await Promise.all([\n Promise.all(\n sources.map(async (source) => ({ source, ...(await source.load()) }))\n ),\n discoverFolderMeta(metaSources, {\n localeDirs,\n versionDirs: config.versions?.archived.map((version) => version.id),\n }),\n ]);\n\n // Expand `<include>` statements before normalization, so heading/link/\n // component extraction sees the spliced content every render surface shows.\n // Filesystem entries only: includes resolve as filesystem paths bounded by\n // the owning source's content root. Unresolvable statements stay verbatim\n // and surface as error diagnostics here.\n const includeDiagnostics: Diagnostic[] = [];\n await Promise.all(\n loaded.flatMap(({ source, entries }) => {\n if (source.staged || !source.contentRoot) {\n return [];\n }\n return entries.map(async (entry) => {\n if (!entry.sourcePath || !hasIncludeStatements(entry.body.text)) {\n return;\n }\n const expansion = await expandIncludes(entry.body.text, {\n contentRoot: source.contentRoot,\n lineOffset: strippedLineOffset(entry.raw, entry.body.text),\n sourcePath: entry.sourcePath,\n });\n entry.expanded = expansion;\n includeDiagnostics.push(...expansion.errors);\n });\n })\n );\n\n // Folder meta contributed by the sources themselves (the OpenAPI source\n // labels each tag directory with the spec's own tag name). It applies to\n // every locale, so it merges into the shared map — beneath user-authored\n // entries, which are spread last and win.\n const sharedFolderMeta = new Map([\n ...loaded.flatMap(({ folderMeta: sourceMeta }) =>\n Object.entries(sourceMeta ?? {})\n ),\n ...folderMeta.shared,\n ]);\n\n const {\n diagnostics: contentDiagnostics,\n droppedPages,\n pages: allPages,\n } = normalizeLoadedEntries(loaded, config);\n\n // Drafts render in dev and in preview, but are excluded from production builds.\n const pages =\n mode === \"build\" && !preview\n ? allPages.filter((page) => !page.meta.draft)\n : allPages;\n\n // Resolve \"last updated\" dates before the graph is built so the manifest\n // (which shares these page objects) picks them up. Frontmatter always wins;\n // git applies to filesystem entries, other sources supply dates on the entry.\n const lastModified = resolveLastModifiedConfig(config.lastModified);\n const lastModifiedWarnings: Diagnostic[] = [];\n if (lastModified.enabled && lastModified.source === \"git\") {\n const fsPaths = pages\n .map((page) => page.sourcePath)\n .filter((path): path is string => path !== undefined);\n // The git pathspecs must cover where the pages actually live: each local\n // source's own on-disk root — a filesystem source's `root`, or a staged\n // local source's tree (an Obsidian vault) — which diverges from the global\n // `content.root`.\n const gitRoot = gitRepositoryRoot(context.root);\n const contentRoots = gitContentRoots(sources, gitRoot);\n const gitTimes = gitLastModifiedTimes(\n context.root,\n contentRoots,\n fsPaths,\n gitRoot\n );\n for (const page of pages) {\n if (!page.lastModified && page.sourcePath) {\n page.lastModified = gitTimes.get(page.sourcePath);\n }\n }\n // A shallow CI clone (Vercel, actions/checkout) silently drops most dates;\n // surface that instead of letting production diverge from local builds.\n // Only pages the log could have dated count — a page outside every covered\n // root stays undated no matter how deep the clone is, and the warning's\n // suggested fix cannot help it.\n const undated = pages.filter(\n (page) =>\n page.sourcePath &&\n !page.lastModified &&\n contentRoots.some((dir) => isWithin(dir, page.sourcePath ?? \"\"))\n ).length;\n lastModifiedWarnings.push(\n ...lastModifiedShallowWarning(context.root, undated)\n );\n }\n\n // Custom `.astro` pages and the generated changelog index aren't content\n // pages, so name them for navigation: a `/changelog` tab should open the\n // timeline, not the newest entry, and a tab or brand link to a custom page\n // must stay on its route in every locale.\n const customPages = context.pagesRoot\n ? await discoverPages(context.pagesRoot)\n : [];\n const graph = buildContentGraph(pages, {\n bannerHref: bannerLinkHref(config.banner),\n basePath: config.basePath,\n brandHref: logoHref(config.logo),\n extraRoutes: new Set([\n ...customStaticRoutes(customPages),\n ...(hasChangelogIndex(pages, config) ? [CHANGELOG_INDEX_ROUTE] : []),\n ]),\n folderMeta: folderMeta.meta,\n i18n: config.i18n,\n navigation: config.navigation,\n sharedFolderMeta,\n versions: config.versions,\n });\n const manifest = buildManifest({ config, context, graph });\n\n const i18nWarnings = config.i18n\n ? i18nDiagnostics(pages, config.i18n, config.versions)\n : [];\n const versionWarnings = config.versions\n ? versionsDiagnostics(pages, config.versions)\n : [];\n\n return {\n config,\n context,\n diagnostics: [\n ...contentDiagnostics,\n ...includeDiagnostics,\n ...folderMeta.diagnostics,\n ...entryIdDiagnostics(\n pages,\n resolveDocsCollection(config, context.root).base\n ),\n ...graph.diagnostics,\n ...i18nWarnings,\n ...versionWarnings,\n ...lastModifiedWarnings,\n ],\n droppedPages,\n graph,\n manifest,\n mode,\n sources,\n themeFontsConfigured: configResult.themeFontsConfigured,\n };\n};\n",
|
|
7
|
+
"import type { Nodes } from \"mdast\";\nimport { mdxToMdast } from \"satteri\";\n\nimport {\n CALLOUT_ALIASES,\n CALLOUT_TYPES,\n calloutTypeFor,\n} from \"../markdown/directives.ts\";\nimport { MDX_BODY_FEATURES } from \"../markdown/features.ts\";\nimport { strippedLineOffset } from \"./sources/normalize.ts\";\nimport type { SourceEntry } from \"./sources/types.ts\";\nimport type { Diagnostic } from \"./types.ts\";\n\n// A line that may open a container directive: a colon fence and a name, after\n// any quote or list indentation. A page is parsed only when one of these\n// names something other than a callout, so pages without one cost a scan.\nconst CONTAINER_OPENING = /^[\\t >]*:{3,}(?<name>[a-z][\\w-]*)/gimu;\n\nconst codeList = (names: Iterable<string>): string =>\n [...names].map((name) => `\\`${name}\\``).join(\", \");\n\nconst CALLOUT_NAMES = `${codeList(CALLOUT_TYPES)} (or the aliases ${codeList(Object.keys(CALLOUT_ALIASES))})`;\n\n/** A container directive that isn't a callout, and its line in `text`. */\ninterface UnknownContainer {\n line: number;\n name: string;\n}\n\n/**\n * Every container directive in an `.mdx` body whose name isn't a callout, in\n * source order. The body is parsed as the renderer reads it, so a fence in a\n * code block or a `::: name` that isn't a directive at all never counts. A\n * body MDX can't parse fails to render anyway, with its own error.\n */\nconst unknownContainers = (text: string): UnknownContainer[] => {\n const named = [...text.matchAll(CONTAINER_OPENING)].some(\n (match) => calloutTypeFor(match.groups?.name ?? \"\") === null\n );\n if (!named) {\n return [];\n }\n let tree: Nodes;\n try {\n tree = mdxToMdast(text, { features: MDX_BODY_FEATURES });\n } catch {\n return [];\n }\n const found: UnknownContainer[] = [];\n const walk = (node: Nodes): void => {\n if (\n node.type === \"containerDirective\" &&\n calloutTypeFor(node.name) === null\n ) {\n found.push({ line: node.position?.start.line ?? 1, name: node.name });\n }\n if (\"children\" in node) {\n for (const child of node.children) {\n walk(child);\n }\n }\n };\n walk(tree);\n return found;\n};\n\n/**\n * Warn about each `:::name` container in an `.mdx` entry that isn't a callout.\n * The page keeps its content — the body renders between the literal `:::`\n * lines (see `markdown/directives.ts`) — but a typo like `:::warnig`, or a\n * `:::details` carried over from another docs tool, should read as the\n * mistake it is rather than as a finished page. Lines point into the file\n * the author wrote, a partial's own file for a container an `<include>`\n * brought in.\n */\nexport const unknownDirectiveDiagnostics = (\n entry: SourceEntry,\n sourceName: string\n): Diagnostic[] => {\n if (entry.body.format !== \"mdx\") {\n return [];\n }\n const page = entry.sourcePath ?? `${sourceName}:${entry.ref}`;\n const offset =\n entry.bodyLineOffset ?? strippedLineOffset(entry.raw, entry.body.text);\n return unknownContainers(entry.expanded?.text ?? entry.body.text).map(\n ({ line, name }) => {\n const origin = entry.expanded?.origins[line - 1];\n return {\n code: \"BLUME_UNKNOWN_DIRECTIVE\",\n file: origin?.file ?? page,\n line: origin?.line ?? line + offset,\n message: `\\`:::${name}\\` isn't a callout type, so the page shows its \\`:::\\` lines as written around its content.`,\n severity: \"warning\",\n suggestion: `Use a callout type — ${CALLOUT_NAMES} — or remove the \\`:::\\` lines to keep the content as plain prose.`,\n };\n }\n );\n};\n",
|
|
8
|
+
"import { toString as mdastToString } from \"mdast-util-to-string\";\n\nimport { jsxAttribute, jsxFlowElement } from \"./mdast.ts\";\nimport type { MdastNode, MdastVisitorContext } from \"./mdast.ts\";\n\nexport interface DirectiveNode extends MdastNode {\n attributes?: Record<string, string | null | undefined> | null;\n // Satteri gives an empty container directive (`:::note\\n:::`) `children: null`.\n children?: MdastNode[] | null;\n name: string;\n position?: {\n start?: { offset?: number };\n end?: { offset?: number };\n };\n}\n\n/**\n * The visitor-context slice the directive visitors use. `source` is the page\n * the directive offsets index into; without it the literal fallback rebuilds\n * a directive from its node instead. `parent` walks up to an enclosing\n * container directive, which renders the directives inside it itself.\n */\ninterface DirectiveVisitorContext extends MdastVisitorContext {\n parent?: (node: MdastNode) => MdastNode | undefined;\n source?: string;\n}\n\n/** Directive names that map directly onto a Callout type. */\nexport const CALLOUT_TYPES: ReadonlySet<string> = new Set([\n \"danger\",\n \"info\",\n \"note\",\n \"success\",\n \"tip\",\n \"warning\",\n]);\n\n/** Friendly aliases for the canonical Callout types. */\ninterface CalloutAliases {\n [alias: string]: string;\n}\n\nexport const CALLOUT_ALIASES: Readonly<CalloutAliases> = {\n caution: \"warning\",\n error: \"danger\",\n important: \"note\",\n warn: \"warning\",\n};\n\n/** Resolve a directive name to a Callout type, or `null` if it is not one. */\nexport const calloutTypeFor = (name: string): string | null => {\n const lower = name.toLowerCase();\n if (CALLOUT_TYPES.has(lower)) {\n return lower;\n }\n return CALLOUT_ALIASES[lower] ?? null;\n};\n\n/** The markers that open a text (`:name`) and a leaf (`::name`) directive. */\nconst LITERAL_MARKERS = new Map([\n [\"leafDirective\", \"::\"],\n [\"textDirective\", \":\"],\n]);\n\n/** A directive's `[label]` rebuilt from its label's text, or nothing. */\nconst labelSource = (label: MdastNode[]): string =>\n label.length > 0\n ? `[${mdastToString(label, { includeImageAlt: false })}]`\n : \"\";\n\n/** A directive's `{attributes}` rebuilt from its node, or nothing. */\nconst attributeSource = (node: DirectiveNode): string => {\n const attributes = Object.entries(node.attributes ?? {}).map(\n ([key, value]) => (value ? `${key}=\"${value}\"` : key)\n );\n return attributes.length > 0 ? `{${attributes.join(\" \")}}` : \"\";\n};\n\n/** The source a directive node spans, when its offsets point into `source`. */\nconst spannedSource = (node: DirectiveNode, source: string): string | null => {\n const start = node.position?.start?.offset;\n const end = node.position?.end?.offset;\n return start !== undefined && end !== undefined\n ? source.slice(start, end)\n : null;\n};\n\n/**\n * A text or leaf directive exactly as the author wrote it. The slice by\n * offsets is the exact text — `[label]` and `{attrs}` included — and is\n * trusted only when it opens with the directive's own marker and name;\n * content an `<include>` spliced in carries no offsets into this page, so it\n * is rebuilt from the node instead.\n */\nexport const directiveSource = (\n node: DirectiveNode,\n marker: string,\n source: string\n): string => {\n const opening = `${marker}${node.name}`;\n const slice = spannedSource(node, source);\n if (slice?.startsWith(opening)) {\n return slice;\n }\n return `${opening}${labelSource(node.children ?? [])}${attributeSource(node)}`;\n};\n\n/**\n * The node a text or leaf directive renders as: its literal source, as text\n * in place of a text directive and as a paragraph in place of a leaf one.\n */\nconst literalDirective = (\n node: DirectiveNode,\n marker: string,\n source: string\n): MdastNode => {\n const text = { type: \"text\", value: directiveSource(node, marker, source) };\n return marker === \":\" ? text : { children: [text], type: \"paragraph\" };\n};\n\nconst FENCE = /^:{3,}/u;\n\n// The line that closes a container: a colon fence, after whatever quote or\n// list indentation the container sits in.\nconst CLOSING_FENCE = /\\n[\\t >]*(?<fence>:{3,})\\s*$/u;\n\n/** A paragraph holding one line of literal text. */\nconst literalLine = (value: string): MdastNode => ({\n children: [{ type: \"text\", value }],\n type: \"paragraph\",\n});\n\n/**\n * The fence lines of a container directive as the author wrote them — the\n * opening one with its `[label]` and `{attrs}` — or, without offsets into\n * this page (an `<include>`), rebuilt from the node. A container left\n * unclosed runs to the end of its parent and has no closing line.\n */\nconst containerFences = (\n node: DirectiveNode,\n label: MdastNode | undefined,\n source: string\n): string[] => {\n const slice = spannedSource(node, source) ?? \"\";\n const fence = FENCE.exec(slice)?.[0];\n if (fence === undefined || !slice.startsWith(node.name, fence.length)) {\n const labelText = labelSource(label ? [label] : []);\n return [`:::${node.name}${labelText}${attributeSource(node)}`, \":::\"];\n }\n const [opening = \"\"] = slice.split(\"\\n\", 1);\n const closing = CLOSING_FENCE.exec(slice.slice(opening.length))?.groups\n ?.fence;\n // A shorter fence can't close the container; it is the body's last line.\n return closing !== undefined && closing.length >= fence.length\n ? [opening.trimEnd(), closing]\n : [opening.trimEnd()];\n};\n\n/**\n * What a container directive renders as. A callout name becomes a\n * `<Callout>`: the title comes from a `[label]` or a `{title=\"…\"}` attribute,\n * and the body becomes the callout content. Blume has no other container, and\n * dropping one would drop its body with it, so any other name — `:::details`,\n * a `:::warnig` typo — renders its body between its fence lines, as written.\n * The directives inside render here too, since Satteri reaches them only\n * after this replacement has taken their original.\n */\nconst renderContainer = (\n node: DirectiveNode,\n source: string\n): MdastNode | MdastNode[] => {\n const children = (node.children ?? []).flatMap((child) =>\n // oxlint-disable-next-line no-use-before-define -- mutual recursion: a container's body holds directives, containers included\n literalize(child, source)\n );\n\n // A leading `:::name[Label]` parses to a paragraph flagged `directiveLabel`.\n // SAFETY: Satteri stamps `directiveLabel` on that paragraph's `data`; any\n // other node reads undefined and fails the check.\n const labelIndex = children.findIndex(\n (child) =>\n child.type === \"paragraph\" &&\n (child.data as { directiveLabel?: boolean } | undefined)?.directiveLabel\n );\n const [label] = labelIndex === -1 ? [] : children.splice(labelIndex, 1);\n\n const type = calloutTypeFor(node.name);\n if (type === null) {\n const [opening = \"\", closing] = containerFences(node, label, source);\n return [\n literalLine(opening),\n ...children,\n ...(closing === undefined ? [] : [literalLine(closing)]),\n ];\n }\n\n // Flatten the label's phrasing children so `:::note[Read **this**]` yields\n // `Read this`; image alt is excluded (an image is not label text), matching\n // the historical child-values-only behavior.\n const title =\n node.attributes?.title ??\n (label ? mdastToString(label, { includeImageAlt: false }) : undefined);\n const attributes = [jsxAttribute(\"type\", type)];\n if (title) {\n attributes.push(jsxAttribute(\"title\", title));\n }\n return jsxFlowElement(\"Callout\", attributes, children);\n};\n\n/**\n * `node` with every directive in it rendered: text and leaf directives as\n * their literal source, containers as {@link renderContainer}.\n */\nconst literalize = (node: MdastNode, source: string): MdastNode[] => {\n if (node.type === \"containerDirective\") {\n // SAFETY: a `containerDirective` carries a `name` plus optional\n // attributes, children, and position — the `DirectiveNode` shape.\n return [renderContainer(node as DirectiveNode, source)].flat();\n }\n const marker = LITERAL_MARKERS.get(node.type);\n if (marker) {\n // SAFETY: only Satteri's `textDirective`/`leafDirective` nodes have a\n // marker, and they carry a `name` plus optional attributes, children,\n // and position — the `DirectiveNode` shape.\n return [literalDirective(node as DirectiveNode, marker, source)];\n }\n // SAFETY: a parent's `children` is always a node list; leaves carry none.\n const children = node.children as MdastNode[] | null | undefined;\n if (!children) {\n return [node];\n }\n return [\n {\n ...node,\n children: children.flatMap((child) => literalize(child, source)),\n },\n ];\n};\n\n/** Whether a container directive (already rendered, body and all) holds `node`. */\nconst insideContainer = (node: MdastNode, ctx: DirectiveVisitorContext) => {\n for (\n let parent = ctx.parent?.(node);\n parent !== undefined;\n parent = ctx.parent?.(parent)\n ) {\n if (parent.type === \"containerDirective\") {\n return true;\n }\n }\n return false;\n};\n\n/** The text and leaf visitor: render the directive as its literal source. */\nconst renderLiteral =\n (marker: string) => (node: DirectiveNode, ctx: DirectiveVisitorContext) => {\n // A container's body was already rendered into its replacement; a\n // transform queued on the replaced original would be dropped with a\n // warning.\n if (insideContainer(node, ctx)) {\n return;\n }\n ctx.replaceNode(node, literalDirective(node, marker, ctx.source ?? \"\"));\n };\n\n/**\n * Satteri MDAST plugin for directives. Container directives (`:::note`,\n * `:::warning`, `:::tip`, …) map onto Blume's `<Callout>` component, and any\n * other container renders its body between its literal fence lines (see\n * {@link renderContainer}).\n *\n * Blume handles no text (`:name`) or leaf (`::name`) directives, and prose is\n * full of text that parses as one — `16:9`, `10:30am`, `og:image`,\n * `pets:read` — so both render as the literal source the author wrote instead\n * of vanishing.\n */\nexport const directiveToCalloutPlugin = () => ({\n containerDirective(node: DirectiveNode, ctx: DirectiveVisitorContext) {\n // A container inside another was rendered with its enclosing one.\n if (insideContainer(node, ctx)) {\n return;\n }\n ctx.replaceNode(node, renderContainer(node, ctx.source ?? \"\"));\n },\n leafDirective: renderLiteral(\"::\"),\n name: \"blume-directive-callout\",\n // Positions are opt-in since satteri 0.10; the literal fallback slices\n // directives out of the source by offset.\n options: { position: true },\n textDirective: renderLiteral(\":\"),\n});\n",
|
|
9
|
+
"/**\n * Classify author-supplied icon strings — built-in Lucide names vs image\n * paths/URLs vs inline SVG. Shared by nav diagnostics, `<Icon>`, and the\n * Cmd+K popular-icon resolver so the three shapes stay in sync.\n */\n\nconst IMAGE_ICON =\n /^(?:https?:\\/\\/|data:image\\/|\\/|\\.{1,2}\\/)|\\.(?:avif|gif|jpe?g|png|svg|webp)$/iu;\n\nconst INLINE_SVG = /^\\s*<svg[\\s\\S]*<\\/svg>\\s*$/u;\n\n/** Image path, remote URL, or data URI — not a Lucide name or inline SVG. */\nexport const isImageIcon = (value: string): boolean => IMAGE_ICON.test(value);\n\n/** Full `<svg>…</svg>` markup (whitespace-tolerant). */\nexport const isInlineSvg = (value: string): boolean => INLINE_SVG.test(value);\n\n/** Image or inline SVG — anything that is not a built-in icon name. */\nexport const isAssetIcon = (value: string): boolean =>\n isInlineSvg(value) || isImageIcon(value);\n",
|
|
10
|
+
"/**\n * Icon resolution backed by the open Iconify Lucide set. Resolution runs at\n * **build time, server-side**, and returns ready-to-inline SVG, so icons stay\n * zero-JS and fully self-contained (no runtime CDN fetch).\n *\n * Because the Iconify set data is large, this module must only be imported from\n * server contexts (`.astro` frontmatter, the CLI). Client scripts use the tiny\n * hand-inlined set in `./chrome-icons.ts` instead.\n */\nimport { createRequire } from \"node:module\";\n\nimport type { IconifyJSON } from \"@iconify/types\";\nimport { getIconData, iconToSVG } from \"@iconify/utils\";\n\n// Load the icon-set JSON with a CJS `require` rather than an `import ... with\n// { type: \"json\" }`: the bundled Node CLI drops the import attribute (Bun.build\n// strips it when externalizing) and then rejects the module, whereas `require`\n// of a JSON file needs no attribute and works under both Node and Bun.\nconst requireJson = createRequire(import.meta.url);\n// SAFETY: the required file is the Iconify-published icon-set JSON, whose\n// shape is exactly `IconifyJSON`.\nconst loadSet = (pkg: string): IconifyJSON => requireJson(pkg) as IconifyJSON;\n\nconst SETS = {\n lucide: loadSet(\"@iconify-json/lucide/icons.json\"),\n};\n\n/** Blume's only icon library. */\nconst DEFAULT_SET = \"lucide\";\n\n/** Explicit `prefix:name` prefixes. Lucide is the only bundled set. */\nconst PREFIX_SETS = {\n lucide: \"lucide\",\n};\n\n/**\n * Own-property map lookup. Icon names come from content and config, so a value\n * like `constructor:x` would otherwise resolve an Object.prototype member (a\n * function) and crash resolution deep in the build with no pointer to the\n * offending page.\n */\nconst ownEntry = <T>(map: Record<string, T>, key: string): T | undefined =>\n Object.hasOwn(map, key) ? map[key] : undefined;\n\nexport interface ResolvedIcon {\n /** Inner SVG markup (self-styled: carries its own fill/stroke). */\n body: string;\n /** The resolved icon name. */\n name: string;\n /** The icon's viewBox (Lucide is 24×24). */\n viewBox: string;\n}\n\nconst normalize = (name: string): string =>\n name\n .trim()\n .toLowerCase()\n .replaceAll(/[\\s_]+/gu, \"-\");\n\nconst fromSet = (setName: string, iconName: string): ResolvedIcon | null => {\n const set = ownEntry(SETS, setName);\n const data = set && getIconData(set, iconName);\n if (!data) {\n return null;\n }\n const { attributes, body } = iconToSVG(data, { height: \"auto\" });\n return { body, name: iconName, viewBox: attributes.viewBox };\n};\n\n/**\n * Resolve an icon name to inline SVG. Honors an explicit `lucide:name` prefix;\n * a bare name resolves against Lucide.\n */\nexport const resolveIcon = (name: string): ResolvedIcon | null => {\n const normalized = normalize(name);\n const colon = normalized.indexOf(\":\");\n if (colon > 0) {\n const setName = ownEntry(PREFIX_SETS, normalized.slice(0, colon));\n return setName ? fromSet(setName, normalized.slice(colon + 1)) : null;\n }\n return fromSet(DEFAULT_SET, normalized);\n};\n\n/**\n * Whether a name resolves to a renderable icon. Exactly mirrors\n * {@link resolveIcon}: a laxer check (e.g. matching the bare name under an\n * unknown prefix) would let callers suppress their fallback and diagnostics\n * for a name `<Icon>` then renders as nothing.\n */\nexport const hasIcon = (name: string): boolean => resolveIcon(name) !== null;\n",
|
|
11
|
+
"import { isAssetIcon } from \"../theme/icon-kind.ts\";\nimport { hasIcon } from \"../theme/icons.ts\";\nimport { isInternalPath } from \"./base-path.ts\";\nimport { isPageRef, normalizeRef } from \"./navigation.ts\";\nimport type { SidebarItemConfig } from \"./schema.ts\";\nimport type { Diagnostic, NavNode, Navigation, PageRecord } from \"./types.ts\";\n\n/**\n * Navigation diagnostics: catch icon typos and structural mistakes (missing\n * pages, duplicate labels) that otherwise fail silently — a wrong icon just\n * doesn't render, a bad tab path just 404s. Run over the built navigation so it\n * covers every source (config, folder meta, frontmatter) at once.\n */\n\nconst ICON_FORMAT_HINT =\n \"Use a built-in icon name, an image path/URL, or inline SVG markup.\";\n\n/** Flatten a sidebar tree to every node, descending into groups. */\nconst flattenNodes = (nodes: NavNode[]): NavNode[] =>\n nodes.flatMap((node) =>\n node.kind === \"group\" ? [node, ...flattenNodes(node.children)] : [node]\n );\n\n/** Every icon string referenced anywhere in the navigation, with a label. */\nconst collectIcons = (\n navigation: Navigation\n): { icon: string; where: string }[] => {\n const icons: { icon: string; where: string }[] = [];\n const push = (icon: string | undefined, where: string): void => {\n if (icon) {\n icons.push({ icon, where });\n }\n };\n for (const tab of navigation.tabs) {\n push(tab.icon, `tab \"${tab.label}\"`);\n for (const item of tab.items ?? []) {\n push(item.icon, `tab item \"${item.label}\"`);\n }\n }\n for (const selector of navigation.selectors) {\n for (const item of selector.items) {\n push(item.icon, `selector \"${item.label}\"`);\n }\n }\n for (const link of navigation.featured) {\n push(link.icon, `featured link \"${link.label}\"`);\n }\n const sidebars = [navigation.sidebar];\n for (const sidebar of sidebars) {\n for (const node of flattenNodes(sidebar)) {\n push(node.icon, `\"${node.label}\"`);\n }\n }\n return icons;\n};\n\n/** Warn about icon names that aren't in Blume's set (skipping image/SVG icons). */\nconst unknownIconDiagnostics = (\n icons: { icon: string; where: string }[],\n suggestion: string\n): Diagnostic[] => {\n const seen = new Set<string>();\n const diagnostics: Diagnostic[] = [];\n for (const { icon, where } of icons) {\n if (isAssetIcon(icon) || hasIcon(icon) || seen.has(icon)) {\n continue;\n }\n seen.add(icon);\n // Markup that isn't a complete <svg> element (an <img> tag, a truncated\n // svg) is a shape problem, not a set-name typo — say so.\n const message = icon.trimStart().startsWith(\"<\")\n ? `Icon markup \"${icon}\" (${where}) isn't a complete inline <svg> element, so it won't render.`\n : `Unknown icon \"${icon}\" (${where}) — it isn't in Blume's icon set.`;\n diagnostics.push({\n code: \"BLUME_UNKNOWN_ICON\",\n message,\n severity: \"warning\",\n suggestion,\n });\n }\n return diagnostics;\n};\n\n/** Warn about icon names that aren't in Blume's set (skipping image/SVG icons). */\nexport const validateNavIcons = (navigation: Navigation): Diagnostic[] =>\n unknownIconDiagnostics(collectIcons(navigation), ICON_FORMAT_HINT);\n\n/**\n * Warn about unknown icons on curated `search.popular` links. Same accepted\n * input shapes as nav icons — resolved to markup on the server for the island.\n */\nexport const validateSearchPopularIcons = (\n popular: { icon?: string; label: string }[]\n): Diagnostic[] => {\n const icons = popular.flatMap((link) =>\n link.icon\n ? [{ icon: link.icon, where: `popular link \"${link.label}\"` }]\n : []\n );\n return unknownIconDiagnostics(icons, ICON_FORMAT_HINT);\n};\n\n/** Whether an internal path resolves to a page or a section that has pages. */\nconst resolvesToPages = (routes: Set<string>, path: string): boolean =>\n routes.has(path) || [...routes].some((route) => route.startsWith(`${path}/`));\n\n/**\n * Warn when a config-linked tab/selector target has no matching page. `routes`\n * must be the full set of servable routes — content, custom `.astro` pages, and\n * generated routes — so this runs where all three are known (`generateRuntime`),\n * not in the content-only graph build.\n */\nexport const validateNavTargets = (\n navigation: Navigation,\n routes: Set<string>\n): Diagnostic[] => {\n const targets: { label: string; path: string }[] = [\n ...navigation.tabs.map((tab) => ({ label: tab.label, path: tab.path })),\n ...navigation.selectors.flatMap((selector) =>\n selector.items.map((item) => ({ label: item.label, path: item.path }))\n ),\n ...navigation.featured.map((link) => ({\n label: link.label,\n path: link.href,\n })),\n ...(navigation.actions ?? []).map((action) => ({\n label: action.label,\n path: action.href,\n })),\n ...(navigation.cta\n ? [{ label: navigation.cta.label, path: navigation.cta.href }]\n : []),\n ];\n const diagnostics: Diagnostic[] = [];\n const seen = new Set<string>();\n for (const { label, path } of targets) {\n // Only internal, non-anchor paths can be checked against routes. A\n // protocol-relative `//host/path` is external despite its leading slash.\n if (!isInternalPath(path) || path.startsWith(\"/#\") || seen.has(path)) {\n continue;\n }\n // A query or fragment (`/guides?tab=cli`) rides on the route; only the\n // path names a page.\n if (!resolvesToPages(routes, path.split(/[?#]/u)[0] ?? path)) {\n seen.add(path);\n diagnostics.push({\n code: \"BLUME_NAV_MISSING_PAGE\",\n message: `Navigation entry \"${label}\" points to ${path}, but no page matches it.`,\n severity: \"warning\",\n suggestion: \"Fix the path, or add a page at that route.\",\n });\n }\n }\n return diagnostics;\n};\n\n/**\n * Warn about explicit `navigation.sidebar` items that can't render as written,\n * instead of dropping them without a word: a page ref that matches no page\n * (left out of the sidebar), a `root` that matches no page (its link leads\n * nowhere), and an item with no page, `href`, `root`, or `items` (left out).\n * `resolves` answers whether a normalized ref (`/guides`) names a page — in\n * any locale — or a route served outside the content tree.\n */\nexport const validateSidebarItems = (\n items: SidebarItemConfig[],\n resolves: (route: string) => boolean\n): Diagnostic[] => {\n const diagnostics: Diagnostic[] = [];\n const walk = (list: SidebarItemConfig[]): void => {\n for (const item of list) {\n if (isPageRef(item)) {\n if (!resolves(normalizeRef(item))) {\n diagnostics.push({\n code: \"BLUME_NAV_MISSING_PAGE\",\n message: `Sidebar entry \"${item}\" matches no page, so it's left out of the sidebar.`,\n severity: \"warning\",\n suggestion: \"Fix the route, or add a page at it.\",\n });\n }\n continue;\n }\n if (item.root && !resolves(normalizeRef(item.root))) {\n diagnostics.push({\n code: \"BLUME_NAV_MISSING_PAGE\",\n message: `Sidebar entry \"${item.label}\" has root \"${item.root}\", but no page matches it, so its link leads nowhere.`,\n severity: \"warning\",\n suggestion: \"Fix the root, or add a page at that route.\",\n });\n }\n if (item.items) {\n walk(item.items);\n } else if (!(item.root || item.href)) {\n diagnostics.push({\n code: \"BLUME_NAV_EMPTY_ITEM\",\n message: `Sidebar entry \"${item.label}\" has no page, href, root, or items, so it's left out of the sidebar.`,\n severity: \"warning\",\n suggestion:\n \"Give it an href or a root to link somewhere, or items to make it a group.\",\n });\n }\n }\n };\n walk(items);\n return diagnostics;\n};\n\n/** Warn about two nav items sharing a label at the same sidebar level. */\nconst duplicateLabelDiagnostics = (navigation: Navigation): Diagnostic[] => {\n const diagnostics: Diagnostic[] = [];\n const checkLevel = (nodes: NavNode[], where: string): void => {\n const counts = new Map<string, number>();\n for (const node of nodes) {\n counts.set(node.label, (counts.get(node.label) ?? 0) + 1);\n }\n for (const [label, count] of counts) {\n if (count > 1) {\n diagnostics.push({\n code: \"BLUME_NAV_DUPLICATE_LABEL\",\n message: `Duplicate sidebar label \"${label}\" appears ${count} times ${where}.`,\n severity: \"warning\",\n suggestion: \"Give the entries distinct titles.\",\n });\n }\n }\n for (const node of nodes) {\n if (node.kind === \"group\") {\n checkLevel(node.children, `under \"${node.label}\"`);\n }\n }\n };\n const sidebars: { nodes: NavNode[]; where: string }[] = [\n { nodes: navigation.sidebar, where: \"at the top level\" },\n ];\n for (const { nodes, where } of sidebars) {\n checkLevel(nodes, where);\n }\n return diagnostics;\n};\n\n/** Warn when a page shown in the sidebar is marked hidden (so pagination hits it). */\nconst hiddenInSidebarDiagnostics = (\n navigation: Navigation,\n pages: PageRecord[]\n): Diagnostic[] => {\n const hidden = new Set(\n pages.flatMap((page) => (page.meta.sidebar.hidden ? [page.id] : []))\n );\n if (hidden.size === 0) {\n return [];\n }\n const sidebars = [navigation.sidebar];\n const diagnostics: Diagnostic[] = [];\n const seen = new Set<string>();\n for (const sidebar of sidebars) {\n for (const node of flattenNodes(sidebar)) {\n if (\n node.kind === \"page\" &&\n hidden.has(node.pageId) &&\n !seen.has(node.pageId)\n ) {\n seen.add(node.pageId);\n diagnostics.push({\n code: \"BLUME_NAV_HIDDEN_IN_SIDEBAR\",\n message: `Page \"${node.label}\" is marked hidden but appears in the sidebar (and its pagination).`,\n severity: \"warning\",\n suggestion:\n \"Remove it from the navigation config, or unset sidebar.hidden.\",\n });\n }\n }\n }\n return diagnostics;\n};\n\n/**\n * Structural navigation diagnostics that need only the built navigation +\n * content pages: duplicate sidebar labels at a level, and hidden pages that\n * still surface in the sidebar (so pagination lands on them). Target existence\n * is checked separately by {@link validateNavTargets}, which needs the full\n * route set.\n */\nexport const validateNavStructure = (\n navigation: Navigation,\n pages: PageRecord[]\n): Diagnostic[] => [\n ...duplicateLabelDiagnostics(navigation),\n ...hiddenInSidebarDiagnostics(navigation, pages),\n];\n",
|
|
12
|
+
"import { mountBasePath, withBasePath } from \"./base-path.ts\";\nimport {\n localizeInternalPath,\n localizeRoute,\n resolveFallbackLocale,\n} from \"./i18n.ts\";\nimport {\n validateNavIcons,\n validateNavStructure,\n validateSidebarItems,\n} from \"./nav-diagnostics.ts\";\nimport { buildNavigation, isPageRef, pagesByRef } from \"./navigation.ts\";\nimport type {\n FolderMeta,\n LocalizableLabel,\n ResolvedConfig,\n ResolvedI18nConfig,\n ResolvedVersionsConfig,\n SidebarItemConfig,\n} from \"./schema.ts\";\nimport type {\n ContentGraph,\n Diagnostic,\n Navigation,\n NavTab,\n PageRecord,\n} from \"./types.ts\";\nimport { versionizeRoute } from \"./versions.ts\";\n\ninterface BuildContentGraphOptions {\n /** The banner's link target as configured (`banner.link.href`), if any. */\n bannerHref?: string;\n /** Site-wide route mount point (`\"\"` or `/seg`); invisible to the nav tree. */\n basePath?: string;\n /** The header brand link as configured (`logo.href`, default `/`). */\n brandHref?: string;\n /** Routes served outside the content tree that tabs may link to directly. */\n extraRoutes?: ReadonlySet<string>;\n folderMeta: Map<string, FolderMeta>;\n sharedFolderMeta?: Map<string, FolderMeta>;\n navigation: ResolvedConfig[\"navigation\"];\n i18n?: ResolvedI18nConfig;\n versions?: ResolvedVersionsConfig;\n}\n\ntype FallbackLocale = ReturnType<typeof resolveFallbackLocale>;\n\n/** Build the route → page-id map, flagging any duplicate-route collisions. */\nconst collectRoutes = (pages: PageRecord[]) => {\n const routes = new Map<string, string>();\n const diagnostics: Diagnostic[] = [];\n for (const page of pages) {\n const existing = routes.get(page.route);\n if (existing) {\n diagnostics.push({\n code: \"BLUME_DUPLICATE_ROUTE\",\n file: page.sourcePath ?? page.id,\n message: `Two files resolve to ${page.route}: ${existing} and ${page.id}`,\n severity: \"error\",\n suggestion: \"Rename or move one of the files so each route is unique.\",\n });\n continue;\n }\n routes.set(page.route, page.id);\n }\n return { diagnostics, routes };\n};\n\n/**\n * A locale's pages, padded with fallback-locale entries for any translation it\n * hasn't authored yet, so navigation mirrors the default structure instead of\n * showing an empty (or partial) tree.\n */\nconst localePagesFor = (\n code: string,\n real: PageRecord[],\n fallback: FallbackLocale,\n fallbackByKey: Map<string, PageRecord>,\n i18n: ResolvedI18nConfig,\n basePath: string\n): PageRecord[] => {\n if (!(fallback && code !== fallback)) {\n return real;\n }\n const present = new Set(real.map((page) => page.translationKey));\n const filled: PageRecord[] = [];\n for (const [key, source] of fallbackByKey) {\n if (!present.has(key)) {\n filled.push({\n ...source,\n fallback: true,\n locale: code,\n route: mountBasePath(basePath, localizeRoute(key, code, i18n)),\n });\n }\n }\n return [...real, ...filled];\n};\n\n/**\n * Resolve a possibly-per-locale header label to the string a locale renders:\n * the active locale's entry, else the default locale's, else the map's first\n * entry (which is also what a single-locale site gets).\n */\n/** A label is either one string for every locale or a per-locale map. */\nconst isSingleLabel = (label: LocalizableLabel): label is string =>\n typeof label === \"string\";\n\nconst resolveLabel = (\n label: LocalizableLabel,\n locale: string,\n defaultLocale?: string\n): string => {\n if (isSingleLabel(label)) {\n return label;\n }\n return (\n label[locale] ??\n (defaultLocale === undefined ? undefined : label[defaultLocale]) ??\n Object.values(label)[0] ??\n \"\"\n );\n};\n\n/**\n * An explicit sidebar with every item's `href` passed through `localize` (page\n * refs and `root`s resolve against each locale's own pages already).\n */\nconst localizeSidebarHrefs = (\n items: SidebarItemConfig[],\n localize: (href: string) => string\n): SidebarItemConfig[] =>\n items.map((item) => {\n if (isPageRef(item)) {\n return item;\n }\n const localized = { ...item };\n if (item.href !== undefined) {\n localized.href = localize(item.href);\n }\n if (item.items) {\n localized.items = localizeSidebarHrefs(item.items, localize);\n }\n return localized;\n });\n\n/** Resolve every localizable label in the configured tabs for one locale. */\nconst resolveTabLabels = (\n tabs: BuildContentGraphOptions[\"navigation\"][\"tabs\"],\n locale: string,\n defaultLocale?: string\n): NavTab[] =>\n (tabs ?? []).map((tab) => ({\n ...tab,\n items: tab.items?.map((item) => ({\n ...item,\n label: resolveLabel(item.label, locale, defaultLocale),\n })),\n label: resolveLabel(tab.label, locale, defaultLocale),\n }));\n\n/**\n * Mount a configured link authored as if the site were served at root (the\n * banner's): under `basePath`, except a route served outside the content tree\n * — a custom page, the generated changelog index — which answers only at its\n * own path.\n */\nconst mountConfiguredLink = (\n path: string,\n basePath: string,\n extraRoutes: ReadonlySet<string>\n): string => (extraRoutes.has(path) ? path : withBasePath(basePath, path));\n\n/**\n * Build one locale's navigation tree from its own pages and folder meta.\n * `version` is the archived version id when building a snapshot's tree\n * (`\"\"` for the current docs): it shifts the folder-meta lookups into the\n * snapshot's key space and roots the tree at the localized version root.\n */\nconst buildLocaleNavigation = (\n code: string,\n pages: PageRecord[],\n fallback: FallbackLocale,\n fallbackByKey: Map<string, PageRecord>,\n options: BuildContentGraphOptions,\n i18n: ResolvedI18nConfig,\n diagnostics: Diagnostic[],\n version = \"\"\n): Navigation => {\n const basePath = options.basePath ?? \"\";\n const real = pages.filter((page) => page.locale === code);\n const localePages = localePagesFor(\n code,\n real,\n fallback,\n fallbackByKey,\n i18n,\n basePath\n );\n // Every route the build serves: each locale's pages (this locale's fallback\n // copies included) plus the routes outside the content tree — custom pages\n // and the generated changelog index, which mount outside `basePath`.\n const pageRoutes = [\n ...new Set([...pages, ...localePages].map((page) => page.route)),\n ];\n const extraRoutes = options.extraRoutes ?? new Set<string>();\n // Whether `path` (authored at root, like a tab path) is served — exactly, or\n // with `section`, as the prefix of a served page (a tab resolves to its\n // section's first page, a plain link doesn't).\n const serves = (path: string, section: boolean): boolean => {\n if (extraRoutes.has(path)) {\n return true;\n }\n const based = withBasePath(basePath, path);\n const prefix = based === \"/\" ? \"/\" : `${based}/`;\n return pageRoutes.some(\n (route) => route === based || (section && route.startsWith(prefix))\n );\n };\n // Localize an internal link so it points to its in-locale route (e.g. `/docs`\n // -> `/fr/docs`); external paths pass through. A route this locale doesn't\n // serve while the default one does — a custom page, the generated changelog\n // index — exists only at its own path, so the link stays there instead of\n // 404ing. Tab paths (and their dropdown items') resolve to a section's first\n // page, so a translated section counts; hrefs must be served exactly.\n // Selectors are left alone: a language selector's items intentionally target\n // specific locales. The header's brand link resolves the same way, so the\n // logo and the tabs beside it always agree on the reader's locale.\n const localizeServed = (path: string, section: boolean): string => {\n const localized = localizeInternalPath(path, code, i18n);\n return localized === path ||\n serves(localized, section) ||\n !serves(path, section)\n ? localized\n : path;\n };\n const localizePath = (path: string): string => localizeServed(path, true);\n const tabs = resolveTabLabels(\n options.navigation.tabs,\n code,\n i18n.defaultLocale\n ).map((tab) => {\n const localized = {\n ...tab,\n items: tab.items?.map((item) => ({\n ...item,\n path: localizePath(item.path),\n })),\n path: localizePath(tab.path),\n };\n if (localized.href) {\n localized.href = localizeServed(localized.href, false);\n }\n return localized;\n });\n // Meta files live in locale directories only under the `dir` parser\n // (`fr/guides/meta.ts` -> key `fr/guides`). Under `dot`, translations sit\n // next to the originals and `guides/meta.ts` applies to every locale —\n // prefixing would look up keys that can never exist. Inside a snapshot the\n // version dir is hoisted in front (`v1.0/fr`), matching `discoverFolderMeta`.\n const localeDirOf = (locale: string): string =>\n i18n.parser === \"dir\" && locale !== i18n.defaultLocale ? locale : \"\";\n const localeDir = localeDirOf(code);\n // A locale padded from the fallback locale mirrors that locale's folder meta\n // for any folder it has no meta of its own for.\n const fallbackMetaPrefix =\n fallback && code !== fallback\n ? [version, localeDirOf(fallback)].filter(Boolean).join(\"/\")\n : undefined;\n // Internal featured and header hrefs are localized like tab paths — a pinned\n // `/changelog` link rendered on `/fr/…` pages must stay inside the reader's\n // locale, not kick them back to the default one.\n const localizeHref = <T extends { href: string }>(item: T): T => ({\n ...item,\n href: localizeServed(item.href, false),\n });\n const { actions, cta, featured } = options.navigation;\n const sidebarItems = options.navigation.sidebar.items;\n\n const navigation = buildNavigation(localePages, {\n actions: actions?.map(localizeHref),\n basePath,\n cta: cta ? localizeHref(cta) : null,\n diagnostics,\n display: options.navigation.sidebar.display,\n extraRoutes: options.extraRoutes,\n fallbackMetaPrefix,\n featured: featured?.map(localizeHref),\n folderMeta: options.folderMeta,\n // The localized tree root (\"/\" for the hidden default, \"/fr\" otherwise;\n // \"/fr/v1.0\" inside a snapshot): the tab pointing here spans the whole\n // tree and must not be treated as a tab section.\n localizedRoot: localizeRoute(versionizeRoute(\"/\", version), code, i18n),\n metaPrefix: [version, localeDir].filter(Boolean).join(\"/\"),\n refByLogical: true,\n selectors: options.navigation.selectors,\n sharedFolderMeta: options.sharedFolderMeta,\n // Shared `meta.$.*` files are locale-agnostic but version-specific: a\n // snapshot's shared meta keys under its version dir.\n sharedMetaPrefix: version,\n // A configured explicit sidebar describes the current docs; a frozen\n // snapshot's structure comes from the snapshot itself, so archived trees\n // always build from the filesystem. Its `href` links are localized like\n // featured links.\n sidebar:\n version || !sidebarItems\n ? undefined\n : localizeSidebarHrefs(sidebarItems, (href) =>\n localizeServed(href, false)\n ),\n // Tab paths are written against the current docs' root, which a\n // snapshot's versionized `localizedRoot` no longer is.\n tabRoot: localizeRoute(\"/\", code, i18n),\n tabs,\n });\n // `serves` checked the localized brand link against based routes, and\n // unlike tab and header links it isn't rebased later, so a localized one\n // takes the base here (`/fr` is served at `/docs/fr`).\n const localizedBrand = (href: string): string => {\n const localized = localizeServed(href, false);\n return localized === href ? localized : withBasePath(basePath, localized);\n };\n const branded =\n options.brandHref === undefined\n ? navigation\n : { ...navigation, brandHref: localizedBrand(options.brandHref) };\n // The banner link is authored like a featured href — in the default\n // locale's path space, as if mounted at root — so it is localized the same\n // way before it is mounted.\n return options.bannerHref === undefined\n ? branded\n : {\n ...branded,\n bannerHref: mountConfiguredLink(\n localizeServed(options.bannerHref, false),\n basePath,\n extraRoutes\n ),\n };\n};\n\n/**\n * Per-locale navigation trees plus the default-locale tree for i18n sites.\n * Called once for the current docs and once per archived version (with that\n * version's pages and its id as `version`).\n */\nconst buildI18nNavigation = (\n pages: PageRecord[],\n options: BuildContentGraphOptions,\n i18n: ResolvedI18nConfig,\n diagnostics: Diagnostic[],\n version = \"\"\n) => {\n // Pages of the fallback locale, by translation key — used to fill in a\n // locale's sidebar for pages it hasn't translated yet.\n const fallback = resolveFallbackLocale(i18n);\n // Monolingual pages (GitHub releases) are left out: they aren't served at the\n // other locales' URLs (see the route manifest), so no sidebar may link there.\n const fallbackByKey = new Map<string, PageRecord>();\n if (fallback) {\n for (const page of pages) {\n if (page.locale === fallback && !page.monolingual) {\n fallbackByKey.set(page.translationKey, page);\n }\n }\n }\n\n // Each locale gets an independent tree, so navigation may diverge per language.\n // Untranslated pages are padded into every locale from the fallback, so a tie\n // in shared content would otherwise be re-reported once per locale — dedupe on\n // code + file + message, which are all locale-stable for padded pages. A\n // locale-specific tie names its own translated files/labels and survives.\n const navigationByLocale: Record<string, Navigation> = {};\n const seen = new Set<string>();\n for (const { code } of i18n.locales) {\n const localeDiagnostics: Diagnostic[] = [];\n navigationByLocale[code] = buildLocaleNavigation(\n code,\n pages,\n fallback,\n fallbackByKey,\n options,\n i18n,\n localeDiagnostics,\n version\n );\n for (const diagnostic of localeDiagnostics) {\n const key = `${diagnostic.code}\\n${diagnostic.file ?? \"\"}\\n${diagnostic.message}`;\n if (!seen.has(key)) {\n seen.add(key);\n diagnostics.push(diagnostic);\n }\n }\n }\n const navigation: Navigation = navigationByLocale[i18n.defaultLocale] ?? {\n featured: [],\n selectors: [],\n sidebar: [],\n tabs: [],\n };\n return { navigation, navigationByLocale };\n};\n\n/** One archived version's navigation trees, keyed by locale (`\"\"` sans i18n). */\nconst buildVersionNavigation = (\n id: string,\n versionPages: PageRecord[],\n options: BuildContentGraphOptions,\n diagnostics: Diagnostic[]\n) => {\n const { i18n } = options;\n if (i18n) {\n return buildI18nNavigation(versionPages, options, i18n, diagnostics, id)\n .navigationByLocale;\n }\n return {\n \"\": buildNavigation(versionPages, {\n actions: options.navigation.actions,\n basePath: options.basePath ?? \"\",\n cta: options.navigation.cta,\n diagnostics,\n display: options.navigation.sidebar.display,\n extraRoutes: options.extraRoutes,\n featured: options.navigation.featured,\n folderMeta: options.folderMeta,\n localizedRoot: versionizeRoute(\"/\", id),\n metaPrefix: id,\n selectors: options.navigation.selectors,\n sharedFolderMeta: options.sharedFolderMeta,\n sharedMetaPrefix: id,\n // Tab paths are written against the current docs' root.\n tabRoot: \"/\",\n // A configured explicit sidebar describes the current docs; a snapshot's\n // structure comes from the snapshot itself.\n tabs: resolveTabLabels(options.navigation.tabs, \"\"),\n }),\n };\n};\n\n/** Assemble the content graph: routes map, nav, and duplicate diagnostics. */\nexport const buildContentGraph = (\n pages: PageRecord[],\n options: BuildContentGraphOptions\n): ContentGraph => {\n const { diagnostics, routes } = collectRoutes(pages);\n const { i18n } = options;\n\n // Under versioning, each version gets independent trees: navPath is\n // version-stripped, so mixing versions into one build would collide the\n // same logical page once per version.\n const currentPages = options.versions\n ? pages.filter((page) => page.version === \"\")\n : pages;\n\n // A single-locale site has no per-locale trees; the empty map is its\n // navigationByLocale.\n let navigationByLocale: Record<string, Navigation> = {};\n let navigation: Navigation;\n if (i18n) {\n ({ navigation, navigationByLocale } = buildI18nNavigation(\n currentPages,\n options,\n i18n,\n diagnostics\n ));\n } else {\n navigation = buildNavigation(currentPages, {\n actions: options.navigation.actions,\n basePath: options.basePath ?? \"\",\n cta: options.navigation.cta,\n diagnostics,\n display: options.navigation.sidebar.display,\n extraRoutes: options.extraRoutes,\n featured: options.navigation.featured,\n folderMeta: options.folderMeta,\n selectors: options.navigation.selectors,\n sharedFolderMeta: options.sharedFolderMeta,\n sidebar: options.navigation.sidebar.items,\n // No locale to prefer: a per-locale label map resolves to its first\n // entry on a single-locale site.\n tabs: resolveTabLabels(options.navigation.tabs, \"\"),\n });\n if (options.bannerHref !== undefined) {\n navigation = {\n ...navigation,\n bannerHref: mountConfiguredLink(\n options.bannerHref,\n options.basePath ?? \"\",\n options.extraRoutes ?? new Set<string>()\n ),\n };\n }\n }\n\n const navigationByVersion: Record<string, Record<string, Navigation>> = {};\n for (const { id } of options.versions?.archived ?? []) {\n navigationByVersion[id] = buildVersionNavigation(\n id,\n pages.filter((page) => page.version === id),\n options,\n diagnostics\n );\n }\n\n // Icon typos, duplicate labels, and hidden-page-in-sidebar are validated on\n // the built navigation. Missing-target detection needs the full route set\n // (incl. custom + generated pages), so it runs later in generateRuntime.\n diagnostics.push(\n ...validateNavIcons(navigation),\n ...validateNavStructure(navigation, currentPages)\n );\n // Checked once against every locale's pages, not per locale tree: a page\n // missing from one locale's sidebar is an untranslated page, not a typo.\n const sidebarItems = options.navigation.sidebar.items;\n if (sidebarItems) {\n const byRef = pagesByRef(\n currentPages,\n options.basePath ?? \"\",\n Boolean(i18n)\n );\n const extraRoutes = options.extraRoutes ?? new Set<string>();\n diagnostics.push(\n ...validateSidebarItems(\n sidebarItems,\n (route) => byRef.has(route) || extraRoutes.has(route)\n )\n );\n }\n\n return {\n diagnostics,\n navigation,\n navigationByLocale,\n navigationByVersion,\n pages,\n routes,\n };\n};\n",
|
|
13
|
+
"import { execFileSync } from \"node:child_process\";\nimport { realpathSync } from \"node:fs\";\n\nimport { normalize, relative } from \"pathe\";\n\nimport type { Diagnostic } from \"./types.ts\";\n\n/** Normalized form of the `lastModified` config. */\nexport interface ResolvedLastModified {\n enabled: boolean;\n source: \"git\" | \"frontmatter\";\n}\n\n/**\n * Env for git invocations, with the repo-locating GIT_* variables stripped. A\n * parent git process exports an absolute GIT_DIR (and friends) to its hooks —\n * husky pre-commit in a linked worktree, post-merge, CI wrappers — and an\n * inherited GIT_DIR overrides `-C` discovery, silently pointing every call at\n * the parent's repository instead of the project's.\n */\nconst GIT_LOCATION_VARS = new Set([\n \"GIT_COMMON_DIR\",\n \"GIT_DIR\",\n \"GIT_INDEX_FILE\",\n \"GIT_OBJECT_DIRECTORY\",\n \"GIT_PREFIX\",\n \"GIT_WORK_TREE\",\n]);\n\nconst gitEnv = (): NodeJS.ProcessEnv =>\n Object.fromEntries(\n Object.entries(process.env).filter(([key]) => !GIT_LOCATION_VARS.has(key))\n );\n\n/** Normalize the `lastModified` config (`false | \"git\" | \"frontmatter\"`) into `{ enabled, source }`. */\nexport const resolveLastModifiedConfig = (\n value: false | \"git\" | \"frontmatter\"\n): ResolvedLastModified =>\n value === false\n ? { enabled: false, source: \"git\" }\n : { enabled: true, source: value };\n\n/**\n * Parse `git log --format=%x00%cI --name-only` output into a map of\n * repo-root-relative path → most recent committer ISO date. Each commit emits a\n * NUL-prefixed date line followed by the paths it touched; since git logs\n * newest-first, the first date seen for a path wins. Blank lines are ignored.\n */\nexport const parseGitLog = (output: string): Map<string, string> => {\n const times = new Map<string, string>();\n let current: string | null = null;\n for (const line of output.split(\"\\n\")) {\n if (line.startsWith(\"\\0\")) {\n current = line.slice(1);\n } else if (line && current && !times.has(line)) {\n times.set(line, current);\n }\n }\n return times;\n};\n\n/**\n * `path` with every symlink resolved, the spelling git prints paths in: a\n * project opened through a link (`/tmp/site` on macOS, where `/tmp` links to\n * `/private/tmp`) gets `/private/tmp/site` from `rev-parse --show-toplevel`,\n * so the project's own spelling would compare as outside the repository and\n * every page would go undated. The native realpath also expands a Windows\n * 8.3 short name (`RUNNER~1`), which git does too. A path that doesn't exist\n * stays as written.\n */\nexport const realPath = (path: string): string => {\n try {\n return normalize(realpathSync.native(path));\n } catch {\n return path;\n }\n};\n\n/**\n * The toplevel of the repository containing `root`, or null when git is\n * unavailable or the project isn't a repo. Callers use it to decide which\n * content roots a `git log` pathspec can cover at all — a root outside the\n * repository would fail the log outright and can never yield dates.\n */\nexport const gitRepositoryRoot = (root: string): string | null => {\n try {\n return execFileSync(\n // oxlint-disable-next-line sonarjs/no-os-command-from-path -- git is a required dev-tool dependency resolved from PATH\n \"git\",\n [\"-C\", root, \"rev-parse\", \"--show-toplevel\"],\n // stderr silenced: outside a repository the probe fails by design.\n { encoding: \"utf-8\", env: gitEnv(), stdio: [\"ignore\", \"pipe\", \"ignore\"] }\n ).trim();\n } catch {\n return null;\n }\n};\n\n/**\n * Resolve each source file's last-modified date from git history, keyed by\n * absolute source path. Runs a single `git log` over the given content roots\n * (each local source's own root, which may diverge from `content.root`)\n * and maps repo-root-relative paths back to the given absolute paths\n * (monorepo-safe via `rev-parse --show-toplevel`). Returns an empty map if git\n * is unavailable or the project isn't a repo — the feature then simply shows\n * no dates.\n */\nexport const gitLastModifiedTimes = (\n root: string,\n contentRoots: string[],\n sourcePaths: string[],\n repositoryRoot?: string | null\n): Map<string, string> => {\n // Nothing to date, or nowhere bounded to look — either way, don't pay for a\n // git scan. Both guards matter: `git log -- ` with no pathspec logs the\n // entire repository, which is what an all-staged project produces (a staged\n // source contributes no content root, yet its entries can still carry a\n // `sourcePath`).\n if (sourcePaths.length === 0 || contentRoots.length === 0) {\n return new Map();\n }\n // The caller that bounded `contentRoots` already resolved the repo root;\n // reuse it rather than spawning `rev-parse` a second time per scan.\n const gitRoot =\n repositoryRoot === undefined ? gitRepositoryRoot(root) : repositoryRoot;\n if (gitRoot === null) {\n return new Map();\n }\n // Git names paths by their real location, so the pathspecs and the paths\n // read back against the log are compared in that spelling too (see\n // `realPath`).\n const top = realPath(gitRoot);\n try {\n const output = execFileSync(\n // oxlint-disable-next-line sonarjs/no-os-command-from-path -- git is a required dev-tool dependency resolved from PATH\n \"git\",\n [\n \"-C\",\n root,\n \"-c\",\n \"core.quotePath=false\",\n \"log\",\n \"--format=%x00%cI\",\n \"--name-only\",\n \"--\",\n ...contentRoots.map(realPath),\n ],\n { encoding: \"utf-8\", env: gitEnv(), maxBuffer: 256 * 1024 * 1024 }\n );\n const byRepoPath = parseGitLog(output);\n const result = new Map<string, string>();\n for (const sourcePath of sourcePaths) {\n const iso = byRepoPath.get(relative(top, realPath(sourcePath)));\n if (iso) {\n result.set(sourcePath, iso);\n }\n }\n return result;\n } catch {\n return new Map();\n }\n};\n\n/**\n * Whether the repository containing `root` is a shallow clone. Returns false\n * when git is unavailable or the project isn't a repo — those cases already\n * yield no dates at all, and the shallow warning would only mislead.\n */\nexport const isShallowGitRepository = (root: string): boolean => {\n try {\n return (\n execFileSync(\n // oxlint-disable-next-line sonarjs/no-os-command-from-path -- git is a required dev-tool dependency resolved from PATH\n \"git\",\n [\"-C\", root, \"rev-parse\", \"--is-shallow-repository\"],\n // stderr silenced: outside a repository the probe fails by design.\n {\n encoding: \"utf-8\",\n env: gitEnv(),\n stdio: [\"ignore\", \"pipe\", \"ignore\"],\n }\n ).trim() === \"true\"\n );\n } catch {\n return false;\n }\n};\n\n/**\n * A warning for git-derived dates silently missing because the build ran in a\n * shallow clone — the default on Vercel and `actions/checkout`, where `git log`\n * only sees the last few commits, so most pages get no date and the sitemap's\n * `<lastmod>` / \"Last updated\" stamps quietly disappear in production while\n * working locally. Empty when every page got a date or the clone isn't\n * shallow.\n */\nexport const lastModifiedShallowWarning = (\n root: string,\n undatedCount: number\n): Diagnostic[] => {\n if (undatedCount === 0 || !isShallowGitRepository(root)) {\n return [];\n }\n return [\n {\n code: \"BLUME_SHALLOW_GIT_HISTORY\",\n message: `lastModified is on, but this build runs in a shallow git clone, so ${undatedCount} page(s) have no git-derived date — their sitemap <lastmod> and \"Last updated\" stamps are omitted.`,\n severity: \"warning\",\n suggestion:\n \"Fetch full history in CI: set the VERCEL_DEEP_CLONE=true environment variable on Vercel, or fetch-depth: 0 for actions/checkout.\",\n },\n ];\n};\n",
|
|
14
|
+
"import { basename, dirname, relative } from \"pathe\";\nimport { glob, isDynamicPattern } from \"tinyglobby\";\n\nimport { diagnosticsFromZod } from \"./diagnostics.ts\";\nimport { createModuleLoader } from \"./load-module.ts\";\nimport { folderMetaSchema } from \"./schema.ts\";\nimport type { FolderMeta } from \"./schema.ts\";\nimport { trimChar } from \"./trim.ts\";\nimport type { Diagnostic } from \"./types.ts\";\n\nconst META_FILES = [\n \"**/meta.ts\",\n \"**/meta.js\",\n \"**/meta.mjs\",\n // Shared, locale-agnostic folder meta (applies to every locale).\n \"**/meta.$.ts\",\n \"**/meta.$.js\",\n \"**/meta.$.mjs\",\n];\n\n/**\n * A meta module's default export before validation: `folderMetaSchema` parses\n * it only after any factory is resolved, so it carries the loader's raw type.\n */\ntype MetaModuleExport = Awaited<\n ReturnType<ReturnType<typeof createModuleLoader>>\n>;\n\n/** A factory-style meta module default-exports a function returning the meta. */\nconst isMetaFactory = (\n mod: MetaModuleExport\n): mod is () => MetaModuleExport | Promise<MetaModuleExport> =>\n typeof mod === \"function\";\n\n/** Resolve a meta module's default export, calling it if it is a factory. */\nconst resolveMeta = async (mod: MetaModuleExport) =>\n isMetaFactory(mod) ? await mod() : mod;\n\n/** A filesystem content source to scan for folder meta: its on-disk root and\n * optional route prefix. The prefix is folded into every key so meta lines up\n * with the sidebar group path, which carries the same prefix. `include` and\n * `exclude` are the source's content globs, which scope the scan to the\n * folders the source reads. */\nexport interface FolderMetaSource {\n root: string;\n prefix?: string;\n include?: readonly string[];\n exclude?: readonly string[];\n}\n\n/**\n * The literal directory a glob starts from (`docs/**\\/*.md` → `docs`, `\"\"`\n * when it matches from the root): its leading segments up to the first one\n * with glob syntax, the file-name segment excluded.\n */\nconst globBase = (pattern: string): string => {\n const literal: string[] = [];\n for (const segment of pattern.split(\"/\").slice(0, -1)) {\n if (isDynamicPattern(segment)) {\n break;\n }\n if (segment !== \"\" && segment !== \".\") {\n literal.push(segment);\n }\n }\n return literal.join(\"/\");\n};\n\n/** Whether `dir` is `base`, or nested beneath or above it (`\"\"` is the root). */\nconst onPathOf = (dir: string, base: string): boolean =>\n dir === base ||\n base === \"\" ||\n dir === \"\" ||\n dir.startsWith(`${base}/`) ||\n base.startsWith(`${dir}/`);\n\n/**\n * Whether a meta file's directory can configure a sidebar group of the\n * source: it lies on the path of some `include` glob's base — the folders\n * that glob reads, or the ancestors whose groups hold them. Negated globs\n * only narrow what a source reads, so they never qualify a folder.\n */\nconst withinInclude = (\n dir: string,\n include: readonly string[] | undefined\n): boolean =>\n include === undefined ||\n include.some(\n (pattern) => !pattern.startsWith(\"!\") && onPathOf(dir, globBase(pattern))\n );\n\n/**\n * The folder-meta files a filesystem source contributes: `patterns` (every\n * meta module name, shared `meta.$.*` included, by default) under the source's\n * root, minus its `exclude` globs, dependencies, and build output, and only in\n * folders its `include` globs reach. `blume translate` finds the meta files it\n * translates through this too, so it covers exactly the ones the scan reads.\n */\nexport const findFolderMetaFiles = async (\n source: FolderMetaSource,\n patterns: readonly string[] = META_FILES\n): Promise<string[]> => {\n const found = await glob([...patterns], {\n absolute: true,\n cwd: source.root,\n // Never descend into dependencies or build output — relevant when the\n // root is the project root (e.g. a `.`-rooted or all-staged project).\n // The source's own `exclude` applies too: a `meta.ts` under an excluded\n // folder (`src/lib/meta.ts` beside `exclude: [\"src/**\"]`) is application\n // code, not folder meta, and importing it would fail.\n ignore: [\n ...(source.exclude ?? []),\n \"**/node_modules/**\",\n \"**/.blume/**\",\n \"**/dist/**\",\n ],\n onlyFiles: true,\n });\n return found.filter((file) =>\n withinInclude(relative(source.root, dirname(file)), source.include)\n );\n};\n\n/**\n * The folder-meta key for a directory. Mirrors the sidebar group path: the\n * source's route prefix (`docs`) followed by the directory relative to the\n * source root (`provider`) — so `docs/provider/meta.ts` under a `prefix: \"docs\"`\n * source keys to `docs/provider`, exactly the group path navigation builds.\n */\nconst metaKeyFor = (prefix: string | undefined, dir: string): string => {\n const clean = prefix ? trimChar(prefix, \"/\") : \"\";\n if (!clean) {\n return dir;\n }\n return dir ? `${clean}/${dir}` : clean;\n};\n\n/**\n * Discover `meta.{ts,js,mjs}` files across the given filesystem sources. Keys are\n * the source's route prefix joined with the directory relative to that source's\n * root (`\"\"`/the prefix itself for a source's root directory), so meta matches\n * the prefixed sidebar group path. A bare string is shorthand for a single,\n * unprefixed source (the default project layout). `meta.$.*` files are returned\n * in `shared` — folder meta that applies to that directory in every locale (a\n * locale-specific `meta.*` overrides it). Each file default-exports an object or\n * a (sync/async) function returning one.\n *\n * `localeDirs` names the top-level locale directories of a `dir`-parser i18n\n * project. Navigation looks locale meta up as `locale/<group path>` where the\n * group path starts with the source prefix, so a locale directory found at a\n * source root is hoisted in front of the prefix (`docs/fr/guides/meta.ts` keys\n * to `fr/docs/guides`, not `docs/fr/guides`).\n *\n * `versionDirs` names the archived-version snapshot directories, which sit\n * outermost on disk — a locale directory inside a snapshot is one level deeper.\n * Both are hoisted, version first (`v1.0/fr/guides/meta.ts` keys to\n * `v1.0/fr/<prefix>/guides`), matching navigation's version-aware meta prefix.\n */\nexport const discoverFolderMeta = async (\n sources: string | FolderMetaSource[],\n options: {\n localeDirs?: readonly string[];\n versionDirs?: readonly string[];\n } = {}\n): Promise<{\n meta: Map<string, FolderMeta>;\n shared: Map<string, FolderMeta>;\n diagnostics: Diagnostic[];\n}> => {\n const list: FolderMetaSource[] = Array.isArray(sources)\n ? sources\n : [{ root: sources }];\n // Locale folders match case-insensitively, like content does (`pt-br/` for\n // a configured `pt-BR`), and key under the configured casing, which is what\n // navigation looks meta up by.\n const localeDirs = new Map(\n (options.localeDirs ?? []).map((code) => [code.toLowerCase(), code])\n );\n const versionDirs = new Set(options.versionDirs);\n\n const load = createModuleLoader();\n const meta = new Map<string, FolderMeta>();\n const shared = new Map<string, FolderMeta>();\n const diagnostics: Diagnostic[] = [];\n\n // Scan every source under its own root so a source rooted outside\n // `content.root` still contributes its folder meta.\n const perSource = await Promise.all(\n list.map(async (source) => {\n const files = await findFolderMetaFiles(source);\n const loaded = await Promise.all(\n files.map(\n async (\n file\n ): Promise<\n | { ok: true; file: string; value: unknown }\n | { ok: false; file: string; error: Error }\n > => {\n try {\n return {\n file,\n ok: true,\n value: await resolveMeta(await load(file)),\n };\n } catch (error) {\n // SAFETY: jiti surfaces load/evaluate failures as Error\n // instances, and only `message` is read downstream.\n return { error: error as Error, file, ok: false };\n }\n }\n )\n );\n return { loaded, source };\n })\n );\n\n for (const { loaded, source } of perSource) {\n for (const entry of loaded) {\n const dir = relative(source.root, dirname(entry.file));\n // A version snapshot dir is outermost, with a locale dir one level\n // deeper; both are hoisted in front of the (prefixed) group path, in\n // that order — the lookup key reads `version/locale/prefix/dir`.\n const [head, ...tail] = dir.split(\"/\");\n const version = head && versionDirs.has(head) ? head : \"\";\n const afterVersion = version ? tail : [head ?? \"\", ...tail];\n const [localeHead, ...localeTail] = afterVersion;\n const locale = localeHead\n ? (localeDirs.get(localeHead.toLowerCase()) ?? \"\")\n : \"\";\n const rest = (locale ? localeTail : afterVersion)\n .filter(Boolean)\n .join(\"/\");\n const key = [version, locale, metaKeyFor(source.prefix, rest)]\n .filter(Boolean)\n .join(\"/\");\n\n if (!entry.ok) {\n diagnostics.push({\n code: \"BLUME_META_LOAD_FAILED\",\n file: entry.file,\n message: `Could not load meta file: ${entry.error.message}`,\n severity: \"error\",\n });\n continue;\n }\n\n const result = folderMetaSchema.safeParse(entry.value);\n if (result.success) {\n const target = basename(entry.file).startsWith(\"meta.$.\")\n ? shared\n : meta;\n target.set(key, result.data);\n } else {\n diagnostics.push(\n ...diagnosticsFromZod(result.error, {\n code: \"BLUME_META_INVALID\",\n file: entry.file,\n })\n );\n }\n }\n }\n\n return { diagnostics, meta, shared };\n};\n",
|
|
15
|
+
"import { join } from \"pathe\";\n\nimport { blumeReferences } from \"../../openapi/references.ts\";\nimport { openApiSource } from \"../../openapi/source.ts\";\nimport type { ContentSourceAdapter } from \"../../sources/registry.ts\";\nimport type { ResolvedConfig } from \"../schema.ts\";\nimport { trimChar } from \"../trim.ts\";\nimport type { ProjectContext } from \"../types.ts\";\nimport { hashText } from \"./cache.ts\";\nimport { contentfulSource } from \"./contentful.ts\";\nimport { filesystemSource } from \"./filesystem.ts\";\nimport { githubReleasesSource } from \"./github-releases.ts\";\nimport { mdxRemoteSource } from \"./mdx-remote.ts\";\nimport { notionSource } from \"./notion.ts\";\nimport { obsidianSource } from \"./obsidian.ts\";\nimport { payloadSource } from \"./payload.ts\";\nimport { sanitySource } from \"./sanity.ts\";\nimport { strapiSource } from \"./strapi.ts\";\nimport type { ContentSource, SourceContext } from \"./types.ts\";\n\nexport {\n resolveDocsCollection,\n resolveSourceRoot,\n sourcesOfKind,\n} from \"./collection.ts\";\nexport type { DocsCollection } from \"./collection.ts\";\n\n/** Allocate a unique, stable source name from a base (prefix or kind). */\nconst uniqueNamer = (): ((base: string) => string) => {\n const used = new Set<string>();\n return (base) => {\n let name = base;\n let n = 2;\n while (used.has(name)) {\n name = `${base}-${n}`;\n n += 1;\n }\n used.add(name);\n return name;\n };\n};\n\n/** Runtime knobs for a scan: dev/build mode, preview, and cache refresh. */\nexport interface SourceRuntime {\n mode: \"dev\" | \"build\";\n preview?: boolean;\n refresh?: boolean;\n}\n\n/**\n * The directory under `.blume/cache/<source>/` a source keeps its snapshot in:\n * one per preview mode and set of adapter options. A preview snapshot holds\n * drafts, so a published load must never read it, neither as dev's cache-first\n * snapshot nor as a build's offline fallback; and a snapshot fetched with\n * another `query` or `fields` is another result, not a cached copy of this\n * one. `pollInterval` only paces dev re-fetches, so it is left out. A\n * `custom()` source is a live instance rather than options, so its snapshot\n * is keyed by preview mode alone.\n */\nexport const snapshotKey = (\n adapter: ContentSourceAdapter,\n runtime: SourceRuntime\n): string => {\n const mode = runtime.preview ? \"preview\" : \"published\";\n if (adapter.kind === \"custom\") {\n return mode;\n }\n const { pollInterval: _paced, ...fetched } = adapter.options;\n return `${mode}-${hashText(JSON.stringify(fetched))}`;\n};\n\nconst sourceContext = (\n context: ProjectContext,\n name: string,\n runtime: SourceRuntime,\n snapshot?: string\n): SourceContext => ({\n assetsBaseUrl: `/blume-assets/${name}`,\n assetsDir: join(context.outDir, \"public\", \"blume-assets\", name),\n cacheDir:\n snapshot === undefined\n ? join(context.outDir, \"cache\", name)\n : join(context.outDir, \"cache\", name, snapshot),\n mode: runtime.mode,\n preview: runtime.preview,\n projectRoot: context.root,\n refresh: runtime.refresh ?? runtime.mode === \"build\",\n});\n\n/**\n * The frontmatter keys each content type declares (`content.types[*].frontmatter`),\n * keyed by type — so a source that drops unknown keys keeps, per note, exactly\n * the ones `normalizeEntry` will accept for that note's type.\n */\nconst typeFrontmatterKeys = (\n config: ResolvedConfig\n): Record<string, string[]> =>\n Object.fromEntries(\n Object.entries(config.content.types).map(([type, def]) => [\n type,\n Object.keys(def.frontmatter),\n ])\n );\n\n/**\n * Instantiate the engine-side source for a descriptor. Each adapter's options\n * spread straight into its engine factory: the descriptor carries exactly the\n * fields the factory documents, plus the shared `prefix`/`pollInterval`.\n */\nconst buildSource = (\n adapter: ContentSourceAdapter,\n name: string,\n config: ResolvedConfig,\n context: ProjectContext,\n runtime: SourceRuntime\n): ContentSource => {\n const ctx = (): SourceContext =>\n sourceContext(context, name, runtime, snapshotKey(adapter, runtime));\n switch (adapter.kind) {\n case \"filesystem\": {\n const { pollInterval: _ignored, ...options } = adapter.options;\n return filesystemSource({ ...options, name, projectRoot: context.root });\n }\n case \"custom\": {\n // An instance built by one of Blume's engine factories rebuilds itself\n // on this scan's context, so `custom(sanitySource({…}))` reads drafts\n // under `--preview` and caches in the runtime directory like the\n // built-in adapter. Any other instance manages its own context and\n // caching. Either way its name is made unique across the project for\n // id namespacing.\n const source = adapter.options.withContext?.(ctx()) ?? adapter.options;\n return source.name === name ? source : { ...source, name };\n }\n case \"sanity\": {\n return sanitySource({ ...adapter.options, name }, ctx());\n }\n case \"notion\": {\n return notionSource({ ...adapter.options, name }, ctx());\n }\n case \"contentful\": {\n return contentfulSource({ ...adapter.options, name }, ctx());\n }\n case \"payload\": {\n return payloadSource({ ...adapter.options, name }, ctx());\n }\n case \"strapi\": {\n return strapiSource({ ...adapter.options, name }, ctx());\n }\n case \"obsidian\": {\n const { pollInterval: _ignored, ...options } = adapter.options;\n return obsidianSource(\n {\n ...options,\n defaultType: config.content.defaultType,\n frontmatterKeys: Object.keys(config.frontmatter.extend),\n i18n: config.i18n,\n name,\n typeFrontmatterKeys: typeFrontmatterKeys(config),\n versions: config.versions,\n },\n ctx()\n );\n }\n case \"github-releases\": {\n return githubReleasesSource(\n { ...adapter.options, name, site: config.deployment.options.site },\n ctx()\n );\n }\n default: {\n // Only `mdx-remote` is left, and TypeScript has narrowed `adapter` to it.\n // It returns after the switch rather than from a last case block: Bun\n // 1.4.0's line coverage never credits the closing brace of a switch's\n // final block, which would fail the 100% gate in CI.\n break;\n }\n }\n return mdxRemoteSource({ ...adapter.options, name }, ctx());\n};\n\n/** The base name to allocate for a descriptor (before deduplication). */\nconst baseName = (adapter: ContentSourceAdapter): string => {\n if (adapter.kind === \"custom\") {\n return adapter.options.name;\n }\n // The name is the prefix as a route spells it: `/guides` and `guides/`\n // route under `guides`, so the source's staged entry ids, cache directory,\n // and asset URLs must use `guides` too.\n return trimChar(adapter.options.prefix ?? \"\", \"/\") || adapter.kind;\n};\n\n/**\n * Build the ordered list of content sources for a project. The config schema\n * already desugared the zero-config shorthand into a single `filesystem()`\n * descriptor, so every project has at least one entry here. A Blume-rendered\n * OpenAPI reference contributes an internal staged source that lowers each\n * operation into a real content page (routing/nav/search/OG).\n */\nexport const resolveSources = (\n config: ResolvedConfig,\n context: ProjectContext,\n runtime: SourceRuntime\n): ContentSource[] => {\n const nameFor = uniqueNamer();\n const sources = config.content.sources.map((adapter) =>\n buildSource(adapter, nameFor(baseName(adapter)), config, context, runtime)\n );\n\n const references = blumeReferences(config);\n if (references.length > 0) {\n sources.push(\n openApiSource(references, sourceContext(context, \"openapi\", runtime))\n );\n }\n\n return sources;\n};\n",
|
|
16
|
+
"/**\n * The JSON a CMS API returns: arbitrary values a query or REST endpoint\n * hands back, typed as a closed union so a source narrows with predicates\n * instead of casting.\n */\nexport type JsonValue =\n | string\n | number\n | boolean\n | null\n | JsonValue[]\n | { [key: string]: JsonValue };\n\n/** A JSON object: a document, a node in a rich-text tree, an API envelope. */\nexport interface JsonObject {\n [key: string]: JsonValue;\n}\n\n/**\n * A node a dot path can descend into. Arrays pass too (matching their runtime\n * string-key indexing, e.g. `items.0`), so the predicate types them as the\n * keyed form both traverse through.\n */\nexport const isJsonObject = (\n value: JsonValue | undefined\n): value is JsonObject => typeof value === \"object\" && value !== null;\n\n/** Resolve a dot path (`slug.current`) against a document. */\nexport const getPath = (\n doc: JsonObject,\n path: string\n): JsonValue | undefined => {\n let current: JsonValue | undefined = doc;\n for (const key of path.split(\".\")) {\n if (isJsonObject(current)) {\n current = current[key];\n } else {\n return;\n }\n }\n return current;\n};\n\nexport const isStringValue = (value: JsonValue | undefined): value is string =>\n typeof value === \"string\";\n\nexport const asString = (value: JsonValue | undefined): string | undefined =>\n isStringValue(value) ? value : undefined;\n\n/** The elements of an array value, or none when the value is not an array. */\nexport const asArray = (value: JsonValue | undefined): JsonValue[] =>\n Array.isArray(value) ? value : [];\n\n/** The object form of a value, or none when it is a scalar, array, or absent. */\nexport const asObject = (\n value: JsonValue | undefined\n): JsonObject | undefined =>\n isJsonObject(value) && !Array.isArray(value) ? value : undefined;\n\nexport const isNumberValue = (value: JsonValue | undefined): value is number =>\n typeof value === \"number\";\n\nexport const asNumber = (value: JsonValue | undefined): number | undefined =>\n isNumberValue(value) ? value : undefined;\n\n/** The object elements of an array value; scalars and nested arrays are dropped. */\nexport const objectsIn = (value: JsonValue | undefined): JsonObject[] =>\n asArray(value).flatMap((item) => {\n const object = asObject(item);\n return object ? [object] : [];\n });\n",
|
|
17
|
+
"/**\n * Contentful rich text → Markdown. Covers the block, inline, and mark types\n * the Rich Text editor emits; embedded entries fall through to a serializer\n * keyed by content type or are noted in a comment. Links to assets and\n * entries arrive unresolved from the Delivery API (`data.target` is a\n * `sys` link), so the source hands in resolvers over the response's\n * `includes`; a document the SDK already resolved (with `fields` inline)\n * lowers the same way.\n */\nimport type { JsonObject } from \"./json.ts\";\nimport { asString, getPath, objectsIn } from \"./json.ts\";\nimport type { InlineMarks, InlinePart } from \"./lower.ts\";\nimport {\n absoluteUrl,\n blockquote,\n guardBlockStart,\n headingPrefix,\n image,\n joinBlocks,\n linkParts,\n listItem,\n markdownDocument,\n renderInline,\n unsupported,\n writesMdx,\n} from \"./lower.ts\";\n\n/** An asset's file as a Markdown image needs it. */\nexport interface ContentfulAsset {\n description?: string;\n title?: string;\n url: string;\n}\n\nexport interface ContentfulRichTextOptions {\n /** Resolve an asset link (`data.target.sys.id`) to its file. */\n resolveAsset?: (id: string) => ContentfulAsset | null;\n /** Resolve an entry link to the entry (`sys` plus `fields`). */\n resolveEntry?: (id: string) => JsonObject | null;\n /**\n * Serializers for embedded entries, keyed by content type id; return\n * Markdown or MDX. Setting any writes the source's entries as MDX.\n */\n serializers?: Record<string, (entry: JsonObject) => string>;\n}\n\nconst HEADING = /^heading-(?<level>[1-6])$/u;\nconst CELL_PIPE = /\\|/gu;\nconst CELL_BREAK = /\\n+/gu;\n\n/** The file behind an asset entry, or null when it carries none. */\nexport const assetFromEntry = (asset: JsonObject): ContentfulAsset | null => {\n const url = asString(getPath(asset, \"fields.file.url\"));\n if (!url) {\n return null;\n }\n return {\n description: asString(getPath(asset, \"fields.description\")),\n title: asString(getPath(asset, \"fields.title\")),\n url: absoluteUrl(url),\n };\n};\n\nconst children = (node: JsonObject): JsonObject[] => objectsIn(node.content);\n\nconst marksOf = (node: JsonObject): InlineMarks => {\n const marks: InlineMarks = {};\n for (const mark of objectsIn(node.marks)) {\n switch (asString(mark.type)) {\n case \"bold\": {\n marks.bold = true;\n break;\n }\n case \"italic\": {\n marks.italic = true;\n break;\n }\n case \"code\": {\n marks.code = true;\n break;\n }\n case \"strikethrough\": {\n marks.strike = true;\n break;\n }\n default:\n }\n }\n return marks;\n};\n\n/**\n * The linked object a node points at: inline when the document was resolved\n * (`data.target.fields`), else through the resolver by `data.target.sys.id`.\n */\nconst linkedTarget = (\n node: JsonObject,\n resolve?: (id: string) => JsonObject | null\n): JsonObject | null => {\n const target = getPath(node, \"data.target\");\n if (getPath(node, \"data.target.fields\") !== undefined) {\n // SAFETY: `data.target.fields` resolved through the object, so `target`\n // is that object.\n return target as JsonObject;\n }\n const id = asString(getPath(node, \"data.target.sys.id\"));\n return id ? (resolve?.(id) ?? null) : null;\n};\n\nconst linkedAsset = (\n node: JsonObject,\n options: ContentfulRichTextOptions\n): ContentfulAsset | null => {\n if (getPath(node, \"data.target.fields\") !== undefined) {\n const inline = linkedTarget(node);\n return inline ? assetFromEntry(inline) : null;\n }\n const id = asString(getPath(node, \"data.target.sys.id\"));\n return id ? (options.resolveAsset?.(id) ?? null) : null;\n};\n\nconst embeddedEntry = (\n node: JsonObject,\n options: ContentfulRichTextOptions\n): string => {\n const entry = linkedTarget(node, options.resolveEntry);\n const contentType = entry\n ? asString(getPath(entry, \"sys.contentType.sys.id\"))\n : undefined;\n const serializer = contentType\n ? options.serializers?.[contentType]\n : undefined;\n if (entry && serializer) {\n return serializer(entry);\n }\n return unsupported(\n `Contentful embedded entry${contentType ? `: ${contentType}` : \"\"}`,\n writesMdx(options.serializers)\n );\n};\n\nconst inlineParts = (\n nodes: JsonObject[],\n options: ContentfulRichTextOptions\n): InlinePart[] =>\n // oxlint-disable-next-line no-use-before-define -- mutual recursion: a link's label is itself inline content\n nodes.flatMap((node) => inlineNodeParts(node, options));\n\nconst inlineNodeParts = (\n node: JsonObject,\n options: ContentfulRichTextOptions\n): InlinePart[] => {\n switch (asString(node.nodeType)) {\n case \"text\": {\n return [{ marks: marksOf(node), text: asString(node.value) ?? \"\" }];\n }\n case \"hyperlink\": {\n return linkParts(\n inlineParts(children(node), options),\n asString(getPath(node, \"data.uri\"))\n );\n }\n case \"asset-hyperlink\": {\n return linkParts(\n inlineParts(children(node), options),\n linkedAsset(node, options)?.url\n );\n }\n case \"embedded-entry-inline\": {\n return [{ markdown: embeddedEntry(node, options) }];\n }\n default: {\n // entry-hyperlink (no route to point at) and anything unknown: the label.\n return inlineParts(children(node), options);\n }\n }\n};\n\n/** A block's inline children as Markdown, guarded as one block. */\nconst blockText = (\n node: JsonObject,\n options: ContentfulRichTextOptions\n): string =>\n guardBlockStart(renderInline(inlineParts(children(node), options)));\n\nconst renderList = (\n items: JsonObject[],\n marker: (index: number) => string,\n options: ContentfulRichTextOptions\n): string =>\n items\n .map((item, index) =>\n listItem(\n marker(index),\n // A single newline between an item's paragraph and its nested list\n // keeps the list tight.\n children(item)\n // oxlint-disable-next-line no-use-before-define -- mutual recursion: an item holds blocks, including nested lists\n .map((child) => renderBlock(child, options))\n .filter((block) => block !== \"\")\n .join(\"\\n\")\n )\n )\n .join(\"\\n\");\n\n// Each of a cell's paragraphs is a block as much as a page's is, so it goes\n// through the same guard: two struck runs in one still meet as `~~~~`.\nconst cellText = (\n cell: JsonObject,\n options: ContentfulRichTextOptions\n): string =>\n children(cell)\n .map((paragraph) => blockText(paragraph, options))\n .join(\" \")\n .replaceAll(CELL_BREAK, \" \")\n .replaceAll(CELL_PIPE, String.raw`\\|`);\n\nconst tableRow = (cells: string[]): string => `| ${cells.join(\" | \")} |`;\n\nconst renderTable = (\n node: JsonObject,\n options: ContentfulRichTextOptions\n): string => {\n const rows = children(node).map((row) =>\n children(row).map((cell) => cellText(cell, options))\n );\n const [head, ...body] = rows;\n if (!head) {\n return \"\";\n }\n return [\n tableRow(head),\n tableRow(head.map(() => \"---\")),\n ...body.map((cells) => tableRow(cells)),\n ].join(\"\\n\");\n};\n\nconst renderBlock = (\n node: JsonObject,\n options: ContentfulRichTextOptions\n): string => {\n const type = asString(node.nodeType) ?? \"\";\n const heading = HEADING.exec(type)?.groups?.level;\n if (heading) {\n return `${headingPrefix(Number(heading))}${blockText(node, options)}`;\n }\n switch (type) {\n case \"paragraph\": {\n return blockText(node, options);\n }\n case \"blockquote\": {\n return blockquote(\n joinBlocks(children(node).map((child) => renderBlock(child, options)))\n );\n }\n case \"unordered-list\": {\n return renderList(children(node), () => \"-\", options);\n }\n case \"ordered-list\": {\n return renderList(children(node), (index) => `${index + 1}.`, options);\n }\n case \"hr\": {\n return \"---\";\n }\n case \"embedded-asset-block\": {\n const asset = linkedAsset(node, options);\n // Contentful's own rich-text renderer uses the description as alt text\n // and the title only as a fallback.\n return asset\n ? image(asset.description ?? asset.title ?? \"\", asset.url)\n : \"\";\n }\n case \"embedded-entry-block\": {\n return embeddedEntry(node, options);\n }\n case \"table\": {\n return renderTable(node, options);\n }\n default: {\n return unsupported(\n `Contentful node: ${type}`,\n writesMdx(options.serializers)\n );\n }\n }\n};\n\n/** Serialize a rich text document (`nodeType: \"document\"`) to Markdown. */\nexport const contentfulRichTextToMarkdown = (\n doc: JsonObject,\n options: ContentfulRichTextOptions = {}\n): string =>\n markdownDocument(children(doc).map((node) => renderBlock(node, options)));\n",
|
|
18
|
+
"import { join } from \"pathe\";\n\nimport matter from \"../frontmatter.ts\";\nimport { neutralizeUnsafeLinks } from \"../safe-links.ts\";\nimport {\n hashText,\n loadWithCache,\n pollingWatch,\n snapshotCache,\n} from \"./cache.ts\";\nimport type { JsonObject, JsonValue } from \"./json.ts\";\nimport { asString, getPath, isStringValue } from \"./json.ts\";\nimport { slugify, slugifyPath } from \"./normalize.ts\";\nimport type {\n ContentSource,\n SourceContext,\n SourceEntry,\n SourceLoadResult,\n} from \"./types.ts\";\n\n/** What every REST-backed CMS source needs to be a staged, cached source. */\nexport interface RemoteSourceOptions {\n /**\n * Throw a `BlumeError` when the source cannot load as configured. Runs\n * before the cache is consulted: thrown from `fetchEntries` instead, a\n * misconfiguration would be served over by a stale snapshot with only an\n * offline warning.\n */\n assertConfigured?: () => void;\n /** Pull every entry from the API; called on refresh and on each poll. */\n fetchEntries: () => Promise<SourceEntry[]>;\n name: string;\n /** Opt-in dev polling interval (seconds); omit to freeze for the session. */\n pollInterval?: number;\n prefix?: string;\n /** The adapter rebuilt on another context (`ContentSource.withContext`). */\n withContext?: (ctx: SourceContext) => ContentSource;\n}\n\n/**\n * The staged-source scaffolding a CMS adapter shares: a snapshot cache under\n * `.blume/cache/<source>/` (or a name-derived dir when constructed directly\n * as a custom source, without a context), cache-first dev loads, offline\n * fallback, `read()` for raw export, and an opt-in polling `watch`.\n */\nexport const remoteSource = (\n options: RemoteSourceOptions,\n ctx?: SourceContext\n): ContentSource => {\n const cache = snapshotCache(\n ctx?.cacheDir ?? join(\".blume\", \"cache\", options.name)\n );\n let snapshot = new Map<string, SourceEntry>();\n\n const load = async (\n refresh = ctx?.refresh ?? true\n ): Promise<SourceLoadResult> => {\n options.assertConfigured?.();\n const result = await loadWithCache(\n options.name,\n cache,\n options.fetchEntries,\n refresh\n );\n snapshot = new Map(result.entries.map((entry) => [entry.ref, entry]));\n return result;\n };\n\n const read = async (ref: string): Promise<string> => {\n const cached = snapshot.get(ref);\n if (cached) {\n return cached.raw ?? cached.body.text;\n }\n const all = await cache.read();\n return all.find((entry) => entry.ref === ref)?.raw ?? \"\";\n };\n\n return {\n load,\n name: options.name,\n prefix: options.prefix,\n read,\n staged: true,\n watch: options.pollInterval\n ? pollingWatch(\n () => load(true),\n options.pollInterval,\n () => load()\n )\n : undefined,\n withContext: options.withContext,\n };\n};\n\n/** The frontmatter a CMS document maps to. */\nexport interface RemoteFrontmatter {\n description?: string;\n draft?: boolean;\n title?: string;\n}\n\n/**\n * A staged entry: Markdown body plus the frontmatter it was mapped from,\n * written as `.mdx` when the body carries serializer output (see\n * `writesMdx`).\n */\nexport const stagedEntry = (\n slug: string,\n data: RemoteFrontmatter,\n markdown: string,\n lastModified: string | undefined,\n format: \"md\" | \"mdx\" = \"md\"\n): SourceEntry => {\n const raw = matter.stringify(markdown, data);\n return {\n body: { format, text: markdown },\n // Spread into a fresh literal: `SourceEntry.data` wants an\n // index-signature type, which the named interface lacks.\n data: { ...data },\n hash: hashText(raw),\n lastModified,\n raw,\n ref: `${slug}.${format}`,\n };\n};\n\n/** How a source calls its CMS: the API's auth headers and an injectable fetch. */\nexport interface RestClient {\n /** Injected for tests; defaults to the global `fetch`. */\n fetchImpl?: typeof fetch;\n headers?: Record<string, string>;\n}\n\n/**\n * How long one CMS request may take before the source gives up on it. A\n * stalled CMS would otherwise hold `scanProject()` — and every `blume dev`\n * rescan — indefinitely, and the cache fallback can only kick in once the\n * request rejects.\n */\nexport const REMOTE_TIMEOUT_MS = 30_000;\n\n/**\n * GET a JSON endpoint. A non-2xx response is an error naming the status, so\n * the source's cache fallback reports why (`401 Unauthorized`) instead of a\n * parse failure on an error body; a request past {@link REMOTE_TIMEOUT_MS}\n * rejects with a `TimeoutError`.\n */\nexport const fetchJson = async (\n url: string,\n client: RestClient\n): Promise<JsonValue> => {\n const doFetch = client.fetchImpl ?? globalThis.fetch;\n const res = await doFetch(url, {\n headers: { accept: \"application/json\", ...client.headers },\n signal: AbortSignal.timeout(REMOTE_TIMEOUT_MS),\n });\n if (!res.ok) {\n throw new Error(`${url} responded ${res.status} ${res.statusText}`.trim());\n }\n // SAFETY: the endpoint answered 2xx with a JSON body; whatever shape it\n // holds is narrowed by the caller's predicates before use.\n return (await res.json()) as JsonValue;\n};\n\n/** A query string from the fixed params plus a user's extra ones. */\nexport const queryString = (\n params: Record<string, string | undefined>\n): string => {\n const search = new URLSearchParams();\n for (const [key, value] of Object.entries(params)) {\n if (value !== undefined) {\n search.set(key, value);\n }\n }\n return search.toString();\n};\n\n/** Field paths mapping a CMS document onto Blume meta + body. */\nexport interface RemoteFieldMap {\n /** Field holding the renderable body (rich text, or a Markdown string). */\n body?: string;\n /** Field holding the page description. */\n description?: string;\n /** Field holding the last-modified ISO date. */\n lastModified?: string;\n /** Field holding the route slug. */\n slug?: string;\n /** Field holding the page title. */\n title?: string;\n}\n\n/**\n * Map a document to a staged entry. The slug falls back to the document's id\n * when the slug field is missing or slugifies to nothing (pure punctuation),\n * so distinct documents never collapse onto one `untitled.md`; a slashed\n * slug keeps its segments. A string body is Markdown and passes through as\n * `.md`, its unsafe links reduced to their labels; any other shape goes to\n * the CMS's lowerer, and is written as `.mdx`\n * when `lowersToMdx` (the source has serializers, see `writesMdx`).\n */\nexport const documentEntry = (\n doc: JsonObject,\n fields: Required<RemoteFieldMap>,\n id: string,\n lower: (body: JsonValue) => string,\n draft = false,\n lowersToMdx = false\n): SourceEntry => {\n const slugValue = asString(getPath(doc, fields.slug)) ?? id;\n const slug = slugifyPath(slugValue) || slugify(id) || \"untitled\";\n const data: RemoteFrontmatter = {};\n const title = asString(getPath(doc, fields.title));\n const description = asString(getPath(doc, fields.description));\n if (title) {\n data.title = title;\n }\n if (description) {\n data.description = description;\n }\n if (draft) {\n data.draft = true;\n }\n const body = getPath(doc, fields.body);\n let markdown = \"\";\n let format: \"md\" | \"mdx\" = \"md\";\n if (isStringValue(body)) {\n // The CMS's Markdown is its editors', not the site author's: a link whose\n // destination isn't a web, mail, or relative address (`javascript:`)\n // keeps only its label, as release notes do (see `safe-links.ts`).\n markdown = neutralizeUnsafeLinks(`${body.trimEnd()}\\n`);\n } else if (body !== undefined) {\n markdown = lower(body);\n format = lowersToMdx ? \"mdx\" : \"md\";\n }\n return stagedEntry(\n slug,\n data,\n markdown,\n asString(getPath(doc, fields.lastModified)),\n format\n );\n};\n",
|
|
19
|
+
"import { BlumeError } from \"../diagnostics.ts\";\nimport {\n assetFromEntry,\n contentfulRichTextToMarkdown,\n} from \"./contentful-rich-text.ts\";\nimport type { JsonObject } from \"./json.ts\";\nimport {\n asNumber,\n asObject,\n asString,\n getPath,\n isJsonObject,\n objectsIn,\n} from \"./json.ts\";\nimport { writesMdx } from \"./lower.ts\";\nimport type { RemoteFieldMap, RestClient } from \"./remote.ts\";\nimport {\n documentEntry,\n fetchJson,\n queryString,\n remoteSource,\n} from \"./remote.ts\";\nimport type { ContentSource, SourceContext, SourceEntry } from \"./types.ts\";\n\nexport interface ContentfulSourceOptions {\n name: string;\n prefix?: string;\n /** Space id. */\n space: string;\n /** Environment id; default `master`. */\n environment?: string;\n /** The content type id whose entries become pages. */\n contentType: string;\n /** Locale code to fetch; omit for the space's default locale. */\n locale?: string;\n /**\n * Field ids mapping an entry onto Blume meta + body. Paths resolve against\n * the entry's `fields`, with `sys` reachable as `sys.<key>`. Defaults:\n * `title`, `description`, `slug`, `body`, `sys.updatedAt`.\n */\n fields?: RemoteFieldMap;\n /** Extra query parameters for the entries request (`fields.section: \"sdk\"`). */\n params?: Record<string, string>;\n /** Serializers for embedded entries, keyed by content type id. */\n serializers?: Record<string, (entry: JsonObject) => string>;\n /** Delivery API token; defaults to `CONTENTFUL_ACCESS_TOKEN`. */\n token?: string;\n /** Preview API token for `--preview`; defaults to `CONTENTFUL_PREVIEW_TOKEN`. */\n previewToken?: string;\n /** Opt-in dev polling interval (seconds); omit to freeze for the session. */\n pollInterval?: number;\n /** Injected for tests; defaults to the global `fetch`. */\n fetchImpl?: typeof fetch;\n}\n\nconst DELIVERY_HOST = \"https://cdn.contentful.com\";\nconst PREVIEW_HOST = \"https://preview.contentful.com\";\nconst PAGE_SIZE = 100;\n\nconst DEFAULT_FIELDS: Required<RemoteFieldMap> = {\n body: \"body\",\n description: \"description\",\n lastModified: \"sys.updatedAt\",\n slug: \"slug\",\n title: \"title\",\n};\n\n/** The included objects of a page, keyed by `sys.id`. */\nconst byId = (objects: JsonObject[]): Map<string, JsonObject> =>\n new Map(\n objects.map((object) => [asString(getPath(object, \"sys.id\")) ?? \"\", object])\n );\n\n/**\n * Contentful content source. Pages through the Delivery API (the Preview API\n * under `--preview`), maps each entry's fields to frontmatter, and lowers its\n * rich text body to Markdown with the page's `includes` resolving asset and\n * entry links.\n */\nexport const contentfulSource = (\n options: ContentfulSourceOptions,\n ctx?: SourceContext\n): ContentSource => {\n const fields = { ...DEFAULT_FIELDS, ...options.fields };\n\n const toEntry = (\n item: JsonObject,\n assets: Map<string, JsonObject>,\n linked: Map<string, JsonObject>\n ): SourceEntry => {\n const sys = asObject(item.sys) ?? {};\n const id = asString(sys.id) ?? \"\";\n // Field paths resolve against the entry's fields, with `sys` beside them.\n const view: JsonObject = { ...asObject(item.fields), sys };\n return documentEntry(\n view,\n fields,\n id,\n (body) =>\n isJsonObject(body)\n ? contentfulRichTextToMarkdown(body, {\n resolveAsset: (assetId) => {\n const asset = assets.get(assetId);\n return asset ? assetFromEntry(asset) : null;\n },\n resolveEntry: (entryId) => linked.get(entryId) ?? null,\n serializers: options.serializers,\n })\n : \"\",\n false,\n writesMdx(options.serializers)\n );\n };\n\n const preview = ctx?.preview ?? false;\n const previewToken = (): string | undefined =>\n options.previewToken ?? process.env.CONTENTFUL_PREVIEW_TOKEN;\n\n // The Preview API rejects delivery tokens, so falling back to one would\n // only trade a clear config error for a 401. Checked before the cache, so\n // a warm snapshot can't stand in for the drafts `--preview` asked for.\n const assertConfigured = (): void => {\n if (preview && !previewToken()) {\n throw new BlumeError({\n code: \"BLUME_SOURCE_MISCONFIGURED\",\n message: `Source \"${options.name}\": Contentful --preview needs a Preview API token.`,\n severity: \"error\",\n suggestion:\n \"Set previewToken on contentfulSource, or the CONTENTFUL_PREVIEW_TOKEN environment variable.\",\n });\n }\n };\n\n const fetchEntries = async (): Promise<SourceEntry[]> => {\n const token = preview\n ? previewToken()\n : (options.token ?? process.env.CONTENTFUL_ACCESS_TOKEN);\n const client: RestClient = {\n fetchImpl: options.fetchImpl,\n headers: token ? { authorization: `Bearer ${token}` } : {},\n };\n const host = preview ? PREVIEW_HOST : DELIVERY_HOST;\n const base = `${host}/spaces/${options.space}/environments/${options.environment ?? \"master\"}/entries`;\n const entries: SourceEntry[] = [];\n let skip = 0;\n let more = true;\n while (more) {\n // The user's params go first so the paging controls always win — a\n // `params` key that shadowed them would refetch the same page forever.\n const query = queryString({\n ...options.params,\n content_type: options.contentType,\n include: \"2\",\n limit: String(PAGE_SIZE),\n locale: options.locale,\n skip: String(skip),\n });\n // oxlint-disable-next-line no-await-in-loop -- pages are sequential: each response says whether another exists.\n const page = asObject(await fetchJson(`${base}?${query}`, client));\n if (!page) {\n throw new Error(\"Contentful returned a non-object response\");\n }\n const assets = byId(objectsIn(getPath(page, \"includes.Asset\")));\n const linked = byId(objectsIn(getPath(page, \"includes.Entry\")));\n const items = objectsIn(page.items);\n for (const item of items) {\n entries.push(toEntry(item, assets, linked));\n }\n skip += items.length;\n more = items.length > 0 && skip < (asNumber(page.total) ?? 0);\n }\n return entries;\n };\n\n return remoteSource(\n {\n assertConfigured,\n fetchEntries,\n name: options.name,\n pollInterval: options.pollInterval,\n prefix: options.prefix,\n withContext: (next) => contentfulSource(options, next),\n },\n ctx\n );\n};\n",
|
|
20
|
+
"import { existsSync, watch as fsWatch } from \"node:fs\";\nimport { readFile } from \"node:fs/promises\";\n\nimport { extname, isAbsolute, join, relative, resolve } from \"pathe\";\nimport { glob } from \"tinyglobby\";\n\nimport { BlumeError } from \"../diagnostics.ts\";\nimport matter from \"../frontmatter.ts\";\nimport type { Diagnostic } from \"../types.ts\";\nimport { unloadablePathDiagnostic } from \"./normalize.ts\";\nimport type { ContentSource, SourceEntry, SourceLoadResult } from \"./types.ts\";\nimport {\n baselineScanIgnore,\n BLUME_WATCH_IGNORE_DIRS,\n excludeDirSegments,\n ignoringWatchListener,\n} from \"./watch.ts\";\n\n/** Options for the built-in filesystem source. */\nexport interface FilesystemSourceOptions {\n /** Stable source name; namespaces ids and diagnostics. */\n name: string;\n /** Optional route prefix. */\n prefix?: string;\n /** Content root, absolute or relative to `projectRoot`. */\n root: string;\n include: string[];\n exclude: string[];\n /** Absolute project root, used to resolve a relative `root`. */\n projectRoot: string;\n /** When set, a missing root throws this code instead of the generic one. */\n missingCode?: string;\n}\n\n/**\n * The built-in content source: discover and read `.md`/`.mdx` files under a\n * content root. This is the extraction of the original `discoverContent()` scan\n * into a `ContentSource`; every other adapter follows the same contract.\n */\nexport const filesystemSource = (\n options: FilesystemSourceOptions\n): ContentSource & { readonly contentRoot: string } => {\n const contentRoot = isAbsolute(options.root)\n ? options.root\n : join(resolve(options.projectRoot), options.root);\n\n const load = async (): Promise<SourceLoadResult> => {\n const files = await glob(options.include, {\n absolute: true,\n cwd: contentRoot,\n // Union the user's `exclude` with the baseline never-content dirs so a\n // broadly-scoped root (`.` or an app dir) can't glob `node_modules`,\n // `dist`, `.blume`, etc. — even when the user overrode `exclude`.\n ignore: [...options.exclude, ...baselineScanIgnore()],\n onlyFiles: true,\n });\n files.sort();\n\n // A file Astro's content loader can't read would publish a route that\n // only ever renders \"Page not found\"; it is reported and left out.\n const diagnostics: Diagnostic[] = [];\n const loadable: string[] = [];\n for (const file of files) {\n const unloadable = unloadablePathDiagnostic(\n relative(contentRoot, file),\n file\n );\n if (unloadable) {\n diagnostics.push(unloadable);\n } else {\n loadable.push(file);\n }\n }\n\n const entries = await Promise.all(\n loadable.map(async (file): Promise<SourceEntry> => {\n const source = await readFile(file, \"utf-8\");\n const ext = extname(file).toLowerCase();\n const format = ext === \".mdx\" ? \"mdx\" : \"md\";\n const parsed = matter(source);\n return {\n body: { format, text: parsed.content },\n data: parsed.data,\n // The unstripped text: lets `normalizeEntry` offset link line numbers\n // by the frontmatter block's height, so diagnostics point at the real\n // file line (and spares a re-read on the frontmatter-error path).\n raw: source,\n ref: relative(contentRoot, file),\n sourcePath: file,\n };\n })\n );\n\n return { diagnostics, entries };\n };\n\n const validate = (): void => {\n if (!existsSync(contentRoot)) {\n throw new BlumeError({\n code: options.missingCode ?? \"BLUME_CONTENT_ROOT_MISSING\",\n file: contentRoot,\n message: `Content root not found: ${options.root}`,\n severity: \"error\",\n // `content.root` is rejected beside `content.sources`, so the hint\n // names both places a root is set.\n suggestion: `Create a \"${options.root}\" folder with at least one .md or .mdx file, or point Blume at your docs folder: content.root in blume.config.ts, or the filesystem() source's root when you list content.sources.`,\n });\n }\n };\n\n // When `content.root` is the project root (a migrated `.`-rooted project),\n // the recursive dev watcher would otherwise see Blume's own `.blume/` output\n // and loop; skip it, VCS/dependency trees, and every excluded dir so the\n // watcher stays in sync with what `load()` globs. See {@link ignoringWatchListener}.\n const watchIgnoreDirs = new Set([\n ...BLUME_WATCH_IGNORE_DIRS,\n ...excludeDirSegments(options.exclude),\n ]);\n\n const watch = (onChange: () => void): (() => void) => {\n if (!existsSync(contentRoot)) {\n return () => {\n // Nothing to dispose when the root doesn't exist yet.\n };\n }\n const watcher = fsWatch(\n contentRoot,\n { recursive: true },\n ignoringWatchListener(onChange, watchIgnoreDirs)\n );\n return () => watcher.close();\n };\n\n return {\n contentRoot,\n exclude: options.exclude,\n include: options.include,\n load,\n name: options.name,\n prefix: options.prefix,\n read: (ref: string): Promise<string> =>\n readFile(join(contentRoot, ref), \"utf-8\"),\n staged: false,\n validate,\n watch,\n };\n};\n",
|
|
21
|
+
"import type { Definition, Heading, Link, Nodes } from \"mdast\";\nimport { fromMarkdown } from \"mdast-util-from-markdown\";\nimport { gfmFromMarkdown } from \"mdast-util-gfm\";\nimport { toString as mdastToString } from \"mdast-util-to-string\";\nimport { gfm } from \"micromark-extension-gfm\";\nimport stringWidth from \"string-width\";\n\nimport matter from \"../frontmatter.ts\";\nimport { PUBLIC_API_URL } from \"../github.ts\";\nimport { neutralizeUnsafeLinks } from \"../safe-links.ts\";\nimport { columnsPrefix } from \"../text-width.ts\";\nimport {\n hashText,\n loadWithCache,\n pollingWatch,\n snapshotCache,\n} from \"./cache.ts\";\nimport { destination, escapeMarkdownText } from \"./lower.ts\";\nimport { REMOTE_TIMEOUT_MS } from \"./remote.ts\";\nimport type {\n ContentSource,\n SourceContext,\n SourceEntry,\n SourceLoadResult,\n} from \"./types.ts\";\n\n/** Options for the built-in GitHub Releases changelog source. */\nexport interface GithubReleasesSourceOptions {\n /** GitHub REST API base; overridable for GitHub Enterprise / tests. */\n baseUrl?: string;\n /** Include draft releases (needs a token with repo write access). Default off. */\n drafts?: boolean;\n /** Injected for tests; defaults to the global `fetch`. */\n fetchImpl?: typeof fetch;\n /** Cap the number of releases materialized, newest-first. Default 100. */\n limit?: number;\n name: string;\n /** Repository owner (user or org). */\n owner: string;\n /** Opt-in dev polling interval (seconds); omit to freeze for the session. */\n pollInterval?: number;\n /** Namespaces the source's routes under `/<prefix>/`; e.g. `changelog`. */\n prefix?: string;\n /** Include prereleases. Default off. */\n prereleases?: boolean;\n /** Repository name. */\n repo: string;\n /**\n * The site's own URL (`deployment.site`). A link in the notes back to it\n * becomes root-relative, the way a page links to another page.\n */\n site?: string;\n}\n\n/** The subset of the GitHub release payload the adapter reads. */\ninterface GithubRelease {\n body: string | null;\n created_at: string;\n draft: boolean;\n html_url: string;\n id: number;\n name: string | null;\n prerelease: boolean;\n published_at: string | null;\n tag_name: string;\n}\n\nconst DEFAULT_LIMIT = 100;\nconst PER_PAGE = 100;\n\nconst LEADING_V = /^v/iu;\nconst NON_SLUG = /[^a-z0-9]+/gu;\nconst EDGE_DASHES = /^-+|-+$/gu;\n\n// `blume audit` grades meta descriptions against the 110–160 display-column\n// search snippet range (audit/types.ts thresholds), so the derived summary\n// budgets in the same columns and aims for the longest word-boundary cut\n// under the cap.\nconst DESCRIPTION_MAX = 160;\nconst DESCRIPTION_MIN = 110;\n\n// Changesets-generated release bullets open with the changeset's short commit\n// hash (`- cf8fa22: Fix …`) — noise in a search snippet. Stripped from the\n// raw lines (where the bullet anchor still exists) before parsing.\nconst CHANGESET_HASH = /^(?<mark>\\s*(?:[-*+]|\\d+[.)])\\s+)[0-9a-f]{7,40}:\\s+/gmu;\nconst WHITESPACE = /\\s+/gu;\nconst TRAILING_FRAGMENT = /[\\s,;:.—–-]+$/u;\n\n/** Block nodes with no place in a search snippet. */\nconst NON_PROSE = new Set([\"code\", \"heading\", \"html\", \"thematicBreak\"]);\n\n/**\n * Derive a meta description from release notes: GitHub-flavored markdown\n * parsed to mdast and reduced to the plain text of its prose blocks —\n * section headings (\"### Patch Changes\"), code fences, and changesets'\n * commit-hash bullet prefixes dropped; link/emphasis text and inline code\n * content kept — then cut at a word boundary to fit the search snippet cap.\n * Undefined when the notes have no prose at all.\n */\nconst releaseDescription = (body: string): string | undefined => {\n const tree = fromMarkdown(body.replaceAll(CHANGESET_HASH, \"$<mark>\"), {\n extensions: [gfm()],\n mdastExtensions: [gfmFromMarkdown()],\n });\n const text = tree.children\n .filter((node) => !NON_PROSE.has(node.type))\n // Images vanish (their alt is not prose) and raw HTML/JSX tags drop,\n // matching what a reader of the rendered notes would see as text.\n .map((node) =>\n mdastToString(node, { includeHtml: false, includeImageAlt: false })\n )\n .join(\" \")\n .replaceAll(WHITESPACE, \" \")\n .trim();\n if (!text) {\n return undefined;\n }\n if (stringWidth(text) <= DESCRIPTION_MAX) {\n return text;\n }\n // Cut before the cap at a word boundary (kept only when it doesn't drop the\n // summary under the minimum), shed any dangling punctuation, and mark the cut.\n const slice = columnsPrefix(text, DESCRIPTION_MAX - 1);\n const boundary = slice.lastIndexOf(\" \");\n const head = (\n boundary !== -1 && stringWidth(slice.slice(0, boundary)) >= DESCRIPTION_MIN\n ? slice.slice(0, boundary)\n : slice\n ).replace(TRAILING_FRAGMENT, \"\");\n return `${head}…`;\n};\n\n/** Every heading in a tree, in document order (lists and quotes included). */\nconst headingsIn = (node: Nodes): Heading[] => {\n if (node.type === \"heading\") {\n return [node];\n }\n const found: Heading[] = [];\n if (\"children\" in node) {\n for (const child of node.children) {\n found.push(...headingsIn(child));\n }\n }\n return found;\n};\n\n/**\n * Lift the notes' headings so the shallowest is an h2. The release page's\n * title is its h1, and changesets opens every section at `### Patch Changes`,\n * which would skip a level on every page. Only ATX headings can sit deeper\n * than h2, so each lift drops leading `#`s; headings are read from the parsed\n * tree, so a `#` inside a code fence is never touched.\n */\nconst liftHeadings = (body: string): string => {\n const headings = headingsIn(\n fromMarkdown(body, {\n extensions: [gfm()],\n mdastExtensions: [gfmFromMarkdown()],\n })\n );\n const lift = Math.min(...headings.map((heading) => heading.depth)) - 2;\n if (headings.length === 0 || lift <= 0) {\n return body;\n }\n let lifted = body;\n // Back to front, so each cut leaves the earlier offsets valid.\n for (const heading of headings.toReversed()) {\n const marker = lifted.indexOf(\"#\", heading.position?.start.offset);\n lifted = lifted.slice(0, marker) + lifted.slice(marker + lift);\n }\n return lifted;\n};\n\nconst WEB_PROTOCOL = /^https?:$/u;\nconst TITLE_SPECIALS = /[\"\\\\]/gu;\n\n/** The origin of an http(s) site URL, or null when there is none. */\nconst webOrigin = (site?: string): string | null => {\n const parsed = site ? URL.parse(site) : null;\n return parsed && WEB_PROTOCOL.test(parsed.protocol) ? parsed.origin : null;\n};\n\n/** A link title as Markdown carries it, or nothing when there is none. */\nconst titleSuffix = (title?: string | null): string =>\n title ? ` \"${title.replaceAll(TITLE_SPECIALS, String.raw`\\$&`)}\"` : \"\";\n\n/**\n * The Markdown a link or definition to one of the site's own pages becomes,\n * pointing at `path` instead. An inline link keeps its label as written, and\n * an autolink keeps showing the URL it was written as.\n */\nconst rewrittenLink = (\n markdown: string,\n node: Definition | Link,\n path: string\n): string => {\n const target = `${destination(path)}${titleSuffix(node.title)}`;\n if (node.type === \"definition\") {\n return `[${node.label ?? node.identifier}]: ${target}`;\n }\n const start = node.position?.start.offset ?? 0;\n if (markdown[start] !== \"[\") {\n return `[${escapeMarkdownText(mdastToString(node))}](${target})`;\n }\n const first = node.children.at(0)?.position?.start.offset ?? start + 1;\n const last = node.children.at(-1)?.position?.end.offset ?? first;\n return `[${markdown.slice(first, last)}](${target})`;\n};\n\n/**\n * Point the notes' links to the site itself (`https://acme.dev/docs/x` when\n * `deployment.site` is `https://acme.dev`) at the root-relative path, as a\n * page on the site links to another: an absolute link hardcodes production,\n * so it leaves a preview deploy and trips `blume audit`'s own-origin check.\n */\nconst relativizeOwnLinks = (markdown: string, site?: string): string => {\n const origin = webOrigin(site);\n if (!origin) {\n return markdown;\n }\n const spans: { end: number; start: number; text: string }[] = [];\n const visit = (node: Nodes): void => {\n if (node.type === \"link\" || node.type === \"definition\") {\n const parsed = URL.parse(node.url);\n if (parsed?.origin === origin) {\n spans.push({\n // fromMarkdown stamps every node's position.\n end: node.position?.end.offset ?? 0,\n start: node.position?.start.offset ?? 0,\n text: rewrittenLink(\n markdown,\n node,\n `${parsed.pathname}${parsed.search}${parsed.hash}`\n ),\n });\n return;\n }\n }\n if (\"children\" in node) {\n for (const child of node.children) {\n visit(child);\n }\n }\n };\n visit(\n fromMarkdown(markdown, {\n extensions: [gfm()],\n mdastExtensions: [gfmFromMarkdown()],\n })\n );\n let out = \"\";\n let cursor = 0;\n for (const span of spans) {\n out += markdown.slice(cursor, span.start) + span.text;\n cursor = span.end;\n }\n return out + markdown.slice(cursor);\n};\n\n/** Slugify a tag into a stable, URL-safe source ref (`v1.2.0` -> `v1-2-0`). */\nconst slugifyTag = (tag: string): string =>\n tag.toLowerCase().replaceAll(NON_SLUG, \"-\").replaceAll(EDGE_DASHES, \"\");\n\n/** Build request headers, reading `GITHUB_TOKEN` fresh at call time. */\nconst githubHeaders = (): Headers => {\n const headers = new Headers({ Accept: \"application/vnd.github+json\" });\n const token = process.env.GITHUB_TOKEN;\n if (token) {\n headers.set(\"Authorization\", `Bearer ${token}`);\n }\n return headers;\n};\n\n/**\n * The changelog frontmatter one release lowers to. `title`/`type` are always\n * assigned (optional only so assignment order can keep the emitted YAML key\n * order — and so each entry's content hash — stable).\n */\ninterface ChangelogFrontmatter {\n changelog: { category: string; version: string };\n date: string;\n seo?: { description: string };\n title?: string;\n type?: string;\n}\n\n/**\n * Lower one release to a staged Markdown entry: the notes become the body,\n * `type: changelog` frontmatter (title/date/version/category) drives the\n * generated `/changelog` timeline and RSS feed, and a summary derived from the\n * notes becomes the release page's meta description.\n */\nconst releaseToEntry = (release: GithubRelease, site?: string): SourceEntry => {\n const version = release.tag_name.replace(LEADING_V, \"\");\n const title = release.name?.trim() || release.tag_name;\n const date = release.published_at ?? release.created_at;\n const category = release.prerelease ? \"Prerelease\" : \"Release\";\n // Release notes are the repository's content, not the site author's, so a\n // link whose destination isn't a web, mail, or relative address\n // (`javascript:`, `data:`) keeps only its label (see `safe-links.ts`), and\n // a link back to the site becomes root-relative.\n const body = liftHeadings(\n relativizeOwnLinks(\n neutralizeUnsafeLinks(\n (release.body ?? \"\").replaceAll(\"\\r\\n\", \"\\n\")\n ).trim(),\n site\n )\n );\n // A summary in `seo.description` gives each release page a unique meta\n // description (instead of the site-wide fallback) without also rendering the\n // visible lede paragraph a top-level `description` would add.\n const description = releaseDescription(body);\n // Assignment order matters: js-yaml serializes keys in insertion order, so\n // `seo` lands between `date` and `title` exactly as it always has.\n const data: ChangelogFrontmatter = {\n changelog: { category, version },\n date,\n };\n if (description) {\n data.seo = { description };\n }\n data.title = title;\n data.type = \"changelog\";\n const raw = matter.stringify(`${body}\\n`, data);\n const fallbackRef = `release-${release.id}`;\n const ref = `${slugifyTag(release.tag_name) || fallbackRef}.md`;\n return {\n body: { format: \"md\", text: body },\n // Spread: `SourceEntry.data` is an open dictionary, which the interface\n // (no index signature) only satisfies as a fresh object literal.\n data: { ...data },\n editUrl: release.html_url,\n hash: hashText(raw),\n lastModified: date,\n raw,\n ref,\n };\n};\n\n/**\n * GitHub Releases content source. Pulls a repo's releases from the REST API and\n * materializes each as a `type: changelog` entry, so a project's release notes\n * become its changelog with no files to maintain. A private repo authenticates\n * with `GITHUB_TOKEN`. A snapshot under `.blume/cache/<source>/` keeps rebuilds\n * offline-tolerant.\n */\nexport const githubReleasesSource = (\n options: GithubReleasesSourceOptions,\n ctx: SourceContext\n): ContentSource => {\n const doFetch = options.fetchImpl ?? globalThis.fetch;\n const base = (options.baseUrl ?? PUBLIC_API_URL).replace(/\\/$/u, \"\");\n const max = options.limit ?? DEFAULT_LIMIT;\n const cache = snapshotCache(ctx.cacheDir);\n let snapshot = new Map<string, SourceEntry>();\n\n const include = (release: GithubRelease): boolean =>\n (options.drafts || !release.draft) &&\n (options.prereleases || !release.prerelease);\n\n // Each page gets {@link REMOTE_TIMEOUT_MS}, body included: an API that\n // accepts the connection and never answers would otherwise hold the scan,\n // and so the build or every dev rescan, indefinitely. The timeout's error\n // names the URL and the limit, so the empty-changelog warning says what\n // stalled instead of only that an operation was aborted.\n const fetchPage = async (page: number): Promise<GithubRelease[]> => {\n const url = `${base}/repos/${options.owner}/${options.repo}/releases?per_page=${PER_PAGE}&page=${page}`;\n try {\n const res = await doFetch(url, {\n headers: githubHeaders(),\n signal: AbortSignal.timeout(REMOTE_TIMEOUT_MS),\n });\n if (!res.ok) {\n throw new Error(`${url} -> ${res.status}`);\n }\n // SAFETY: GitHub's releases endpoint returns a JSON array of release\n // objects; `GithubRelease` models only the fields the adapter reads.\n return (await res.json()) as GithubRelease[];\n } catch (error) {\n if (error instanceof DOMException && error.name === \"TimeoutError\") {\n throw new Error(\n `${url} did not respond within ${REMOTE_TIMEOUT_MS / 1000}s`,\n { cause: error }\n );\n }\n throw error;\n }\n };\n\n const fetchReleases = async (): Promise<GithubRelease[]> => {\n const collected: GithubRelease[] = [];\n let page = 1;\n while (collected.length < max) {\n // oxlint-disable-next-line no-await-in-loop -- pages are sequential: each page's length decides whether another exists.\n const batch = await fetchPage(page);\n collected.push(...batch.filter(include));\n if (batch.length < PER_PAGE) {\n break;\n }\n page += 1;\n }\n return collected.slice(0, max);\n };\n\n const load = async (\n refresh = ctx.refresh ?? true\n ): Promise<SourceLoadResult> => {\n try {\n const result = await loadWithCache(\n options.name,\n cache,\n async () => {\n const releases = await fetchReleases();\n return releases.map((release) =>\n releaseToEntry(release, options.site)\n );\n },\n refresh\n );\n snapshot = new Map(result.entries.map((entry) => [entry.ref, entry]));\n return result;\n } catch (error) {\n // A changelog is supplementary. When releases can't be fetched and nothing\n // is cached (e.g. CI or a deploy without a `GITHUB_TOKEN` for a private\n // repo), degrade to an empty changelog with a warning rather than failing\n // the whole build.\n snapshot = new Map();\n // SAFETY: everything thrown on this path is an Error — fetch rejects\n // with a TypeError, fetchPage and loadWithCache throw Error instances.\n return {\n diagnostics: [\n {\n code: \"BLUME_SOURCE_UNAVAILABLE\",\n message: `Source \"${options.name}\" could not fetch GitHub releases (${(error as Error).message}); the changelog will be empty. Set GITHUB_TOKEN to include it (required for a private repository).`,\n severity: \"warning\",\n },\n ],\n entries: [],\n };\n }\n };\n\n const read = async (ref: string): Promise<string> => {\n const cached = snapshot.get(ref);\n if (cached) {\n return cached.raw ?? cached.body.text;\n }\n const all = await cache.read();\n const entry = all.find((e) => e.ref === ref);\n return entry?.raw ?? entry?.body.text ?? \"\";\n };\n\n return {\n load,\n // Releases are written once, in one language: no locale can translate them.\n monolingual: true,\n name: options.name,\n prefix: options.prefix,\n read,\n staged: true,\n watch: options.pollInterval\n ? pollingWatch(\n () => load(true),\n options.pollInterval,\n () => load()\n )\n : undefined,\n withContext: (next) => githubReleasesSource(options, next),\n };\n};\n",
|
|
22
|
+
"import picomatch from \"picomatch\";\n\nimport { BlumeError } from \"../diagnostics.ts\";\nimport matter from \"../frontmatter.ts\";\nimport type { Diagnostic } from \"../types.ts\";\nimport {\n hashText,\n loadWithCache,\n pollingWatch,\n snapshotCache,\n} from \"./cache.ts\";\nimport { REMOTE_TIMEOUT_MS } from \"./remote.ts\";\nimport type {\n ContentSource,\n SourceContext,\n SourceEntry,\n SourceLoadResult,\n} from \"./types.ts\";\n\n/** Options for the built-in remote Markdown/MDX source. */\nexport interface MdxRemoteSourceOptions {\n name: string;\n prefix?: string;\n /** Raw base URL for `files`, e.g. `https://raw.githubusercontent.com/o/r/main/docs`. */\n url?: string;\n /** Explicit source-relative file paths to fetch from `url`. */\n files?: string[];\n /** Enumerate a GitHub repo subtree via the git-trees API. */\n github?: { owner: string; repo: string; ref: string; path: string };\n include: string[];\n /** Opt-in dev polling interval (seconds); omit to freeze for the session. */\n pollInterval?: number;\n /** Injected for tests; defaults to the global `fetch`. */\n fetchImpl?: typeof fetch;\n}\n\n// Include globs compile through picomatch — what the filesystem source's\n// tinyglobby uses under the hood — so the same `include` array means the same\n// thing on every source type: negation, character classes, nested braces, and\n// extglobs included. Compiled once per enumeration, not per ref.\n\n/** A file to fetch: its source-local ref plus where to read it from. */\ninterface RemoteRef {\n ref: string;\n fetchUrl: string;\n editUrl?: string;\n}\n\n// Hosts the GITHUB_TOKEN may be sent to. A configured `url` base can point at\n// any server, and leaking the token there would hand a repo credential to an\n// arbitrary third party.\nconst GITHUB_HOSTS = new Set([\"api.github.com\", \"raw.githubusercontent.com\"]);\n\nconst githubHeaders = (url: string): Record<string, string> => {\n const token = process.env.GITHUB_TOKEN;\n if (!token) {\n return {};\n }\n let host = \"\";\n try {\n host = new URL(url).hostname;\n } catch {\n return {};\n }\n return GITHUB_HOSTS.has(host) ? { authorization: `Bearer ${token}` } : {};\n};\n\n/**\n * GET `url`'s body, giving up after {@link REMOTE_TIMEOUT_MS}: a server that\n * accepts the connection and never finishes answering would otherwise hold the\n * scan, and so the build or every dev rescan, indefinitely. A timeout rejects\n * with an error naming the URL and the limit, so the skip or offline\n * diagnostic says what stalled instead of only that an operation was aborted.\n */\nconst fetchText = async (\n url: string,\n doFetch: typeof fetch\n): Promise<string> => {\n try {\n // The signal bounds the body read too, not only the response headers.\n const res = await doFetch(url, {\n headers: githubHeaders(url),\n signal: AbortSignal.timeout(REMOTE_TIMEOUT_MS),\n });\n if (!res.ok) {\n throw new Error(`${url} -> ${res.status}`);\n }\n return await res.text();\n } catch (error) {\n if (error instanceof DOMException && error.name === \"TimeoutError\") {\n throw new Error(\n `${url} did not respond within ${REMOTE_TIMEOUT_MS / 1000}s`,\n { cause: error }\n );\n }\n throw error;\n }\n};\n\ninterface GithubTreeEntry {\n path: string;\n type: string;\n}\n\n/** Enumerate a GitHub repo subtree, mapping blobs to remote refs. */\nconst enumerateGithub = async (\n github: { owner: string; repo: string; ref: string; path: string },\n include: string[],\n doFetch: typeof fetch\n): Promise<{ refs: RemoteRef[]; truncated: boolean }> => {\n const { owner, repo, ref } = github;\n const base = github.path.replaceAll(/^\\/|\\/$/gu, \"\");\n const treeUrl = `https://api.github.com/repos/${owner}/${repo}/git/trees/${ref}?recursive=1`;\n const text = await fetchText(treeUrl, doFetch);\n // SAFETY: GitHub's git/trees endpoint returns this envelope; a missing or\n // differently-typed field falls through the `?? []` and blob filters below.\n const body = JSON.parse(text) as {\n tree?: GithubTreeEntry[];\n truncated?: boolean;\n };\n const prefix = base ? `${base}/` : \"\";\n const included = picomatch(include);\n const refs = (body.tree ?? []).flatMap((node) => {\n if (!(node.type === \"blob\" && node.path.startsWith(prefix))) {\n return [];\n }\n const rel = node.path.slice(prefix.length);\n if (!included(rel)) {\n return [];\n }\n return [\n {\n editUrl: `https://github.com/${owner}/${repo}/edit/${ref}/${prefix}${rel}`,\n fetchUrl: `https://raw.githubusercontent.com/${owner}/${repo}/${ref}/${prefix}${rel}`,\n ref: rel,\n },\n ];\n });\n // GitHub caps the recursive tree response (~100k entries / 7MB) and flags it\n // with `truncated`; ignoring it would silently import only part of the repo.\n return { refs, truncated: body.truncated === true };\n};\n\n/**\n * Remote Markdown/MDX content source. Fetches raw `.md`/`.mdx` over HTTP and\n * passes the text straight through `normalizeEntry`. A snapshot under\n * `.blume/cache/<source>/` makes rebuilds offline-tolerant.\n */\nexport const mdxRemoteSource = (\n options: MdxRemoteSourceOptions,\n ctx: SourceContext\n): ContentSource => {\n const doFetch = options.fetchImpl ?? globalThis.fetch;\n const cache = snapshotCache(ctx.cacheDir);\n let snapshot = new Map<string, SourceEntry>();\n\n // Validated up front in `load`, *before* the cached-fetch path: thrown from\n // inside `loadWithCache`'s fetch callback, a misconfiguration would be masked\n // as BLUME_SOURCE_FETCH_FAILED (no cache) or downgraded to a stale-cache\n // BLUME_SOURCE_OFFLINE warning (cache present).\n const assertConfigured = (): void => {\n if (options.github || (options.files && options.url)) {\n return;\n }\n throw new BlumeError({\n code: \"BLUME_SOURCE_MISCONFIGURED\",\n message: `Source \"${options.name}\" needs either { github } or { url, files }.`,\n severity: \"error\",\n });\n };\n\n const enumerate = async (): Promise<{\n refs: RemoteRef[];\n truncated: boolean;\n }> => {\n if (options.github) {\n return await enumerateGithub(options.github, options.include, doFetch);\n }\n const base = (options.url ?? \"\").replace(/\\/$/u, \"\");\n const included = picomatch(options.include);\n const refs = (options.files ?? []).flatMap((ref) =>\n included(ref)\n ? [{ editUrl: `${base}/${ref}`, fetchUrl: `${base}/${ref}`, ref }]\n : []\n );\n return { refs, truncated: false };\n };\n\n const fetchEntry = async (item: RemoteRef): Promise<SourceEntry> => {\n const text = await fetchText(item.fetchUrl, doFetch);\n const parsed = matter(text);\n const format = item.ref.toLowerCase().endsWith(\".mdx\") ? \"mdx\" : \"md\";\n return {\n body: { format, text: parsed.content },\n data: parsed.data,\n editUrl: item.editUrl,\n hash: hashText(text),\n raw: text,\n ref: item.ref,\n };\n };\n\n const load = async (\n refresh = ctx.refresh ?? true\n ): Promise<SourceLoadResult> => {\n assertConfigured();\n const skipped: Diagnostic[] = [];\n const result = await loadWithCache(\n options.name,\n cache,\n async () => {\n const { refs, truncated } = await enumerate();\n if (truncated) {\n skipped.push({\n code: \"BLUME_SOURCE_TRUNCATED\",\n message: `Source \"${options.name}\" hit GitHub's tree listing limit; some files were not enumerated. Narrow the source path or split the repo.`,\n severity: \"warning\",\n });\n }\n const reasons: string[] = [];\n const settled = await Promise.all(\n refs.map(async (ref) => {\n try {\n return await fetchEntry(ref);\n } catch (error) {\n // SAFETY: fetch and decode failures throw Error instances;\n // only the message is read for the skip diagnostic.\n const reason = (error as Error).message;\n reasons.push(reason);\n skipped.push({\n code: \"BLUME_SOURCE_FETCH_FAILED\",\n message: `Source \"${options.name}\" skipped \"${ref.ref}\" (${reason}); the rest were imported.`,\n severity: \"warning\",\n });\n return null;\n }\n })\n );\n const entries = settled.filter(\n (entry): entry is SourceEntry => entry !== null\n );\n // Only a total wipeout is a hard failure — let loadWithCache fall back\n // to cache or fail loudly rather than silently importing nothing. A\n // partial failure keeps the healthy pages and warns about the rest.\n // The first failure's reason rides along, so a server that timed out\n // on every file says so.\n if (refs.length > 0 && entries.length === 0) {\n skipped.length = 0;\n throw new Error(\n `all ${refs.length} remote file(s) failed to fetch (${reasons[0]})`\n );\n }\n return entries;\n },\n refresh\n );\n snapshot = new Map(result.entries.map((entry) => [entry.ref, entry]));\n return {\n ...result,\n diagnostics: [...result.diagnostics, ...skipped],\n };\n };\n\n const read = async (ref: string): Promise<string> => {\n const cached = snapshot.get(ref);\n if (cached) {\n return cached.raw ?? cached.body.text;\n }\n const all = await cache.read();\n const entry = all.find((e) => e.ref === ref);\n return entry?.raw ?? entry?.body.text ?? \"\";\n };\n\n return {\n load,\n name: options.name,\n // Refs are the remote tree's file and folder names, so an ordering prefix\n // (`01-getting-started/02-install.mdx`) sorts the sidebar and drops from\n // the route, the way it does in a local content folder.\n orderedNames: true,\n prefix: options.prefix,\n read,\n staged: true,\n watch: options.pollInterval\n ? pollingWatch(\n () => load(true),\n options.pollInterval,\n () => load()\n )\n : undefined,\n withContext: (next) => mdxRemoteSource(options, next),\n };\n};\n",
|
|
23
|
+
"import { setTimeout as sleep } from \"node:timers/promises\";\n\nimport pLimit from \"p-limit\";\nimport { join } from \"pathe\";\n\nimport { parseYouTubeId } from \"../../components/content/youtube.ts\";\nimport { BlumeError } from \"../diagnostics.ts\";\nimport matter from \"../frontmatter.ts\";\nimport { nodeRequire } from \"../node-require.ts\";\nimport type { Diagnostic } from \"../types.ts\";\nimport { materializeAssets } from \"./assets.ts\";\nimport {\n hashText,\n loadWithCache,\n pollingWatch,\n snapshotCache,\n} from \"./cache.ts\";\nimport type { InlineRun } from \"./lower.ts\";\nimport {\n codeFence,\n guardBlockStart,\n image,\n linkParts,\n renderInline,\n} from \"./lower.ts\";\nimport { slugify, slugifyPath } from \"./normalize.ts\";\nimport type {\n ContentSource,\n SourceContext,\n SourceEntry,\n SourceLoadResult,\n} from \"./types.ts\";\n\ninterface NotionRichText {\n plain_text: string;\n href?: string | null;\n annotations?: {\n bold?: boolean;\n italic?: boolean;\n code?: boolean;\n strikethrough?: boolean;\n };\n}\n\ninterface NotionProperty {\n type: string;\n title?: NotionRichText[];\n rich_text?: NotionRichText[];\n select?: { name: string } | null;\n status?: { name: string } | null;\n number?: number | null;\n}\n\ninterface NotionPage {\n id: string;\n properties: Record<string, NotionProperty>;\n last_edited_time?: string;\n}\n\n/** The per-type payload a block carries under the key matching its `type`. */\ninterface NotionBlockPayload {\n caption?: NotionRichText[];\n checked?: boolean;\n external?: { url: string };\n file?: { url: string };\n language?: string;\n rich_text?: NotionRichText[];\n}\n\ninterface NotionBlock {\n id: string;\n type: string;\n has_children?: boolean;\n [key: string]: NotionBlockPayload | boolean | string | undefined;\n}\n\ninterface NotionList<T> {\n results: T[];\n has_more: boolean;\n next_cursor: string | null;\n}\n\n/**\n * The slice of `@notionhq/client` (v5+) this adapter relies on, so it's\n * mockable. Since API version 2025-09-03 a database's rows live in a data\n * source: `databases.retrieve` names the data sources and\n * `dataSources.query` pages through one of them.\n */\nexport interface NotionClientLike {\n databases: {\n retrieve: (args: { database_id: string }) => Promise<{\n data_sources: { id: string; name: string }[];\n }>;\n };\n dataSources: {\n query: (args: {\n data_source_id: string;\n start_cursor?: string;\n }) => Promise<NotionList<NotionPage>>;\n };\n blocks: {\n children: {\n list: (args: {\n block_id: string;\n start_cursor?: string;\n }) => Promise<NotionList<NotionBlock>>;\n };\n };\n}\n\n/** Notion property names mapped onto Blume meta. */\nexport interface NotionPropertyMap {\n /** Title property name; defaults to the database's `title`-typed property. */\n title?: string;\n /** Description property (rich_text); default `Description`. */\n description?: string;\n /** Slug property (rich_text); default `Slug`, else the slugified title. */\n slug?: string;\n /** Status property (select/status); default `Status`. */\n status?: string;\n /** Sidebar order property (number); default `Order`. */\n order?: string;\n}\n\nexport interface NotionSourceOptions {\n name: string;\n prefix?: string;\n /** The Notion database id. */\n database: string;\n /**\n * Maximum concurrent Notion API requests. Notion allows an average of 3\n * requests per second per integration, so a large database must pace its\n * block-tree fan-out or every request 429s. Default 3.\n */\n concurrency?: number;\n /** Integration token; defaults to `NOTION_TOKEN`. */\n token?: string;\n properties?: NotionPropertyMap;\n /**\n * The `Status` property is treated as a publish signal: any value other than\n * this maps to `draft: true`. Defaults to `Published`; pages without a\n * Status/select property are always imported as published.\n */\n publishedValue?: string;\n /** Opt-in dev polling interval (seconds); omit to freeze for the session. */\n pollInterval?: number;\n /** Injected for tests; otherwise built from `@notionhq/client`. */\n client?: NotionClientLike;\n /** Injected for tests; used to download images. */\n fetchImpl?: typeof fetch;\n}\n\n/** The frontmatter Blume derives from a Notion page's properties. */\ninterface NotionFrontmatter {\n description?: string;\n draft?: boolean;\n sidebar?: { order: number };\n title?: string;\n // Frontmatter stays an open bag downstream (`SourceEntry.data`), so admit\n // the value shapes this source writes under any future key.\n [key: string]: boolean | string | { order: number } | undefined;\n}\n\n/** Neighboring rich-text runs that link to one `href` (or none), and it. */\ninterface LinkedRuns {\n href?: string;\n runs: InlineRun[];\n}\n\n/**\n * Rich text as Markdown for an MDX body. The runs go through the shared\n * lowering, so a literal `{`, `<`, `*`, or `[` typed in Notion renders as\n * itself instead of opening a JSX expression, a tag, or emphasis, a code run\n * keeps its text verbatim inside a long-enough code span, neighbors share\n * their marks' delimiters, and neighbors that link to one `href` share one\n * link.\n */\nconst richToMarkdown = (rich: NotionRichText[] = []): string => {\n const linked: LinkedRuns[] = [];\n for (const { annotations = {}, href, plain_text: text } of rich) {\n const run: InlineRun = {\n marks: {\n bold: annotations.bold,\n code: annotations.code,\n italic: annotations.italic,\n strike: annotations.strikethrough,\n },\n text,\n };\n const last = linked.at(-1);\n if (last && last.href === (href ?? undefined)) {\n last.runs.push(run);\n } else {\n linked.push({ href: href ?? undefined, runs: [run] });\n }\n }\n return renderInline(\n linked.flatMap(({ href, runs }) => linkParts(runs, href))\n );\n};\n\nconst isBlockPayload = (\n value: NotionBlockPayload | boolean | string | undefined\n): value is NotionBlockPayload => typeof value === \"object\";\n\n/** The payload object stored under a block's own `type` key, if present. */\nconst payloadOf = (block: NotionBlock): NotionBlockPayload | undefined => {\n const value = block[block.type];\n return isBlockPayload(value) ? value : undefined;\n};\n\nconst blockField = (block: NotionBlock): NotionRichText[] =>\n payloadOf(block)?.rich_text ?? [];\n\nconst RATE_LIMITED = 429;\nconst MAX_RETRIES = 4;\nconst BASE_DELAY_MS = 500;\nconst SECOND_MS = 1000;\nconst DEFAULT_CONCURRENCY = 3;\nconst ASSET_DOWNLOAD_CONCURRENCY = 4;\n\n/**\n * Retry a Notion API call on a `429 rate_limited`, honoring the `Retry-After`\n * header and otherwise backing off exponentially. A large workspace fans out\n * many concurrent block-children requests, so without this a single 429 would\n * reject the batch and abort the whole import. The exponential wait is\n * jittered so calls rate-limited together don't retry in lockstep and trip\n * the limit again as a herd.\n */\nconst withNotionRetry = async <T>(\n call: () => Promise<T>,\n attempt = 0\n): Promise<T> => {\n try {\n return await call();\n } catch (error) {\n // SAFETY: Notion SDK failures are APIResponseError-shaped, carrying the\n // failed request's HTTP status; anything else reads `undefined` and is\n // rethrown below.\n const { status } = error as { status?: number };\n if (status !== RATE_LIMITED || attempt === MAX_RETRIES) {\n throw error;\n }\n // SAFETY: same APIResponseError shape — `headers` is the failed\n // response's fetch `Headers`, read with `get()`; a missing header (or\n // headers without `get`) yields no positive wait and falls back to\n // exponential backoff.\n const { headers } = error as { headers?: Headers };\n const retryAfter = Number(headers?.get?.(\"retry-after\"));\n const wait =\n retryAfter > 0\n ? retryAfter * SECOND_MS\n : BASE_DELAY_MS * 2 ** attempt * (1 + Math.random());\n await sleep(wait);\n return withNotionRetry(call, attempt + 1);\n }\n};\n\n/** Paginate a Notion list endpoint via recursion (no await-in-loop). */\nconst collectAll = async <T>(\n page: (cursor?: string) => Promise<NotionList<T>>,\n cursor?: string,\n acc: T[] = []\n): Promise<T[]> => {\n const res = await page(cursor);\n const all = [...acc, ...res.results];\n return res.has_more && res.next_cursor\n ? collectAll(page, res.next_cursor, all)\n : all;\n};\n\nconst LIST_BLOCKS = new Set([\n \"bulleted_list_item\",\n \"numbered_list_item\",\n \"to_do\",\n]);\n\n/** Whether a block is a list item (so consecutive ones render as a tight list). */\nconst isListItem = (block: NotionBlock | undefined): boolean =>\n block !== undefined && LIST_BLOCKS.has(block.type);\n\nconst richToPlain = (rich: NotionRichText[] = []): string =>\n rich.map((node) => node.plain_text).join(\"\");\n\n/**\n * A string prop in JSX expression form. The quoted form (`title=\"…\"`) keeps\n * `\\\"` and `\\n` as literal characters — MDX decodes no escapes there, so a\n * caption holding a double quote fails to compile the whole page — where the\n * expression form (`title={\"…\"}`) is a JSON string literal and decodes them.\n */\nconst jsxString = (value: string): string => `{${JSON.stringify(value)}}`;\n\n// `<Frame>` renders its caption as Markdown after escaping `<` and `>` itself\n// (so a caption can't inject HTML), which turns the lowering's own `\\<` into\n// a visible `<`. Its caption drops those backslashes and leaves the\n// escaping to Frame; every other escape still applies.\nconst FRAME_ESCAPED = /\\\\(?=[<>])/gu;\n\nconst YOUTUBE_HOST = /(?:^|\\.)(?:youtube(?:-nocookie)?\\.com|youtu\\.be)$/u;\n\n// `parseYouTubeId` is host-agnostic on purpose (the component accepts bare\n// ids), so gate on the hostname first: `https://cdn.example.com/live/promo.mp4`\n// matches its `/live/<11 chars>` shape but is a media file, not an embed.\nconst isYouTubeUrl = (url: string): boolean => {\n const host = URL.parse(url)?.hostname ?? \"\";\n return YOUTUBE_HOST.test(host) && parseYouTubeId(url) !== null;\n};\n\n/**\n * Render a Notion `video` block. Kept out of `renderLeaf`'s switch so that\n * function stays under the complexity limit.\n */\nconst renderVideo = (data: NotionBlockPayload): string => {\n const url = data.external?.url ?? data.file?.url;\n if (!url) {\n return \"\";\n }\n const caption = richToMarkdown(data.caption);\n // A YouTube link pasted into Notion becomes a `video` block holding an\n // external URL. That URL is a watch page, not a media file, so it has to\n // become the embed component — a `<video src>` pointing at it plays nothing,\n // and `materializeAssets` would download the HTML page. The iframe's title\n // is its accessible name, so it gets the caption's plain text; the Markdown\n // rendering goes to the Frame's caption, as it does for an upload.\n const title = caption ? ` title=${jsxString(richToPlain(data.caption))}` : \"\";\n // Everything else (a Notion upload, or a direct link to a media file) is a\n // real video file: `materializeAssets` rewrites the `src`, which matters most\n // for uploads, whose Notion URLs are signed and expire. It finds the file by\n // that quoted `src`, where MDX decodes no backslash escapes — a JSON\n // string's `\\\\` and `\\\"` would corrupt the URL or end the attribute — so the\n // URL goes in as written, with a double quote as the `%22` it means anyway.\n // The embed's `url` takes the expression form, like every string prop here.\n const media = isYouTubeUrl(url)\n ? `<YouTube${title} url=${jsxString(url)} />`\n : `<video controls src=\"${url.replaceAll('\"', \"%22\")}\" />`;\n return caption\n ? `<Frame caption=${jsxString(caption.replaceAll(FRAME_ESCAPED, \"\"))}>\\n${media}\\n</Frame>`\n : media;\n};\n\n/** Render a leaf (non-container) block to Markdown, or null for containers. */\nconst renderLeaf = (block: NotionBlock): string | null => {\n const data = payloadOf(block) ?? {};\n const text = guardBlockStart(richToMarkdown(blockField(block)));\n switch (block.type) {\n case \"paragraph\": {\n return text;\n }\n case \"heading_1\": {\n return `# ${text}`;\n }\n case \"heading_2\": {\n return `## ${text}`;\n }\n case \"heading_3\": {\n return `### ${text}`;\n }\n case \"bulleted_list_item\": {\n return `- ${text}`;\n }\n case \"numbered_list_item\": {\n return `1. ${text}`;\n }\n case \"to_do\": {\n return `- [${data.checked ? \"x\" : \" \"}] ${text}`;\n }\n case \"quote\": {\n return `> ${text}`;\n }\n case \"divider\": {\n return \"---\";\n }\n case \"code\": {\n // The code's own text, verbatim: escapes would show inside the fence.\n return codeFence(richToPlain(blockField(block)), data.language ?? \"\");\n }\n case \"image\": {\n const url = data.external?.url ?? data.file?.url;\n return url ? image(richToPlain(data.caption), url) : \"\";\n }\n case \"video\": {\n return renderVideo(data);\n }\n default: {\n return null;\n }\n }\n};\n\n/**\n * Notion content source. Maps a database to a collection: each page becomes an\n * entry, its properties become frontmatter, and its block tree is converted to\n * MDX with Blume components. Images are materialized so signed URLs don't rot.\n */\nexport const notionSource = (\n options: NotionSourceOptions,\n ctx?: SourceContext\n): ContentSource => {\n const props = options.properties ?? {};\n // A FIFO semaphore: at most N calls run at once, the rest queue. Notion's\n // rate limit is per-integration (an average of 3 req/s), and a large\n // database fans out one block-children request per page plus one per nested\n // container — an unbounded burst guarantees 429s that even the retry loop\n // can't recover from, so every API call funnels through this limiter.\n const limit = pLimit(Math.max(1, options.concurrency ?? DEFAULT_CONCURRENCY));\n // Asset downloads get their own gate: they go to Notion's S3, not its rate-\n // limited API, and a slow video must not hold API slots. One gate per source\n // bounds the fan-out across every page, not just within one.\n const downloads = pLimit(ASSET_DOWNLOAD_CONCURRENCY);\n // Shared with the gate so two pages naming one asset share one download.\n const inFlight = new Map<string, Promise<string>>();\n // Every Notion API call goes through the limiter, inside the retry — so a\n // call sleeping through a backoff doesn't hold a slot while it waits.\n const notionCall = <T>(call: () => Promise<T>): Promise<T> =>\n withNotionRetry(() => limit(call));\n const cache = snapshotCache(\n ctx?.cacheDir ?? join(\".blume\", \"cache\", options.name)\n );\n const assetsDir =\n ctx?.assetsDir ?? join(\".blume\", \"public\", \"blume-assets\", options.name);\n const assetsBaseUrl = ctx?.assetsBaseUrl ?? `/blume-assets/${options.name}`;\n let snapshot = new Map<string, SourceEntry>();\n\n const resolveClient = (): NotionClientLike => {\n if (options.client) {\n return options.client;\n }\n let Client: new (config: { auth?: string }) => NotionClientLike;\n try {\n // `require`, not `import()`: an ejected app scans in\n // `astro:build:done` (see `core/node-require.ts`). The module is\n // widened first because the SDK's response unions are broader than the\n // NotionClientLike slice, so a direct assertion is rejected as\n // non-overlapping.\n const sdk: unknown = nodeRequire(\"@notionhq/client\");\n // SAFETY: `@notionhq/client` exports a `Client` class constructable with\n // an `auth` token whose instances cover the NotionClientLike slice; the\n // local type keeps the SDK mockable without importing its types.\n ({ Client } = sdk as {\n Client: new (config: { auth?: string }) => NotionClientLike;\n });\n } catch {\n throw new BlumeError({\n code: \"BLUME_SOURCE_SDK_MISSING\",\n message: `Source \"${options.name}\" needs \"@notionhq/client\". Install it (e.g. \\`npm install @notionhq/client\\`).`,\n severity: \"error\",\n });\n }\n return new Client({ auth: options.token ?? process.env.NOTION_TOKEN });\n };\n\n const childrenOf = (\n client: NotionClientLike,\n blockId: string\n ): Promise<NotionBlock[]> =>\n collectAll((cursor) =>\n notionCall(() =>\n client.blocks.children.list({ block_id: blockId, start_cursor: cursor })\n )\n );\n\n // `render` is injected (rather than referenced) so this stays a forward-free\n // definition; `renderBlocks` passes itself, the way `collectAll` recurses.\n const renderContainer = async (\n client: NotionClientLike,\n block: NotionBlock,\n render: (c: NotionClientLike, b: NotionBlock[]) => Promise<string>\n ): Promise<string> => {\n const children = async (target: NotionBlock): Promise<string> => {\n if (!target.has_children) {\n return \"\";\n }\n return render(client, await childrenOf(client, target.id));\n };\n\n if (block.type === \"callout\") {\n const body = [\n guardBlockStart(richToMarkdown(blockField(block))),\n await children(block),\n ]\n .filter(Boolean)\n .join(\"\\n\\n\");\n return `<Callout>\\n${body}\\n</Callout>`;\n }\n if (block.type === \"toggle\") {\n // The item's title renders as text, not Markdown.\n const title = jsxString(richToPlain(blockField(block)));\n return `<Accordion>\\n<AccordionItem title=${title}>\\n${await children(block)}\\n</AccordionItem>\\n</Accordion>`;\n }\n if (block.type === \"column_list\") {\n const cols = await childrenOf(client, block.id);\n const rendered = await Promise.all(\n cols.map(async (col) => `<Column>\\n${await children(col)}\\n</Column>`)\n );\n return `<Columns>\\n${rendered.join(\"\\n\")}\\n</Columns>`;\n }\n return `{/* unsupported Notion block: ${block.type} */}`;\n };\n\n const renderBlocks = async (\n client: NotionClientLike,\n blocks: NotionBlock[]\n ): Promise<string> => {\n const parts = await Promise.all(\n blocks.map(async (block) => {\n const leaf = renderLeaf(block);\n if (leaf === null) {\n return renderContainer(client, block, renderBlocks);\n }\n // Leaf blocks can still carry children (nested list items, indented\n // paragraphs); dropping them would silently lose content.\n if (!block.has_children) {\n return leaf;\n }\n const nested = await renderBlocks(\n client,\n await childrenOf(client, block.id)\n );\n if (!nested) {\n return leaf;\n }\n if (isListItem(block)) {\n // Indent past the list marker so the children belong to the item\n // (`1. ` needs three columns, `- `/`- [x] ` two).\n const indent = \" \".repeat(\n block.type === \"numbered_list_item\" ? 3 : 2\n );\n const indented = nested\n .split(\"\\n\")\n .map((line) => (line ? `${indent}${line}` : line))\n .join(\"\\n\");\n return `${leaf}\\n${indented}`;\n }\n // Other leaves (paragraph, quote) keep their children as following\n // sibling blocks — the indentation semantics are lost but the content\n // survives.\n return `${leaf}\\n\\n${nested}`;\n })\n );\n // Join with a blank line, except between consecutive list items, which stay\n // tight so they render as a single list rather than separate loose ones.\n const pairs = blocks.flatMap((block, i) => {\n const text = parts[i] ?? \"\";\n return text ? [{ block, text }] : [];\n });\n return pairs\n .map((pair, i) => {\n if (i === 0) {\n return pair.text;\n }\n const tight = isListItem(pairs[i - 1]?.block) && isListItem(pair.block);\n return `${tight ? \"\\n\" : \"\\n\\n\"}${pair.text}`;\n })\n .join(\"\");\n };\n\n const titleProperty = (page: NotionPage): NotionProperty | undefined => {\n if (props.title) {\n return page.properties[props.title];\n }\n return Object.values(page.properties).find((p) => p.type === \"title\");\n };\n\n const isDraft = (page: NotionPage): boolean => {\n const prop = page.properties[props.status ?? \"Status\"];\n const status = prop?.status?.name ?? prop?.select?.name;\n // A page without a Status/select property stays published; one with a\n // status is a draft unless it matches the published value.\n return Boolean(\n status && status !== (options.publishedValue ?? \"Published\")\n );\n };\n\n const orderOf = (page: NotionPage): number | undefined => {\n const order = page.properties[props.order ?? \"Order\"]?.number;\n return order === null ? undefined : order;\n };\n\n const frontmatter = (page: NotionPage) => {\n const data: NotionFrontmatter = {};\n // Frontmatter holds plain text: Markdown marks or escapes would show.\n const title = richToPlain(titleProperty(page)?.title);\n if (title) {\n data.title = title;\n }\n const description = richToPlain(\n page.properties[props.description ?? \"Description\"]?.rich_text\n );\n if (description) {\n data.description = description;\n }\n if (isDraft(page)) {\n data.draft = true;\n }\n const order = orderOf(page);\n if (order !== undefined) {\n data.sidebar = { order };\n }\n const slugProp = richToPlain(\n page.properties[props.slug ?? \"Slug\"]?.rich_text\n );\n // A Slug property is path-aware: `guides/setup` keeps its `/` (per-segment\n // slugging) instead of mashing into `guidessetup`. A title is one name, so\n // its slashes become hyphens: \"CI/CD Setup\" routes to `ci-cd-setup`, not\n // to `cd-setup` inside an invented `ci` group.\n const slug =\n (slugProp\n ? slugifyPath(slugProp)\n : slugify(title.replaceAll(\"/\", \"-\"))) || page.id;\n return { data, slug };\n };\n\n const toEntry = async (\n client: NotionClientLike,\n page: NotionPage\n ): Promise<{ entry: SourceEntry; diagnostics: Diagnostic[] }> => {\n const { data, slug } = frontmatter(page);\n const mdx = await renderBlocks(client, await childrenOf(client, page.id));\n const assets = await materializeAssets(mdx, {\n assetsBaseUrl,\n assetsDir,\n fetchImpl: options.fetchImpl,\n inFlight,\n limit: downloads,\n });\n const raw = matter.stringify(assets.markdown, data);\n return {\n diagnostics: assets.diagnostics,\n entry: {\n body: { format: \"mdx\", text: assets.markdown },\n data,\n hash: hashText(raw),\n lastModified: page.last_edited_time,\n raw,\n ref: `${slug}.mdx`,\n },\n };\n };\n\n // A database's rows live in its data source (API 2025-09-03). Blume reads\n // the first one, which is the only one a database created in Notion has.\n const resolveDataSource = async (\n client: NotionClientLike\n ): Promise<string> => {\n const database = await notionCall(() =>\n client.databases.retrieve({ database_id: options.database })\n );\n const [dataSource] = database.data_sources;\n if (!dataSource) {\n throw new BlumeError({\n code: \"BLUME_SOURCE_MISCONFIGURED\",\n message: `Source \"${options.name}\": Notion database \"${options.database}\" has no data source to read pages from.`,\n severity: \"error\",\n });\n }\n return dataSource.id;\n };\n\n // Hoisted out of `load` so the retry closure doesn't nest past the linter's\n // 4-level limit (source factory → queryDataSource → notionCall callback).\n const queryDataSource = (\n client: NotionClientLike,\n dataSourceId: string,\n cursor?: string\n ): Promise<NotionList<NotionPage>> =>\n notionCall(() =>\n client.dataSources.query({\n data_source_id: dataSourceId,\n start_cursor: cursor,\n })\n );\n\n const load = async (\n refresh = ctx?.refresh ?? true\n ): Promise<SourceLoadResult> => {\n const assetDiagnostics: Diagnostic[] = [];\n const result = await loadWithCache(\n options.name,\n cache,\n async () => {\n const client = resolveClient();\n const dataSourceId = await resolveDataSource(client);\n const pages = await collectAll((cursor) =>\n queryDataSource(client, dataSourceId, cursor)\n );\n const built = await Promise.all(\n pages.map((page) => toEntry(client, page))\n );\n for (const item of built) {\n assetDiagnostics.push(...item.diagnostics);\n }\n return built.map((item) => item.entry);\n },\n refresh\n );\n snapshot = new Map(result.entries.map((entry) => [entry.ref, entry]));\n return {\n diagnostics: [...result.diagnostics, ...assetDiagnostics],\n entries: result.entries,\n };\n };\n\n const read = async (ref: string): Promise<string> => {\n const cached = snapshot.get(ref);\n if (cached) {\n return cached.raw ?? cached.body.text;\n }\n const all = await cache.read();\n return all.find((e) => e.ref === ref)?.raw ?? \"\";\n };\n\n return {\n load,\n name: options.name,\n prefix: options.prefix,\n read,\n staged: true,\n watch: options.pollInterval\n ? pollingWatch(\n () => load(true),\n options.pollInterval,\n () => load()\n )\n : undefined,\n withContext: (next) => notionSource(options, next),\n };\n};\n",
|
|
24
|
+
"import { randomUUID } from \"node:crypto\";\nimport {\n access,\n mkdir,\n readdir,\n rename,\n rm,\n writeFile,\n} from \"node:fs/promises\";\n\nimport { extname, join } from \"pathe\";\n\nimport type { Diagnostic } from \"../types.ts\";\nimport { hashText } from \"./cache.ts\";\n\n// A Markdown image as the lowerers write it (`image()` in `lower.ts`): the\n// alt text can hold backslash escapes (`\\]`), and a destination holding\n// spaces or parentheses sits in angle brackets, both with its own escapes.\nconst MD_IMAGE =\n /!\\[(?<alt>(?:\\\\.|[^\\\\\\]])*)\\]\\((?:<(?<pointy>(?:\\\\.|[^\\\\<>\\n])*)>|(?<bare>(?:\\\\.|[^\\\\\\s)])+))\\)/gu;\n// A backslash escape inside a destination, which Markdown reads as the\n// character it escapes.\nconst DESTINATION_ESCAPE = /\\\\(?<char>[!-/:-@[-`{-~])/gu;\n// A `<video>` tag's `src`, split so the rewrite can swap the URL and keep the\n// surrounding attributes untouched. Notion's uploaded videos arrive as signed,\n// expiring URLs exactly like its images, so they rot the same way. `[^>]`\n// bounds the attribute run to a single tag.\nconst HTML_VIDEO_SRC =\n /(?<open><video\\b[^>]*?\\ssrc=\")(?<url>[^\"]+)(?<close>\")/gu;\nconst REMOTE = /^https?:\\/\\//u;\nconst CODE_FENCE_BLOCK =\n /^(?<fence>`{3,}|~{3,})[^\\n]*\\n[\\s\\S]*?^\\k<fence>[^\\n]*(?=\\n|$)/gmu;\n// NUL delimiters cannot appear in authored markdown, so tokens never collide.\n// oxlint-disable-next-line no-control-regex -- the NUL is the collision guard.\nconst FENCE_TOKEN = /\\u0000blume-fence-(?<index>\\d+)\\u0000/gu;\n\n// The extension an asset is saved under, keyed by the media type the server\n// reports. Only images and videos are ever written: the file is served from\n// the docs origin by its extension, so an HTML page (a Vimeo/Loom watch page,\n// or a hostile \"image\" URL) saved as `.html` would render as a page on the\n// site. A static host serves by extension, so a `.png` holding JPEG bytes is\n// mislabeled and a `.png` holding a video is refused by strict players — the\n// response is the source of truth, not the reference kind.\nconst EXT_BY_MIME = new Map([\n [\"image/apng\", \".apng\"],\n [\"image/avif\", \".avif\"],\n [\"image/bmp\", \".bmp\"],\n [\"image/gif\", \".gif\"],\n [\"image/jpeg\", \".jpg\"],\n [\"image/jpg\", \".jpg\"],\n [\"image/png\", \".png\"],\n [\"image/svg+xml\", \".svg\"],\n [\"image/tiff\", \".tiff\"],\n [\"image/vnd.microsoft.icon\", \".ico\"],\n [\"image/webp\", \".webp\"],\n [\"image/x-icon\", \".ico\"],\n [\"video/mp4\", \".mp4\"],\n [\"video/ogg\", \".ogv\"],\n [\"video/quicktime\", \".mov\"],\n [\"video/webm\", \".webm\"],\n]);\n// The same images and videos as a URL's path names them, for a response\n// whose type says nothing about the bytes.\nconst MEDIA_EXTS = new Set([...EXT_BY_MIME.values(), \".jpeg\"]);\n// Response types that don't say what the bytes are; object stores answer\n// uploads this way, so the URL's extension decides.\nconst OPAQUE_TYPES = new Set([\n \"\",\n \"application/octet-stream\",\n \"binary/octet-stream\",\n]);\nconst PART_SUFFIX = \".part\";\n// Generous enough for a multi-hundred-megabyte recording on an ordinary\n// connection; its job is to fail a stalled download rather than hang the build.\nconst DEFAULT_TIMEOUT_MS = 120_000;\n\n/** Where to write downloaded assets and how to reference them publicly. */\nexport interface AssetContext {\n assetsDir: string;\n assetsBaseUrl: string;\n /** Injected for tests; defaults to the global `fetch`. */\n fetchImpl?: typeof fetch;\n /**\n * Gate for concurrent downloads. A source shares one gate across every page\n * it materializes, so a database of video-heavy pages doesn't open every\n * download at once. Defaults to no gate.\n */\n limit?: <T>(task: () => Promise<T>) => Promise<T>;\n /**\n * Downloads in progress, keyed by asset stem. A source shares one table\n * (alongside `limit`) across every page it materializes, so two pages that\n * reference the same asset share one download instead of racing to publish\n * the same file. Defaults to a table private to this call.\n */\n inFlight?: Map<string, Promise<string>>;\n /** Abort a download that hasn't completed within this many milliseconds. */\n timeoutMs?: number;\n}\n\n/** The image or video extension in a URL's path, or null when it names none. */\nconst extFromUrl = (url: string): string | null => {\n const clean = url.split(\"?\")[0] ?? url;\n const ext = extname(clean).toLowerCase();\n return MEDIA_EXTS.has(ext) ? ext : null;\n};\n\n/** An image's destination as the URL it names, its escapes decoded. */\nconst imageUrl = (pointy?: string, bare?: string): string =>\n (pointy ?? bare ?? \"\").replaceAll(DESTINATION_ESCAPE, \"$<char>\");\n\n/** The response's media type, lowercased and stripped of parameters. */\nconst mediaType = (res: Response): string =>\n res.headers.get(\"content-type\")?.split(\";\")[0]?.trim().toLowerCase() ?? \"\";\n\nconst exists = async (path: string): Promise<boolean> => {\n try {\n await access(path);\n return true;\n } catch {\n return false;\n }\n};\n\n/**\n * The completed file for a stem whose extension came from an earlier response\n * rather than the URL, so the asset is not fetched on every poll. Only a lone\n * image or video counts: query-less URLs can collide, picking between two\n * files would be a guess, and a partial download (or any other file an older\n * version saved) is never reused.\n */\nconst completedFor = async (\n dir: string,\n stem: string\n): Promise<string | null> => {\n let names: string[];\n try {\n names = await readdir(dir);\n } catch {\n return null;\n }\n const candidates = names.filter(\n (name) => name.startsWith(stem) && MEDIA_EXTS.has(name.slice(stem.length))\n );\n return candidates.length === 1 ? (candidates[0] ?? null) : null;\n};\n\n/**\n * The extension to save a response under: the image or video type it\n * reports, or the URL's image or video extension when the type says nothing.\n * Anything else is refused rather than written where the site would serve it.\n */\nconst extFor = (type: string, urlExt: string | null): string => {\n const ext = EXT_BY_MIME.get(type) ?? (OPAQUE_TYPES.has(type) ? urlExt : null);\n if (ext) {\n return ext;\n }\n throw new Error(\n OPAQUE_TYPES.has(type)\n ? `responded with ${type || \"no content type\"} and its URL names no image or video file`\n : `responded with ${type}, not an image or video`\n );\n};\n\n/**\n * Download remote media referenced in a Markdown body into the asset dir and\n * rewrite the reference to the local public path. Markdown images and the\n * `src` of a `<video>` tag are both covered. Remote CMS URLs (notably Notion's\n * signed, expiring links) would otherwise rot a static build. Assets are\n * content-addressed by URL hash, so repeated builds are stable and deduped —\n * and a file already on disk is not fetched again, which keeps a dev poll\n * from re-downloading every video on every tick.\n */\nexport const materializeAssets = async (\n markdown: string,\n ctx: AssetContext\n): Promise<{ markdown: string; diagnostics: Diagnostic[] }> => {\n const doFetch = ctx.fetchImpl ?? globalThis.fetch;\n const limit = ctx.limit ?? ((task) => task());\n const inFlight = ctx.inFlight ?? new Map<string, Promise<string>>();\n const timeoutMs = ctx.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n const diagnostics: Diagnostic[] = [];\n\n // Mask fenced code blocks so an image URL inside a code sample is neither\n // downloaded nor rewritten — the sample must keep showing what the author\n // wrote.\n const fences: string[] = [];\n const masked = markdown.replace(CODE_FENCE_BLOCK, (block) => {\n fences.push(block);\n return `\\u0000blume-fence-${fences.length - 1}\\u0000`;\n });\n\n const referenced = [\n ...[...masked.matchAll(MD_IMAGE)].map((match) =>\n imageUrl(match.groups?.pointy, match.groups?.bare)\n ),\n ...[...masked.matchAll(HTML_VIDEO_SRC)].map(\n (match) => match.groups?.url ?? \"\"\n ),\n ];\n const urls = new Set(referenced.filter((url) => REMOTE.test(url)));\n\n /** Fetch one asset into the asset dir and return its file name. */\n const download = async (url: string, stem: string): Promise<string> => {\n const urlExt = extFromUrl(url);\n // The name is usually known up front from the path's extension (every\n // Notion upload has one), so a file from an earlier run is reused as is;\n // otherwise the extension came from the last response, so look for it.\n if (urlExt && (await exists(join(ctx.assetsDir, `${stem}${urlExt}`)))) {\n return `${stem}${urlExt}`;\n }\n const completed = await completedFor(ctx.assetsDir, stem);\n if (completed) {\n return completed;\n }\n const res = await doFetch(url, { signal: AbortSignal.timeout(timeoutMs) });\n if (!res.ok) {\n throw new Error(`${res.status}`);\n }\n // Writing a watch page or an API response out as `.mp4` gives a player\n // that can't play and a green build, and one saved as `.html` would be\n // served as a page on the docs origin: only images and videos are kept.\n const ext = extFor(mediaType(res), urlExt);\n if (!res.body) {\n throw new Error(\"empty response body\");\n }\n const file = `${stem}${ext}`;\n const target = join(ctx.assetsDir, file);\n await mkdir(ctx.assetsDir, { recursive: true });\n // Stream the body to disk rather than buffering it: a video is hundreds of\n // megabytes where an image was a hundred kilobytes. Write to a temporary\n // name unique to this attempt and rename on completion, so a download that\n // dies midway never leaves a truncated file the next run would trust as\n // complete. Two pages fetching the same asset at once share one download\n // through `inFlight` (below), so no two attempts ever publish one file.\n const part = `${target}.${randomUUID()}${PART_SUFFIX}`;\n try {\n await writeFile(part, res.body);\n } catch (error) {\n await rm(part, { force: true });\n throw error;\n }\n await rename(part, target);\n return file;\n };\n\n /**\n * Publish one asset, joining a download another page already has in\n * flight. The gate sits inside the shared promise, so a page waiting on\n * another page's download holds no slot of its own. A waiter whose\n * publisher failed makes its own attempt, exactly as it would have without\n * sharing — one transient error must not fail every page naming the asset.\n */\n const publish = async (url: string): Promise<string> => {\n // Hash the query-less URL: CMS asset URLs are pre-signed, so the query\n // changes on every fetch of the same file — hashing it would mint a new\n // file each refresh and re-dirty the content digest. Two real assets\n // sharing scheme+host+path and differing only in query are rare enough to\n // accept colliding.\n const stem = hashText(url.split(\"?\")[0] ?? url);\n const pending = inFlight.get(stem);\n if (pending) {\n try {\n return await pending;\n } catch {\n // The failed download has left the table by now: join whichever\n // waiter retried first, or go it alone.\n return await publish(url);\n }\n }\n const own = (async () => {\n try {\n return await limit(() => download(url, stem));\n } finally {\n inFlight.delete(stem);\n }\n })();\n inFlight.set(stem, own);\n return await own;\n };\n\n const rewrites = new Map<string, string>();\n await Promise.all(\n [...urls].map(async (url) => {\n try {\n const file = await publish(url);\n rewrites.set(url, `${ctx.assetsBaseUrl}/${file}`);\n } catch (error) {\n // SAFETY: everything thrown in this block is an Error — the manual\n // throws in `download`, and fetch/fs failures.\n diagnostics.push({\n code: \"BLUME_ASSET_FETCH_FAILED\",\n message: `Failed to download asset ${url}: ${(error as Error).message}`,\n severity: \"warning\",\n });\n }\n })\n );\n\n const rewritten = masked\n .replaceAll(\n MD_IMAGE,\n (match: string, alt: string, pointy?: string, bare?: string) => {\n const local = rewrites.get(imageUrl(pointy, bare));\n return local ? `` : match;\n }\n )\n .replaceAll(HTML_VIDEO_SRC, (match, open, url, close) => {\n const local = rewrites.get(url);\n return local ? `${open}${local}${close}` : match;\n })\n .replaceAll(FENCE_TOKEN, (token, index) => fences[Number(index)] ?? token);\n\n return { diagnostics, markdown: rewritten };\n};\n",
|
|
25
|
+
"import { existsSync, watch as fsWatch, statSync } from \"node:fs\";\nimport type { Dirent } from \"node:fs\";\nimport { readdir, readFile, stat } from \"node:fs/promises\";\n\nimport { basename, isAbsolute, join, relative, resolve } from \"pathe\";\n\nimport { nextFenceState } from \"../code-fences.ts\";\nimport type { FenceState } from \"../code-fences.ts\";\nimport { BlumeError } from \"../diagnostics.ts\";\nimport matter from \"../frontmatter.ts\";\nimport { pageMetaSchema } from \"../schema.ts\";\nimport type { ResolvedI18nConfig, ResolvedVersionsConfig } from \"../schema.ts\";\nimport type { Diagnostic } from \"../types.ts\";\nimport { hashText } from \"./cache.ts\";\nimport type { EntryPlacement } from \"./normalize.ts\";\nimport {\n extractHeadings,\n isStringValue,\n localizedRoute,\n placeEntryRef,\n resolveEntryRoute,\n slugifyPath,\n strippedLineOffset,\n unloadablePathDiagnostic,\n} from \"./normalize.ts\";\nimport type {\n ContentSource,\n SourceContext,\n SourceEntry,\n SourceLoadResult,\n} from \"./types.ts\";\nimport { BLUME_IGNORE_DIRS, ignoringWatchListener } from \"./watch.ts\";\n\n/** Options for the built-in Obsidian vault source. */\nexport interface ObsidianSourceOptions {\n /**\n * The project's default content type (`content.defaultType`), which a note\n * without a `type` resolves to when {@link typeFrontmatterKeys} are picked.\n */\n defaultType?: string;\n /** Vault folder names to skip at any depth, in addition to dot-folders. */\n exclude?: string[];\n /**\n * Frontmatter keys the project declares site-wide beyond Blume's page meta\n * (`frontmatter.extend`). Any other Obsidian property is dropped when a note\n * is lowered, since the strict meta schema would reject it and fail the\n * build.\n */\n frontmatterKeys?: readonly string[];\n /** The project's i18n config, when locale directories place vault notes. */\n i18n?: ResolvedI18nConfig;\n /** Stable source name; namespaces ids and diagnostics. */\n name: string;\n /** Namespaces the source's routes under `/<prefix>/`; e.g. `vault`. */\n prefix?: string;\n /**\n * Frontmatter keys each content type declares (`content.types.<type>.frontmatter`),\n * keyed by type. A note keeps only the keys of its own type — the meta parse\n * merges only that type's schema, so a key another type declares would still\n * reach the strict page schema and fail.\n */\n typeFrontmatterKeys?: Readonly<Record<string, readonly string[]>>;\n /** Vault directory, absolute or relative to `projectRoot`. */\n vault: string;\n /** The project's versions config, when snapshot directories hold notes. */\n versions?: ResolvedVersionsConfig;\n}\n\nconst MARKDOWN_FILE = /\\.md$/iu;\n/** `%%comment%%` on a single line; multi-line comments are not stripped yet. */\nconst OBSIDIAN_COMMENT = /%%.*?%%/gu;\n/**\n * `[[target]]`, `[[target|alias]]`, `[[target#heading]]`, `![[embed]]`. The\n * target and heading are lazy so the `\\|` Obsidian writes for an alias inside\n * a table cell (a bare `|` would end the cell) is read as the alias separator\n * rather than as a backslash ending the target. A target cannot open with `[`:\n * Obsidian reads `[[[Note]]]` as a literal `[`, the link, and a literal `]`,\n * and so does the match once the first bracket is left out of it.\n */\nconst WIKILINK =\n /(?<embed>!)?\\[\\[(?<target>(?:[^\\][|#\\n][^\\]|#\\n]*?)?)(?:#(?<heading>[^\\]|\\n]+?))?(?:\\\\?\\|(?<alias>[^\\]\\n]+))?\\]\\]/gu;\n\n/** A vault note, read and split into frontmatter and body. */\ninterface ParsedNote {\n absPath: string;\n /** The note body, frontmatter stripped and otherwise untouched. */\n content: string;\n data: SourceEntry[\"data\"];\n /** How many lines of the file the frontmatter block (if any) spans. */\n frontmatterLines: number;\n placement: EntryPlacement;\n /** Vault-relative path, e.g. `guides/Getting Started.md`. */\n rel: string;\n}\n\n/**\n * The key a note name or heading is indexed and looked up under. macOS writes\n * filenames as NFD and editors type NFC, so both sides normalize before\n * casefolding.\n */\nconst indexKey = (value: string): string =>\n value.normalize(\"NFC\").toLowerCase();\n\n/** A Markdown link or image, reduced to its text by {@link headingKey}. */\nconst INLINE_LINK = /!?\\[(?<text>[^\\]]*)\\]\\([^)]*\\)/gu;\n/**\n * Inline formatting Obsidian drops when it autocompletes a heading link:\n * emphasis and strikethrough marks, code-span backticks, and an `_` that opens\n * or closes a word (one inside `snake_case` is text).\n */\nconst INLINE_MARKS = /[*~`]+|(?<![\\p{L}\\p{N}])_+|_+(?![\\p{L}\\p{N}])/gu;\n\n/**\n * The key a heading is indexed and looked up under: its text with inline\n * Markdown stripped, because Obsidian writes `[[Note#Bold heading]]` for a\n * `## **Bold** heading` — and because that is the text the rendered id is\n * slugged from.\n */\nconst headingKey = (text: string): string =>\n indexKey(\n text.replaceAll(INLINE_LINK, \"$<text>\").replaceAll(INLINE_MARKS, \"\")\n );\n\n/**\n * Obsidian's own default properties (the Properties UI writes the plural\n * spellings; older vaults carry the singular ones). They are not Blume\n * frontmatter, so they are dropped even when a project happens to declare a\n * key of the same name. `aliases` is dropped rather than resolved; alias link\n * targets are not supported yet.\n */\nconst OBSIDIAN_NATIVE_KEYS = new Set([\n \"alias\",\n \"aliases\",\n \"cssclass\",\n \"cssclasses\",\n \"tag\",\n \"tags\",\n]);\n\n/** Every key Blume's page meta schema accepts. */\nconst PAGE_META_KEYS = new Set<string>(pageMetaSchema.keyof().options);\n\n/**\n * A note's route input: its locale- and version-stripped path, slugged. Vault\n * filenames are prose (`Getting Started.md`) and the route mapper does not\n * slug, so this does — through {@link slugifyPath}, which keeps a non-Latin\n * name routable.\n */\nconst entrySlugFor = (navPath: string): string =>\n slugifyPath(navPath.replace(MARKDOWN_FILE, \"\"));\n\n/** One note as the wikilink index knows it: where it routes, and its anchors. */\ninterface IndexedNote {\n /** Anchor id per heading, keyed by {@link headingKey}. */\n anchors: Map<string, string>;\n /** The locales the note publishes in — every locale for a shared `$` note. */\n locales: string[];\n /** The note's version-prefixed, locale-agnostic route, prefix included. */\n logicalRoute: string;\n}\n\n/** The wikilink lookup table. */\ninterface LinkIndex {\n /**\n * Bare note names claimed by more than one file, keyed by {@link indexKey},\n * with the diagnostic text naming the claimants. A name some note owns as\n * its exact vault path is not here — Obsidian resolves a path before a name.\n */\n ambiguous: Map<string, string>;\n i18n?: ResolvedI18nConfig;\n notes: Map<string, IndexedNote>;\n}\n\n/** Dead or ambiguous wikilink targets, accumulated across one load. */\ninterface UnresolvedTargets {\n /** Bare names a wikilink resolved through a collision, with the claimants. */\n ambiguous: string[];\n /** `Note#Heading` targets whose note exists but whose heading does not. */\n anchors: string[];\n /** Targets that matched no note in the vault. */\n notes: string[];\n}\n\n/** A note paired with its own index entry, so a rewrite can address itself. */\ninterface IndexedPair {\n note: ParsedNote;\n self: IndexedNote;\n}\n\n/** The wikilink lookup table, plus each note's own entry in it. */\ninterface NoteIndex {\n index: LinkIndex;\n pairs: IndexedPair[];\n}\n\n/** A fresh accumulator. */\nconst noTargets = (): UnresolvedTargets => ({\n ambiguous: [],\n anchors: [],\n notes: [],\n});\n\n/**\n * The href a link from one note to another takes. A shared `Note.$.md`\n * publishes in every locale, so a link to it stays in the linking note's\n * locale rather than jumping to whichever locale is configured first; a target\n * that does not publish in that locale links to the first locale it does. The\n * Markdown pipeline prefixes every root-absolute link with `deployment.base`\n * at render time, so the base is left off here.\n */\nconst hrefBetween = (\n target: IndexedNote,\n from: IndexedNote,\n i18n: ResolvedI18nConfig | undefined\n): string => {\n const own = from.locales[0] ?? \"\";\n const locale = target.locales.includes(own) ? own : (target.locales[0] ?? \"\");\n return localizedRoute(target.logicalRoute, locale, i18n);\n};\n\n/**\n * Every path suffix a wikilink can address a note by: `docs/guides/Setup.md`\n * yields `docs/guides/setup`, `guides/setup`, and `setup`. Obsidian's default\n * \"shortest path when possible\" setting writes any of them into a link, so all\n * of them must resolve.\n */\nconst suffixKeysOf = (rel: string): string[] => {\n const segments = rel.replace(MARKDOWN_FILE, \"\").split(\"/\");\n return segments.map((_, from) => indexKey(segments.slice(from).join(\"/\")));\n};\n\n/** Every run of backticks — the only delimiter a code span has. */\nconst BACKTICK_RUN = /`+/gu;\n\n/** A run of backticks: where it starts and how many. */\ninterface BacktickRun {\n at: number;\n length: number;\n}\n\n/** Whether the character at `at` sits behind an odd number of backslashes. */\nconst isEscaped = (text: string, at: number): boolean => {\n let backslashes = 0;\n for (let i = at - 1; i >= 0 && text[i] === \"\\\\\"; i -= 1) {\n backslashes += 1;\n }\n return backslashes % 2 === 1;\n};\n\n/**\n * The backtick runs that can delimit a code span. A backslash escapes the one\n * backtick after it (CommonMark 2.4), so an escaped run loses its first tick\n * — and drops out entirely when that was its only one.\n */\nconst backtickRuns = (text: string): BacktickRun[] => {\n const runs: BacktickRun[] = [];\n for (const match of text.matchAll(BACKTICK_RUN)) {\n const escaped = isEscaped(text, match.index);\n const at = escaped ? match.index + 1 : match.index;\n const length = escaped ? match[0].length - 1 : match[0].length;\n if (length > 0) {\n runs.push({ at, length });\n }\n }\n return runs;\n};\n\n/** One piece of a line: literal text the rewrite must not touch, or prose. */\ninterface LineChunk {\n literal: boolean;\n text: string;\n}\n\n/** A span of text that is literal: a code span or an HTML comment. */\ninterface LiteralSpan {\n end: number;\n start: number;\n}\n\n/** The index of the next backtick run of exactly `length`, or -1. */\nconst closerAfter = (\n runs: BacktickRun[],\n from: number,\n length: number\n): number => {\n for (let i = from; i < runs.length; i += 1) {\n if (runs[i]?.length === length) {\n return i;\n }\n }\n return -1;\n};\n\n/**\n * The first code span opening at or after `from`. A run of N backticks opens\n * a span only if a run of exactly N follows — one regex expresses neither the\n * length match nor that condition — and a run with no closer is literal text\n * that the scan steps over.\n */\nconst nextCodeSpan = (\n runs: BacktickRun[],\n from: number\n): LiteralSpan | null => {\n for (let i = 0; i < runs.length; i += 1) {\n const run = runs[i];\n if (run === undefined || run.at < from) {\n continue;\n }\n const close = closerAfter(runs, i + 1, run.length);\n if (close !== -1) {\n return { end: (runs[close]?.at ?? 0) + run.length, start: run.at };\n }\n }\n return null;\n};\n\nconst COMMENT_OPEN = \"<!--\";\nconst COMMENT_CLOSE = \"-->\";\n\n/**\n * The first HTML comment opening at or after `from`. Obsidian hides one in\n * reading view, so a wikilink inside it is never a link the reader sees; an\n * unclosed `<!--` is literal text (CommonMark 6.6), not a comment to the end.\n */\nconst nextComment = (text: string, from: number): LiteralSpan | null => {\n const start = text.indexOf(COMMENT_OPEN, from);\n if (start === -1) {\n return null;\n }\n const close = text.indexOf(COMMENT_CLOSE, start + COMMENT_OPEN.length);\n return close === -1 ? null : { end: close + COMMENT_CLOSE.length, start };\n};\n\n/**\n * Split prose into literal and rewritable chunks. Code spans and HTML comments\n * have equal precedence and the leftmost wins (CommonMark 6.6): a `<!--`\n * inside a code span is code, and a backtick inside a comment is comment. The\n * scan runs over a whole run of prose rather than a line, because either may\n * hold newlines while an unclosed opener is literal text.\n */\nconst splitLiterals = (text: string): LineChunk[] => {\n const runs = backtickRuns(text);\n const chunks: LineChunk[] = [];\n let cursor = 0;\n for (;;) {\n const code = nextCodeSpan(runs, cursor);\n const comment = nextComment(text, cursor);\n const span =\n code === null || (comment !== null && comment.start < code.start)\n ? comment\n : code;\n if (span === null) {\n break;\n }\n chunks.push(\n { literal: false, text: text.slice(cursor, span.start) },\n { literal: true, text: text.slice(span.start, span.end) }\n );\n cursor = span.end;\n }\n chunks.push({ literal: false, text: text.slice(cursor) });\n return chunks;\n};\n\n/** Rewrite one chunk's wikilinks and strip single-line comments. */\nconst transformChunk = (\n chunk: string,\n index: LinkIndex,\n self: IndexedNote,\n unresolved: UnresolvedTargets\n): string =>\n chunk.replaceAll(OBSIDIAN_COMMENT, \"\").replaceAll(WIKILINK, (...args) => {\n // SAFETY: `String.replaceAll` passes the groups object last whenever the\n // pattern has named groups, and WIKILINK has four.\n const groups = args.at(-1) as Record<string, string | undefined>;\n const target = (groups.target ?? \"\").trim();\n const heading = groups.heading?.trim();\n const label = groups.alias?.trim() || heading || target;\n // Rewriting an embed means serving the attachment, which this source\n // does not do yet.\n if (groups.embed) {\n // SAFETY: the replacer's first argument is always the matched substring.\n return args[0] as string;\n }\n // `[[#Heading]]` addresses the note it sits in; an empty target with no\n // heading is not a link at all.\n if (target === \"\") {\n if (heading === undefined) {\n // SAFETY: the replacer's first argument is always the matched substring.\n return args[0] as string;\n }\n const ownHref = hrefBetween(self, self, index.i18n);\n // `#^block-id` names a block, not a heading. Blocks render with no\n // anchor to land on, so the link goes to the page itself rather than\n // warning about a heading that never existed. The caret never reaches\n // the label — `[^id]` would read as a GFM footnote reference.\n if (heading.startsWith(\"^\")) {\n return `[${groups.alias?.trim() || heading.slice(1)}](${ownHref})`;\n }\n const own = self.anchors.get(headingKey(heading));\n if (own === undefined) {\n // Same rule as a missing heading in another note: keep the page link,\n // drop only the anchor.\n unresolved.anchors.push(`#${heading}`);\n return `[${label}](${ownHref})`;\n }\n return `[${label}](${ownHref}#${own})`;\n }\n // `[[Note.md]]` is the path form Obsidian also accepts; the index is keyed\n // without the extension.\n const key = indexKey(target.replace(MARKDOWN_FILE, \"\"));\n const note = index.notes.get(key);\n if (note === undefined) {\n unresolved.notes.push(target);\n return label;\n }\n const clash = index.ambiguous.get(key);\n if (clash !== undefined) {\n unresolved.ambiguous.push(clash);\n }\n const href = hrefBetween(note, self, index.i18n);\n if (heading === undefined) {\n return `[${label}](${href})`;\n }\n // A block reference links to its note without an anchor; block ids are\n // generated noise (`^a1b2c3`), so an unaliased one reads as the note name.\n if (heading.startsWith(\"^\")) {\n return `[${groups.alias?.trim() || target}](${href})`;\n }\n const anchor = note.anchors.get(headingKey(heading));\n if (anchor === undefined) {\n // The note is real, so keep the link and drop only the anchor — landing\n // on the page beats degrading the whole link to plain text.\n unresolved.anchors.push(`${target}#${heading}`);\n return `[${label}](${href})`;\n }\n return `[${label}](${href}#${anchor})`;\n });\n\n/**\n * Rewrite a run of prose, leaving inline code spans (`` `[[x]]` ``) and HTML\n * comments (`<!-- [[x]] -->`) verbatim.\n */\nconst transformProse = (\n text: string,\n index: LinkIndex,\n self: IndexedNote,\n unresolved: UnresolvedTargets\n): string =>\n splitLiterals(text)\n .map((chunk) =>\n chunk.literal\n ? chunk.text\n : transformChunk(chunk.text, index, self, unresolved)\n )\n .join(\"\");\n\n/** An indented code block's marker: four spaces or a tab (CommonMark 4.4). */\nconst INDENTED_CODE = /^(?: {4}|\\t)/u;\n/** A list item opener (CommonMark 5.2), which makes later indentation prose. */\nconst LIST_ITEM = /^ {0,3}(?:[-+*]|\\d{1,9}[.)])(?: |$)/u;\n\n/** How many leading whitespace characters a line carries. */\nconst indentOf = (line: string): number =>\n line.length - line.trimStart().length;\n\n/**\n * Whether a list is open after `line`: an item opens one, and it stays open\n * until a non-blank line that is neither an item nor indented.\n */\nconst listAfter = (line: string, inList: boolean): boolean => {\n if (line.trim() === \"\") {\n return inList;\n }\n if (LIST_ITEM.test(line)) {\n return true;\n }\n return line.startsWith(\" \") || line.startsWith(\"\\t\") ? inList : false;\n};\n\n/**\n * The fence state after `line`. Outside a list, a line indented four or more\n * spaces is never a fence delimiter: it is indented code or a continuation\n * line. Inside a list item the content indent is the item's, so a fence\n * there is written four spaces deep (`1. step` then ` ```js`) and must\n * still open. Once a fence is open only a run at most three spaces deeper\n * than it (CommonMark 4.5) closes it; a deeper run is content.\n */\nconst fenceAfter = (\n line: string,\n fence: FenceState,\n fenceIndent: number,\n inList: boolean\n): FenceState => {\n const canDelimit =\n fence === null\n ? inList || !INDENTED_CODE.test(line)\n : indentOf(line) <= fenceIndent + 3;\n return canDelimit ? nextFenceState(line, fence) : fence;\n};\n\n/**\n * Transform an Obsidian body to Blume-ready Markdown: wikilinks become route\n * links and `%%comments%%` are stripped, while fenced, indented, and inline\n * code pass through so a note documenting the syntax survives. Callouts stay\n * blockquotes.\n */\nconst transformBody = (\n body: string,\n index: LinkIndex,\n self: IndexedNote,\n unresolved: UnresolvedTargets\n): string => {\n let fence: FenceState = null;\n // The indent the open fence was written at: a closing fence may sit up to\n // three spaces deeper (CommonMark 4.5), and a deeper run is content.\n let fenceIndent = 0;\n // An indented code block opens only after a blank line (CommonMark: it\n // cannot interrupt a paragraph) and outside a list (a loose item's indented\n // continuation paragraph is prose), then runs while lines stay indented or\n // blank. A list stays open until a non-indented line that is not an item.\n let afterBlank = true;\n let indentedCode = false;\n let inList = false;\n const out: string[] = [];\n let prose: string[] = [];\n const flush = (): void => {\n if (prose.length > 0) {\n out.push(transformProse(prose.join(\"\\n\"), index, self, unresolved));\n prose = [];\n }\n };\n for (const line of body.split(\"\\n\")) {\n const blank = line.trim() === \"\";\n if (fence === null && indentedCode) {\n if (blank || INDENTED_CODE.test(line)) {\n out.push(line);\n afterBlank = blank;\n continue;\n }\n indentedCode = false;\n }\n inList = listAfter(line, inList);\n if (\n fence === null &&\n afterBlank &&\n !(blank || inList) &&\n INDENTED_CODE.test(line)\n ) {\n flush();\n indentedCode = true;\n out.push(line);\n afterBlank = false;\n continue;\n }\n const next: FenceState = fenceAfter(line, fence, fenceIndent, inList);\n if (fence !== null || next !== null) {\n if (fence === null) {\n fenceIndent = indentOf(line);\n }\n flush();\n fence = next;\n out.push(line);\n afterBlank = blank;\n continue;\n }\n if (blank) {\n // A code span cannot cross a blank line (CommonMark 6.1), so each prose\n // run flushes at one — a stray backtick stays confined to its paragraph\n // instead of pairing with another paragraph's and swallowing the links\n // between them.\n flush();\n out.push(line);\n afterBlank = true;\n continue;\n }\n prose.push(line);\n afterBlank = false;\n }\n flush();\n return out.join(\"\\n\");\n};\n\n/**\n * Fill a note's anchors: the id the renderer emits for each heading, keyed by\n * {@link headingKey}. The ids are whatever {@link extractHeadings} assigns —\n * the same pass fills the page manifest from the staged body, so a\n * `[#custom-id]` pin and the `setup-1` a later collision gets are honored\n * rather than re-slugged. When no heading holds anything the rewrite changes,\n * the raw body's headings are the staged body's; otherwise the body is lowered\n * once more so the headings are scanned exactly as they ship. A heading link\n * inside a heading is rewritten here before every note's anchors are known,\n * so its href may still lack the anchor it ships with. That moves no id: a\n * heading is slugged by its rendered text, where the link is its label alone,\n * so the heading stays as addressable as any other.\n */\nconst fillAnchors = (pair: IndexedPair, index: LinkIndex): void => {\n const raw = extractHeadings(pair.note.content);\n const rewritten = raw.some(\n (heading) =>\n transformProse(heading.text, index, pair.self, noTargets()) !==\n heading.text\n );\n const headings = rewritten\n ? extractHeadings(\n transformBody(pair.note.content, index, pair.self, noTargets())\n )\n : raw;\n for (const heading of headings) {\n const key = headingKey(heading.text);\n // Obsidian points a repeated-heading link at the first match; the manifest\n // scan has already disambiguated the later ones (`setup-1`).\n if (!pair.self.anchors.has(key)) {\n pair.self.anchors.set(key, heading.slug);\n }\n }\n};\n\n/**\n * Obsidian addresses `[[Name]]` by note name, not only by path; index each note\n * under every suffix of its vault-relative path, bare basename included. A key\n * two notes share resolves to the first in vault order — Obsidian disambiguates\n * by the linking note's location, which a rewrite cannot know — except that a\n * note's exact full path always wins for its own key, the way Obsidian resolves\n * a link as a path before a name. Only a bare-name collision is recorded, and\n * only so a link that actually resolves through it can warn; a longer shared\n * suffix is already the author's disambiguation.\n */\nconst buildLinkIndex = (\n notes: ParsedNote[],\n options: Pick<ObsidianSourceOptions, \"i18n\" | \"prefix\" | \"versions\">\n): NoteIndex => {\n const entries = new Map<string, IndexedNote>();\n const indexed = (note: ParsedNote): IndexedNote => {\n const cached = entries.get(note.rel);\n if (cached) {\n return cached;\n }\n // The route the pipeline will assign, from the same resolver\n // `normalizeEntry` uses — keyed by the same ref, slug, and frontmatter\n // `slug` the staged entry carries, so the two cannot disagree.\n const route = resolveEntryRoute(\n { ref: note.rel, slug: entrySlugFor(note.placement.navPath) },\n \".md\",\n isStringValue(note.data.slug) ? note.data.slug : undefined,\n { ...options, orderingPrefixes: true }\n );\n const built: IndexedNote = {\n anchors: new Map(),\n locales: route.locales,\n logicalRoute: route.logicalRoute,\n };\n entries.set(note.rel, built);\n return built;\n };\n const claims = new Map<string, [ParsedNote, ...ParsedNote[]]>();\n for (const note of notes) {\n for (const key of suffixKeysOf(note.rel)) {\n const claimed = claims.get(key);\n if (claimed) {\n claimed.push(note);\n } else {\n claims.set(key, [note]);\n }\n }\n }\n const exactKeys = new Set(\n notes.map((note) => indexKey(note.rel.replace(MARKDOWN_FILE, \"\")))\n );\n const index: LinkIndex = {\n ambiguous: new Map(),\n i18n: options.i18n,\n notes: new Map(),\n };\n for (const [key, claimants] of claims) {\n const [first, ...rest] = claimants;\n if (rest.length > 0 && !key.includes(\"/\") && !exactKeys.has(key)) {\n index.ambiguous.set(\n key,\n `${key} (${claimants.map((note) => note.rel).join(\", \")})`\n );\n }\n index.notes.set(key, indexed(first));\n }\n const pairs = notes.map((note) => {\n const self = indexed(note);\n // Set last, over any name or suffix claim another note holds on this key:\n // an exact vault-relative path is never ambiguous.\n index.notes.set(indexKey(note.rel.replace(MARKDOWN_FILE, \"\")), self);\n return { note, self };\n });\n // Anchors need every href in place (a heading may hold a wikilink), so they\n // fill in after the routes.\n for (const pair of pairs) {\n fillAnchors(pair, index);\n }\n return { index, pairs };\n};\n\n/**\n * An explicit frontmatter title wins, then the filename — how Obsidian titles\n * notes. `index` names a route rather than a note, so an untitled one falls\n * through to Blume's own derivation instead of publishing as \"index\".\n */\nconst titleFor = (\n rel: string,\n frontmatterTitle: SourceEntry[\"data\"][string]\n): string | undefined => {\n if (isStringValue(frontmatterTitle)) {\n return frontmatterTitle;\n }\n const noteName = basename(rel).replace(MARKDOWN_FILE, \"\");\n return noteName.toLowerCase() === \"index\" ? undefined : noteName;\n};\n\n/**\n * The declared keys a note keeps: the site-wide ones plus those of its own\n * content type, resolved the way the meta parse resolves it (`type` falling\n * back to the project default). A non-string `type` fails the strict parse\n * later regardless, so which per-type list it picks never matters.\n */\nconst keptKeysFor = (\n data: SourceEntry[\"data\"],\n options: Pick<\n ObsidianSourceOptions,\n \"defaultType\" | \"frontmatterKeys\" | \"typeFrontmatterKeys\"\n >\n): Set<string> => {\n const entryType = isStringValue(data.type) ? data.type : options.defaultType;\n const typeKeys =\n entryType === undefined\n ? []\n : (options.typeFrontmatterKeys?.[entryType] ?? []);\n return new Set([...(options.frontmatterKeys ?? []), ...typeKeys]);\n};\n\n/**\n * Lower one vault note to a staged Markdown entry, keyed by the same route\n * input the link index used when rewriting links to it. Frontmatter keeps\n * what Blume's page meta accepts plus what the project declares for the note's\n * type; every other Obsidian property (Dataview fields, Templater dates,\n * `publish`, …) is dropped rather than failing the strict schema.\n */\nconst noteToEntry = (\n pair: IndexedPair,\n index: LinkIndex,\n options: Pick<\n ObsidianSourceOptions,\n \"defaultType\" | \"frontmatterKeys\" | \"typeFrontmatterKeys\"\n >,\n unresolved: UnresolvedTargets\n): SourceEntry => {\n const { note } = pair;\n const title = titleFor(note.rel, note.data.title);\n const keep = keptKeysFor(note.data, options);\n const data = Object.fromEntries(\n Object.entries(note.data).filter(\n ([key]) =>\n !OBSIDIAN_NATIVE_KEYS.has(key) &&\n (PAGE_META_KEYS.has(key) || keep.has(key))\n )\n );\n const merged = title === undefined ? data : { ...data, title };\n const body = note.content.trim();\n const text = transformBody(body, index, pair.self, unresolved);\n const raw = matter.stringify(`${text}\\n`, merged);\n // `raw` re-serializes the frontmatter without the dropped properties, so\n // its height is not the file's: count the file's own frontmatter and the\n // blank lines the trim dropped, so a link diagnostic names the line the\n // author sees in the note.\n const leading = note.content.slice(0, note.content.indexOf(body));\n return {\n body: { format: \"md\", text },\n bodyLineOffset: note.frontmatterLines + leading.split(\"\\n\").length - 1,\n data: merged,\n hash: hashText(raw),\n raw,\n ref: note.rel,\n slug: entrySlugFor(note.placement.navPath),\n sourcePath: note.absPath,\n };\n};\n\n/**\n * Read and split one note, or null when it vanished between the walk and the\n * read. Obsidian renames and deletes notes while a dev server watches the\n * vault, and one file disappearing mid-load must not fail the whole reload.\n * Any other read failure still throws.\n */\nconst readNote = async (\n vaultDir: string,\n rel: string,\n options: Pick<ObsidianSourceOptions, \"i18n\" | \"versions\">\n): Promise<ParsedNote | null> => {\n const absPath = join(vaultDir, rel);\n try {\n const file = await readFile(absPath, \"utf-8\");\n const { content, data } = matter(file);\n return {\n absPath,\n content,\n data,\n frontmatterLines: strippedLineOffset(file, content),\n // Version outermost, then locale, the way `normalizeEntry` reads the\n // ref — the slug is built from what remains, so the pipeline's own\n // re-prefixing does not stack a second `fr/` or `v1.0/` onto the route.\n placement: placeEntryRef(rel, \".md\", options),\n rel,\n };\n } catch (error) {\n // SAFETY: a rejected `readFile` always yields a Node system error, whose\n // `code` is the only field read here.\n if ((error as NodeJS.ErrnoException).code === \"ENOENT\") {\n return null;\n }\n throw error;\n }\n};\n\n/** A directory entry with symlinks resolved to what they point at. */\ninterface VaultEntry {\n directory: boolean;\n file: boolean;\n name: string;\n}\n\n/**\n * A dot-name is never content: Obsidian's own `.obsidian/` and `.trash/`, and\n * the caches plugins keep in dot-folders. The walk and the watcher apply the\n * same rule, so a write into one of them neither publishes nor rescans.\n */\nconst isHidden = (name: string): boolean => name.startsWith(\".\");\n\n/**\n * Classify one directory entry, following a symlink to what it points at the\n * way the filesystem source's glob does — a vault that symlinks a shared\n * folder in must publish it, not report every link into it as a missing\n * note. A link that cannot be followed (dangling, or a loop) is left to the\n * read when it names a note: a missing target is then the same vanished file\n * a mid-load delete produces, and anything else is the real failure it is.\n */\nconst classify = async (dir: string, entry: Dirent): Promise<VaultEntry> => {\n if (!entry.isSymbolicLink()) {\n return {\n directory: entry.isDirectory(),\n file: entry.isFile(),\n name: entry.name,\n };\n }\n try {\n const target = await stat(join(dir, entry.name));\n return {\n directory: target.isDirectory(),\n file: target.isFile(),\n name: entry.name,\n };\n } catch {\n return {\n directory: false,\n file: MARKDOWN_FILE.test(entry.name),\n name: entry.name,\n };\n }\n};\n\n/**\n * Vault order, the way Obsidian's file explorer lists a folder: subfolders\n * first, then notes, each case-insensitively and numeric-aware (`Note 2`\n * before `Note 10`), with a code-point tiebreak so the order is total.\n */\nconst byVaultOrder = (a: VaultEntry, b: VaultEntry): number =>\n Number(b.directory) - Number(a.directory) ||\n a.name.localeCompare(b.name, \"en\", { numeric: true, sensitivity: \"base\" }) ||\n (a.name < b.name ? -1 : 1);\n\n/** Recursively list vault-relative `.md` paths, skipping dot/excluded dirs. */\nconst walkVault = async (\n dir: string,\n root: string,\n exclude: ReadonlySet<string>\n): Promise<string[]> => {\n const found: string[] = [];\n const listed = await readdir(dir, { withFileTypes: true });\n const entries = await Promise.all(\n listed\n .filter((entry) => !(isHidden(entry.name) || exclude.has(entry.name)))\n .map((entry) => classify(dir, entry))\n );\n for (const entry of entries.toSorted(byVaultOrder)) {\n const full = join(dir, entry.name);\n if (entry.directory) {\n // oxlint-disable-next-line no-await-in-loop -- vault trees are shallow; parallelizing complicates ordering for no measurable win.\n found.push(...(await walkVault(full, root, exclude)));\n } else if (entry.file && MARKDOWN_FILE.test(entry.name)) {\n found.push(relative(root, full));\n }\n }\n return found;\n};\n\n/** The first few dead targets, deduplicated, for a diagnostic message. */\nconst sample = (targets: string[]): string =>\n [...new Set(targets)].slice(0, 5).join(\", \");\n\n/** The warnings one load raises: dead notes, then dead headings. */\nconst unresolvedDiagnostics = (\n name: string,\n unresolved: UnresolvedTargets\n): Diagnostic[] => {\n const diagnostics: Diagnostic[] = [];\n if (unresolved.notes.length > 0) {\n diagnostics.push({\n code: \"BLUME_WIKILINK_UNRESOLVED\",\n message: `Source \"${name}\" found ${unresolved.notes.length} wikilink(s) to a missing note (${sample(unresolved.notes)}); rendered as plain text.`,\n severity: \"warning\",\n suggestion:\n \"Create the missing note, or fix the link target in Obsidian — note names are matched case-insensitively across the whole vault.\",\n });\n }\n if (unresolved.ambiguous.length > 0) {\n const names = new Set(unresolved.ambiguous).size;\n diagnostics.push({\n code: \"BLUME_WIKILINK_AMBIGUOUS\",\n message: `Source \"${name}\" found ${unresolved.ambiguous.length} wikilink(s) to ${names} note name(s) claimed by more than one file (${sample(unresolved.ambiguous)}); each resolves to the first note in vault order.`,\n severity: \"warning\",\n suggestion:\n \"Rename one of the notes, or link to the full vault-relative path (`[[folder/Note]]`) so the target is unambiguous.\",\n });\n }\n if (unresolved.anchors.length > 0) {\n diagnostics.push({\n code: \"BLUME_WIKILINK_UNRESOLVED\",\n message: `Source \"${name}\" found ${unresolved.anchors.length} wikilink(s) to a missing heading (${sample(unresolved.anchors)}); linked to the page without an anchor.`,\n severity: \"warning\",\n suggestion:\n \"Fix the heading text in the link, or add the heading to the target note.\",\n });\n }\n return diagnostics;\n};\n\n/**\n * The built-in Obsidian vault source: read a vault directly — no export step,\n * no generated files in the user's repo — lowering Obsidian's dialect to\n * Blume-ready Markdown at load time.\n *\n * Staged, since the body is rewritten before Blume sees it; `sourcePath` still\n * points at the note, so diagnostics and relative image checks name the real\n * file. A wikilink Blume cannot resolve degrades rather than failing the build.\n */\nexport const obsidianSource = (\n options: ObsidianSourceOptions,\n ctx: SourceContext\n): ContentSource => {\n const vaultDir = resolve(ctx.projectRoot, options.vault);\n // The never-content directories every filesystem scan skips, so a vault\n // rooted at the project (`vault: \".\"`) doesn't publish dependency READMEs\n // or build output — and so the scan and the watcher agree on what is\n // content.\n const exclude = new Set([...BLUME_IGNORE_DIRS, ...(options.exclude ?? [])]);\n\n const load = async (): Promise<SourceLoadResult> => {\n // A note is staged under its vault path, where Astro's content loader\n // can't read a name holding `#` or `?`; such a note is reported and left\n // out, and links to it degrade like links to any missing note.\n const skipped: Diagnostic[] = [];\n const files: string[] = [];\n for (const rel of await walkVault(vaultDir, vaultDir, exclude)) {\n const unloadable = unloadablePathDiagnostic(rel, join(vaultDir, rel));\n if (unloadable) {\n skipped.push(unloadable);\n } else {\n files.push(rel);\n }\n }\n const read = await Promise.all(\n files.map((rel) => readNote(vaultDir, rel, options))\n );\n const notes = read.filter((note): note is ParsedNote => note !== null);\n // Resolving `[[Note#H]]` needs the target's route and headings, so every\n // note is parsed and indexed before any body is rewritten.\n const { index, pairs } = buildLinkIndex(notes, options);\n const unresolved = noTargets();\n const entries = pairs.map((pair) =>\n noteToEntry(pair, index, options, unresolved)\n );\n return {\n diagnostics: [\n ...skipped,\n ...unresolvedDiagnostics(options.name, unresolved),\n ],\n entries,\n };\n };\n\n // The note as written, for the SPI's lazy read. The lowered body is what\n // `load` stages, and the pipeline reads that copy; this serves the vault\n // file itself, the way the filesystem source does, without pinning a second\n // copy of every note for the life of a dev server.\n const read = async (ref: string): Promise<string> => {\n const absPath = resolve(vaultDir, ref);\n // Lexical containment only — `..` and sibling paths are refused, but a\n // symlink inside the vault is followed wherever it points, the way\n // `walkVault` and the filesystem source follow theirs. Vault symlinks are\n // the author's own and are trusted; the Obsidian source docs say so.\n const rel = relative(vaultDir, absPath);\n if (rel.startsWith(\"..\") || isAbsolute(rel)) {\n throw new BlumeError({\n code: \"BLUME_SOURCE_MISCONFIGURED\",\n file: absPath,\n message: `Source \"${options.name}\" cannot read \"${ref}\": it resolves outside the vault.`,\n severity: \"error\",\n suggestion: \"Reference notes by their vault-relative path.\",\n });\n }\n return await readFile(absPath, \"utf-8\");\n };\n\n const validate = (): void => {\n // `existsSync` alone also accepts a regular file, which `validate` would\n // wave through and `load` would then fail on with a raw ENOTDIR.\n const stats = statSync(vaultDir, { throwIfNoEntry: false });\n if (stats?.isDirectory()) {\n return;\n }\n const problem = stats ? \"is not a directory\" : \"does not exist\";\n throw new BlumeError({\n code: \"BLUME_SOURCE_MISCONFIGURED\",\n file: vaultDir,\n message: `Source \"${options.name}\" points at \"${options.vault}\", which ${problem}.`,\n severity: \"error\",\n suggestion:\n \"Set `vault` to your Obsidian vault directory, relative to the project root.\",\n });\n };\n\n // Obsidian rewrites `.obsidian/workspace.json` as you move a pane and moves\n // a deleted note into `.trash/`; plugins keep caches in dot-folders of their\n // own. The walk publishes none of them, so a write there must not trigger a\n // rescan either. See {@link ignoringWatchListener}.\n const watch = (onChange: () => void): (() => void) => {\n if (!existsSync(vaultDir)) {\n return () => {\n // Nothing to dispose when the vault doesn't exist yet.\n };\n }\n const watcher = fsWatch(\n vaultDir,\n { recursive: true },\n ignoringWatchListener(onChange, exclude, isHidden)\n );\n return () => watcher.close();\n };\n\n return {\n // The vault is a real on-disk tree, so exposing it lets git last-modified\n // bound its log pathspec to the notes. Folder-meta discovery still skips\n // it — that scan is guarded on `staged`.\n contentRoot: vaultDir,\n load,\n name: options.name,\n // Notes are named like files: `01 Intro` sorts first and routes `/intro`.\n orderedNames: true,\n prefix: options.prefix,\n read,\n staged: true,\n validate,\n watch,\n };\n};\n",
|
|
26
|
+
"/**\n * Lexical (Payload's rich text editor) → Markdown. Covers the nodes Payload's\n * default features emit — paragraphs, headings, lists (bullet, numbered,\n * check), quotes, links, uploads, horizontal rules — and hands `block` /\n * `inlineBlock` nodes to a serializer keyed by their `blockType`, or notes\n * them in a comment.\n */\nimport type { JsonObject } from \"./json.ts\";\nimport { asNumber, asObject, asString, getPath, objectsIn } from \"./json.ts\";\nimport type { InlineMarks, InlinePart } from \"./lower.ts\";\nimport {\n absoluteUrl,\n blockquote,\n guardBlockStart,\n headingPrefix,\n image,\n indent,\n linkParts,\n listItem,\n markdownDocument,\n renderInline,\n unsupported,\n writesMdx,\n} from \"./lower.ts\";\n\nexport interface LexicalOptions {\n /** The CMS origin a relative upload URL (`/media/x.png`) resolves against. */\n baseUrl?: string;\n /** Serializers for `block`/`inlineBlock` nodes, keyed by `fields.blockType`. */\n serializers?: Record<string, (fields: JsonObject) => string>;\n}\n\n// Lexical's text `format` bitmask.\nconst IS_BOLD = 1;\nconst IS_ITALIC = 2;\nconst IS_STRIKETHROUGH = 4;\nconst IS_CODE = 16;\n\n/** Whether a power-of-two flag is set in the format bitmask. */\nconst hasFlag = (format: number, flag: number): boolean =>\n Math.floor(format / flag) % 2 === 1;\n\nconst marksOf = (node: JsonObject): InlineMarks => {\n const format = asNumber(node.format) ?? 0;\n return {\n bold: hasFlag(format, IS_BOLD),\n code: hasFlag(format, IS_CODE),\n italic: hasFlag(format, IS_ITALIC),\n strike: hasFlag(format, IS_STRIKETHROUGH),\n };\n};\n\nconst children = (node: JsonObject): JsonObject[] => objectsIn(node.children);\n\nconst typeOf = (node: JsonObject): string => asString(node.type) ?? \"\";\n\nconst customBlock = (node: JsonObject, options: LexicalOptions): string => {\n const fields = asObject(node.fields);\n const blockType = fields ? asString(fields.blockType) : undefined;\n const serializer = blockType ? options.serializers?.[blockType] : undefined;\n if (fields && serializer) {\n return serializer(fields);\n }\n return unsupported(\n `Lexical block${blockType ? `: ${blockType}` : \"\"}`,\n writesMdx(options.serializers)\n );\n};\n\nconst inlineParts = (\n nodes: JsonObject[],\n options: LexicalOptions\n): InlinePart[] =>\n // oxlint-disable-next-line no-use-before-define -- mutual recursion: a link's label is itself inline content\n nodes.flatMap((node) => inlineNodeParts(node, options));\n\nconst inlineNodeParts = (\n node: JsonObject,\n options: LexicalOptions\n): InlinePart[] => {\n switch (typeOf(node)) {\n case \"text\": {\n return [{ marks: marksOf(node), text: asString(node.text) ?? \"\" }];\n }\n case \"linebreak\": {\n return [{ markdown: \"\\n\" }];\n }\n case \"tab\": {\n return [{ markdown: \"\\t\" }];\n }\n case \"link\":\n case \"autolink\": {\n // `fields.url` on Payload 3; `url` on older Lexical payloads. An\n // internal link (`linkType: \"internal\"`) carries a document, not a\n // URL, so its label stays.\n return linkParts(\n inlineParts(children(node), options),\n asString(getPath(node, \"fields.url\")) ?? asString(node.url)\n );\n }\n case \"inlineBlock\": {\n return [{ markdown: customBlock(node, options) }];\n }\n default: {\n return inlineParts(children(node), options);\n }\n }\n};\n\n/** Inline nodes as Markdown, guarded as one block. */\nconst inlineText = (nodes: JsonObject[], options: LexicalOptions): string =>\n guardBlockStart(renderInline(inlineParts(nodes, options)));\n\n/** A text block's inline children, joined and guarded as one block. */\nconst blockText = (node: JsonObject, options: LexicalOptions): string =>\n inlineText(children(node), options);\n\nconst renderUpload = (node: JsonObject, options: LexicalOptions): string => {\n const value = asObject(node.value);\n const url = value ? asString(value.url) : undefined;\n if (!(value && url)) {\n return unsupported(\n \"Lexical upload (fetch with depth 1 to populate it)\",\n writesMdx(options.serializers)\n );\n }\n const alt = asString(value.alt) ?? asString(value.filename) ?? \"\";\n return image(alt, absoluteUrl(url, options.baseUrl));\n};\n\nconst renderList = (node: JsonObject, options: LexicalOptions): string => {\n const listType = asString(node.listType) ?? \"bullet\";\n const start = asNumber(node.start) ?? 1;\n const items: string[] = [];\n let lastMarker = \"-\";\n let index = 0;\n for (const item of children(node)) {\n const kids = children(item);\n const nested = kids.filter((kid) => typeOf(kid) === \"list\");\n const inline = kids.filter((kid) => typeOf(kid) !== \"list\");\n const sub = nested.map((list) => renderList(list, options)).join(\"\\n\");\n if (inline.length === 0 && nested.length > 0) {\n // Lexical nests a sub-list as its own item holding only the list; it\n // hangs under the item before it, or stands alone when there is none.\n items.push(items.length > 0 ? indent(sub, lastMarker.length + 1) : sub);\n continue;\n }\n lastMarker = listType === \"number\" ? `${start + index}.` : \"-\";\n let check = \"\";\n if (listType === \"check\") {\n check = item.checked === true ? \"[x] \" : \"[ ] \";\n }\n const line = `${check}${inlineText(inline, options)}`;\n items.push(listItem(lastMarker, sub ? `${line}\\n${sub}` : line));\n index += 1;\n }\n return items.join(\"\\n\");\n};\n\nconst renderBlock = (node: JsonObject, options: LexicalOptions): string => {\n switch (typeOf(node)) {\n case \"paragraph\": {\n return blockText(node, options);\n }\n case \"heading\": {\n const level = Number((asString(node.tag) ?? \"h1\").slice(1));\n return `${headingPrefix(level)}${blockText(node, options)}`;\n }\n case \"quote\": {\n return blockquote(blockText(node, options));\n }\n case \"list\": {\n return renderList(node, options);\n }\n case \"horizontalrule\": {\n return \"---\";\n }\n case \"upload\": {\n return renderUpload(node, options);\n }\n case \"block\": {\n return customBlock(node, options);\n }\n default: {\n return unsupported(\n `Lexical node: ${typeOf(node)}`,\n writesMdx(options.serializers)\n );\n }\n }\n};\n\n/**\n * Serialize a Lexical editor state (`{ root: { children } }`, or the root\n * node itself) to Markdown.\n */\nexport const lexicalToMarkdown = (\n state: JsonObject,\n options: LexicalOptions = {}\n): string => {\n const root = asObject(state.root) ?? state;\n return markdownDocument(\n children(root).map((node) => renderBlock(node, options))\n );\n};\n",
|
|
27
|
+
"import type { JsonObject } from \"./json.ts\";\nimport { asObject, asString, isJsonObject, objectsIn } from \"./json.ts\";\nimport { lexicalToMarkdown } from \"./lexical.ts\";\nimport { writesMdx } from \"./lower.ts\";\nimport type { RemoteFieldMap, RestClient } from \"./remote.ts\";\nimport {\n documentEntry,\n fetchJson,\n queryString,\n remoteSource,\n} from \"./remote.ts\";\nimport type { ContentSource, SourceContext, SourceEntry } from \"./types.ts\";\n\nexport interface PayloadSourceOptions {\n name: string;\n prefix?: string;\n /** The Payload app's origin (`https://cms.acme.dev`); its REST API is at `/api`. */\n url: string;\n /** The collection slug whose documents become pages. */\n collection: string;\n /**\n * The auth-enabled collection the API key belongs to; default `users`. It\n * names the scheme in the `Authorization` header (`users API-Key <key>`).\n */\n authCollection?: string;\n /** Relationship depth to populate; default `1`, enough for upload URLs. */\n depth?: number;\n /**\n * Field paths mapping a document onto Blume meta + body. Defaults:\n * `title`, `description`, `slug`, `content`, `updatedAt`.\n */\n fields?: RemoteFieldMap;\n /** Extra query parameters for the collection request (`where[...]`, `sort`). */\n params?: Record<string, string>;\n /** Serializers for `block`/`inlineBlock` nodes, keyed by `blockType`. */\n serializers?: Record<string, (fields: JsonObject) => string>;\n /** API key; defaults to `PAYLOAD_API_KEY`. */\n token?: string;\n /** Opt-in dev polling interval (seconds); omit to freeze for the session. */\n pollInterval?: number;\n /** Injected for tests; defaults to the global `fetch`. */\n fetchImpl?: typeof fetch;\n}\n\nconst PAGE_SIZE = 100;\n\nconst DEFAULT_FIELDS: Required<RemoteFieldMap> = {\n body: \"content\",\n description: \"description\",\n lastModified: \"updatedAt\",\n slug: \"slug\",\n title: \"title\",\n};\n\n/**\n * Payload content source. Pages through a collection's REST endpoint, maps\n * each document's fields to frontmatter, and lowers its Lexical body to\n * Markdown. Published documents only, unless `--preview` asks for drafts.\n */\nexport const payloadSource = (\n options: PayloadSourceOptions,\n ctx?: SourceContext\n): ContentSource => {\n const fields = { ...DEFAULT_FIELDS, ...options.fields };\n const origin = options.url.replace(/\\/+$/u, \"\");\n\n const lower = (body: JsonObject): string =>\n lexicalToMarkdown(body, {\n baseUrl: origin,\n serializers: options.serializers,\n });\n\n const fetchEntries = async (): Promise<SourceEntry[]> => {\n const preview = ctx?.preview ?? false;\n const token = options.token ?? process.env.PAYLOAD_API_KEY;\n const client: RestClient = {\n fetchImpl: options.fetchImpl,\n headers: token\n ? {\n authorization: `${options.authCollection ?? \"users\"} API-Key ${token}`,\n }\n : {},\n };\n const base = `${origin}/api/${options.collection}`;\n const entries: SourceEntry[] = [];\n let page = 1;\n let more = true;\n while (more) {\n // The user's params go first so the paging controls always win — a\n // `params` key that shadowed them would refetch the same page forever.\n const query = queryString({\n ...options.params,\n depth: String(options.depth ?? 1),\n draft: preview ? \"true\" : undefined,\n limit: String(PAGE_SIZE),\n page: String(page),\n });\n // oxlint-disable-next-line no-await-in-loop -- pages are sequential: each response says whether another exists.\n const result = asObject(await fetchJson(`${base}?${query}`, client));\n if (!result) {\n throw new Error(\"Payload returned a non-object response\");\n }\n for (const doc of objectsIn(result.docs)) {\n const draft = asString(doc._status) === \"draft\";\n if (draft && !preview) {\n continue;\n }\n const id = asString(doc.id) ?? String(doc.id ?? \"\");\n entries.push(\n documentEntry(\n doc,\n fields,\n id,\n (body) => (isJsonObject(body) ? lower(body) : \"\"),\n draft,\n writesMdx(options.serializers)\n )\n );\n }\n more = result.hasNextPage === true;\n page += 1;\n }\n return entries;\n };\n\n return remoteSource(\n {\n fetchEntries,\n name: options.name,\n pollInterval: options.pollInterval,\n prefix: options.prefix,\n withContext: (next) => payloadSource(options, next),\n },\n ctx\n );\n};\n",
|
|
28
|
+
"import { join } from \"pathe\";\n\nimport { BlumeError } from \"../diagnostics.ts\";\nimport matter from \"../frontmatter.ts\";\nimport { nodeRequire } from \"../node-require.ts\";\nimport {\n hashText,\n loadWithCache,\n pollingWatch,\n snapshotCache,\n} from \"./cache.ts\";\nimport type { JsonObject } from \"./json.ts\";\nimport { asString, getPath } from \"./json.ts\";\nimport { writesMdx } from \"./lower.ts\";\nimport { slugify, slugifyPath } from \"./normalize.ts\";\nimport { portableTextToMarkdown } from \"./portable-text.ts\";\nimport type { PortableTextBlock } from \"./portable-text.ts\";\nimport type {\n ContentSource,\n SourceContext,\n SourceEntry,\n SourceLoadResult,\n} from \"./types.ts\";\n\n/** The slice of `@sanity/client` this adapter relies on (so it's mockable). */\nexport interface SanityClientLike {\n fetch: <T = unknown>(query: string) => Promise<T>;\n}\n\n/** Field paths mapping a Sanity document onto Blume meta + body. */\nexport interface SanityFieldMap {\n /** Frontmatter title; default `title`. */\n title?: string;\n /** Frontmatter description; default `description`. */\n description?: string;\n /** Route slug (dot path); default `slug.current`. */\n slug?: string;\n /** Portable Text body field; default `body`. */\n body?: string;\n /** Last-modified ISO date; default `_updatedAt`. */\n lastModified?: string;\n}\n\nexport interface SanitySourceOptions {\n name: string;\n prefix?: string;\n projectId: string;\n dataset: string;\n /** Sanity API version (a date); default `2024-01-01`. */\n apiVersion?: string;\n /** GROQ query selecting the documents to import. */\n query: string;\n fields?: SanityFieldMap;\n /** Custom Portable Text block serializers, keyed by `_type`. */\n serializers?: Record<string, (block: PortableTextBlock) => string>;\n /** Read token for private datasets; defaults to `SANITY_TOKEN`. */\n token?: string;\n /** Opt-in dev polling interval (seconds); omit to freeze for the session. */\n pollInterval?: number;\n /** Injected for tests; otherwise built from `@sanity/client`. */\n client?: SanityClientLike;\n}\n\nconst IMAGE_REF = /^image-(?<id>[a-f0-9]+)-(?<dims>\\d+x\\d+)-(?<ext>\\w+)$/u;\n\n/** A document as the GROQ query returns it. */\ntype SanityDocument = JsonObject;\n\n/** The frontmatter fields this adapter maps from a document. */\ninterface SanityFrontmatter {\n description?: string;\n title?: string;\n}\n\n/** Build a Sanity CDN URL from an image asset `_ref`. */\nconst imageUrlFromRef = (\n ref: string,\n projectId: string,\n dataset: string\n): string | null => {\n const match = ref.match(IMAGE_REF);\n if (!match?.groups) {\n return null;\n }\n const { id, dims, ext } = match.groups;\n return `https://cdn.sanity.io/images/${projectId}/${dataset}/${id}-${dims}.${ext}`;\n};\n\n/** The `createClient` config slice this adapter passes. */\ninterface SanityClientConfig {\n apiVersion: string;\n dataset: string;\n perspective: \"previewDrafts\" | \"published\";\n projectId: string;\n token: string | undefined;\n useCdn: boolean;\n}\n\nconst resolveClient = (\n options: SanitySourceOptions,\n preview: boolean\n): SanityClientLike => {\n if (options.client) {\n return options.client;\n }\n let createClient: (config: SanityClientConfig) => SanityClientLike;\n try {\n // `require`, not `import()`: an ejected app scans in `astro:build:done`\n // (see `core/node-require.ts`).\n // SAFETY: `@sanity/client` is an optional dependency; its `createClient`\n // accepts a superset of this config slice and returns a client exposing\n // the `fetch` method this adapter uses.\n ({ createClient } = nodeRequire(\"@sanity/client\") as {\n createClient: (config: SanityClientConfig) => SanityClientLike;\n });\n } catch {\n throw new BlumeError({\n code: \"BLUME_SOURCE_SDK_MISSING\",\n message: `Source \"${options.name}\" needs \"@sanity/client\". Install it (e.g. \\`npm install @sanity/client\\`).`,\n severity: \"error\",\n });\n }\n return createClient({\n apiVersion: options.apiVersion ?? \"2024-01-01\",\n dataset: options.dataset,\n // Preview reads draft documents through the API; published builds use the CDN.\n perspective: preview ? \"previewDrafts\" : \"published\",\n projectId: options.projectId,\n token: options.token ?? process.env.SANITY_TOKEN,\n useCdn: !preview,\n });\n};\n\n/**\n * Sanity content source. Runs a GROQ query, maps each document's fields to Blume\n * frontmatter and its Portable Text body to Markdown, and stages the result.\n */\nexport const sanitySource = (\n options: SanitySourceOptions,\n ctx?: SourceContext\n): ContentSource => {\n const fields = options.fields ?? {};\n // When constructed directly (custom-source SPI) without a context, cache under\n // a name-derived dir relative to the project; the built-in type passes a ctx.\n const cache = snapshotCache(\n ctx?.cacheDir ?? join(\".blume\", \"cache\", options.name)\n );\n let snapshot = new Map<string, SourceEntry>();\n\n const toEntry = (doc: SanityDocument): SourceEntry => {\n const slugValue =\n asString(getPath(doc, fields.slug ?? \"slug.current\")) ??\n asString(doc._id) ??\n \"untitled\";\n // Fall back to the unique `_id` when a slug (e.g. a non-ASCII `slug.current`)\n // slugifies to empty, so distinct documents don't all collapse to the same\n // `untitled.md` ref and silently overwrite each other. Path-aware: a\n // `guides/setup` slug keeps its `/` (per-segment slugging) instead of\n // mashing into `guidessetup`.\n const slug =\n slugifyPath(slugValue) || slugify(asString(doc._id) ?? \"\") || \"untitled\";\n\n const data: SanityFrontmatter = {};\n const title = asString(getPath(doc, fields.title ?? \"title\"));\n const description = asString(\n getPath(doc, fields.description ?? \"description\")\n );\n if (title) {\n data.title = title;\n }\n if (description) {\n data.description = description;\n }\n\n // SAFETY: the configured body field holds Portable Text blocks; a value of\n // any other shape fails the `Array.isArray` check below and yields an\n // empty body, and the serializer tolerates malformed blocks.\n const blocks = (getPath(doc, fields.body ?? \"body\") ??\n []) as PortableTextBlock[];\n const markdown = Array.isArray(blocks)\n ? portableTextToMarkdown(blocks, {\n imageUrl: (block) => {\n // SAFETY: `asset` on an image block is Sanity's asset reference\n // object; any other shape yields no `_ref` and the image is\n // skipped.\n const ref = (block.asset as { _ref?: string } | undefined)?._ref;\n return ref\n ? imageUrlFromRef(ref, options.projectId, options.dataset)\n : null;\n },\n serializers: options.serializers,\n })\n : \"\";\n\n // Serializer output is MDX (components, directives), so a source with\n // serializers writes its entries as `.mdx`.\n const format = writesMdx(options.serializers) ? \"mdx\" : \"md\";\n const raw = matter.stringify(markdown, data);\n return {\n body: { format, text: markdown },\n // Spread into a fresh literal: `SourceEntry.data` wants an\n // index-signature type, which the named interface lacks.\n data: { ...data },\n hash: hashText(raw),\n lastModified: asString(getPath(doc, fields.lastModified ?? \"_updatedAt\")),\n raw,\n ref: `${slug}.${format}`,\n };\n };\n\n const load = async (\n refresh = ctx?.refresh ?? true\n ): Promise<SourceLoadResult> => {\n const result = await loadWithCache(\n options.name,\n cache,\n async () => {\n const client = resolveClient(options, ctx?.preview ?? false);\n const docs = await client.fetch<SanityDocument[]>(options.query);\n return docs.map(toEntry);\n },\n refresh\n );\n snapshot = new Map(result.entries.map((entry) => [entry.ref, entry]));\n return result;\n };\n\n const read = async (ref: string): Promise<string> => {\n const cached = snapshot.get(ref);\n if (cached) {\n return cached.raw ?? cached.body.text;\n }\n const all = await cache.read();\n return all.find((e) => e.ref === ref)?.raw ?? \"\";\n };\n\n return {\n load,\n name: options.name,\n prefix: options.prefix,\n read,\n staged: true,\n watch: options.pollInterval\n ? pollingWatch(\n () => load(true),\n options.pollInterval,\n () => load()\n )\n : undefined,\n withContext: (next) => sanitySource(options, next),\n };\n};\n",
|
|
29
|
+
"/**\n * A minimal Portable Text → Markdown serializer. Covers the standard block,\n * list, and mark types Sanity emits; custom block/mark types fall through to a\n * user-supplied serializer or are skipped with a noted comment. Output is\n * Markdown text that flows through Blume's normal pipeline.\n */\nimport type { InlineRun } from \"./lower.ts\";\nimport {\n guardBlockStart,\n image,\n linkParts,\n renderInline,\n unsupported,\n writesMdx,\n} from \"./lower.ts\";\n\n/**\n * A field value on a Portable Text node: the arbitrary JSON the CMS query\n * returned (custom blocks carry whatever fields the studio schema defines).\n * Spans and mark defs appear as members so the block's typed fields conform\n * to its index signature.\n */\nexport type PortableTextValue =\n | string\n | number\n | boolean\n | null\n | undefined\n | PortableTextSpan\n | PortableTextMarkDef\n | PortableTextValue[]\n | { [key: string]: PortableTextValue };\n\n/** A single Portable Text node (block, image, or a custom type). */\nexport interface PortableTextBlock {\n _type: string;\n _key?: string;\n style?: string;\n listItem?: string;\n level?: number;\n children?: PortableTextSpan[];\n markDefs?: PortableTextMarkDef[];\n [key: string]: PortableTextValue;\n}\n\ninterface PortableTextSpan {\n _type: string;\n text?: string;\n marks?: string[];\n}\n\ninterface PortableTextMarkDef {\n _key: string;\n _type: string;\n href?: string;\n}\n\nexport interface PortableTextOptions {\n /** Resolve an image block to a URL (the adapter knows project/dataset). */\n imageUrl?: (block: PortableTextBlock) => string | null;\n /**\n * Custom block-type serializers, keyed by `_type`; return Markdown or MDX.\n * Setting any writes the source's entries as MDX.\n */\n serializers?: Record<string, (block: PortableTextBlock) => string>;\n}\n\nconst HEADING_STYLES = new Map([\n [\"h1\", \"# \"],\n [\"h2\", \"## \"],\n [\"h3\", \"### \"],\n [\"h4\", \"#### \"],\n [\"h5\", \"##### \"],\n [\"h6\", \"###### \"],\n]);\n\n/** A span's text and the marks Markdown has syntax for. */\nconst spanRun = (span: PortableTextSpan): InlineRun => {\n const marks = span.marks ?? [];\n return {\n marks: {\n bold: marks.includes(\"strong\"),\n code: marks.includes(\"code\"),\n italic: marks.includes(\"em\"),\n strike: marks.includes(\"strike-through\"),\n },\n text: span.text ?? \"\",\n };\n};\n\n/** Neighboring spans under one link def (or none), and that def. */\ninterface LinkedSpans {\n link?: PortableTextMarkDef;\n runs: InlineRun[];\n}\n\n/**\n * Render the inline children of a block to a single Markdown string, through\n * the shared lowering every CMS uses: prose is escaped, a code span outruns\n * the backticks inside it, edge whitespace stays outside emphasis, neighbors\n * share their marks' delimiters, and neighbors under one link def share one\n * link — wrapped last (so its label keeps emphasis), and only when its\n * destination is safe to click.\n */\nconst renderChildren = (block: PortableTextBlock): string => {\n const defs = new Map(\n (block.markDefs ?? []).map((def) => [def._key, def] as const)\n );\n const linked: LinkedSpans[] = [];\n for (const span of block.children ?? []) {\n const link = (span.marks ?? [])\n .map((mark) => defs.get(mark))\n .findLast((def) => def?._type === \"link\");\n const last = linked.at(-1);\n if (last && last.link === link) {\n last.runs.push(spanRun(span));\n } else {\n linked.push({ link, runs: [spanRun(span)] });\n }\n }\n return guardBlockStart(\n renderInline(\n linked.flatMap(({ link, runs }) => linkParts(runs, link?.href))\n )\n );\n};\n\n/** Whether an image block's `alt` field is usable alt text (CMS JSON may hold anything). */\nconst isAltText = (value: PortableTextValue): value is string =>\n typeof value === \"string\";\n\n/** The marker a list block opens with. */\nconst listMarker = (block: PortableTextBlock): string =>\n block.listItem === \"number\" ? \"1.\" : \"-\";\n\n/**\n * How far each block is indented. Portable Text lists are flat blocks with a\n * `level`, while Markdown nests an item by indenting it to its parent item's\n * content column: two past a `-` item's indent, three past a `1.` item's. The\n * column comes from the item actually above, so a skipped level nests under\n * the deepest open item instead of indenting into a code block.\n */\nconst listIndents = (blocks: PortableTextBlock[]): number[] => {\n const columns: number[] = [];\n return blocks.map((block) => {\n if (!block.listItem) {\n columns.length = 0;\n return 0;\n }\n const depth = Math.min(Math.max(0, (block.level ?? 1) - 1), columns.length);\n const indent = columns[depth - 1] ?? 0;\n columns.length = depth;\n columns.push(indent + listMarker(block).length + 1);\n return indent;\n });\n};\n\nconst renderBlock = (\n block: PortableTextBlock,\n options: PortableTextOptions,\n indent: number\n): string => {\n const custom = options.serializers?.[block._type];\n if (custom) {\n return custom(block);\n }\n if (block._type === \"image\") {\n const url = options.imageUrl?.(block);\n const alt = isAltText(block.alt) ? block.alt : \"\";\n return url ? image(alt, url) : \"\";\n }\n if (block._type !== \"block\") {\n return unsupported(\n `Portable Text block: ${block._type}`,\n writesMdx(options.serializers)\n );\n }\n\n const inline = renderChildren(block);\n if (block.listItem) {\n return `${\" \".repeat(indent)}${listMarker(block)} ${inline}`;\n }\n if (block.style === \"blockquote\") {\n return `> ${inline}`;\n }\n return `${HEADING_STYLES.get(block.style ?? \"normal\") ?? \"\"}${inline}`;\n};\n\n/** Serialize a Portable Text array into a Markdown string. */\nexport const portableTextToMarkdown = (\n blocks: PortableTextBlock[],\n options: PortableTextOptions = {}\n): string => {\n const indents = listIndents(blocks);\n const lines = blocks.map((block, i) =>\n renderBlock(block, options, indents[i] ?? 0)\n );\n // List items are single-newline separated; everything else gets a blank line.\n const out: string[] = [];\n for (let i = 0; i < lines.length; i += 1) {\n const line = lines[i] ?? \"\";\n const prevList = Boolean(blocks[i - 1]?.listItem);\n const thisList = Boolean(blocks[i]?.listItem);\n if (i > 0) {\n out.push(prevList && thisList ? \"\\n\" : \"\\n\\n\");\n }\n out.push(line);\n }\n return `${out.join(\"\")}\\n`;\n};\n",
|
|
30
|
+
"/**\n * Strapi's Rich text (Blocks) field → Markdown. Covers every block the\n * editor emits: paragraphs, headings, lists (nested), quotes, code, images,\n * and links with the text modifiers.\n */\nimport type { JsonObject, JsonValue } from \"./json.ts\";\nimport { asNumber, asObject, asString, objectsIn } from \"./json.ts\";\nimport type { InlinePart } from \"./lower.ts\";\nimport {\n absoluteUrl,\n blockquote,\n codeFence,\n guardBlockStart,\n headingPrefix,\n image,\n linkParts,\n listItem,\n markdownDocument,\n renderInline,\n unsupported,\n} from \"./lower.ts\";\n\nexport interface StrapiBlocksOptions {\n /** The Strapi origin a relative upload URL (`/uploads/x.png`) resolves against. */\n baseUrl?: string;\n}\n\nconst children = (node: JsonObject): JsonObject[] => objectsIn(node.children);\n\nconst typeOf = (node: JsonObject): string => asString(node.type) ?? \"\";\n\nconst inlineParts = (nodes: JsonObject[]): InlinePart[] =>\n // oxlint-disable-next-line no-use-before-define -- mutual recursion: a link's label is itself inline content\n nodes.flatMap((node) => inlineNodeParts(node));\n\nconst inlineNodeParts = (node: JsonObject): InlinePart[] => {\n switch (typeOf(node)) {\n case \"text\": {\n return [\n {\n marks: {\n bold: node.bold === true,\n code: node.code === true,\n italic: node.italic === true,\n strike: node.strikethrough === true,\n },\n text: asString(node.text) ?? \"\",\n },\n ];\n }\n case \"link\": {\n return linkParts(inlineParts(children(node)), asString(node.url));\n }\n default: {\n return inlineParts(children(node));\n }\n }\n};\n\n/** A text block's inline nodes, joined and guarded as one block. */\nconst blockText = (nodes: JsonObject[]): string =>\n guardBlockStart(renderInline(inlineParts(nodes)));\n\nconst renderList = (node: JsonObject): string => {\n const ordered = asString(node.format) === \"ordered\";\n return children(node)\n .map((item, index) => {\n const kids = children(item);\n const line = blockText(kids.filter((kid) => typeOf(kid) !== \"list\"));\n const sub = kids\n .filter((kid) => typeOf(kid) === \"list\")\n .map((list) => renderList(list))\n .join(\"\\n\");\n const body = sub ? `${line}\\n${sub}` : line;\n return listItem(ordered ? `${index + 1}.` : \"-\", body);\n })\n .join(\"\\n\");\n};\n\nconst renderImage = (\n node: JsonObject,\n options: StrapiBlocksOptions\n): string => {\n const file = asObject(node.image);\n const url = file ? asString(file.url) : undefined;\n if (!(file && url)) {\n return \"\";\n }\n return image(\n asString(file.alternativeText) ?? asString(file.name) ?? \"\",\n absoluteUrl(url, options.baseUrl)\n );\n};\n\nconst renderBlock = (\n node: JsonObject,\n options: StrapiBlocksOptions\n): string => {\n switch (typeOf(node)) {\n case \"paragraph\": {\n return blockText(children(node));\n }\n case \"heading\": {\n return `${headingPrefix(asNumber(node.level) ?? 1)}${blockText(children(node))}`;\n }\n case \"quote\": {\n return blockquote(blockText(children(node)));\n }\n case \"list\": {\n return renderList(node);\n }\n case \"code\": {\n // Code children are plain text nodes; their text is literal.\n const code = children(node)\n .map((child) => asString(child.text) ?? \"\")\n .join(\"\");\n return codeFence(code, asString(node.language) ?? \"\");\n }\n case \"image\": {\n return renderImage(node, options);\n }\n default: {\n return unsupported(`Strapi block: ${typeOf(node)}`);\n }\n }\n};\n\n/** Serialize a Blocks field value (an array of block nodes) to Markdown. */\nexport const strapiBlocksToMarkdown = (\n blocks: JsonValue,\n options: StrapiBlocksOptions = {}\n): string =>\n markdownDocument(objectsIn(blocks).map((node) => renderBlock(node, options)));\n",
|
|
31
|
+
"import type { JsonObject } from \"./json.ts\";\nimport { asNumber, asObject, asString, getPath, objectsIn } from \"./json.ts\";\nimport type { RemoteFieldMap, RestClient } from \"./remote.ts\";\nimport {\n documentEntry,\n fetchJson,\n queryString,\n remoteSource,\n} from \"./remote.ts\";\nimport { strapiBlocksToMarkdown } from \"./strapi-blocks.ts\";\nimport type { ContentSource, SourceContext, SourceEntry } from \"./types.ts\";\n\nexport interface StrapiSourceOptions {\n name: string;\n prefix?: string;\n /** The Strapi origin (`https://cms.acme.dev`); its REST API is at `/api`. */\n url: string;\n /** The content type's plural API id (`articles`). */\n contentType: string;\n /** Locale code to fetch, for a localized content type. */\n locale?: string;\n /** The `populate` parameter; default `*`, which fills media and relations. */\n populate?: string;\n /**\n * Field paths mapping an entry onto Blume meta + body. Defaults:\n * `title`, `description`, `slug`, `content`, `updatedAt`.\n */\n fields?: RemoteFieldMap;\n /** Extra query parameters for the request (`filters[...]`, `sort`). */\n params?: Record<string, string>;\n /** API token; defaults to `STRAPI_API_TOKEN`. */\n token?: string;\n /** Opt-in dev polling interval (seconds); omit to freeze for the session. */\n pollInterval?: number;\n /** Injected for tests; defaults to the global `fetch`. */\n fetchImpl?: typeof fetch;\n}\n\nconst PAGE_SIZE = 100;\n\nconst DEFAULT_FIELDS: Required<RemoteFieldMap> = {\n body: \"content\",\n description: \"description\",\n lastModified: \"updatedAt\",\n slug: \"slug\",\n title: \"title\",\n};\n\n/**\n * The entry as a flat document. Strapi 5 returns fields at the top level\n * beside `documentId`; Strapi 4 wraps them in `attributes` under a numeric\n * `id`, and that envelope is flattened so the same field paths apply.\n */\nconst flatten = (item: JsonObject): JsonObject => {\n const attributes = asObject(item.attributes);\n return attributes ? { ...attributes, id: item.id ?? null } : item;\n};\n\nconst idOf = (doc: JsonObject): string => {\n const raw = doc.documentId ?? doc.id;\n return asString(raw) ?? String(asNumber(raw) ?? \"\");\n};\n\n/** Every entry one query returns, across its pages, flattened. */\ninterface StrapiListing {\n docs: JsonObject[];\n /** Whether any entry came in Strapi 4's `attributes` envelope. */\n v4: boolean;\n}\n\n/** Strapi 4 marks an unpublished entry with a null `publishedAt`. */\nconst unpublished = (doc: JsonObject): boolean => doc.publishedAt === null;\n\n/**\n * Strapi content source. Pages through a content type's REST endpoint, maps\n * each entry's fields to frontmatter, and lowers its Blocks body to\n * Markdown. Published entries only, unless `--preview` asks for drafts.\n */\nexport const strapiSource = (\n options: StrapiSourceOptions,\n ctx?: SourceContext\n): ContentSource => {\n const fields = { ...DEFAULT_FIELDS, ...options.fields };\n const origin = options.url.replace(/\\/+$/u, \"\");\n\n const fetchEntries = async (): Promise<SourceEntry[]> => {\n const token = options.token ?? process.env.STRAPI_API_TOKEN;\n const client: RestClient = {\n fetchImpl: options.fetchImpl,\n headers: token ? { authorization: `Bearer ${token}` } : {},\n };\n const base = `${origin}/api/${options.contentType}`;\n\n const list = async (\n publication: Record<string, string>\n ): Promise<StrapiListing> => {\n const listing: StrapiListing = { docs: [], v4: false };\n let page = 1;\n let more = true;\n while (more) {\n // The user's params go first so the paging controls always win — a\n // `params` key that shadowed them would refetch the same page forever.\n const query = queryString({\n ...options.params,\n locale: options.locale,\n \"pagination[pageSize]\": String(PAGE_SIZE),\n \"pagination[page]\": String(page),\n populate: options.populate ?? \"*\",\n ...publication,\n });\n // oxlint-disable-next-line no-await-in-loop -- pages are sequential: each response says whether another exists.\n const result = asObject(await fetchJson(`${base}?${query}`, client));\n if (!result) {\n throw new Error(\"Strapi returned a non-object response\");\n }\n const items = objectsIn(result.data);\n for (const item of items) {\n listing.v4 ||= asObject(item.attributes) !== undefined;\n listing.docs.push(flatten(item));\n }\n const pageCount =\n asNumber(getPath(result, \"meta.pagination.pageCount\")) ?? 1;\n more = items.length > 0 && page < pageCount;\n page += 1;\n }\n return listing;\n };\n\n const toEntries = (\n docs: JsonObject[],\n isDraft: (doc: JsonObject) => boolean\n ): SourceEntry[] =>\n docs.map((doc) =>\n documentEntry(\n doc,\n fields,\n idOf(doc),\n (body) => strapiBlocksToMarkdown(body, { baseUrl: origin }),\n isDraft(doc)\n )\n );\n\n // The default publication state is the published one in both Strapi 4\n // and 5, and its response shape says which version is answering.\n const published = await list({});\n if (!ctx?.preview) {\n return toEntries(published.docs, () => false);\n }\n // Strapi 4 previews with `publicationState=preview`, which returns drafts\n // and published entries together, a draft's `publishedAt` being null.\n if (published.v4) {\n const previewed = await list({ publicationState: \"preview\" });\n return toEntries(previewed.docs, unpublished);\n }\n // Strapi 5 answers `status=draft` with the draft version of every\n // document, `publishedAt` null even when a published version exists, so a\n // document is a draft only when it has no published version.\n const drafts = await list({ status: \"draft\" });\n if (drafts.docs.length === 0 && published.docs.length === 0) {\n // Strapi 4 ignores `status`, so a Strapi 4 content type holding only\n // drafts reads as empty on both requests. Ask with its own parameter;\n // Strapi 5 either ignores it or rejects it (`strictParams`), and in\n // both cases has nothing more to return.\n const previewed = await list({ publicationState: \"preview\" }).catch(\n () => null\n );\n return previewed ? toEntries(previewed.docs, unpublished) : [];\n }\n const publishedIds = new Set(published.docs.map(idOf));\n const draftIds = new Set(drafts.docs.map(idOf));\n // A published document with no draft version (a content type without\n // Draft & Publish) still belongs in the preview.\n return [\n ...toEntries(drafts.docs, (doc) => !publishedIds.has(idOf(doc))),\n ...toEntries(\n published.docs.filter((doc) => !draftIds.has(idOf(doc))),\n () => false\n ),\n ];\n };\n\n return remoteSource(\n {\n fetchEntries,\n name: options.name,\n pollInterval: options.pollInterval,\n prefix: options.prefix,\n withContext: (next) => strapiSource(options, next),\n },\n ctx\n );\n};\n"
|
|
32
|
+
],
|
|
33
|
+
"mappings": "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",
|
|
34
|
+
"debugId": "B7A86608D56AB00864756E2164756E21",
|
|
35
|
+
"names": []
|
|
36
|
+
}
|