blume 1.7.3 → 2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +19 -0
- package/CHANGELOG.md +245 -0
- package/README.md +34 -21
- package/dist/cli/{chunk-vacwm2hv.js → chunk-27g6wdth.js} +2 -2
- package/dist/cli/{chunk-ps4m1xh4.js → chunk-2hn4b8z7.js} +513 -550
- package/dist/cli/chunk-2hn4b8z7.js.map +12 -0
- package/dist/cli/chunk-5shv93fd.js +39 -0
- package/dist/cli/chunk-5shv93fd.js.map +10 -0
- package/dist/cli/chunk-6crbhc3x.js +361 -0
- package/dist/cli/chunk-6crbhc3x.js.map +14 -0
- package/dist/cli/{chunk-8cjtbafj.js → chunk-6hsn950k.js} +109 -383
- package/dist/cli/chunk-6hsn950k.js.map +10 -0
- package/dist/cli/chunk-6vm74dry.js +148 -0
- package/dist/cli/chunk-6vm74dry.js.map +10 -0
- package/dist/cli/{chunk-yg63d42r.js → chunk-79jhk4py.js} +87 -58
- package/dist/cli/chunk-79jhk4py.js.map +35 -0
- package/dist/cli/chunk-82bbrxdn.js +51 -0
- package/dist/cli/chunk-82bbrxdn.js.map +10 -0
- package/dist/cli/chunk-abh8yjkn.js +31 -0
- package/dist/cli/chunk-abh8yjkn.js.map +10 -0
- package/dist/cli/chunk-ah61y8py.js +75 -0
- package/dist/cli/chunk-ah61y8py.js.map +11 -0
- package/dist/cli/{chunk-dwgcp5sm.js → chunk-ce574jw2.js} +1 -1
- package/dist/cli/chunk-ch6g3ar0.js +102 -0
- package/dist/cli/chunk-ch6g3ar0.js.map +10 -0
- package/dist/cli/chunk-dh8cwk36.js +279 -0
- package/dist/cli/chunk-dh8cwk36.js.map +10 -0
- package/dist/cli/{chunk-rqy0s5wh.js → chunk-epjnccmv.js} +17 -16
- package/dist/cli/{chunk-rqy0s5wh.js.map → chunk-epjnccmv.js.map} +3 -3
- package/dist/cli/chunk-f2z5v128.js +97 -0
- package/dist/cli/chunk-f2z5v128.js.map +10 -0
- package/dist/cli/{chunk-2z47ypj8.js → chunk-fa25z98p.js} +16 -3
- package/dist/cli/chunk-fa25z98p.js.map +11 -0
- package/dist/cli/{chunk-1jefwnfs.js → chunk-fs23ddbb.js} +1076 -2592
- package/dist/cli/chunk-fs23ddbb.js.map +35 -0
- package/dist/cli/{chunk-e7f42gdj.js → chunk-fxypxtvm.js} +2 -2
- package/dist/cli/{chunk-bvwwhd84.js → chunk-fz5wtpmh.js} +15 -15
- package/dist/cli/{chunk-bvwwhd84.js.map → chunk-fz5wtpmh.js.map} +1 -1
- package/dist/cli/chunk-hdpx1tax.js +91 -0
- package/dist/cli/chunk-hdpx1tax.js.map +10 -0
- package/dist/cli/{chunk-ct47dqpx.js → chunk-jts8mvcz.js} +67 -7
- package/dist/cli/{chunk-2mzebbbz.js.map → chunk-jts8mvcz.js.map} +6 -4
- package/dist/cli/{chunk-ahnw3kxw.js → chunk-jwyddg7y.js} +27 -22
- package/dist/cli/chunk-jwyddg7y.js.map +15 -0
- package/dist/cli/{chunk-cjtn640a.js → chunk-kdp5q7ke.js} +43 -20
- package/dist/cli/chunk-kdp5q7ke.js.map +10 -0
- package/dist/cli/{chunk-5g0w1e2c.js → chunk-kpf8rrjc.js} +28 -16
- package/dist/cli/{chunk-5g0w1e2c.js.map → chunk-kpf8rrjc.js.map} +4 -4
- package/dist/cli/chunk-m3vmjgmq.js +133 -0
- package/dist/cli/chunk-m3vmjgmq.js.map +10 -0
- package/dist/cli/{chunk-b27xqwn9.js → chunk-mb2919y2.js} +9 -5
- package/dist/cli/chunk-mb2919y2.js.map +10 -0
- package/dist/cli/{chunk-5qk08vmp.js → chunk-q5163e60.js} +133 -54
- package/dist/cli/chunk-q5163e60.js.map +11 -0
- package/dist/cli/chunk-qkqwkpte.js +12437 -0
- package/dist/cli/chunk-qkqwkpte.js.map +182 -0
- package/dist/cli/{chunk-4x36ddpw.js → chunk-qs4q5p4e.js} +81 -87
- package/dist/cli/chunk-qs4q5p4e.js.map +10 -0
- package/dist/cli/{chunk-0xjyb285.js → chunk-qwsrynx5.js} +15 -5
- package/dist/cli/{chunk-0xjyb285.js.map → chunk-qwsrynx5.js.map} +4 -4
- package/dist/cli/chunk-s1p84fyh.js +261 -0
- package/dist/cli/chunk-s1p84fyh.js.map +10 -0
- package/dist/cli/chunk-s6jhgk0q.js +176 -0
- package/dist/cli/chunk-s6jhgk0q.js.map +11 -0
- package/dist/cli/chunk-vtk4a6dg.js +374 -0
- package/dist/cli/chunk-vtk4a6dg.js.map +10 -0
- package/dist/cli/{chunk-k79xp7av.js → chunk-wgm7m9qk.js} +230 -700
- package/dist/cli/chunk-wgm7m9qk.js.map +36 -0
- package/dist/cli/chunk-wm7js3j9.js +145 -0
- package/dist/cli/chunk-wm7js3j9.js.map +11 -0
- package/dist/cli/chunk-yt5n7ppj.js +79 -0
- package/dist/cli/chunk-yt5n7ppj.js.map +10 -0
- package/dist/cli/{chunk-3r45185y.js → chunk-yw7dm696.js} +9 -11
- package/dist/cli/{chunk-3r45185y.js.map → chunk-yw7dm696.js.map} +3 -3
- package/dist/cli/{chunk-nn13znc2.js → chunk-zxcczpyx.js} +1 -1
- package/dist/cli/chunk-zxh4d9vy.js +122 -0
- package/dist/cli/chunk-zxh4d9vy.js.map +11 -0
- package/dist/cli/index.js +214 -57
- package/dist/cli/index.js.map +8 -7
- package/dist/types/ai/agent-readability.d.ts +52 -0
- package/dist/types/ai/ai-catalog.d.ts +42 -0
- package/dist/types/ai/api/paths.d.ts +17 -0
- package/dist/types/ai/api-catalog.d.ts +18 -0
- package/dist/types/ai/ask-context.d.ts +7 -7
- package/dist/types/ai/ask.d.ts +368 -0
- package/dist/types/ai/changelog-markdown.d.ts +2 -0
- package/dist/types/ai/component-markdown.d.ts +2 -2
- package/dist/types/ai/index.d.ts +24 -0
- package/dist/types/ai/link-headers.d.ts +24 -0
- package/dist/types/ai/llms.d.ts +25 -0
- package/dist/types/ai/markdown.d.ts +45 -0
- package/dist/types/ai/mcp/discovery.d.ts +68 -0
- package/dist/types/ai/mcp/tools.d.ts +16 -0
- package/dist/types/ai/openapi-components.d.ts +43 -0
- package/dist/types/ai/relative-links.d.ts +26 -0
- package/dist/types/ai/serializers.d.ts +15 -0
- package/dist/types/ai/skills.d.ts +42 -0
- package/dist/types/ai/tar.d.ts +25 -0
- package/dist/types/ai/visibility.d.ts +17 -0
- package/dist/types/ai/web-bot-auth.d.ts +16 -0
- package/dist/types/analytics/adobe.d.ts +35 -0
- package/dist/types/analytics/amplitude.d.ts +50 -0
- package/dist/types/analytics/clarity.d.ts +31 -0
- package/dist/types/analytics/clearbit.d.ts +29 -0
- package/dist/types/analytics/cloudflare.d.ts +41 -0
- package/dist/types/analytics/fathom.d.ts +41 -0
- package/dist/types/analytics/google-analytics.d.ts +44 -0
- package/dist/types/analytics/google-tag-manager.d.ts +40 -0
- package/dist/types/analytics/head.d.ts +30 -0
- package/dist/types/analytics/heap.d.ts +40 -0
- package/dist/types/analytics/hightouch.d.ts +44 -0
- package/dist/types/analytics/hotjar.d.ts +33 -0
- package/dist/types/analytics/index.d.ts +61 -0
- package/dist/types/analytics/inline.d.ts +17 -0
- package/dist/types/analytics/logrocket.d.ts +44 -0
- package/dist/types/analytics/mixpanel.d.ts +67 -0
- package/dist/types/analytics/pirsch.d.ts +42 -0
- package/dist/types/analytics/plausible.d.ts +56 -0
- package/dist/types/analytics/posthog.d.ts +45 -0
- package/dist/types/analytics/schema.d.ts +320 -0
- package/dist/types/analytics/script.d.ts +46 -0
- package/dist/types/analytics/segment.d.ts +50 -0
- package/dist/types/analytics/vercel.d.ts +48 -0
- package/dist/types/astro/integration.d.ts +76 -0
- package/dist/types/astro/markdown-negotiation.d.ts +23 -0
- package/dist/types/astro/module-types.d.ts +14 -0
- package/dist/types/astro/pages.d.ts +44 -0
- package/dist/types/cli/env.d.ts +12 -0
- package/dist/types/cli/init/scaffold.d.ts +154 -0
- package/dist/types/components/layout/nav-utils.d.ts +11 -0
- package/dist/types/core/adapter.d.ts +47 -0
- package/dist/types/core/api-name.d.ts +7 -0
- package/dist/types/core/changelog-index.d.ts +13 -0
- package/dist/types/core/config-input.d.ts +197 -526
- package/dist/types/core/config.d.ts +61 -35
- package/dist/types/core/content-assets.d.ts +76 -0
- package/dist/types/core/custom-pages.d.ts +33 -0
- package/dist/types/core/data.d.ts +30 -15
- package/dist/types/core/define-components.d.ts +12 -9
- package/dist/types/core/deployment-env.d.ts +6 -11
- package/dist/types/core/frontmatter.d.ts +10 -0
- package/dist/types/core/graph.d.ts +18 -0
- package/dist/types/core/heading-markers.d.ts +54 -0
- package/dist/types/core/i18n-ui.d.ts +12 -10
- package/dist/types/core/i18n.d.ts +87 -0
- package/dist/types/core/includes.d.ts +138 -0
- package/dist/types/core/last-modified.d.ts +47 -0
- package/dist/types/core/links.d.ts +95 -0
- package/dist/types/core/locale-links.d.ts +60 -0
- package/dist/types/core/manifest.d.ts +17 -0
- package/dist/types/core/meta.d.ts +38 -0
- package/dist/types/core/nav-diagnostics.d.ts +26 -0
- package/dist/types/core/navigation.d.ts +6 -0
- package/dist/types/core/node-require.d.ts +19 -0
- package/dist/types/core/package-json.d.ts +13 -0
- package/dist/types/core/probe.d.ts +42 -0
- package/dist/types/core/project-graph.d.ts +51 -0
- package/dist/types/core/project.d.ts +2 -0
- package/dist/types/core/safe-href.d.ts +2 -0
- package/dist/types/core/safe-links.d.ts +26 -0
- package/dist/types/core/schema.d.ts +2284 -454
- package/dist/types/core/site-url.d.ts +16 -0
- package/dist/types/core/sources/assets.d.ts +36 -0
- package/dist/types/core/sources/cache.d.ts +37 -0
- package/dist/types/core/sources/collection.d.ts +28 -0
- package/dist/types/core/sources/contentful-rich-text.d.ts +31 -0
- package/dist/types/core/sources/contentful.d.ts +40 -0
- package/dist/types/core/sources/filesystem.d.ts +24 -0
- package/dist/types/core/sources/github-releases.d.ts +31 -0
- package/dist/types/core/sources/json.d.ts +30 -0
- package/dist/types/core/sources/lexical.d.ts +19 -0
- package/dist/types/core/sources/lower.d.ts +75 -0
- package/dist/types/core/sources/mdx-remote.d.ts +28 -0
- package/dist/types/core/sources/normalize.d.ts +147 -0
- package/dist/types/core/sources/notion.d.ts +131 -0
- package/dist/types/core/sources/obsidian.d.ts +46 -0
- package/dist/types/core/sources/payload.d.ts +39 -0
- package/dist/types/core/sources/portable-text.d.ts +42 -0
- package/dist/types/core/sources/read.d.ts +23 -0
- package/dist/types/core/sources/remote.d.ts +74 -0
- package/dist/types/core/sources/resolve.d.ts +19 -0
- package/dist/types/core/sources/sanity.d.ts +43 -0
- package/dist/types/core/sources/strapi-blocks.d.ts +12 -0
- package/dist/types/core/sources/strapi.d.ts +33 -0
- package/dist/types/core/sources/types.d.ts +7 -0
- package/dist/types/core/sources/watch.d.ts +45 -0
- package/dist/types/core/text-width.d.ts +11 -0
- package/dist/types/core/types.d.ts +15 -1
- package/dist/types/core/unrecognized-keys.d.ts +7 -0
- package/dist/types/core/versions.d.ts +72 -0
- package/dist/types/core/yaml.d.ts +9 -0
- package/dist/types/deploy/adapter-output.d.ts +45 -0
- package/dist/types/deploy/adapters/cloudflare.d.ts +40 -0
- package/dist/types/deploy/adapters/index.d.ts +29 -0
- package/dist/types/deploy/adapters/netlify.d.ts +37 -0
- package/dist/types/deploy/adapters/node.d.ts +37 -0
- package/dist/types/deploy/adapters/registry.d.ts +133 -0
- package/dist/types/deploy/adapters/types.d.ts +71 -0
- package/dist/types/deploy/adapters/vercel.d.ts +38 -0
- package/dist/types/deploy/artifacts.d.ts +65 -0
- package/dist/types/deploy/cloudflare-negotiation.d.ts +196 -0
- package/dist/types/deploy/function-bundle.d.ts +80 -0
- package/dist/types/deploy/headers.d.ts +50 -0
- package/dist/types/deploy/node-headers.d.ts +42 -0
- package/dist/types/deploy/platforms/cloudflare.d.ts +40 -0
- package/dist/types/deploy/platforms/index.d.ts +18 -0
- package/dist/types/deploy/platforms/netlify.d.ts +13 -0
- package/dist/types/deploy/platforms/node.d.ts +11 -0
- package/dist/types/deploy/platforms/paths.d.ts +27 -0
- package/dist/types/deploy/platforms/static.d.ts +10 -0
- package/dist/types/deploy/platforms/types.d.ts +104 -0
- package/dist/types/deploy/platforms/vercel.d.ts +32 -0
- package/dist/types/deploy/redirects.d.ts +53 -0
- package/dist/types/deploy/robots.d.ts +8 -0
- package/dist/types/deploy/rss.d.ts +31 -0
- package/dist/types/deploy/sitemap.d.ts +21 -0
- package/dist/types/deploy/vercel-negotiation.d.ts +109 -0
- package/dist/types/markdown/code-title.d.ts +32 -0
- package/dist/types/markdown/fence-meta.d.ts +23 -0
- package/dist/types/markdown/themes.d.ts +3 -3
- package/dist/types/openapi/asyncapi.d.ts +129 -0
- package/dist/types/openapi/graphql-build.d.ts +8 -0
- package/dist/types/openapi/graphql.d.ts +122 -0
- package/dist/types/openapi/model.d.ts +158 -0
- package/dist/types/openapi/parse.d.ts +57 -0
- package/dist/types/openapi/references.d.ts +37 -25
- package/dist/types/openapi/render-mdx.d.ts +33 -0
- package/dist/types/openapi/sentence.d.ts +7 -0
- package/dist/types/openapi/signature.d.ts +10 -0
- package/dist/types/openapi/source.d.ts +22 -0
- package/dist/types/openapi/spec-dependency-error.d.ts +10 -0
- package/dist/types/reference/asyncapi.d.ts +166 -0
- package/dist/types/reference/graphql.d.ts +181 -0
- package/dist/types/reference/index.d.ts +32 -0
- package/dist/types/reference/openapi.d.ts +165 -0
- package/dist/types/reference/options.d.ts +157 -0
- package/dist/types/reference/scalar.d.ts +136 -0
- package/dist/types/reference/schema.d.ts +630 -0
- package/dist/types/search/adapters/algolia.d.ts +32 -0
- package/dist/types/search/adapters/flexsearch.d.ts +12 -0
- package/dist/types/search/adapters/index.d.ts +31 -0
- package/dist/types/search/adapters/mixedbread.d.ts +22 -0
- package/dist/types/search/adapters/orama-cloud.d.ts +33 -0
- package/dist/types/search/adapters/orama.d.ts +13 -0
- package/dist/types/search/adapters/pagefind.d.ts +12 -0
- package/dist/types/search/adapters/registry.d.ts +228 -0
- package/dist/types/search/adapters/types.d.ts +34 -0
- package/dist/types/search/adapters/typesense.d.ts +42 -0
- package/dist/types/search/build.d.ts +23 -0
- package/dist/types/search/documents.d.ts +90 -0
- package/dist/types/search/facets.d.ts +3 -0
- package/dist/types/search/orama-index.d.ts +1 -1
- package/dist/types/search/sync/algolia.d.ts +14 -0
- package/dist/types/search/sync/index.d.ts +14 -0
- package/dist/types/search/sync/orama-cloud.d.ts +10 -0
- package/dist/types/search/sync/typesense.d.ts +14 -0
- package/dist/types/sources/contentful.d.ts +68 -0
- package/dist/types/sources/custom.d.ts +20 -0
- package/dist/types/sources/filesystem.d.ts +42 -0
- package/dist/types/sources/github-releases.d.ts +46 -0
- package/dist/types/sources/index.d.ts +48 -0
- package/dist/types/sources/mdx-remote.d.ts +63 -0
- package/dist/types/sources/notion.d.ts +65 -0
- package/dist/types/sources/obsidian.d.ts +34 -0
- package/dist/types/sources/payload.d.ts +68 -0
- package/dist/types/sources/registry.d.ts +1414 -0
- package/dist/types/sources/sanity.d.ts +69 -0
- package/dist/types/sources/shared.d.ts +46 -0
- package/dist/types/sources/strapi.d.ts +66 -0
- package/dist/types/theme/icon-kind.d.ts +11 -0
- package/dist/types/theme/icons.d.ts +20 -0
- package/docs/01-quickstart.mdx +18 -18
- package/docs/02-deployment.mdx +52 -28
- package/docs/03-upgrading.mdx +364 -0
- package/docs/04-migrating.mdx +58 -0
- package/docs/08-faq.mdx +5 -5
- package/docs/advanced/blog.mdx +13 -6
- package/docs/advanced/changelog.mdx +23 -35
- package/docs/advanced/custom-pages.mdx +22 -10
- package/docs/advanced/meta.ts +1 -8
- package/docs/advanced/skills.mdx +8 -0
- package/docs/cli/audit.mdx +646 -0
- package/docs/cli/doctor.mdx +29 -0
- package/docs/{reference/eval.mdx → cli/evals.mdx} +12 -11
- package/docs/cli/index.mdx +105 -0
- package/docs/cli/meta.ts +7 -0
- package/docs/{reference → cli}/translate.mdx +9 -9
- package/docs/cli/validate.mdx +41 -0
- package/docs/cli/version.mdx +40 -0
- package/docs/configuration/analytics.mdx +350 -59
- package/docs/configuration/assistant.mdx +357 -0
- package/docs/configuration/customization.mdx +16 -10
- package/docs/configuration/index.mdx +50 -31
- package/docs/configuration/meta.ts +1 -1
- package/docs/configuration/search.mdx +76 -55
- package/docs/configuration/theming.mdx +24 -15
- package/docs/content/components.mdx +21 -5
- package/docs/{reference → content}/frontmatter.mdx +35 -1
- package/docs/content/i18n.mdx +9 -7
- package/docs/content/includes.mdx +2 -4
- package/docs/content/index.mdx +1 -1
- package/docs/content/islands.mdx +11 -6
- package/docs/content/meta.mdx +1 -1
- package/docs/content/meta.ts +1 -0
- package/docs/content/navigation.mdx +9 -7
- package/docs/content/sources.mdx +145 -50
- package/docs/content/syntax.mdx +16 -12
- package/docs/content/versioning.mdx +3 -3
- package/docs/discoverability/agent-discovery.mdx +12 -12
- package/docs/discoverability/index.mdx +4 -4
- package/docs/discoverability/json-api.mdx +4 -4
- package/docs/discoverability/llms-txt.mdx +6 -6
- package/docs/discoverability/markdown.mdx +4 -4
- package/docs/discoverability/mcp.mdx +11 -11
- package/docs/discoverability/sitemap-and-robots.mdx +3 -3
- package/docs/index.mdx +4 -4
- package/docs/references/asyncapi.mdx +59 -0
- package/docs/{advanced → references}/graphql.mdx +45 -32
- package/docs/{reference → references}/meta.ts +2 -2
- package/docs/references/openapi.mdx +171 -0
- package/docs/references/scalar.mdx +64 -0
- package/package.json +42 -8
- package/skills/blume/SKILL.md +23 -10
- package/skills/blume-migrate/SKILL.md +22 -21
- package/skills/blume-migrate/assets/oxfmt@0.67.0.patch +49 -0
- package/skills/blume-migrate/references/docusaurus.md +5 -4
- package/skills/blume-migrate/references/fumadocs.md +4 -4
- package/skills/blume-migrate/references/mintlify.md +23 -8
- package/skills/blume-migrate/references/monorepo.md +6 -6
- package/skills/blume-migrate/references/starlight.md +4 -4
- package/src/ai/agent-readability.ts +25 -26
- package/src/ai/ai-catalog.ts +50 -32
- package/src/ai/api/handlers.ts +46 -11
- package/src/ai/api/paths.ts +1 -1
- package/src/ai/api-catalog.ts +8 -8
- package/src/ai/ask-context.ts +7 -7
- package/src/ai/ask-data.ts +3 -3
- package/src/ai/ask.ts +637 -100
- package/src/ai/changelog-markdown.ts +91 -0
- package/src/ai/component-markdown.ts +328 -12
- package/src/ai/cors.ts +3 -3
- package/src/ai/index.ts +45 -0
- package/src/ai/link-headers.ts +7 -6
- package/src/ai/llms.ts +20 -15
- package/src/ai/markdown.ts +35 -5
- package/src/ai/mcp/data.ts +9 -5
- package/src/ai/openapi-components.ts +5 -2
- package/src/ai/relative-links.ts +170 -0
- package/src/ai/serializers.ts +3 -3
- package/src/ai/skills.ts +1 -1
- package/src/ai/visibility.ts +1 -1
- package/src/ai/web-bot-auth.ts +2 -2
- package/src/analytics/adobe.ts +46 -0
- package/src/analytics/amplitude.ts +79 -0
- package/src/analytics/clarity.ts +46 -0
- package/src/analytics/clearbit.ts +47 -0
- package/src/analytics/cloudflare.ts +67 -0
- package/src/analytics/fathom.ts +64 -0
- package/src/analytics/google-analytics.ts +84 -0
- package/src/analytics/google-tag-manager.ts +61 -0
- package/src/analytics/head.ts +130 -0
- package/src/analytics/heap.ts +65 -0
- package/src/analytics/hightouch.ts +77 -0
- package/src/analytics/hotjar.ts +47 -0
- package/src/analytics/index.ts +71 -0
- package/src/analytics/inline.ts +21 -0
- package/src/analytics/logrocket.ts +71 -0
- package/src/analytics/mixpanel.ts +92 -0
- package/src/analytics/pirsch.ts +66 -0
- package/src/analytics/plausible.ts +83 -0
- package/src/analytics/posthog.ts +78 -0
- package/src/analytics/schema.ts +60 -0
- package/src/analytics/script.ts +60 -0
- package/src/analytics/segment.ts +85 -0
- package/src/analytics/vercel.ts +50 -0
- package/src/astro/adapter-root.ts +7 -9
- package/src/astro/component-slots.ts +131 -91
- package/src/astro/generate.ts +129 -361
- package/src/astro/integration.ts +2 -6
- package/src/astro/module-types.ts +1 -1
- package/src/astro/pages.ts +13 -101
- package/src/astro/render-deps.ts +379 -0
- package/src/astro/runtime-deps.ts +201 -0
- package/src/astro/templates.ts +575 -562
- package/src/audit/agent.ts +24 -0
- package/src/audit/catalog.ts +2 -2
- package/src/audit/checks/assets.ts +2 -2
- package/src/audit/checks/dns-aid.ts +1 -1
- package/src/audit/checks/duplicates.ts +3 -1
- package/src/audit/checks/i18n.ts +1 -1
- package/src/audit/checks/indexability.ts +7 -7
- package/src/audit/checks/links.ts +2 -2
- package/src/audit/checks/llms.ts +6 -6
- package/src/audit/checks/network.ts +3 -3
- package/src/audit/checks/og-image.ts +2 -2
- package/src/audit/checks/robots.ts +1 -1
- package/src/audit/checks/sitemap.ts +2 -2
- package/src/audit/checks/social.ts +1 -1
- package/src/audit/run.ts +4 -3
- package/src/audit/terms.ts +31 -0
- package/src/audit/url.ts +13 -13
- package/src/blume-modules.d.ts +2 -2
- package/src/cli/command-meta.ts +10 -0
- package/src/cli/commands/audit.ts +40 -21
- package/src/cli/commands/build.ts +71 -316
- package/src/cli/commands/check.ts +1 -0
- package/src/cli/commands/dev.ts +40 -1
- package/src/cli/commands/doctor.ts +92 -14
- package/src/cli/commands/eject.ts +44 -7
- package/src/cli/commands/eval.ts +5 -5
- package/src/cli/commands/init.ts +161 -41
- package/src/cli/commands/migrate.ts +121 -0
- package/src/cli/commands/preview.ts +7 -1
- package/src/cli/commands/translate.ts +5 -5
- package/src/cli/commands/upgrade.ts +141 -0
- package/src/cli/commands/version.ts +57 -44
- package/src/cli/eject-scripts.ts +121 -6
- package/src/cli/index.ts +11 -1
- package/src/cli/init/install.ts +70 -0
- package/src/cli/init/questions.ts +7 -0
- package/src/cli/init/scaffold.ts +459 -75
- package/src/cli/lazy-command.ts +37 -1
- package/src/cli/prepare.ts +40 -6
- package/src/cli/required-secrets.ts +39 -12
- package/src/cli/unknown-flags.ts +266 -0
- package/src/cli/yarn-pnp.ts +52 -0
- package/src/components/content/AccordionItem.astro +7 -1
- package/src/components/content/Card.astro +4 -3
- package/src/components/content/ColorItem.astro +22 -4
- package/src/components/content/GithubInfo.astro +2 -2
- package/src/components/content/Prompt.astro +25 -25
- package/src/components/content/Tabs.astro +3 -1
- package/src/components/content/Tile.astro +2 -3
- package/src/components/content/Tooltip.astro +57 -9
- package/src/components/content/content-strings.ts +34 -0
- package/src/components/content/diff.ts +1 -1
- package/src/components/content/mermaid-element.ts +13 -2
- package/src/components/content/prompt-markdown.ts +292 -0
- package/src/components/content/tooltip-id.ts +41 -0
- package/src/components/copy-feedback.ts +1 -1
- package/src/components/islands/{AskAI.astro → Assistant.astro} +10 -10
- package/src/components/islands/{ask-ai.tsx → assistant.tsx} +23 -23
- package/src/components/islands/hooks.ts +36 -11
- package/src/components/layout/Analytics.astro +21 -79
- package/src/components/layout/Banner.astro +3 -1
- package/src/components/layout/DiscoveryLinks.astro +69 -0
- package/src/components/layout/Header.astro +93 -30
- package/src/components/layout/LanguageSwitcher.astro +4 -1
- package/src/components/layout/Logo.astro +23 -2
- package/src/components/layout/NavSelector.astro +13 -2
- package/src/components/layout/NavTree.astro +12 -3
- package/src/components/layout/NavTreeScript.astro +17 -3
- package/src/components/layout/PageActions.astro +3 -3
- package/src/components/layout/PageFeedback.astro +11 -1
- package/src/components/layout/PageLayout.astro +58 -11
- package/src/components/layout/Pagination.astro +7 -7
- package/src/components/layout/ReferenceLayout.astro +22 -13
- package/src/components/layout/RootLayout.astro +75 -72
- package/src/components/layout/Search.astro +43 -19
- package/src/components/layout/WebMcp.astro +1 -1
- package/src/components/layout/analytics-client.ts +73 -16
- package/src/components/layout/drawer-inert.ts +113 -15
- package/src/components/layout/dropdown-clamp.ts +105 -0
- package/src/components/layout/head-scripts.ts +15 -6
- package/src/components/layout/nav-utils.ts +17 -0
- package/src/components/layout/search/algolia.ts +8 -8
- package/src/components/layout/search/orama-cloud.ts +9 -7
- package/src/components/layout/search/typesense.ts +11 -17
- package/src/components/openapi/MessageComposer.astro +2 -2
- package/src/components/openapi/Playground.astro +2 -2
- package/src/components/openapi/description.ts +2 -2
- package/src/components/openapi/playground-client.ts +79 -10
- package/src/core/changelog-index.ts +24 -0
- package/src/core/code-fences.ts +1 -1
- package/src/core/component-overrides.ts +399 -154
- package/src/core/config-input.ts +201 -566
- package/src/core/config.ts +131 -42
- package/src/core/custom-pages.ts +105 -0
- package/src/core/data.ts +34 -15
- package/src/core/define-components.ts +12 -9
- package/src/core/deployment-env.ts +18 -74
- package/src/core/diagnostics.ts +14 -7
- package/src/core/graph.ts +69 -25
- package/src/core/i18n-ui.ts +41 -11
- package/src/core/i18n.ts +17 -0
- package/src/core/includes.ts +156 -38
- package/src/core/last-modified.ts +6 -11
- package/src/core/links.ts +166 -2
- package/src/core/manifest.ts +10 -3
- package/src/core/navigation.ts +115 -16
- package/src/core/new-tab.ts +35 -0
- package/src/core/node-require.ts +21 -0
- package/src/core/project-graph.ts +42 -26
- package/src/core/project.ts +9 -4
- package/src/core/request-body.ts +61 -0
- package/src/core/safe-href.ts +28 -0
- package/src/core/safe-links.ts +68 -0
- package/src/core/schema.ts +528 -745
- package/src/core/server-features.ts +10 -11
- package/src/core/sources/assets.ts +47 -11
- package/src/core/sources/collection.ts +67 -0
- package/src/core/sources/contentful-rich-text.ts +285 -0
- package/src/core/sources/contentful.ts +173 -0
- package/src/core/sources/github-releases.ts +51 -1
- package/src/core/sources/json.ts +71 -0
- package/src/core/sources/lexical.ts +195 -0
- package/src/core/sources/lower.ts +226 -0
- package/src/core/sources/normalize.ts +52 -2
- package/src/core/sources/notion.ts +39 -28
- package/src/core/sources/payload.ts +135 -0
- package/src/core/sources/portable-text.ts +11 -16
- package/src/core/sources/remote.ts +226 -0
- package/src/core/sources/resolve.ts +104 -166
- package/src/core/sources/sanity.ts +16 -49
- package/src/core/sources/strapi-blocks.ts +124 -0
- package/src/core/sources/strapi.ts +191 -0
- package/src/core/sources/types.ts +12 -1
- package/src/core/types.ts +15 -1
- package/src/core/ui-packs/ar.ts +10 -7
- package/src/core/ui-packs/bg.ts +10 -7
- package/src/core/ui-packs/bn.ts +10 -7
- package/src/core/ui-packs/ca.ts +7 -6
- package/src/core/ui-packs/cs.ts +10 -7
- package/src/core/ui-packs/da.ts +10 -7
- package/src/core/ui-packs/de.ts +10 -7
- package/src/core/ui-packs/el.ts +7 -6
- package/src/core/ui-packs/es.ts +7 -6
- package/src/core/ui-packs/fa.ts +10 -7
- package/src/core/ui-packs/fi.ts +10 -7
- package/src/core/ui-packs/fr.ts +7 -6
- package/src/core/ui-packs/he.ts +10 -7
- package/src/core/ui-packs/hi.ts +10 -7
- package/src/core/ui-packs/hr.ts +10 -7
- package/src/core/ui-packs/hu.ts +10 -7
- package/src/core/ui-packs/id.ts +10 -7
- package/src/core/ui-packs/it.ts +7 -6
- package/src/core/ui-packs/ja.ts +7 -6
- package/src/core/ui-packs/ko.ts +7 -6
- package/src/core/ui-packs/nl.ts +10 -7
- package/src/core/ui-packs/no.ts +10 -7
- package/src/core/ui-packs/pl.ts +10 -7
- package/src/core/ui-packs/pt-br.ts +7 -6
- package/src/core/ui-packs/pt.ts +7 -6
- package/src/core/ui-packs/ro.ts +10 -7
- package/src/core/ui-packs/ru.ts +10 -7
- package/src/core/ui-packs/sk.ts +10 -7
- package/src/core/ui-packs/sr.ts +10 -7
- package/src/core/ui-packs/sv.ts +10 -7
- package/src/core/ui-packs/th.ts +7 -6
- package/src/core/ui-packs/tr.ts +10 -7
- package/src/core/ui-packs/uk.ts +10 -7
- package/src/core/ui-packs/vi.ts +7 -6
- package/src/core/ui-packs/zh-tw.ts +7 -6
- package/src/core/ui-packs/zh.ts +7 -6
- package/src/core/unrecognized-keys.ts +10 -0
- package/src/core/version-cut.ts +116 -8
- package/src/deploy/adapter-output.ts +57 -97
- package/src/deploy/adapters/cloudflare.ts +39 -0
- package/src/deploy/adapters/index.ts +41 -0
- package/src/deploy/adapters/netlify.ts +34 -0
- package/src/deploy/adapters/node.ts +34 -0
- package/src/deploy/adapters/registry.ts +127 -0
- package/src/deploy/adapters/types.ts +92 -0
- package/src/deploy/adapters/vercel.ts +35 -0
- package/src/deploy/artifacts.ts +55 -27
- package/src/deploy/cloudflare-negotiation.ts +179 -100
- package/src/deploy/function-bundle.ts +18 -3
- package/src/deploy/headers.ts +124 -23
- package/src/deploy/node-headers.ts +198 -0
- package/src/deploy/platforms/cloudflare.ts +312 -0
- package/src/deploy/platforms/index.ts +67 -0
- package/src/deploy/platforms/netlify.ts +52 -0
- package/src/deploy/platforms/node.ts +40 -0
- package/src/deploy/platforms/paths.ts +42 -0
- package/src/deploy/platforms/static.ts +29 -0
- package/src/deploy/platforms/types.ts +112 -0
- package/src/deploy/platforms/vercel.ts +193 -0
- package/src/deploy/redirects.ts +30 -18
- package/src/deploy/robots.ts +3 -3
- package/src/deploy/rss.ts +2 -2
- package/src/deploy/sitemap.ts +2 -2
- package/src/deploy/vercel-negotiation.ts +2 -2
- package/src/eval/agents.ts +32 -1
- package/src/eval/findings.ts +19 -11
- package/src/eval/report.ts +10 -2
- package/src/markdown/external-links.ts +65 -0
- package/src/markdown/include.ts +45 -27
- package/src/markdown/index.ts +34 -9
- package/src/markdown/inline-code.ts +12 -6
- package/src/markdown/relative-links.ts +324 -0
- package/src/markdown/themes.ts +3 -3
- package/src/migrate/migrate.ts +149 -0
- package/src/openapi/parse.ts +64 -16
- package/src/openapi/proxy.ts +62 -10
- package/src/openapi/references.ts +147 -147
- package/src/openapi/render-mdx.ts +32 -10
- package/src/openapi/scalar.ts +15 -13
- package/src/openapi/sentence.ts +14 -0
- package/src/openapi/source.ts +37 -21
- package/src/openapi/spec-dependency-error.ts +15 -0
- package/src/reference/asyncapi.ts +83 -0
- package/src/reference/graphql.ts +89 -0
- package/src/reference/index.ts +40 -0
- package/src/reference/openapi.ts +83 -0
- package/src/reference/options.ts +201 -0
- package/src/reference/scalar.ts +136 -0
- package/src/reference/schema.ts +88 -0
- package/src/registry/eject.ts +109 -53
- package/src/search/adapters/algolia.ts +46 -0
- package/src/search/adapters/flexsearch.ts +29 -0
- package/src/search/adapters/index.ts +39 -0
- package/src/search/adapters/mixedbread.ts +39 -0
- package/src/search/adapters/orama-cloud.ts +51 -0
- package/src/search/adapters/orama.ts +24 -0
- package/src/search/adapters/pagefind.ts +27 -0
- package/src/search/adapters/registry.ts +131 -0
- package/src/search/adapters/types.ts +46 -0
- package/src/search/adapters/typesense.ts +57 -0
- package/src/search/build.ts +8 -4
- package/src/search/documents.ts +8 -2
- package/src/search/orama-index.ts +1 -1
- package/src/search/sync/algolia.ts +12 -11
- package/src/search/sync/index.ts +35 -20
- package/src/search/sync/orama-cloud.ts +12 -9
- package/src/search/sync/typesense.ts +15 -13
- package/src/sources/contentful.ts +63 -0
- package/src/sources/custom.ts +38 -0
- package/src/sources/filesystem.ts +51 -0
- package/src/sources/github-releases.ts +52 -0
- package/src/sources/index.ts +60 -0
- package/src/sources/mdx-remote.ts +76 -0
- package/src/sources/notion.ts +63 -0
- package/src/sources/obsidian.ts +38 -0
- package/src/sources/payload.ts +60 -0
- package/src/sources/registry.ts +182 -0
- package/src/sources/sanity.ts +66 -0
- package/src/sources/shared.ts +52 -0
- package/src/sources/strapi.ts +58 -0
- package/src/theme/entry.ts +24 -2
- package/src/translate/report.ts +40 -7
- package/src/upgrade/upgrade.ts +499 -0
- package/dist/cli/chunk-0qymqwzz.js +0 -164
- package/dist/cli/chunk-0qymqwzz.js.map +0 -15
- package/dist/cli/chunk-1jefwnfs.js.map +0 -48
- package/dist/cli/chunk-2mzebbbz.js +0 -69
- package/dist/cli/chunk-2z47ypj8.js.map +0 -11
- package/dist/cli/chunk-4x36ddpw.js.map +0 -11
- package/dist/cli/chunk-5093q3n7.js +0 -68
- package/dist/cli/chunk-5093q3n7.js.map +0 -10
- package/dist/cli/chunk-5qk08vmp.js.map +0 -11
- package/dist/cli/chunk-7s8hm3b6.js +0 -5347
- package/dist/cli/chunk-7s8hm3b6.js.map +0 -58
- package/dist/cli/chunk-8cjtbafj.js.map +0 -13
- package/dist/cli/chunk-97r59kpr.js +0 -381
- package/dist/cli/chunk-97r59kpr.js.map +0 -12
- package/dist/cli/chunk-ahnw3kxw.js.map +0 -15
- package/dist/cli/chunk-b27xqwn9.js.map +0 -10
- package/dist/cli/chunk-bf6bt1xt.js +0 -185
- package/dist/cli/chunk-bf6bt1xt.js.map +0 -11
- package/dist/cli/chunk-cjtn640a.js.map +0 -10
- package/dist/cli/chunk-ct47dqpx.js.map +0 -11
- package/dist/cli/chunk-esphfr8p.js +0 -107
- package/dist/cli/chunk-esphfr8p.js.map +0 -11
- package/dist/cli/chunk-ex56aa81.js +0 -1016
- package/dist/cli/chunk-ex56aa81.js.map +0 -13
- package/dist/cli/chunk-garjf5z9.js +0 -30
- package/dist/cli/chunk-garjf5z9.js.map +0 -10
- package/dist/cli/chunk-js7saxwm.js +0 -1045
- package/dist/cli/chunk-js7saxwm.js.map +0 -22
- package/dist/cli/chunk-k79xp7av.js.map +0 -39
- package/dist/cli/chunk-ps4m1xh4.js.map +0 -15
- package/dist/cli/chunk-q4rae3bg.js +0 -60
- package/dist/cli/chunk-q4rae3bg.js.map +0 -10
- package/dist/cli/chunk-rz9jmfhz.js +0 -108
- package/dist/cli/chunk-rz9jmfhz.js.map +0 -10
- package/dist/cli/chunk-vh9w1sgp.js +0 -73
- package/dist/cli/chunk-vh9w1sgp.js.map +0 -10
- package/dist/cli/chunk-vrfp10qk.js +0 -81
- package/dist/cli/chunk-vrfp10qk.js.map +0 -10
- package/dist/cli/chunk-yg63d42r.js.map +0 -34
- package/docs/advanced/api-reference.mdx +0 -240
- package/docs/configuration/ask-ai.mdx +0 -256
- package/docs/reference/cli.mdx +0 -197
- package/skills/blume-migrate/assets/oxfmt@0.55.0.patch +0 -20
- package/src/components/content/changelog-element.ts +0 -69
- package/src/search/providers.ts +0 -91
- /package/dist/cli/{chunk-vacwm2hv.js.map → chunk-27g6wdth.js.map} +0 -0
- /package/dist/cli/{chunk-dwgcp5sm.js.map → chunk-ce574jw2.js.map} +0 -0
- /package/dist/cli/{chunk-e7f42gdj.js.map → chunk-fxypxtvm.js.map} +0 -0
- /package/dist/cli/{chunk-nn13znc2.js.map → chunk-zxcczpyx.js.map} +0 -0
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"version": 3,
|
|
3
|
-
"sources": ["../src/core/i18n.ts", "../src/core/manifest.ts", "../src/core/project-graph.ts", "../src/theme/icon-kind.ts", "../src/theme/icons.ts", "../src/core/nav-diagnostics.ts", "../src/core/navigation.ts", "../src/core/versions.ts", "../src/core/graph.ts", "../src/core/includes.ts", "../src/core/code-fences.ts", "../src/core/frontmatter.ts", "../src/core/yaml.ts", "../src/core/sources/normalize.ts", "../src/core/heading-markers.ts", "../src/core/last-modified.ts", "../src/core/meta.ts", "../src/core/sources/resolve.ts", "../src/core/sources/cache.ts", "../src/openapi/model.ts", "../src/openapi/asyncapi.ts", "../src/openapi/graphql.ts", "../src/openapi/parse.ts", "../src/openapi/graphql-build.ts", "../src/openapi/render-mdx.ts", "../src/core/api-name.ts", "../src/core/text-width.ts", "../src/openapi/signature.ts", "../src/openapi/source.ts", "../src/core/sources/filesystem.ts", "../src/core/sources/watch.ts", "../src/core/sources/github-releases.ts", "../src/core/sources/mdx-remote.ts", "../src/core/sources/notion.ts", "../src/components/content/youtube.ts", "../src/core/sources/assets.ts", "../src/core/sources/obsidian.ts", "../src/core/sources/sanity.ts", "../src/core/sources/portable-text.ts"],
|
|
4
|
-
"sourcesContent": [
|
|
5
|
-
"import type {\n ResolvedConfig,\n ResolvedI18nConfig,\n ResolvedVersionsConfig,\n} from \"./schema.ts\";\nimport type { Diagnostic, PageRecord } from \"./types.ts\";\nimport { UI_PACKS } from \"./ui-packs/index.ts\";\n\n/**\n * Locale logic, centralized. Every seam that needs to reason about locales\n * (content discovery, navigation, manifest, runtime generation, the catch-all)\n * goes through these helpers so the routing rules live in exactly one place.\n */\n\n/** Locale codes Blume recognizes (those it ships a UI pack for, plus English). */\nconst KNOWN_LOCALES = new Set(\n [...Object.keys(UI_PACKS), \"en\"].map((code) => code.toLowerCase())\n);\n\n/** True when the project opts into i18n. */\nexport const i18nEnabled = (\n config: ResolvedConfig\n): config is ResolvedConfig & { i18n: ResolvedI18nConfig } =>\n config.i18n !== undefined;\n\n/** All configured locale codes, default first as authored. */\nexport const localeCodes = (i18n: ResolvedI18nConfig): string[] =>\n i18n.locales.map((locale) => locale.code);\n\n/** Text direction for a locale (`ltr` when unknown). */\nexport const localeDir = (\n code: string,\n i18n: ResolvedI18nConfig\n): \"ltr\" | \"rtl\" =>\n i18n.locales.find((locale) => locale.code === code)?.dir ?? \"ltr\";\n\n/**\n * The locale a missing translation falls back to: `fallbackLocale` when set,\n * the default locale when `fallbackLocale` is omitted, or `null` (disabled)\n * when explicitly set to `null`.\n */\nexport const resolveFallbackLocale = (\n i18n: ResolvedI18nConfig\n): string | null => {\n if (i18n.fallbackLocale === null) {\n return null;\n }\n return i18n.fallbackLocale ?? i18n.defaultLocale;\n};\n\n/**\n * The slice of the i18n settings URL routing reads. `ResolvedI18nConfig` and\n * the `blume:data` runtime shape both satisfy it, so route helpers serve the\n * Node-side graph and the rendered page alike.\n */\nexport interface LocaleRouting {\n defaultLocale: string;\n hideDefaultLocalePrefix: boolean;\n locales: { code: string }[];\n}\n\n/** URL prefix for a locale: `\"\"` for the hidden default, else `/<code>`. */\nexport const localePrefix = (code: string, i18n: LocaleRouting): string =>\n code === i18n.defaultLocale && i18n.hideDefaultLocalePrefix ? \"\" : `/${code}`;\n\n/**\n * Prefix a locale-agnostic route (e.g. `/guides/x` or `/`) with its locale.\n * `/` becomes `/fr` (or stays `/` for the hidden default).\n */\nexport const localizeRoute = (\n logicalRoute: string,\n code: string,\n i18n: LocaleRouting\n): string => {\n const prefix = localePrefix(code, i18n);\n if (!prefix) {\n return logicalRoute;\n }\n return logicalRoute === \"/\" ? prefix : `${prefix}${logicalRoute}`;\n};\n\n/**\n * Detect a leading non-default locale directory in a path's segments. The\n * default locale lives at the content root, so only non-default codes are\n * matched as a leading segment. Returns the resolved locale and the remaining\n * (locale-stripped) segments.\n */\nexport const detectLocale = (parts: string[], i18n: ResolvedI18nConfig) => {\n // BCP 47 codes are case-insensitive: a conventional lowercase folder\n // (`pt-br/`) must match a configured `pt-BR`. The configured casing is what\n // flows into routes and labels.\n const first = parts[0]?.toLowerCase();\n const matched = i18n.locales.find(\n (locale) =>\n locale.code !== i18n.defaultLocale && locale.code.toLowerCase() === first\n );\n if (matched) {\n return { locale: matched.code, rest: parts.slice(1) };\n }\n return { locale: i18n.defaultLocale, rest: parts };\n};\n\n/**\n * Resolve where a content file lives across locales, by parser:\n * - `dir`: a leading locale directory (`fr/page.mdx`)\n * - `dot`: a filename suffix (`page.fr.mdx`)\n *\n * A `.$.` infix (e.g. `changelog.$.mdx`) marks a shared, locale-agnostic file\n * that is materialized into every configured locale. Returns the locale-stripped\n * path (used for nav grouping) and the locale codes the file maps to (one for a\n * normal file, all locales for a shared one).\n */\nexport const localePlacement = (\n rel: string,\n ext: string,\n i18n: ResolvedI18nConfig\n) => {\n const base = rel.slice(0, rel.length - ext.length);\n\n // Shared `$` file: the same content in every locale. A shared file placed\n // inside a locale directory (`fr/changelog.$.mdx`) still sheds that\n // directory from its nav path — otherwise every locale's record would route\n // under `/fr/…`, nesting the default locale inside the French namespace and\n // the French copy at `/fr/fr/…`.\n if (base.endsWith(\".$\")) {\n const shared = `${base.slice(0, -2)}${ext}`;\n return {\n locales: i18n.locales.map((locale) => locale.code),\n navPath:\n i18n.parser === \"dir\"\n ? detectLocale(shared.split(\"/\"), i18n).rest.join(\"/\")\n : shared,\n };\n }\n\n if (i18n.parser === \"dot\") {\n const lastDot = base.lastIndexOf(\".\");\n // Only a dot inside the filename (not a directory) is a locale suffix. Any\n // configured locale counts — including the default, so the symmetric\n // authoring `intro.en.mdx` + `intro.fr.mdx` shares one translation key\n // instead of routing the default file to a literal `/intro.en`.\n if (lastDot > base.lastIndexOf(\"/\")) {\n // Case-insensitive, like `detectLocale`: `intro.pt-br.mdx` matches a\n // configured `pt-BR` and adopts its casing.\n const suffix = base.slice(lastDot + 1).toLowerCase();\n const matched = i18n.locales.find(\n (locale) => locale.code.toLowerCase() === suffix\n );\n if (matched) {\n return {\n locales: [matched.code],\n navPath: `${base.slice(0, lastDot)}${ext}`,\n };\n }\n }\n return { locales: [i18n.defaultLocale], navPath: rel };\n }\n\n const { locale, rest } = detectLocale(rel.split(\"/\"), i18n);\n return { locales: [locale], navPath: rest.join(\"/\") };\n};\n\n/**\n * The inverse of {@link localePlacement}: where a default-locale file's\n * translation into `locale` lives, by parser:\n * - `dir`: a leading locale directory (`guides/x.mdx` → `fr/guides/x.mdx`)\n * - `dot`: a filename suffix (`guides/x.mdx` → `guides/x.fr.mdx`)\n *\n * Under `dot`, a source authored with an explicit default-locale suffix\n * (`x.en.mdx`) swaps it for the target's (`x.fr.mdx`) — same case-insensitive\n * last-dot-inside-filename logic as `localePlacement`, so both spellings of a\n * default-locale file resolve to one canonical target.\n */\nexport const localeTargetPath = (\n rel: string,\n ext: string,\n locale: string,\n i18n: ResolvedI18nConfig\n): string => {\n if (i18n.parser === \"dot\") {\n let base = rel.slice(0, rel.length - ext.length);\n const lastDot = base.lastIndexOf(\".\");\n if (lastDot > base.lastIndexOf(\"/\")) {\n const suffix = base.slice(lastDot + 1).toLowerCase();\n if (i18n.locales.some((entry) => entry.code.toLowerCase() === suffix)) {\n base = base.slice(0, lastDot);\n }\n }\n return `${base}.${locale}${ext}`;\n }\n return `${locale}/${rel}`;\n};\n\n/**\n * Warn about top-level content folders that look like a locale (a code Blume\n * recognizes) but aren't declared in `i18n.locales`. Without this they're\n * silently treated as default-locale content under a `/<code>/…` route, which\n * is almost never intended — usually a translation that wasn't registered.\n */\nexport const i18nDiagnostics = (\n pages: PageRecord[],\n i18n: ResolvedI18nConfig,\n versions?: ResolvedVersionsConfig\n): Diagnostic[] => {\n const configured = new Set(\n i18n.locales.map((locale) => locale.code.toLowerCase())\n );\n const versionDirs = new Set(versions?.archived.map((version) => version.id));\n const seen = new Set<string>();\n const diagnostics: Diagnostic[] = [];\n for (const page of pages) {\n // The locale-looking folder is the first segment of the source-local ref\n // (e.g. `fr/guide.md`), not the namespaced id (`filesystem:fr/guide.md`).\n // Inside a version snapshot the locale dir sits one level deeper\n // (`v1.0/fr/guide.md`), so a configured version segment is skipped first.\n const parts = page.source.ref.split(\"/\");\n const first = (\n parts[0] && versionDirs.has(parts[0]) ? parts[1] : parts[0]\n )?.toLowerCase();\n if (\n first &&\n !seen.has(first) &&\n KNOWN_LOCALES.has(first) &&\n !configured.has(first)\n ) {\n seen.add(first);\n diagnostics.push({\n code: \"BLUME_I18N_UNCONFIGURED_LOCALE\",\n message: `Folder \"${first}/\" looks like a locale, but \"${first}\" is not in i18n.locales — its pages are treated as \"${i18n.defaultLocale}\" content at /${first}/….`,\n severity: \"warning\",\n suggestion: `Add { code: \"${first}\", label: \"…\" } to i18n.locales, or rename the folder if it isn't a translation.`,\n });\n }\n }\n return diagnostics;\n};\n",
|
|
6
|
-
"import { withBasePath } 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 const fallbackPages = new Map(\n graph.pages.flatMap((page) =>\n page.locale === fallback ? [[page.translationKey, page] as const] : []\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 = withBasePath(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 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 !== \"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 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 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.output,\n projectRoot: context.root,\n routes,\n version: MANIFEST_VERSION,\n };\n};\n",
|
|
7
|
-
"import { isAbsolute, relative } from \"pathe\";\n\nimport { loadConfig } from \"./config.ts\";\nimport { applyDeploymentEnv } from \"./deployment-env.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 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 /** Override `deployment.adapter` (`blume build --adapter`). */\n adapter?: ResolvedConfig[\"deployment\"][\"adapter\"];\n /** Override `deployment.base` (`blume build --base`). */\n base?: string;\n /** Override `deployment.output` (`blume build --output`). */\n output?: ResolvedConfig[\"deployment\"][\"output\"];\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 return {\n ...config,\n content: {\n ...config.content,\n root: overrides.contentRoot ?? config.content.root,\n },\n deployment: {\n ...config.deployment,\n adapter: overrides.adapter ?? config.deployment.adapter,\n base: overrides.base ?? config.deployment.base,\n output: overrides.output ?? config.deployment.output,\n },\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 root every filesystem source at content.root and partition them with include globs — a root at a subdirectory of content.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 name: source.name,\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 }\n }\n return { diagnostics: allDiagnostics, droppedPages, pages };\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 */\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 // Re-run platform detection after CLI overrides: `loadConfig` already ran it,\n // but adapter inference keys off `deployment.output`, which `--output server`\n // only sets here. Idempotent for already-resolved fields — `deployment.site`\n // keeps loadConfig's explicit > platform env > devServerUrl precedence.\n const config = applyDeploymentEnv(\n applyConfigOverrides(configResult.config, options.overrides)\n );\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 : [{ prefix: source.prefix, root: source.contentRoot }]\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`. A root outside the repository would fail the log outright\n // and can never yield dates, so those are dropped up front.\n const gitRoot = gitRepositoryRoot(context.root);\n const contentRoots = sources\n .flatMap((source) => (source.contentRoot ? [source.contentRoot] : []))\n .filter((dir) => gitRoot !== null && isWithin(gitRoot, dir));\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 const graph = buildContentGraph(pages, {\n basePath: config.basePath,\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(pages, resolveDocsCollection(config, context).base),\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",
|
|
8
|
-
"/**\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",
|
|
9
|
-
"/**\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",
|
|
10
|
-
"import { isAssetIcon } from \"../theme/icon-kind.ts\";\nimport { hasIcon } from \"../theme/icons.ts\";\nimport { isInternalPath } from \"./base-path.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 if (!resolvesToPages(routes, path.split(\"#\")[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/** 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",
|
|
11
|
-
"import { extname } from \"pathe\";\n\nimport { stripBasePath, withBasePath } from \"./base-path.ts\";\nimport type {\n FolderMeta,\n SidebarDisplay,\n SidebarItemConfig,\n} from \"./schema.ts\";\nimport type {\n Diagnostic,\n FeaturedLink,\n HeaderAction,\n NavNode,\n Navigation,\n NavSelector,\n NavTab,\n PageRecord,\n} from \"./types.ts\";\n\nconst NUMERIC_PREFIX = /^(?<order>\\d+)[-_.]/u;\nconst GROUP_FOLDER = /^\\((?<label>.+)\\)$/u;\nconst WORD_SPLIT = /[-_]/u;\n\n/**\n * Whether `route` is the section root `base` or nested beneath it. Requires a\n * path boundary, so `/api-reference` is not under `/api`. The root `/` spans\n * every route.\n */\nexport const isUnderPath = (route: string, base: string): boolean =>\n base === \"/\" || route === base || route.startsWith(`${base}/`);\n\n/**\n * Whether `tab` is the root tab of the tree rooted at `root` — the tab that\n * spans the whole sidebar rather than one section. Tab paths and the root\n * normally share one path space, so the root tab sits at `root` exactly (`/`,\n * `/en`, `/docs`); in an archived version tree the root is versionized\n * (`/v1.0`, `/docs/v1.0`) while tab paths stay in current-docs space, so the\n * root tab is any tab the whole root sits under. The one definition shared by\n * sidebar scoping, tab-section hoisting, and the header's current-tab state —\n * consumers that disagree on which tab is the root tab prune an archived\n * sidebar or highlight the wrong tab over it.\n */\nexport const isRootTab = (tab: NavTab, root: string): boolean =>\n isUnderPath(root, tab.path);\n\nconst humanize = (segment: string): string =>\n segment\n .replace(NUMERIC_PREFIX, \"\")\n .split(WORD_SPLIT)\n .filter(Boolean)\n .map((word) => word.charAt(0).toUpperCase() + word.slice(1))\n .join(\" \");\n\nconst numericOrder = (segment: string): number => {\n const value = segment.match(NUMERIC_PREFIX)?.groups?.order;\n return value ? Math.trunc(Number(value)) : Number.POSITIVE_INFINITY;\n};\n\n/** The nav key of a raw path segment: group label or numeric-stripped name. */\nconst segmentKey = (raw: string): string => {\n const group = raw.match(GROUP_FOLDER)?.groups?.label;\n return (group ?? raw).replace(NUMERIC_PREFIX, \"\");\n};\n\n/**\n * Whether a filename stem is a directory index, ignoring an ordering prefix:\n * route mapping strips the prefix before dropping `index`, so `01-index` routes\n * exactly like `index` and must be treated as one here too.\n */\nconst isIndexStem = (stem: string): boolean =>\n stem.replace(NUMERIC_PREFIX, \"\") === \"index\";\n\n/** A filename's stem: the name with its extension stripped. */\nconst stemOf = (filename: string): string =>\n filename.replace(extname(filename), \"\");\n\n/**\n * The stem of a nav path's last segment. The single index-detection input for\n * the warn path (`sidebarDisplayIgnoredDiagnostics`) and the apply path\n * (`buildFileSystemSidebar`), so the two can't drift apart on what counts as\n * a folder's index page.\n */\nconst navStem = (navPath: string): string =>\n stemOf(navPath.split(\"/\").at(-1) ?? navPath);\n\ninterface MutablePage {\n kind: \"page\";\n key: string;\n label: string;\n route: string;\n description?: string;\n icon?: string;\n badge?: string;\n deprecated?: boolean;\n pageId: string;\n /** Absolute source path (filesystem adapter only), to anchor diagnostics. */\n file?: string;\n order: number;\n /**\n * Whether `order` reflects a deliberate authoring choice (explicit\n * `sidebar.order`, a numeric filename prefix, or a folder-meta `pages` rank)\n * rather than a derived value like a changelog entry's publish date — two\n * changelog entries published on the same day aren't an authoring mistake,\n * so they're excluded from the duplicate-order diagnostic.\n */\n orderIsAuthored: boolean;\n}\n\ninterface MutableGroup {\n kind: \"group\";\n key: string;\n path: string;\n /** The group's URL path (folder route prefix); set as pages are inserted. */\n routePath?: string;\n /** The folder's index page route, when it has one; the group row's link. */\n route?: string;\n label: string;\n icon?: string;\n collapsed?: boolean;\n display?: SidebarDisplay;\n order: number;\n children: MutableNode[];\n index: Map<string, MutableGroup>;\n}\n\ntype MutableNode = MutablePage | MutableGroup;\n\nconst createGroup = (\n key: string,\n path: string,\n label: string,\n order: number\n): MutableGroup => ({\n children: [],\n index: new Map(),\n key,\n kind: \"group\",\n label,\n order,\n path,\n});\n\nconst ensureGroup = (\n parent: MutableGroup,\n rawSegment: string\n): MutableGroup => {\n const existing = parent.index.get(rawSegment);\n if (existing) {\n return existing;\n }\n const path = parent.path ? `${parent.path}/${rawSegment}` : rawSegment;\n const group = createGroup(\n segmentKey(rawSegment),\n path,\n humanize(rawSegment.match(GROUP_FOLDER)?.groups?.label ?? rawSegment),\n numericOrder(rawSegment)\n );\n parent.index.set(rawSegment, group);\n parent.children.push(group);\n return group;\n};\n\ninterface PageOrder {\n order: number;\n /** Whether the order came from the author (frontmatter or a rank prefix). */\n orderIsAuthored: boolean;\n}\n\nconst pageOrder = (page: PageRecord, filename: string): PageOrder => {\n if (page.meta.sidebar.order !== undefined) {\n return { order: page.meta.sidebar.order, orderIsAuthored: true };\n }\n if (isIndexStem(stemOf(filename))) {\n return { order: Number.NEGATIVE_INFINITY, orderIsAuthored: false };\n }\n // Changelog entries read newest-first, matching the generated timeline. Sort\n // on the negated publish timestamp so a later date yields a smaller order\n // under the ascending comparator; undated entries fall back to filename order.\n // The date is derived, not an authoring choice, so same-day entries aren't a\n // duplicate-order mistake.\n if (page.contentType === \"changelog\") {\n const iso = page.meta.date ?? page.meta.changelog?.date;\n const time = iso ? Date.parse(iso) : Number.NaN;\n if (!Number.isNaN(time)) {\n return { order: -time, orderIsAuthored: false };\n }\n }\n // An undated changelog entry's numeric filename prefix is usually a date\n // (`2024-01-05-release.md`) rather than a rank, so it is derived too.\n const order = numericOrder(filename);\n return {\n order,\n orderIsAuthored: page.contentType !== \"changelog\" && Number.isFinite(order),\n };\n};\n\n/**\n * Folder-meta lookup key for a group path. Under i18n the meta files live in\n * the locale directory (`fr/guides/meta.ts` -> key `fr/guides`) while the\n * nav group path is locale-stripped (`guides`), so prepend the locale prefix.\n */\nconst metaKey = (path: string, metaPrefix: string): string => {\n if (!metaPrefix) {\n return path;\n }\n return path ? `${metaPrefix}/${path}` : metaPrefix;\n};\n\n/**\n * Apply folder meta (title/order/icon/collapsed/display and explicit page\n * order), plus the index-frontmatter display sugar collected per folder path.\n */\nconst applyFolderMeta = (\n group: MutableGroup,\n folderMeta: Map<string, FolderMeta>,\n sharedMeta: Map<string, FolderMeta>,\n metaPrefix: string,\n sharedMetaPrefix: string,\n indexDisplay: Map<string, SidebarDisplay>,\n indexRoute: Map<string, string>\n): void => {\n // A folder with an index page links its group row to it — the same shape\n // as an explicit-config group's `root`, and the only sidebar link to the\n // section's own page once the index row is hidden. Index-less folders keep\n // no link: their row would 404.\n group.route = indexRoute.get(group.path);\n // Locale-specific meta wins; a shared `meta.$.*` (keyed by the locale-stripped\n // group path — version-prefixed inside a snapshot) applies to every locale\n // otherwise.\n const meta =\n folderMeta.get(metaKey(group.path, metaPrefix)) ??\n sharedMeta.get(metaKey(group.path, sharedMetaPrefix));\n // The group's own render mode, resolved index frontmatter first, then folder\n // meta; `toNavNode` falls back to the global mode. Applies to this group\n // only — nested subgroups resolve their own value through the same chain.\n group.display = indexDisplay.get(group.path) ?? meta?.display;\n if (meta) {\n group.label = meta.title ?? group.label;\n group.icon = meta.icon ?? group.icon;\n group.order = meta.order ?? group.order;\n group.collapsed = meta.collapsed ?? group.collapsed;\n\n if (meta.pages) {\n const rank = new Map(meta.pages.map((key, i) => [key, i]));\n for (const child of group.children) {\n const position = rank.get(child.key);\n if (position !== undefined) {\n child.order = position;\n if (child.kind === \"page\") {\n child.orderIsAuthored = true;\n }\n }\n }\n }\n }\n\n for (const child of group.children) {\n if (child.kind === \"group\") {\n applyFolderMeta(\n child,\n folderMeta,\n sharedMeta,\n metaPrefix,\n sharedMetaPrefix,\n indexDisplay,\n indexRoute\n );\n }\n }\n};\n\n/**\n * Warn when an index page's own frontmatter title diverges from its folder's\n * explicit `meta.title`. The sidebar label and the page's own `<title>`/heading\n * are resolved from two independent sources — under i18n, a translator can\n * update the folder's `meta.ts` and forget the index page's own frontmatter\n * (or vice versa), and a correct-looking sidebar hides the mismatch.\n *\n * Only fires when the page has an explicit frontmatter `title` of its own:\n * when it's absent, `page.title` is derived from the first heading or the\n * filename, so it almost never coincidentally matches a custom folder title —\n * flagging that would be noise on exactly the plain-landing-page case this is\n * least worth warning about. The root group's `meta.title` (an empty\n * `folderPath`) is also skipped: nothing ever renders it as a sidebar label,\n * so a mismatch there wouldn't correspond to anything visible.\n *\n * Fallback-filled pages are exempt: their title belongs to the fallback\n * locale, so comparing it against this locale's `meta.title` would flag every\n * not-yet-translated index page (once per locale) and point the suggestion at\n * the fallback locale's source file, where \"fixing\" it would break that\n * locale. The default locale's own build still checks the real page.\n */\nconst indexTitleMismatchDiagnostic = (\n page: PageRecord,\n folderPath: string,\n folderMeta: Map<string, FolderMeta>,\n sharedMeta: Map<string, FolderMeta>,\n metaPrefix: string,\n sharedMetaPrefix: string\n): Diagnostic | undefined => {\n if (!page.meta.title || page.fallback || folderPath === \"\") {\n return undefined;\n }\n const meta =\n folderMeta.get(metaKey(folderPath, metaPrefix)) ??\n sharedMeta.get(metaKey(folderPath, sharedMetaPrefix));\n if (!meta?.title || meta.title === page.title) {\n return undefined;\n }\n return {\n code: \"BLUME_NAV_INDEX_TITLE_MISMATCH\",\n file: page.sourcePath ?? page.id,\n message: `Index page \"${page.navPath}\" has title \"${page.title}\", but its folder's meta.title is \"${meta.title}\" — the sidebar shows the folder title while the page's own <title>/heading still say \"${page.title}\".`,\n severity: \"warning\",\n suggestion: `Update the page's frontmatter title to match (\"${meta.title}\"), or leave it if the divergence is intentional.`,\n };\n};\n\n/**\n * Warn wherever a page's `sidebar.display` is dead weight, instead of\n * silently dropping it:\n *\n * - On any page under an explicit `navigation.sidebar` config: the config\n * items own each group's display mode, and neither index frontmatter nor\n * folder meta is read — index pages included.\n * - On the content root's own index page: the root is not a sidebar group,\n * so there is nothing for the value to configure; the sidebar-wide mode\n * lives in `navigation.sidebar.display`.\n * - On any other non-index page: only a folder's index page can configure\n * its group's display mode.\n *\n * A non-root folder index under the generated sidebar is the one placement\n * that IS honored, so it alone is exempt. Under i18n fallback fill the same\n * source file is checked once per locale with an identical message, and the\n * graph-level code+file+message dedupe collapses the copies.\n */\nconst sidebarDisplayIgnoredDiagnostics = (\n pages: PageRecord[],\n explicitSidebar: boolean\n): Diagnostic[] => {\n const diagnostics: Diagnostic[] = [];\n for (const page of pages) {\n if (!page.meta.sidebar.display) {\n continue;\n }\n const isIndex = isIndexStem(navStem(page.navPath));\n if (explicitSidebar) {\n diagnostics.push({\n code: \"BLUME_SIDEBAR_DISPLAY_IGNORED\",\n file: page.sourcePath ?? page.id,\n message: `\"${page.navPath}\" sets sidebar.display, but the explicit navigation.sidebar config owns each group's display mode — the value is ignored.`,\n severity: \"warning\",\n suggestion:\n \"Set display on the matching group in navigation.sidebar, or remove the frontmatter key.\",\n });\n continue;\n }\n if (isIndex && !page.navPath.includes(\"/\")) {\n diagnostics.push({\n code: \"BLUME_SIDEBAR_DISPLAY_IGNORED\",\n file: page.sourcePath ?? page.id,\n message: `\"${page.navPath}\" sets sidebar.display, but the content root is not a sidebar group — the value is ignored.`,\n severity: \"warning\",\n suggestion:\n \"Set navigation.sidebar.display in blume.config to change the sidebar-wide mode.\",\n });\n continue;\n }\n if (isIndex) {\n continue;\n }\n diagnostics.push({\n code: \"BLUME_SIDEBAR_DISPLAY_IGNORED\",\n file: page.sourcePath ?? page.id,\n message: `\"${page.navPath}\" sets sidebar.display, but only a folder's index page can set its group's display mode — the value is ignored.`,\n severity: \"warning\",\n suggestion:\n \"Move display to the folder's index page frontmatter, or set it in the folder's meta.ts.\",\n });\n }\n return diagnostics;\n};\n\n/** Whether a node's `order` reflects a deliberate authoring choice. */\nconst isAuthoredOrder = (node: MutableNode): boolean =>\n node.kind === \"group\" || node.orderIsAuthored;\n\n/**\n * Warn when two sibling nodes share an explicit/numeric order (frontmatter\n * `sidebar.order`, a numeric filename prefix, or folder-meta `order`) — they'd\n * otherwise fall back to a silent, arbitrary alphabetical tiebreak. Nodes at\n * the default fallback order (no numeric prefix, no explicit order) are\n * excluded: that's the common, intentional case of \"just sort alphabetically.\"\n * So is a derived, non-authored order (e.g. two changelog entries published\n * on the same day) — not an authoring mistake.\n */\nconst duplicateOrderDiagnostics = (nodes: MutableNode[]): Diagnostic[] => {\n const byOrder = new Map<number, MutableNode[]>();\n for (const node of nodes) {\n if (!Number.isFinite(node.order) || !isAuthoredOrder(node)) {\n continue;\n }\n const tied = byOrder.get(node.order);\n if (tied) {\n tied.push(node);\n } else {\n byOrder.set(node.order, [node]);\n }\n }\n const diagnostics: Diagnostic[] = [];\n for (const [order, tied] of byOrder) {\n if (tied.length > 1) {\n const names = tied.map((node) => `\"${node.label}\"`);\n const list =\n names.length > 2\n ? `${names.slice(0, -1).join(\", \")}, and ${names.at(-1)}`\n : names.join(\" and \");\n const verb = tied.length > 2 ? \"all have\" : \"both have\";\n // Anchor the diagnostic to one tied source file so tooling can point\n // somewhere concrete; the message names the rest. Folder-only ties\n // (folder-meta `order`) have no single file, so `file` stays unset.\n const file = tied.find(\n (node): node is MutablePage => node.kind === \"page\"\n )?.file;\n diagnostics.push({\n code: \"BLUME_DUPLICATE_SIDEBAR_ORDER\",\n file,\n message: `${list} ${verb} sidebar order ${order}; falling back to alphabetical order.`,\n severity: \"warning\",\n suggestion:\n \"Give each item a distinct sidebar.order (or folder meta order).\",\n });\n }\n }\n return diagnostics;\n};\n\nconst sortNodes = (nodes: MutableNode[], diagnostics: Diagnostic[]): void => {\n diagnostics.push(...duplicateOrderDiagnostics(nodes));\n nodes.sort((a, b) => {\n if (a.order !== b.order) {\n return a.order - b.order;\n }\n return a.label.localeCompare(b.label);\n });\n for (const node of nodes) {\n if (node.kind === \"group\") {\n sortNodes(node.children, diagnostics);\n }\n }\n};\n\n/**\n * Hoist loose pages above groups so they read as their own level's entries\n * rather than a preceding group's trailing children (relative order otherwise\n * preserved). The root level always hoists, in every display mode. A deeper\n * level hoists only when a sibling group renders flat — a flat group is a\n * plain section header, so a loose page sorted after it would visually read\n * as its last child — while `group`/`page` groups are self-delimiting\n * disclosure/drill-in rows, so authored interleaving is kept. Each group's\n * render mode is its own resolved `display` falling back to the global mode,\n * mirroring `toNavNode`.\n */\nconst hoistPages = (\n nodes: MutableNode[],\n display: SidebarDisplay,\n hoist: boolean\n): void => {\n const groups = nodes.filter((node) => node.kind === \"group\");\n if (hoist || groups.some((group) => (group.display ?? display) === \"flat\")) {\n const pages = nodes.filter((node) => node.kind === \"page\");\n nodes.splice(0, nodes.length, ...pages, ...groups);\n }\n for (const group of groups) {\n hoistPages(group.children, display, false);\n }\n};\n\n/**\n * With tabs configured, the sidebar shown under a tab is that tab-section\n * group's *children*, not the tree root — so hoisting only the root leaves a\n * tab section's loose pages interleaved with its groups. Hoist the top level of\n * every group that owns a tab (matched on its URL path), mirroring the root.\n */\nconst hoistTabSections = (\n nodes: MutableNode[],\n tabPaths: Set<string>,\n display: SidebarDisplay\n): void => {\n for (const node of nodes) {\n if (node.kind !== \"group\") {\n continue;\n }\n if (node.routePath !== undefined && tabPaths.has(node.routePath)) {\n hoistPages(node.children, display, true);\n }\n hoistTabSections(node.children, tabPaths, display);\n }\n};\n\nconst toNavNode = (node: MutableNode, display: SidebarDisplay): NavNode => {\n if (node.kind === \"page\") {\n return {\n badge: node.badge,\n deprecated: node.deprecated || undefined,\n description: node.description,\n icon: node.icon,\n kind: \"page\",\n label: node.label,\n pageId: node.pageId,\n route: node.route,\n };\n }\n return {\n children: node.children.map((child) => toNavNode(child, display)),\n collapsed: node.collapsed,\n display: node.display ?? display,\n icon: node.icon,\n kind: \"group\",\n label: node.label,\n path: node.routePath,\n route: node.route,\n };\n};\n\n/** Build the sidebar tree from the file system and folder meta. */\nconst buildFileSystemSidebar = (\n pages: PageRecord[],\n folderMeta: Map<string, FolderMeta>,\n sharedMeta: Map<string, FolderMeta>,\n metaPrefix: string,\n sharedMetaPrefix: string,\n display: SidebarDisplay,\n tabPaths: Set<string>,\n diagnostics: Diagnostic[] = []\n): NavNode[] => {\n const root = createGroup(\"\", \"\", \"\", 0);\n // Folder path -> `sidebar.display` from that folder's index page frontmatter.\n // Collected before the hidden filter (like the title check): hiding the index\n // row from the panel shouldn't stop it configuring its group.\n const indexDisplay = new Map<string, SidebarDisplay>();\n // Folder path -> that folder's index page route, for the group row's link.\n // Also collected before the hidden filter: hiding the index row is how a\n // site drops the duplicate label under a linked header, so the link must\n // survive it. The content root is not a group, so its index is skipped.\n const indexRoute = new Map<string, string>();\n\n for (const page of pages) {\n // Group by the locale-stripped path so the locale dir is not a nav group.\n const parts = page.navPath.split(\"/\");\n const filename = parts.at(-1) ?? page.navPath;\n const stem = stemOf(filename);\n const dirs = parts.slice(0, -1);\n\n // Checked before the hidden filter: a sidebar-hidden index page still\n // renders with its own <title>, so title drift matters there just the same.\n if (isIndexStem(stem)) {\n const diagnostic = indexTitleMismatchDiagnostic(\n page,\n dirs.join(\"/\"),\n folderMeta,\n sharedMeta,\n metaPrefix,\n sharedMetaPrefix\n );\n if (diagnostic) {\n diagnostics.push(diagnostic);\n }\n // Fallback-filled pages are exempt (like the title check above): their\n // frontmatter belongs to the fallback locale, and letting it through\n // would override this locale's own authored `meta.ts` display — then\n // silently flip again the day the index gets translated.\n if (page.meta.sidebar.display && !page.fallback) {\n indexDisplay.set(dirs.join(\"/\"), page.meta.sidebar.display);\n }\n if (dirs.length > 0) {\n indexRoute.set(dirs.join(\"/\"), page.route);\n }\n }\n\n if (page.meta.sidebar.hidden) {\n continue;\n }\n\n // Each group's URL path is the matching prefix of the page's route. navPath\n // is locale-stripped while the route may carry a locale/base prefix, so\n // align the folder segments from the right (the extra leading segments are\n // that prefix). Under such a prefix the path won't match a logical tab path,\n // so tab-scoping simply no-ops — same as the header's active-tab logic.\n // An index page's route IS its folder's route (no page segment to drop),\n // and `(group)` folders contribute no route segment at all.\n const routeSegments = page.route.split(\"/\").filter(Boolean);\n const folderParts = isIndexStem(stem)\n ? routeSegments\n : routeSegments.slice(0, -1);\n const routeDirCount = dirs.filter((dir) => !GROUP_FOLDER.test(dir)).length;\n const offset = Math.max(0, folderParts.length - routeDirCount);\n\n let parent = root;\n let consumed = offset;\n for (const dir of dirs) {\n parent = ensureGroup(parent, dir);\n if (!GROUP_FOLDER.test(dir)) {\n consumed += 1;\n }\n parent.routePath ??= `/${folderParts.slice(0, consumed).join(\"/\")}`;\n }\n\n const { order, orderIsAuthored } = pageOrder(page, filename);\n parent.children.push({\n badge: page.meta.sidebar.badge,\n deprecated: page.meta.deprecated || undefined,\n description: page.description,\n file: page.sourcePath,\n icon: page.meta.sidebar.icon,\n key: segmentKey(stem),\n kind: \"page\",\n label: page.meta.sidebar.label ?? page.title,\n order,\n orderIsAuthored,\n pageId: page.id,\n route: page.route,\n });\n }\n\n applyFolderMeta(\n root,\n folderMeta,\n sharedMeta,\n metaPrefix,\n sharedMetaPrefix,\n indexDisplay,\n indexRoute\n );\n sortNodes(root.children, diagnostics);\n hoistPages(root.children, display, true);\n hoistTabSections(root.children, tabPaths, display);\n return root.children.map((child) => toNavNode(child, display));\n};\n\nconst normalizeRef = (ref: string): string => {\n if (ref === \"index\") {\n return \"/\";\n }\n const withSlash = ref.startsWith(\"/\") ? ref : `/${ref}`;\n // Routes are stored slashless (`/guides`, not `/guides/`); a hand-written\n // `\"guides/\"` ref must still find its page instead of being silently\n // dropped from the sidebar.\n const noTrailing = withSlash.replace(/\\/+$/u, \"\");\n const trimmed = noTrailing.endsWith(\"/index\")\n ? noTrailing.slice(0, -\"/index\".length)\n : noTrailing;\n // \"/index\" trims to \"\" — that's the root, not an empty route.\n return trimmed === \"\" ? \"/\" : trimmed;\n};\n\nconst routeForRef = (\n ref: string | undefined,\n byRoute: Map<string, PageRecord>,\n basePath: string\n): string | undefined => {\n if (!ref) {\n return undefined;\n }\n const normalized = normalizeRef(ref);\n // A matched page carries an already-based `route`; an unmatched ref is an\n // author-written root-relative path that still needs the base applied.\n return byRoute.get(normalized)?.route ?? withBasePath(basePath, normalized);\n};\n\n/** An explicit-config sidebar item written as a bare page-ref string. */\nconst isPageRef = (item: SidebarItemConfig): item is string =>\n typeof item === \"string\";\n\n/**\n * Convert one non-group explicit-config sidebar item (string ref, `root`, or\n * `href`) to a nav node, or null to skip. Group items (`item.items`) are handled\n * by `buildConfigSidebar` itself so it owns the recursion.\n */\nconst configItemToNode = (\n item: SidebarItemConfig,\n byRoute: Map<string, PageRecord>,\n basePath: string\n): NavNode | null => {\n if (isPageRef(item)) {\n const page = byRoute.get(normalizeRef(item));\n if (!page) {\n return null;\n }\n return {\n badge: page.meta.sidebar.badge,\n deprecated: page.meta.deprecated || undefined,\n description: page.description,\n icon: page.meta.sidebar.icon,\n kind: \"page\",\n label: page.meta.sidebar.label ?? page.title,\n pageId: page.id,\n route: page.route,\n };\n }\n\n if (item.root) {\n const page = byRoute.get(normalizeRef(item.root));\n return {\n badge: item.badge,\n deprecated: page?.meta.deprecated || undefined,\n icon: item.icon,\n kind: \"page\",\n label: item.label,\n pageId: page?.id ?? \"\",\n route: page?.route ?? withBasePath(basePath, normalizeRef(item.root)),\n };\n }\n\n if (item.href) {\n return {\n badge: item.badge,\n icon: item.icon,\n kind: \"page\",\n label: item.label,\n pageId: \"\",\n route: withBasePath(basePath, item.href),\n };\n }\n\n return null;\n};\n\n/** Build the sidebar tree from an explicit config spec. */\nconst buildConfigSidebar = (\n items: SidebarItemConfig[],\n byRoute: Map<string, PageRecord>,\n display: SidebarDisplay,\n basePath: string\n): NavNode[] => {\n const nodes: NavNode[] = [];\n for (const item of items) {\n if (!isPageRef(item) && item.items) {\n nodes.push({\n badge: item.badge,\n children: buildConfigSidebar(item.items, byRoute, display, basePath),\n collapsed: item.collapsed,\n directory: item.directory,\n display: item.display ?? display,\n icon: item.icon,\n kind: \"group\",\n label: item.label,\n route: routeForRef(item.root, byRoute, basePath),\n });\n continue;\n }\n const node = configItemToNode(item, byRoute, basePath);\n if (node) {\n nodes.push(node);\n }\n }\n return nodes;\n};\n\n/**\n * Resolve a tab's clickable target. A tab's `path` scopes its sidebar section\n * but need not be a real route — a section with no index page would 404 if the\n * tab linked straight to it. Prefer an exact page/group at the path; otherwise\n * fall back to the first linkable route in the section (sidebar order).\n */\nconst resolveTabHref = (sidebar: NavNode[], path: string): string => {\n let first: string | undefined;\n const walk = (nodes: NavNode[]): boolean => {\n for (const node of nodes) {\n const { route } = node;\n if (route === path) {\n return true;\n }\n if (\n first === undefined &&\n route !== undefined &&\n route.startsWith(`${path}/`)\n ) {\n first = route;\n }\n if (node.kind === \"group\" && walk(node.children)) {\n return true;\n }\n }\n return false;\n };\n return walk(sidebar) ? path : (first ?? path);\n};\n\n/**\n * Attach a resolved `href` to each tab whose section has no index page. An\n * author-declared `href` is the tab's stated target, so it's kept as-is —\n * resolution only fills in the tabs that didn't declare one. That's what lets a\n * tab point at a route outside the content tree (a generated `/changelog`\n * index, a custom `.astro` page), which resolution can't see.\n */\nconst withTabHrefs = (tabs: NavTab[], sidebar: NavNode[]): NavTab[] =>\n tabs.map((tab) => {\n const href = tab.href ?? resolveTabHref(sidebar, tab.path);\n return href === tab.path ? tab : { ...tab, href };\n });\n\n/**\n * Rebase config-provided nav chrome (featured links, selectors, tabs) under the\n * site base path. Config paths are authored as if mounted at root, so the base\n * is applied here (idempotently, and only to internal paths — external URLs\n * pass through). With no base this is a pure pass-through — the arrays keep\n * their exact authored shape.\n */\ninterface NavChrome {\n actions: HeaderAction[];\n cta: HeaderAction | null;\n featured: FeaturedLink[];\n selectors: NavSelector[];\n tabs: NavTab[];\n}\n\nconst rebaseNavChrome = (\n basePath: string,\n options: {\n actions?: HeaderAction[];\n cta?: HeaderAction | null;\n featured?: FeaturedLink[];\n selectors?: NavSelector[];\n tabs?: NavTab[];\n }\n): NavChrome => {\n const actions = options.actions ?? [];\n const cta = options.cta ?? null;\n const featured = options.featured ?? [];\n const selectors = options.selectors ?? [];\n const tabs = options.tabs ?? [];\n if (!basePath) {\n return { actions, cta, featured, selectors, tabs };\n }\n const rebaseHref = <T extends { href: string }>(item: T): T => ({\n ...item,\n href: withBasePath(basePath, item.href),\n });\n const rebasePath = <T extends { path: string }>(item: T): T => ({\n ...item,\n path: withBasePath(basePath, item.path),\n });\n return {\n actions: actions.map(rebaseHref),\n cta: cta ? rebaseHref(cta) : null,\n featured: featured.map(rebaseHref),\n selectors: selectors.map((selector) => ({\n ...selector,\n items: selector.items.map(rebasePath),\n })),\n tabs: tabs.map((tab) => {\n const rebased: NavTab = {\n ...tab,\n items: tab.items?.map(rebasePath),\n path: withBasePath(basePath, tab.path),\n };\n if (tab.href) {\n rebased.href = withBasePath(basePath, tab.href);\n }\n return rebased;\n }),\n };\n};\n\n/** Build the complete navigation model from pages, meta, and config. */\nexport const buildNavigation = (\n pages: PageRecord[],\n options: {\n /** Plain links in the header, left of the icon buttons. */\n actions?: HeaderAction[];\n /** Site-wide route mount point (`\"\"` or `/seg`); applied to config paths. */\n basePath?: string;\n /** The single primary call to action in the header. */\n cta?: HeaderAction | null;\n folderMeta: Map<string, FolderMeta>;\n /** Global display mode for every sidebar group (default `flat`). */\n display?: SidebarDisplay;\n featured?: FeaturedLink[];\n selectors?: NavSelector[];\n tabs?: NavTab[];\n sidebar?: SidebarItemConfig[];\n /**\n * Folder-meta lookup prefix (`\"\"` for the default locale of the current\n * version): the version dir and/or locale dir hoisted in front of the\n * group path, e.g. `fr`, `v1.0`, or `v1.0/fr`.\n */\n metaPrefix?: string;\n /**\n * Prefix for shared `meta.$.*` lookups — the version dir inside a\n * snapshot (`v1.0`), since shared meta is locale-agnostic but still\n * version-specific. `\"\"` for the current version.\n */\n sharedMetaPrefix?: string;\n /**\n * Resolve explicit-sidebar references against each page's locale-agnostic\n * `translationKey` instead of its localized `route`. Used under i18n so a\n * single authored sidebar maps onto every locale's pages.\n */\n refByLogical?: boolean;\n /** Shared `meta.$.*` meta, keyed by locale-stripped dir path. */\n sharedFolderMeta?: Map<string, FolderMeta>;\n /**\n * The tree's root route before `basePath` (`\"/\"`, or the locale prefix\n * under i18n, e.g. `/fr` — tab paths arrive already localized). The tab\n * pointing here spans the whole tree, so it is excluded from tab-section\n * scoping.\n */\n localizedRoot?: string;\n /**\n * Sink for diagnostics produced while building the tree (duplicate sidebar\n * `order` values, index-page title/folder-meta-title mismatches). Pushed\n * into in place; omit to discard.\n */\n diagnostics?: Diagnostic[];\n }\n): Navigation => {\n const basePath = options.basePath ?? \"\";\n const display = options.display ?? \"flat\";\n const metaPrefix = options.metaPrefix ?? \"\";\n const sharedMetaPrefix = options.sharedMetaPrefix ?? \"\";\n const sharedFolderMeta = options.sharedFolderMeta ?? new Map();\n\n // Content-derived sidebar routes are already based via `page.route`; the\n // based tab paths also feed tab-scoping below, so they must agree with the\n // based content routes.\n const { actions, cta, featured, selectors, tabs } = rebaseNavChrome(\n basePath,\n options\n );\n const byRoute = new Map(\n pages.map((page) => [\n options.refByLogical ? page.translationKey : page.route,\n page,\n ])\n );\n // Explicit-sidebar refs (`\"foo/index\"`) are authored as if mounted at root,\n // but `page.route` carries the base — alias each page under its base-less\n // route so a bare ref still resolves. (The i18n `refByLogical` map is already\n // keyed by the base-less `translationKey`, so it needs no alias.)\n if (basePath && !options.refByLogical) {\n for (const page of pages) {\n const bare = stripBasePath(basePath, page.route);\n if (!byRoute.has(bare)) {\n byRoute.set(bare, page);\n }\n }\n }\n\n // A tab pointing at the tree root spans the whole sidebar rather than one\n // section, so it must not feed tab-section hoisting. `tabs` carries final\n // paths (localized, then based), so the root is compared in the same space —\n // a root-level `(group)` folder's routePath is exactly the based/localized\n // prefix (`/docs`, `/fr`) and a bare `\"/\"` check would miss the match (or,\n // under a base, falsely scope a group named like the prefix). Carried on the\n // returned navigation so render-time scoping compares in the same space too.\n // In a version snapshot the root arrives versionized (`/v1.0`) while tab\n // paths stay in current-docs space, so root-tab checks use `isRootTab`\n // containment, not equality.\n const rootTabPath = withBasePath(basePath, options.localizedRoot ?? \"/\");\n\n // Emitted here, before the sidebar-mode branch: an explicit config sidebar\n // ignores a stray `sidebar.display` just like the filesystem sidebar does\n // (and ignores it on index pages too), so both paths must warn.\n const diagnostics = options.diagnostics ?? [];\n diagnostics.push(\n ...sidebarDisplayIgnoredDiagnostics(pages, Boolean(options.sidebar))\n );\n\n if (options.sidebar) {\n const sidebar = buildConfigSidebar(\n options.sidebar,\n byRoute,\n display,\n basePath\n );\n return {\n actions,\n cta,\n featured,\n root: rootTabPath,\n selectors,\n sidebar,\n tabs: withTabHrefs(tabs, sidebar),\n };\n }\n\n const sidebar = buildFileSystemSidebar(\n pages,\n options.folderMeta,\n sharedFolderMeta,\n metaPrefix,\n sharedMetaPrefix,\n display,\n new Set(\n tabs.flatMap((tab) => (isRootTab(tab, rootTabPath) ? [] : [tab.path]))\n ),\n diagnostics\n );\n return {\n actions,\n cta,\n featured,\n root: rootTabPath,\n selectors,\n sidebar,\n tabs: withTabHrefs(tabs, sidebar),\n };\n};\n",
|
|
12
|
-
"import { localizeRoute } from \"./i18n.ts\";\nimport type {\n ArchivedVersionConfig,\n ResolvedConfig,\n ResolvedI18nConfig,\n ResolvedVersionsConfig,\n} from \"./schema.ts\";\nimport type { Diagnostic, PageRecord } from \"./types.ts\";\n\n/**\n * Version logic, centralized. Every seam that needs to reason about docs\n * versions (content discovery, navigation, manifest, runtime generation, the\n * catch-all) goes through these helpers so the routing rules live in exactly\n * one place — the same contract `./i18n.ts` holds for locales.\n *\n * The current version is the empty string `\"\"`: the latest docs live at the\n * content root with unprefixed URLs, and only archived snapshots have a\n * directory and a URL segment.\n */\n\n/**\n * Top-level folders that look like a version (`v1`, `V2.0`) — used by the\n * unconfigured-snapshot diagnostic and by `blume version` to keep such folders\n * out of new snapshots.\n */\nexport const VERSION_LIKE = /^v\\d/iu;\n\n/** True when the project opts into versioning. */\nexport const versionsEnabled = (\n config: ResolvedConfig\n): config is ResolvedConfig & { versions: ResolvedVersionsConfig } =>\n config.versions !== undefined;\n\n/** All archived version ids, in configured (switcher) order. */\nexport const archivedIds = (versions: ResolvedVersionsConfig): string[] =>\n versions.archived.map((version) => version.id);\n\n/** Switcher label for a version: the current label for `\"\"`, else per config. */\nexport const versionLabel = (\n id: string,\n versions: ResolvedVersionsConfig\n): string => {\n if (id === \"\") {\n return versions.current.label;\n }\n const archived = versions.archived.find((version) => version.id === id);\n return archived?.label ?? id;\n};\n\n/** The archived version config for an id (`undefined` for current/unknown). */\nexport const archivedVersion = (\n id: string,\n versions: ResolvedVersionsConfig\n): ArchivedVersionConfig | undefined =>\n versions.archived.find((version) => version.id === id);\n\n/** A resolved version (`\"\"` for current) and the version-stripped remainder. */\nexport interface DetectedVersion {\n version: string;\n rest: string[];\n}\n\n/** {@link DetectedVersion} for a slash-joined source ref. */\nexport interface DetectedVersionRef {\n version: string;\n rest: string;\n}\n\n/**\n * Detect a leading archived-version directory in a path's segments. The\n * current version lives at the content root, so only archived ids are matched\n * as a leading segment. Returns the resolved version (`\"\"` for current) and\n * the remaining (version-stripped) segments. Runs BEFORE locale detection —\n * the snapshot directory is outermost on disk, so the locale parser must see\n * a version-stripped path.\n */\nexport const detectVersion = (\n parts: string[],\n versions: ResolvedVersionsConfig\n): DetectedVersion => {\n const [first] = parts;\n if (first && versions.archived.some((version) => version.id === first)) {\n return { rest: parts.slice(1), version: first };\n }\n return { rest: parts, version: \"\" };\n};\n\n/**\n * {@link detectVersion} over a slash-joined source ref (`v1.0/fr/x.mdx`):\n * returns the version and the version-stripped ref for the locale parser and\n * route mapping to consume.\n */\nexport const detectVersionRef = (\n ref: string,\n versions: ResolvedVersionsConfig\n): DetectedVersionRef => {\n const { version, rest } = detectVersion(ref.split(\"/\"), versions);\n return { rest: rest.join(\"/\"), version };\n};\n\n/**\n * Prefix a version-agnostic logical route with its version: `/guides/x`\n * becomes `/v1.0/guides/x`, `/` becomes `/v1.0`. The current version (`\"\"`)\n * is the identity.\n */\nexport const versionizeRoute = (\n logicalRoute: string,\n version: string\n): string => {\n if (!version) {\n return logicalRoute;\n }\n return logicalRoute === \"/\" ? `/${version}` : `/${version}${logicalRoute}`;\n};\n\n/**\n * The localized root route of a version: `/`, `/fr`, `/v1.0`, or `/fr/v1.0`.\n * The locale prefix stays outermost, matching how page routes compose\n * (`localizeRoute(versionizeRoute(...))`).\n */\nexport const versionRoot = (\n version: string,\n locale?: string,\n i18n?: ResolvedI18nConfig\n): string => {\n const logical = versionizeRoute(\"/\", version);\n if (i18n && locale !== undefined) {\n return localizeRoute(logical, locale, i18n);\n }\n return logical;\n};\n\n/**\n * Warn about top-level content folders shaped like a version (`v1`, `v2.0`)\n * that aren't declared in `versions.archived`. Without this they're silently\n * treated as current-version content under a `/<folder>/…` route — usually a\n * snapshot that wasn't registered in config.\n */\nexport const versionsDiagnostics = (\n pages: PageRecord[],\n versions: ResolvedVersionsConfig\n): Diagnostic[] => {\n // Exact-id comparison, matching `detectVersion`: a folder that differs from\n // a configured id only by case (`V1.0/` vs `v1.0`) is NOT routed as that\n // snapshot, so it must still warn. `VERSION_LIKE` is case-insensitive on\n // its own.\n const configured = new Set(versions.archived.map((version) => version.id));\n const seen = new Set<string>();\n const diagnostics: Diagnostic[] = [];\n for (const page of pages) {\n // The version-looking folder is the first segment of the source-local ref\n // (e.g. `v1.0/guide.md`), not the namespaced id (`filesystem:v1.0/guide.md`).\n const [first] = page.source.ref.split(\"/\");\n if (\n first &&\n !seen.has(first) &&\n VERSION_LIKE.test(first) &&\n !configured.has(first)\n ) {\n seen.add(first);\n diagnostics.push({\n code: \"BLUME_VERSIONS_UNCONFIGURED_VERSION\",\n message: `Folder \"${first}/\" looks like a version, but \"${first}\" is not in versions.archived — its pages are treated as current-version content at /${first}/….`,\n severity: \"warning\",\n suggestion: `Add { id: \"${first}\" } to versions.archived, or rename the folder if it isn't a snapshot.`,\n });\n }\n }\n return diagnostics;\n};\n",
|
|
13
|
-
"import { withBasePath } from \"./base-path.ts\";\nimport { localizeRoute, resolveFallbackLocale } from \"./i18n.ts\";\nimport { validateNavIcons, validateNavStructure } from \"./nav-diagnostics.ts\";\nimport { buildNavigation } from \"./navigation.ts\";\nimport type {\n FolderMeta,\n LocalizableLabel,\n ResolvedConfig,\n ResolvedI18nConfig,\n ResolvedVersionsConfig,\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 /** Site-wide route mount point (`\"\"` or `/seg`); invisible to the nav tree. */\n basePath?: 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: withBasePath(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/** 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 * 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 // Localize internal tab paths — the tab's own and its dropdown items' — so a\n // header tab points to its in-locale route (e.g. `/docs` -> `/fr/docs`);\n // external paths pass through. Selectors are left alone: a language\n // selector's items intentionally target specific locales.\n // `//host/path` is protocol-relative — an external URL that happens to start\n // with a slash, so it must not pick up a locale prefix. `withBasePath` draws\n // the same line for the same reason.\n const localizePath = (path: string): string =>\n path.startsWith(\"/\") && !path.startsWith(\"//\")\n ? localizeRoute(path, code, i18n)\n : path;\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 = localizePath(localized.href);\n }\n return localized;\n });\n const real = pages.filter((page) => page.locale === code);\n const localePages = localePagesFor(\n code,\n real,\n fallback,\n fallbackByKey,\n i18n,\n options.basePath ?? \"\"\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 localeDir =\n i18n.parser === \"dir\" && code !== i18n.defaultLocale ? code : \"\";\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: localizePath(item.href),\n });\n const { actions, cta, featured } = options.navigation;\n\n return buildNavigation(localePages, {\n actions: actions?.map(localizeHref),\n basePath: options.basePath ?? \"\",\n cta: cta ? localizeHref(cta) : null,\n diagnostics,\n display: options.navigation.sidebar.display,\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.\n sidebar: version ? undefined : options.navigation.sidebar.items,\n tabs,\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 const fallbackByKey = new Map<string, PageRecord>();\n if (fallback) {\n for (const page of pages) {\n if (page.locale === fallback) {\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 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 // 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 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 }\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\n return {\n diagnostics,\n navigation,\n navigationByLocale,\n navigationByVersion,\n pages,\n routes,\n };\n};\n",
|
|
14
|
-
"import { readFile } from \"node:fs/promises\";\n\nimport { dirname, extname, join, relative, resolve } from \"pathe\";\n\nimport { nextFenceState } from \"./code-fences.ts\";\nimport type { FenceState } from \"./code-fences.ts\";\nimport matter from \"./frontmatter.ts\";\nimport { INLINE_CODE, MD_IMAGE, targetOffsetIn } from \"./sources/normalize.ts\";\nimport type { Diagnostic } from \"./types.ts\";\n\n/**\n * Content includes: `<include>./relative.mdx</include>` on a line of its own\n * splices another file into the page at build time — the syntax Fumadocs\n * ships, so migrated content works unchanged. Markdown/MDX targets are\n * spliced as content (front matter stripped, nested includes resolved, cycle\n * detection); any other extension — or an explicit `lang` attribute — embeds\n * the file as a fenced code block, with optional `meta` for the fence meta\n * string (e.g. `title=\"config.ts\"`).\n *\n * This module is the single owner of include semantics. The string-level\n * {@link expandIncludes} powers every surface that reads raw markdown source\n * (heading/link extraction, search indexing, the `/route.md` mirrors,\n * llms-full.txt), while {@link expandIncludeTarget} powers the Sätteri render\n * plugin, so what renders and what's indexed can't drift apart.\n */\n\n/** Docs page targets: `.md`/`.mdx` splice as content; anything else is code. */\nconst CONTENT_EXTENSIONS = new Set([\".md\", \".mdx\"]);\n\n/**\n * A full include statement occupying one (trimmed) line: optional lowercase\n * attributes, then the target path as the element's text. Attribute values\n * accept both quote styles; a bare attribute (no `=`) is allowed so future\n * boolean flags parse rather than break the statement match.\n *\n * The target is anchored to non-space characters on both ends (rather than a\n * lazy run trimmed by the following `\\s*`): a lazy `[^<>]*?` overlapping that\n * `\\s*` backtracks quadratically on inputs like `<include>x` plus a long run\n * of spaces with no closing tag (CodeQL js/polynomial-redos). With both ends\n * pinned, a trailing space run is consumed by `\\s*` alone and a failed match\n * stays linear.\n */\nconst INCLUDE_STATEMENT =\n /^<include(?<attrs>(?:\\s+[a-z][\\w-]*(?:\\s*=\\s*(?:\"[^\"]*\"|'[^']*'))?)*)\\s*>\\s*(?<target>[^<>\\s](?:[^<>]*[^<>\\s])?)\\s*<\\/include\\s*>$/u;\n\n/** One attribute within a statement's attrs run. */\nconst INCLUDE_ATTRIBUTE =\n /(?<name>[a-z][\\w-]*)(?:\\s*=\\s*(?:\"(?<double>[^\"]*)\"|'(?<single>[^']*)'))?/gu;\n\n/**\n * Cheap pre-filter so include-free content (the overwhelmingly common case)\n * skips the line scan and file reads entirely.\n */\nexport const hasIncludeStatements = (text: string): boolean =>\n text.includes(\"<include\");\n\n/** A line indented four spaces (or a tab) — an indented code block in `.md`,\n * where the renderer shows a statement literally instead of splicing it. */\nconst INDENTED_CODE = /^(?: {4}|\\t)/u;\n\n/** A setext underline (`===`/`---`, up to 3 leading spaces): directly under a\n * statement line it folds the statement into a heading instead of a splice. */\nconst SETEXT_UNDERLINE = /^ {0,3}(?:=+|-+)\\s*$/u;\n\n/** Comment delimiters per format: `.md` uses HTML comments, `.mdx` JSX ones. */\nconst COMMENT_DELIMITERS = {\n md: { close: \"-->\", open: \"<!--\" },\n mdx: { close: \"*/}\", open: \"{/*\" },\n} as const;\n\n/**\n * Advance the comment state across one line: each open/close marker toggles\n * it, left to right. A line that *matches* a statement can't contain a marker\n * (the statement regex spans the whole trimmed line), so per-line state at the\n * line's start is all the statement scan needs.\n */\nconst advanceCommentState = (\n line: string,\n delimiters: { open: string; close: string },\n inComment: boolean\n): boolean => {\n let inside = inComment;\n let cursor = 0;\n for (;;) {\n const marker = inside ? delimiters.close : delimiters.open;\n const index = line.indexOf(marker, cursor);\n if (index === -1) {\n return inside;\n }\n inside = !inside;\n cursor = index + marker.length;\n }\n};\n\n/** The attributes an include statement supports. */\nexport interface IncludeAttributes {\n /** Force code-block mode with this language (even for `.md`/`.mdx`). */\n lang?: string;\n /** Fence meta string in code-block mode (e.g. `title=\"lib.ts\"`). */\n meta?: string;\n}\n\n/** A parsed include statement. */\nexport interface IncludeStatement {\n target: string;\n attributes: IncludeAttributes;\n}\n\n/**\n * Parse one trimmed line as an include statement, or `null` when it isn't\n * one. Only `lang` and `meta` are meaningful; unknown attributes parse and\n * are ignored so a future attribute degrades gracefully on older versions.\n */\nexport const parseIncludeStatement = (\n line: string\n): IncludeStatement | null => {\n const match = INCLUDE_STATEMENT.exec(line);\n if (!match?.groups) {\n return null;\n }\n const attributes: IncludeAttributes = {};\n for (const attr of (match.groups.attrs ?? \"\").matchAll(INCLUDE_ATTRIBUTE)) {\n const name = attr.groups?.name;\n const value = attr.groups?.double ?? attr.groups?.single;\n if (name === \"lang\" && value) {\n attributes.lang = value;\n }\n if (name === \"meta\" && value) {\n attributes.meta = value;\n }\n }\n return { attributes, target: match.groups.target ?? \"\" };\n};\n\n/**\n * Parse one raw `.md` source line as an include statement, applying the\n * line-level rules the renderer sees: a line indented like code (4 spaces or\n * a tab) is an indented code block, shown literally rather than spliced. The\n * render plugin's `.md` visitors share this so the two halves can't drift.\n */\nexport const parseIncludeLine = (line: string): IncludeStatement | null =>\n INDENTED_CODE.test(line) ? null : parseIncludeStatement(line.trim());\n\n/**\n * Advance the HTML comment state across one `.md` line — the render plugin's\n * half of the comment rule (a statement inside `<!-- -->` never splices),\n * sharing the same tracking the string-level scanner uses.\n */\nexport const advanceHtmlCommentState = (\n line: string,\n inComment: boolean\n): boolean => advanceCommentState(line, COMMENT_DELIMITERS.md, inComment);\n\n/** Provenance of one line of expanded output. */\nexport interface LineOrigin {\n /** Absolute path of the file the line came from. */\n file: string;\n /** 1-based line number within that file's raw source. */\n line: number;\n}\n\n/** The result of expanding a document's include statements. */\nexport interface IncludeExpansion {\n /** The document with every include statement replaced by its content. */\n text: string;\n /** Per output line: the source file and raw-file line it came from. */\n origins: LineOrigin[];\n /** Absolute paths of every file included, transitively. */\n includes: string[];\n /** Structured errors (missing target, cycle, escape); statements with an\n * error stay verbatim in the output. */\n errors: Diagnostic[];\n}\n\ninterface ExpandContext {\n /** The owning source's content root; bounds resolution when set. */\n contentRoot?: string;\n includes: Set<string>;\n errors: Diagnostic[];\n}\n\nconst DOCS_SUGGESTION =\n \"Include paths resolve relative to the including file; paths starting with / resolve from the content root.\";\n\nconst includeError = (\n code: string,\n message: string,\n file: string,\n line: number,\n suggestion: string\n): Diagnostic => ({ code, file, line, message, severity: \"error\", suggestion });\n\n/**\n * Whether `path` lies outside `root`. The relative path must be `..` itself or\n * start with a `../` *segment* — a bare `.startsWith(\"..\")` would also reject\n * a legal in-root directory whose name begins with two dots (`..archive/`).\n */\nconst escapesRoot = (root: string, path: string): boolean => {\n const rel = relative(root, path);\n return rel === \"..\" || rel.startsWith(\"../\");\n};\n\n/**\n * Resolve a statement's target to an absolute path, or an error when it can't\n * be resolved safely. `/`-leading targets resolve from the content root;\n * everything else resolves from the including file's directory. Targets must\n * stay within the content root — a partial outside it would be silently\n * dropped from version snapshots and ejects (and an escaping path could\n * splice arbitrary files into published pages). Both branches enforce the\n * bound: a root-relative target can still climb out through `..` segments.\n */\nconst resolveIncludePath = (\n target: string,\n filePath: string,\n ctx: ExpandContext,\n line: number\n): { path: string } | { error: Diagnostic } => {\n if (target.startsWith(\"/\")) {\n if (!ctx.contentRoot) {\n return {\n error: includeError(\n \"BLUME_INCLUDE_OUTSIDE_ROOT\",\n `Include target ${target} is root-relative, but no content root is configured here.`,\n filePath,\n line,\n \"Use a path relative to the including file instead.\"\n ),\n };\n }\n const path = join(ctx.contentRoot, target);\n if (escapesRoot(ctx.contentRoot, path)) {\n return {\n error: includeError(\n \"BLUME_INCLUDE_OUTSIDE_ROOT\",\n `Include target ${target} resolves outside the content root.`,\n filePath,\n line,\n \"Root-relative include paths resolve from the content root and cannot climb above it.\"\n ),\n };\n }\n return { path };\n }\n const path = resolve(dirname(filePath), target);\n if (ctx.contentRoot && escapesRoot(ctx.contentRoot, path)) {\n return {\n error: includeError(\n \"BLUME_INCLUDE_OUTSIDE_ROOT\",\n `Include target ${target} resolves outside the content root.`,\n filePath,\n line,\n \"Move the included file under the content root so builds, version snapshots, and ejects all see it.\"\n ),\n };\n }\n return { path };\n};\n\n/**\n * Rewrite one line's relative image targets from the included file's\n * directory to the including file's, so a partial's colocated\n * `` still resolves once its markdown lives in the\n * includer. Mirrors `rewriteLine` in `content-assets.ts`: matches run on an\n * inline-code-masked copy while replacements splice into the real line.\n */\nconst rebaseImageLine = (\n line: string,\n fromDir: string,\n toDir: string\n): string => {\n const masked = line.replaceAll(INLINE_CODE, (span) =>\n \" \".repeat(span.length)\n );\n let out = \"\";\n let cursor = 0;\n for (const match of masked.matchAll(MD_IMAGE)) {\n const target = match.groups?.target ?? \"\";\n // Only filesystem-relative targets move with the file: URLs, public-dir\n // absolutes, and anchors mean the same thing from either directory.\n if (\n target.startsWith(\"/\") ||\n target.startsWith(\"#\") ||\n URL.canParse(target)\n ) {\n continue;\n }\n const rebased = relative(toDir, resolve(fromDir, target));\n const url = rebased.startsWith(\".\") ? rebased : `./${rebased}`;\n const offset =\n (match.index ?? 0) +\n targetOffsetIn(match[0], target, match.groups?.title);\n out += line.slice(cursor, offset) + url;\n cursor = offset + target.length;\n }\n return out + line.slice(cursor);\n};\n\n/** Rebase every relative image target in expanded lines, skipping fences. */\nconst rebaseImages = (\n lines: string[],\n fromDir: string,\n toDir: string\n): string[] => {\n if (fromDir === toDir) {\n return lines;\n }\n let fence: FenceState = null;\n return lines.map((line) => {\n const next = nextFenceState(line, fence);\n const inFence = fence !== null || next !== null;\n fence = next;\n return inFence ? line : rebaseImageLine(line, fromDir, toDir);\n });\n};\n\n/** Wrap raw file content as a fenced code block that can't be broken by the\n * content's own backtick runs. */\nconst codeBlockLines = (\n content: string,\n path: string,\n attributes: IncludeAttributes\n): string[] => {\n const body = content.replace(/\\n$/u, \"\");\n let longestRun = 0;\n for (const run of body.match(/`+/gu) ?? []) {\n longestRun = Math.max(longestRun, run.length);\n }\n const fence = \"`\".repeat(Math.max(3, longestRun + 1));\n const ext = extname(path);\n const lang = attributes.lang ?? (ext ? ext.slice(1) : \"text\");\n const meta = attributes.meta ? ` ${attributes.meta}` : \"\";\n return [`${fence}${lang}${meta}`, ...body.split(\"\\n\"), fence];\n};\n\n/** Lines-with-origins pair every splice step produces. */\ninterface ExpandedLines {\n lines: string[];\n origins: LineOrigin[];\n}\n\n/**\n * Resolve one include statement to its expanded lines. Content targets are\n * front-matter-stripped, recursively expanded, and image-rebased into the\n * includer's directory; code targets are fence-wrapped verbatim.\n */\nconst expandStatement = async (\n statement: IncludeStatement,\n filePath: string,\n line: number,\n ctx: ExpandContext,\n stack: readonly string[]\n): Promise<ExpandedLines | { error: Diagnostic }> => {\n const resolved = resolveIncludePath(statement.target, filePath, ctx, line);\n if (\"error\" in resolved) {\n return resolved;\n }\n const { path } = resolved;\n if (stack.includes(path)) {\n return {\n error: includeError(\n \"BLUME_INCLUDE_CYCLE\",\n `Circular include: ${[...stack, path]\n .map((entry) => relative(ctx.contentRoot ?? dirname(path), entry))\n .join(\" -> \")}.`,\n filePath,\n line,\n \"Remove the include statement that closes the loop.\"\n ),\n };\n }\n let content: string;\n try {\n content = await readFile(path, \"utf-8\");\n } catch (error) {\n return {\n error: includeError(\n \"BLUME_INCLUDE_NOT_FOUND\",\n `Include target ${statement.target} was not found (looked at ${path}): ${\n error instanceof Error ? error.message : String(error)\n }.`,\n filePath,\n line,\n DOCS_SUGGESTION\n ),\n };\n }\n ctx.includes.add(path);\n\n const asContent =\n !statement.attributes.lang &&\n CONTENT_EXTENSIONS.has(extname(path).toLowerCase());\n if (!asContent) {\n const lines = codeBlockLines(content, path, statement.attributes);\n // Fence delimiters are synthetic; anchor them (and the verbatim body,\n // which link extraction skips as fenced code anyway) to the target file.\n return {\n lines,\n origins: lines.map((_, i) => ({ file: path, line: Math.max(1, i) })),\n };\n }\n\n const parsed = matter(content);\n const body = parsed.content;\n const strippedOffset = Math.max(\n 0,\n content.split(\"\\n\").length - body.split(\"\\n\").length\n );\n // The stack is copied per branch (never mutated) so sibling statements can\n // expand concurrently without seeing each other's frames as cycles.\n // oxlint-disable-next-line no-use-before-define -- mutual recursion: a partial expands its own includes\n const expanded = await expandLines(body, path, strippedOffset, ctx, [\n ...stack,\n path,\n ]);\n // Blank edge lines (the file's trailing newline, cosmetic leading gaps)\n // carry no markdown meaning; trimming them keeps splices tight and the\n // padding in `expandLines` the only blank-line authority.\n const lines = [...expanded.lines];\n const origins = [...expanded.origins];\n while (lines.at(-1)?.trim() === \"\") {\n lines.pop();\n origins.pop();\n }\n while (lines[0]?.trim() === \"\") {\n lines.shift();\n origins.shift();\n }\n return {\n lines: rebaseImages(lines, dirname(path), dirname(filePath)),\n origins,\n };\n};\n\n/**\n * Walk a document's lines, replacing each include statement (outside fenced\n * code, comments, and — in `.md` — indented code blocks) with the target's\n * expanded lines. Statements that error stay verbatim so downstream surfaces\n * show what the author wrote; a blank line is padded around each splice so a\n * partial can't merge into an adjacent paragraph.\n */\nconst expandLines = async (\n body: string,\n filePath: string,\n lineOffset: number,\n ctx: ExpandContext,\n stack: readonly string[]\n): Promise<ExpandedLines> => {\n const sourceLines = body.split(\"\\n\");\n const isMdx = extname(filePath).toLowerCase() === \".mdx\";\n const delimiters = COMMENT_DELIMITERS[isMdx ? \"mdx\" : \"md\"];\n\n // Pass 1: statement detection per line, tracking fenced code and comments —\n // a commented-out statement never renders, so expanding it would leak the\n // partial into search/mirror surfaces the page doesn't show. Indented code\n // and setext underlines only exist in `.md` (MDX has neither, and an\n // indented `<include>` there is still JSX flow the renderer splices).\n let fence: FenceState = null;\n let inComment = false;\n const statements = sourceLines.map((line, index) => {\n if (inComment) {\n inComment = advanceCommentState(line, delimiters, true);\n return null;\n }\n const next = nextFenceState(line, fence);\n const inFence = fence !== null || next !== null;\n fence = next;\n if (inFence) {\n return null;\n }\n inComment = advanceCommentState(line, delimiters, false);\n if (inComment || !hasIncludeStatements(line)) {\n return null;\n }\n const statement = isMdx\n ? parseIncludeStatement(line.trim())\n : parseIncludeLine(line);\n // A setext underline directly below folds the statement line into a\n // heading — the renderer shows a heading, not a splice, so skip it.\n if (\n statement &&\n !isMdx &&\n SETEXT_UNDERLINE.test(sourceLines[index + 1] ?? \"\")\n ) {\n return null;\n }\n return statement;\n });\n\n // Pass 2: expand every statement concurrently — reads are independent, and\n // each branch carries its own cycle stack.\n const expansions = await Promise.all(\n statements.map((statement, index) =>\n statement\n ? expandStatement(\n statement,\n filePath,\n lineOffset + index + 1,\n ctx,\n stack\n )\n : null\n )\n );\n\n // Pass 3: assemble sequentially — the blank-line padding depends on the\n // accumulated output, and error order should follow document order.\n const lines: string[] = [];\n const origins: LineOrigin[] = [];\n for (const [index, line] of sourceLines.entries()) {\n const rawLine = lineOffset + index + 1;\n const expanded = expansions[index];\n if (!expanded || \"error\" in expanded) {\n if (expanded) {\n ctx.errors.push(expanded.error);\n }\n lines.push(line);\n origins.push({ file: filePath, line: rawLine });\n continue;\n }\n const pad = (): void => {\n lines.push(\"\");\n origins.push({ file: filePath, line: rawLine });\n };\n if (lines.at(-1)?.trim()) {\n pad();\n }\n lines.push(...expanded.lines);\n origins.push(...expanded.origins);\n if (sourceLines[index + 1]?.trim()) {\n pad();\n }\n }\n return { lines, origins };\n};\n\n/**\n * Expand a document's include statements at the string level. `lineOffset`\n * shifts the includer's own recorded origin lines — pass the stripped front\n * matter block's height when `body` is the stripped text, so origins point at\n * real file lines. Never throws: unresolvable statements stay verbatim and\n * surface through `errors`.\n */\nexport const expandIncludes = async (\n body: string,\n options: {\n /** Absolute path of the file being expanded. */\n sourcePath: string;\n /** The owning source's content root; bounds include resolution. */\n contentRoot?: string;\n /** Raw-file line of `body`'s first line, minus one (default `0`). */\n lineOffset?: number;\n }\n): Promise<IncludeExpansion> => {\n const ctx: ExpandContext = {\n contentRoot: options.contentRoot,\n errors: [],\n includes: new Set(),\n };\n const { lines, origins } = await expandLines(\n body,\n options.sourcePath,\n options.lineOffset ?? 0,\n ctx,\n [options.sourcePath]\n );\n return {\n errors: ctx.errors,\n includes: [...ctx.includes],\n origins,\n text: lines.join(\"\\n\"),\n };\n};\n\n/**\n * Resolve one include statement to its fully-expanded markdown text — the\n * render plugin's entry point (it finds the statements as AST nodes and only\n * needs each target spliced). Shares every semantic with\n * {@link expandIncludes}.\n */\nexport const expandIncludeTarget = async (\n statement: IncludeStatement,\n options: { sourcePath: string; contentRoot?: string }\n): Promise<{ text: string; errors: Diagnostic[] } | { error: Diagnostic }> => {\n const ctx: ExpandContext = {\n contentRoot: options.contentRoot,\n errors: [],\n includes: new Set(),\n };\n const expanded = await expandStatement(\n statement,\n options.sourcePath,\n 0,\n ctx,\n [options.sourcePath]\n );\n if (\"error\" in expanded) {\n return { error: expanded.error };\n }\n // Nested statements that errored stay verbatim inside the splice (matching\n // the string-level surfaces); they're reported so the render can warn.\n return { errors: ctx.errors, text: expanded.lines.join(\"\\n\") };\n};\n\n/**\n * Invert the scan's page → included-partials edges into the partial →\n * including-pages map `includeHmrPlugin` reads (`generated/includes.json`),\n * so editing a partial invalidates every page that splices it. Localized\n * pages share a source path, hence the dedupe. Shared by `generateRuntime`\n * and `eject`, whose configs both wire the plugin at the same path.\n */\nexport const buildIncludeGraph = (\n pages: { sourcePath?: string; includes?: string[] }[]\n) => {\n const graph: Record<string, string[]> = {};\n for (const page of pages) {\n const { sourcePath } = page;\n if (!sourcePath) {\n continue;\n }\n for (const partial of page.includes ?? []) {\n const includers = graph[partial] ?? [];\n graph[partial] = includers;\n if (!includers.includes(sourcePath)) {\n includers.push(sourcePath);\n }\n }\n }\n return graph;\n};\n",
|
|
15
|
-
"/**\n * Fenced-code state for line scanners. Kept free of imports so request-time\n * code (the Ask AI grounding excerpts) can share it with the build-time source\n * scanners without pulling the filesystem and schema modules along.\n *\n * CommonMark allows backtick *and* tilde fences, three or more characters\n * long. The scanners track which delimiter opened the current fence and how\n * long its run was (`null` when outside one), so a ``` line inside a ~~~\n * block — or inside a ````-delimited block (the wrapper `codeBlockLines`\n * emits around code that contains its own ``` fence) — is content, not a\n * toggle.\n */\nconst CODE_FENCE = /^(?<run>`{3,}|~{3,})/u;\n\n/** The open fence's delimiter char and run length, or null outside one. */\nexport type FenceState = { delimiter: \"`\" | \"~\"; length: number } | null;\n\n/**\n * Advance the fenced-code state for one line: an opening fence records its\n * delimiter and run length, only a bare run of the same character at least as\n * long closes it (CommonMark), and any other line leaves the state untouched.\n */\nexport const nextFenceState = (line: string, fence: FenceState): FenceState => {\n const trimmed = line.trimStart();\n const run = trimmed.match(CODE_FENCE)?.groups?.run;\n if (run === undefined) {\n return fence;\n }\n const rest = trimmed.slice(run.length);\n const delimiter = run.startsWith(\"`\") ? (\"`\" as const) : (\"~\" as const);\n if (fence === null) {\n // A backtick fence's info string cannot itself contain a backtick\n // (CommonMark) — a line-leading ```inline``` span is a paragraph, and\n // opening a phantom fence on it would swallow every heading and link\n // after it. Tilde fences carry no such rule.\n if (delimiter === \"`\" && rest.includes(\"`\")) {\n return fence;\n }\n return { delimiter, length: run.length };\n }\n // A closing fence may be followed only by whitespace: a same-length\n // ```js line inside an open ``` block is content, not a closer.\n return fence.delimiter === delimiter &&\n run.length >= fence.length &&\n rest.trim() === \"\"\n ? null\n : fence;\n};\n",
|
|
16
|
-
"import baseMatter from \"gray-matter\";\nimport { dump, load } from \"js-yaml\";\n\nimport { YAML_SCHEMA } from \"./yaml.ts\";\n\n// gray-matter@4 binds js-yaml 3's `safeLoad`/`safeDump` as its default YAML\n// engine. In a workspace that resolves a newer js-yaml for gray-matter — v4\n// replaced those functions with throwing stubs, v5 dropped them — parsing\n// front matter throws \"Function yaml.safeLoad is removed in js-yaml 4.\" Every\n// Blume front-matter call routes through this `matter` wrapper, which supplies\n// an explicit engine built on Blume's own `load`/`dump`, so Blume is immune to\n// whichever version the consumer's install resolves for gray-matter.\n\ntype MatterInput = Parameters<typeof baseMatter>[0];\ntype MatterOptions = Parameters<typeof baseMatter>[1];\ntype ReadArgs = Parameters<typeof baseMatter.read>;\ntype StringifyArgs = Parameters<typeof baseMatter.stringify>;\n\n/** Front matter data as gray-matter types it (`GrayMatterFile[\"data\"]`). */\ntype FrontMatterData = ReturnType<typeof baseMatter>[\"data\"];\n\nconst yamlEngine = {\n // SAFETY: gray-matter's engine contract expects an object; a front matter\n // block is a YAML mapping, and js-yaml returns a scalar only for degenerate\n // input, which gray-matter treats the same way its bundled engine's output\n // is treated.\n parse: (input: string): object =>\n (load(input, { schema: YAML_SCHEMA }) ?? {}) as object,\n stringify: (data: FrontMatterData): string => dump(data),\n};\n\nconst withYamlEngine = <O extends { engines?: object } | undefined>(\n options: O\n): O =>\n // SAFETY: the spread keeps every field of `options`; adding a default yaml\n // engine (overridden by any caller-supplied `engines`) stays within O's\n // shape — TS just can't prove a spread of a generic re-satisfies O.\n ({\n ...options,\n engines: {\n yaml: yamlEngine,\n ...options?.engines,\n },\n }) as O;\n\n/**\n * True when a document's leading `---` line is a CommonMark thematic break,\n * not a front matter fence. Two shapes qualify (mirroring\n * `linesWithoutFrontMatter` in `sources/normalize.ts`):\n * - the next line is blank (or absent) — YAML metadata starts on the very\n * next line, so a gap means the body *opens* with a divider (e.g. a\n * Notion page whose first block is one);\n * - no closing `---` line follows — gray-matter would swallow the whole\n * document as one unclosed YAML block and hand it to js-yaml, which\n * crashes on ordinary Markdown (`> quote` → \"a line break is expected\").\n */\nconst opensWithThematicBreak = (input: string): boolean => {\n const [first = \"\", second] = input.split(/\\r?\\n/u, 2);\n if (!/^-{3}\\s*$/u.test(first)) {\n return false;\n }\n if (second === undefined || second.trim() === \"\") {\n return true;\n }\n // gray-matter closes the block at the next line-leading `---`; matching its\n // search exactly keeps this guard from firing on any document it parses.\n return !input.includes(\"\\n---\", 1);\n};\n\n/**\n * gray-matter's runtime result carries `isEmpty`, which its declared\n * GrayMatterFile type omits.\n */\ninterface MatterResult extends ReturnType<typeof baseMatter> {\n isEmpty: boolean;\n}\n\n/**\n * The parse result for a document with no front matter: the input passes\n * through as content, untouched. Shaped like gray-matter's own no-matter\n * result (every Blume call site reads only `content` and `data`).\n */\nconst passthrough = (input: string): ReturnType<typeof baseMatter> => {\n const file: MatterResult = {\n content: input,\n data: {},\n excerpt: \"\",\n isEmpty: false,\n language: \"\",\n matter: \"\",\n orig: input,\n // Recomposing a file with no matter and empty data is the content itself.\n stringify: (): string => input,\n };\n return file;\n};\n\n/** Narrows gray-matter's input union to the raw-string form. */\nconst isStringInput = (input: MatterInput): input is string =>\n typeof input === \"string\";\n\n// Every helper that parses or emits YAML (`read`, `stringify`) must be\n// re-wrapped here — Object.assign copies gray-matter's own helpers, which use\n// its default `safeLoad` engine and would reintroduce the crash. `test` only\n// checks for a delimiter, so the copied original is safe.\nconst matter = Object.assign(\n (input: MatterInput, options?: MatterOptions) =>\n isStringInput(input) && opensWithThematicBreak(input)\n ? passthrough(input)\n : baseMatter(input, withYamlEngine(options)),\n baseMatter,\n {\n read: (filepath: ReadArgs[0], options?: ReadArgs[1]) =>\n baseMatter.read(filepath, withYamlEngine(options)),\n // A string is the body to emit, not a document to parse: gray-matter\n // would otherwise run `matter()` over it, and a body that opens with a\n // `---` divider reads as a second front matter block — js-yaml either\n // throws or swallows the opening paragraph into the YAML header.\n stringify: (\n file: StringifyArgs[0],\n data: StringifyArgs[1],\n options?: StringifyArgs[2]\n ): string =>\n baseMatter.stringify(\n isStringInput(file) ? { content: file } : file,\n data,\n withYamlEngine(options)\n ),\n }\n);\n\nexport default matter;\n",
|
|
17
|
-
"import {\n binaryTag,\n CORE_SCHEMA,\n mergeTag,\n omapTag,\n pairsTag,\n setTag,\n timestampTag,\n} from \"js-yaml\";\n\n/**\n * The schema js-yaml 4 loaded with by default: YAML 1.2 core scalars plus the\n * `!!timestamp`, `!!merge`, `!!binary`, `!!omap`, `!!pairs`, and `!!set` tags.\n * js-yaml 5 loads with the bare core schema instead, which turns\n * `date: 2024-01-01` into a string and leaves `<<:` merge keys unresolved — a\n * silent change to every page's front matter. Every Blume `load` call passes\n * this schema so parsing stays identical across the upgrade.\n */\nexport const YAML_SCHEMA = CORE_SCHEMA.withTags(\n timestampTag,\n mergeTag,\n binaryTag,\n omapTag,\n pairsTag,\n setTag\n);\n",
|
|
18
|
-
"import { existsSync, readFileSync } from \"node:fs\";\n\nimport GithubSlugger from \"github-slugger\";\nimport { extname } from \"pathe\";\n\nimport { withBasePath } from \"../base-path.ts\";\nimport { nextFenceState } from \"../code-fences.ts\";\nimport type { FenceState } from \"../code-fences.ts\";\nimport { diagnosticsFromIssues, diagnosticsFromZod } from \"../diagnostics.ts\";\nimport { occupySlug, parseHeadingMarkers } from \"../heading-markers.ts\";\nimport { localePlacement, localizeRoute } from \"../i18n.ts\";\nimport { pageMetaSchema } from \"../schema.ts\";\nimport type {\n FrontmatterExtend,\n PageMeta,\n ResolvedI18nConfig,\n} from \"../schema.ts\";\nimport { trimChar } from \"../trim.ts\";\nimport type { Diagnostic, Heading, PageLink, PageRecord } from \"../types.ts\";\nimport { detectVersionRef, versionizeRoute } from \"../versions.ts\";\nimport type { NormalizeContext, SourceEntry } from \"./types.ts\";\n\nconst NUMERIC_PREFIX = /^\\d+[-_.]/u;\nconst GROUP_FOLDER = /^\\((?<label>.+)\\)$/u;\nconst WORD_SPLIT = /[-_]/u;\n\n/** Strip a leading numeric ordering prefix (`01-intro` -> `intro`). */\nconst stripNumericPrefix = (segment: string): string =>\n segment.replace(NUMERIC_PREFIX, \"\");\n\n/** Detect a group folder `(name)` and return its label, else null. */\nconst groupLabel = (segment: string): string | null =>\n segment.match(GROUP_FOLDER)?.groups?.label ?? null;\n\n/**\n * Slugify a content/route slug (Sanity, Notion, frontmatter `slug`). Heading\n * anchor ids are *not* slugged here — they use a `github-slugger` in\n * {@link extractHeadings}, matching the renderer (see `markdown/heading-anchors`)\n * so `blume validate` checks anchors against the exact rendered heading ids.\n *\n * The keep-class is Unicode letters/marks/numbers, not `\\w`: ASCII slugs are\n * unchanged, but a CJK/Cyrillic/accented slug keeps its characters instead of\n * collapsing to `\"\"` (which forced Sanity/Notion routes onto their opaque\n * document-id fallbacks) or dropping accents (`café` → `caf`). NFC first so a\n * macOS-NFD `é` (e + combining mark) slugs identically to the composed form.\n */\nexport const slugify = (text: string): string =>\n text\n .normalize(\"NFC\")\n .toLowerCase()\n .trim()\n .replaceAll(/[^\\p{L}\\p{M}\\p{N}\\s_-]/gu, \"\")\n .replaceAll(/[\\s_]+/gu, \"-\")\n .replaceAll(/-+/gu, \"-\")\n .replaceAll(/^-|-$/gu, \"\");\n\n/**\n * {@link slugify} for a slug that may span path segments (`guides/setup`).\n * `slugify` deletes `/` along with all other punctuation, which would mash\n * `guides/setup` into `guidessetup` — and collide it with a genuine `guidessetup`\n * document. Each segment is slugged on its own and the separators kept.\n */\nexport const slugifyPath = (text: string): string =>\n text.split(\"/\").map(slugify).filter(Boolean).join(\"/\");\n\n/** Title-case a slug segment for display. */\nconst titleCase = (value: string): string =>\n value\n .split(WORD_SPLIT)\n .filter(Boolean)\n .map((word) => word.charAt(0).toUpperCase() + word.slice(1))\n .join(\" \");\n\n/**\n * Strip characters that cannot survive the route → URL → output-file round\n * trip. A `:` ahead of the first `/` makes `new URL()` read the segment as a\n * scheme (`Guide: Architecture.md` → `guide:`), which crashes Astro's\n * prerender write with \"The URL must be of scheme file\"; control characters\n * (an embedded newline in a filename) are silently dropped by the URL parser,\n * desyncing the route from its output path. Both are legal in macOS/Linux\n * filenames, so they are removed here rather than rejected.\n */\nconst sanitizeSegment = (segment: string): string =>\n segment.replaceAll(/[:\\p{Cc}]/gu, \"\");\n\n/** Fold one raw path part into the accumulating route segments/groups. */\nconst addRouteSegment = (\n part: string,\n segments: string[],\n groups: string[]\n): void => {\n // A leading/trailing/double slash yields an empty part; keeping it would\n // produce a malformed route (`//foo`, `/foo/`) that nothing can link to.\n if (part === \"\") {\n return;\n }\n const group = groupLabel(part);\n if (group !== null) {\n groups.push(group);\n return;\n }\n const clean = stripNumericPrefix(part);\n if (clean === \"index\") {\n return;\n }\n const safe = sanitizeSegment(clean);\n // A part that was nothing but stripped characters cannot name a segment.\n if (safe === \"\") {\n return;\n }\n segments.push(safe);\n};\n\n/** URL + nav metadata mapped from one content-root-relative path. */\ninterface MappedRoute {\n segments: string[];\n groups: string[];\n route: string;\n}\n\n/**\n * Convert a content-root-relative path into URL + nav metadata. Not exported:\n * a source that needs to predict a route goes through\n * {@link resolveEntryRoute}, so there is exactly one derivation.\n */\nconst mapRoute = (relativePath: string): MappedRoute => {\n const withoutExt = relativePath.slice(\n 0,\n relativePath.length - extname(relativePath).length\n );\n const rawParts = withoutExt.split(\"/\");\n\n const segments: string[] = [];\n const groups: string[] = [];\n\n for (const part of rawParts) {\n addRouteSegment(part, segments, groups);\n }\n\n const route = segments.length === 0 ? \"/\" : `/${segments.join(\"/\")}`;\n return { groups, route, segments };\n};\n\n// A closing hash sequence must be preceded by whitespace (CommonMark), so a\n// heading like `## What is C#` keeps its trailing `#`. Up to 3 leading spaces\n// are allowed; 4+ is an indented code block.\nconst ATX_HEADING = /^ {0,3}(?<hashes>#{1,6})\\s+(?<text>.+?)(?:\\s+#+)?\\s*$/u;\n// A setext underline: a run of `=` (level 1) or `-` (level 2) alone on a line,\n// up to 3 leading spaces. It only forms a heading directly under paragraph\n// text — see `scanHeadingLine`.\nconst SETEXT_UNDERLINE = /^ {0,3}(?<marker>=+|-+)\\s*$/u;\n// Lines that end a paragraph without being one (CommonMark): blank lines are\n// checked separately; these cover list items, blockquotes, and thematic\n// breaks, so a `---` after any of them stays a thematic break, not an\n// underline promoting the list/quote text to a heading.\nconst PARAGRAPH_INTERRUPT = /^ {0,3}(?:[-+*][ \\t]|\\d{1,9}[.)][ \\t]|>)/u;\nconst THEMATIC_BREAK =\n /^ {0,3}(?:(?:-[ \\t]*){3,}|(?:\\*[ \\t]*){3,}|(?:_[ \\t]*){3,})$/u;\nconst FRONT_MATTER_CLOSE = /^(?:-{3}|\\.{3})\\s*$/u;\n// `<Prompt>` renders its children into a permanently `hidden` DOM node (see\n// `Prompt.astro`) — the agent-facing prompt text is never visible page\n// content, only read by client JS for the copy button. Any `##` inside it\n// must not surface in the page's heading-derived table of contents. Tracked\n// as an open/close depth, the same way fenced code blocks are tracked above.\n// The opening tag is matched only at the start of a trimmed line: block-level\n// JSX in MDX starts its own line, so a mention mid-prose or mid-heading —\n// \"the `<Prompt>` component\", `## Using <Prompt>` — never opens a hidden\n// region (an unanchored match here silently ate every heading after the\n// mention). The lookahead rejects longer tag names that share the prefix,\n// like `<PromptCard>` or `<Prompt-Custom>`.\nconst PROMPT_OPEN = /^<Prompt(?![\\w-])/u;\n// Unanchored: while inside a prompt the close tag may trail the hidden\n// children text (`...copy this.</Prompt>`), not just sit on its own line.\nconst PROMPT_CLOSE = /<\\/Prompt>/u;\n\n/**\n * The body lines, minus a leading front matter block, plus the height of the\n * block that was dropped (`offset`) so line numbers can be reported against\n * the whole body. Bodies from the normalize pipeline are already\n * frontmatter-stripped, but `scanBody` also runs on raw documents — where a\n * leading `---` block (closed by `---` or `...`) is front matter, not a\n * thematic break whose closing `---` would underline the last metadata line\n * into a phantom setext heading.\n */\nconst linesWithoutFrontMatter = (body: string) => {\n const lines = body.split(\"\\n\");\n if (!/^-{3}\\s*$/u.test(lines[0] ?? \"\")) {\n return { lines, offset: 0 };\n }\n // A blank line directly after the dashes means the body *opens* with a\n // thematic break, not front matter — YAML metadata starts on the very next\n // line. Treating it as an unclosed block ate everything up to the next\n // `---`/`...` line of an already-stripped body.\n if ((lines[1] ?? \"\").trim() === \"\") {\n return { lines, offset: 0 };\n }\n const close = lines.findIndex(\n (line, index) => index > 0 && FRONT_MATTER_CLOSE.test(line)\n );\n return close === -1\n ? { lines, offset: 0 }\n : { lines: lines.slice(close + 1), offset: close + 1 };\n};\n\n// A trailing `{#id}` heading marker written without a backslash escape. Both\n// pipelines resolve escapes before parsing markers (see `ESCAPED_PUNCTUATION`),\n// so `\\{#id\\}` is the same marker — and the only spelling that survives the\n// MDX parser, where a bare `{…}` is a JSX expression and `#id` is not a valid\n// one (`Could not parse expression with acorn`). Further bracket markers may\n// follow the brace (`{#id} [toc]`), nothing else.\nconst BARE_CURLY_MARKER =\n /(?<!\\\\)\\{#(?<id>[^\\s}]+)\\}(?:\\s*\\[(?:#[^\\s\\]]+|!?toc)\\])*\\s*$/u;\n\n// A raw HTML element carrying an `id` — `<a id=\"…\">`, `<section id='…'>`,\n// the unquoted `<a id=plain>`, or the JSX spelling `<div id={\"…\"}>` — whose\n// element is a fragment-link target outside headings. Lowercase tags only: a\n// capitalized `<YouTube id=\"…\">` is a component prop, not a DOM id (the\n// `JSX_OPEN` split below). `[^<>]` bounds the attribute run, so a wrapped tag\n// (`<div\\n id=\"x\"\\n>`) still matches while a long tag-free line can't\n// backtrack quadratically, and the run never reaches into a neighboring tag.\nconst HTML_ID =\n /<[a-z][a-z0-9-]*(?:\\s[^<>]*?)?\\sid=(?:(?<quote>[\"'])(?<quoted>[^\"'<>]+)\\k<quote>|\\{(?<jsxQuote>[\"'])(?<jsx>[^\"'<>]+)\\k<jsxQuote>\\}|(?<bare>[^\\s\"'=<>`{}]+))/gu;\n// Commented-out markup renders nothing; matched across lines once the\n// scannable lines are joined back together. Shared with the search\n// extractor so both agree on what a comment is.\nexport const HTML_COMMENT = /<!--[\\s\\S]*?-->/gu;\nexport const INLINE_CODE = /`[^`]*`/gu;\n\n/** A heading whose trailing `{#id}` marker is unescaped — see `BARE_CURLY_MARKER`. */\nexport interface CurlyMarker {\n id: string;\n /** 1-based line of the heading (a setext heading's first text line) in the body. */\n line: number;\n}\n\n/** Scanner state: the open fence plus the paragraph lines accumulated so far. */\ninterface HeadingScanState {\n /**\n * Lines eligible to hold an HTML `id` — outside fences and `<Prompt>`\n * regions, inline code masked — collected for one `HTML_ID` pass at the\n * end so a tag wrapped over several lines still matches.\n */\n anchorLines: string[];\n curlyMarkers: CurlyMarker[];\n fence: FenceState;\n /** 1-based body line of the line being scanned. */\n line: number;\n /** Where each heading in `headings` sits, index-aligned. */\n sites: HeadingSite[];\n /** Consecutive paragraph lines — the candidate text for a setext underline. */\n paragraph: string[];\n /** Body line of the first line in `paragraph`. */\n paragraphStart: number;\n /** Nesting depth inside `<Prompt>...</Prompt>` — 0 when outside one. */\n promptDepth: number;\n /** True inside a multi-line `<Prompt` opening tag, awaiting its `>`. */\n promptTag: boolean;\n}\n\n/**\n * Consume the rest of a `<Prompt` opening tag, scanning a trimmed line from\n * `start`. The tag's attributes may spread over several lines\n * (`state.promptTag` carries the search onto the next one), and until the\n * terminating `>` arrives it isn't known whether the tag even has children —\n * so the depth only rises once that `>` is found, and not when it turns out\n * to be `/>` or when the element also closes on the same line\n * (`<Prompt ...>copy this</Prompt>`). Attribute values containing `>` are not\n * parsed: the first `>` ends the tag, which errs toward opening a region a\n * real close tag will still exit.\n */\nconst finishPromptTag = (\n line: string,\n start: number,\n state: HeadingScanState\n): void => {\n const end = line.indexOf(\">\", start);\n if (end === -1) {\n state.promptTag = true;\n return;\n }\n state.promptTag = false;\n if (line[end - 1] === \"/\" || line.includes(\"</Prompt>\", end)) {\n return;\n }\n state.promptDepth += 1;\n};\n\n/**\n * Extract ATX and setext headings from a markdown body, skipping fenced code\n * blocks and `<Prompt>` children, exactly as the renderer sees them: ATX\n * headings may be indented up to 3 spaces, and a paragraph underlined with\n * `=`/`-` is a level 1/2 setext heading. Each heading's anchor slug comes\n * from a per-document\n * `github-slugger` — the exact slugger the renderer uses\n * (`markdown/heading-anchors`) — advanced over every heading in document\n * order. Matching it (rather than a hand-rolled slugify) keeps the manifest's\n * anchor ids identical to the rendered ones, so `blume validate` stops\n * false-flagging links like `#the-read--write-fallback` (a hand slugify\n * collapses `--`; github-slugger keeps it) and resolves repeated headings the\n * same way (`setup`, `setup-1`).\n */\n// CommonMark's escapable ASCII punctuation. The renderer only ever sees\n// heading text *after* the Markdown parser has resolved backslash escapes, so\n// `\\[toc]` reaches the hast as plain `[toc]` and the marker still applies;\n// resolving escapes here keeps the two pipelines identical (there is no\n// inline way to write a literal trailing marker — use inline code instead).\nconst ESCAPED_PUNCTUATION = /\\\\(?<char>[!-/:-@[-`{-~])/gu;\n\n// The start of a link-reference definition, as the renderer accepts it:\n// `[label]:` after up to 3 leading spaces, optionally inside block-quote or\n// list-item containers (`> [label]: /url`, `- [label]: /url`) — a definition\n// nested in either still defines the label document-wide. `rest` is whatever\n// follows the colon on the same line.\n//\n// Every container iteration begins with its marker and ends with the\n// whitespace that follows it, so a whitespace run belongs to exactly one\n// iteration. Letting an iteration start with optional whitespace as well\n// gave each run two homes and backtracked exponentially on `>\\t>\\t>…` and\n// `*\\t\\t*\\t\\t*…` lines (CodeQL js/redos).\nconst REF_DEFINITION =\n /^(?: {0,3}|[ \\t]*(?:(?:>|(?:[-*+]|\\d{1,9}[.)])[ \\t])[ \\t]*)+)\\[(?<label>[^\\]]+)\\]:[ \\t]*(?<rest>.*)$/u;\n\n/**\n * The normalized labels of every link-reference definition in the body\n * (outside fenced code). A trailing heading bracket whose label is defined is\n * a CommonMark shortcut link, not a marker — the renderer leaves it in the\n * heading as an `<a>`, so the marker parse must skip it too. Labels match\n * case-insensitively with collapsed internal whitespace (CommonMark).\n *\n * Only a *valid* definition defines a label: the label must contain a\n * non-whitespace character, and a destination must follow — on the same line\n * or, as CommonMark allows, alone on the next. A bare `[toc]:` with nothing\n * after it is paragraph text, and the renderer still treats the heading's\n * `[toc]` as literal — which Blume reads as a marker — so it must not be\n * recorded here.\n */\nconst refDefinitionLabels = (lines: readonly string[]): Set<string> => {\n const labels = new Set<string>();\n let fence: FenceState = null;\n for (const [index, line] of lines.entries()) {\n const next = nextFenceState(line, fence);\n if (fence !== null || next !== null) {\n fence = next;\n continue;\n }\n const groups = line.match(REF_DEFINITION)?.groups;\n if (groups?.label === undefined || groups.label.trim() === \"\") {\n continue;\n }\n const destination = groups.rest ?? \"\";\n if (destination === \"\" && (lines[index + 1] ?? \"\").trim() === \"\") {\n continue;\n }\n labels.add(groups.label.trim().replaceAll(/\\s+/gu, \" \").toLowerCase());\n }\n return labels;\n};\n\n/**\n * A heading record from raw heading text: escapes resolved and trailing\n * markers stripped, exactly as the renderer sees them. A `[#custom-id]` pin\n * becomes the slug verbatim and — matching the renderer — occupies its id in\n * the slugger, so a later heading whose auto-slug collides disambiguates\n * (`setup` → `setup-1`). `[!toc]`/`[toc]` headings stay in the record: their\n * ids exist in the rendered page, so links to them are valid anchors\n * regardless of TOC visibility. A heading that is nothing but markers keeps\n * them as literal text, mirroring the renderer.\n */\n/** A scanned heading plus whether its id came from an author pin. */\ninterface ScannedHeading {\n heading: Heading;\n pinned: boolean;\n}\n\nconst toHeading = (\n depth: number,\n raw: string,\n slugger: GithubSlugger,\n isRefDefined: (label: string) => boolean\n): ScannedHeading => {\n const unescaped = raw.replaceAll(ESCAPED_PUNCTUATION, \"$<char>\");\n const markers = parseHeadingMarkers(unescaped, isRefDefined);\n const text = markers.text.trim();\n if (text === \"\" && (markers.id !== undefined || markers.toc !== undefined)) {\n return {\n heading: { depth, slug: slugger.slug(unescaped), text: unescaped },\n pinned: false,\n };\n }\n if (markers.id !== undefined) {\n occupySlug(slugger, markers.id);\n return { heading: { depth, slug: markers.id, text }, pinned: true };\n }\n return { heading: { depth, slug: slugger.slug(text), text }, pinned: false };\n};\n\n/** Record a heading and where a trailing marker would be written for it. */\nconst pushHeading = (\n headings: Heading[],\n state: HeadingScanState,\n scanned: ScannedHeading,\n line: number\n): void => {\n headings.push(scanned.heading);\n state.sites.push({ line, pinned: scanned.pinned });\n};\n\n/** Record a heading's unescaped trailing `{#id}` so `.mdx` pages can be warned. */\nconst noteCurlyMarker = (\n text: string,\n line: number,\n state: HeadingScanState\n): void => {\n const id = text.match(BARE_CURLY_MARKER)?.groups?.id;\n if (id !== undefined) {\n state.curlyMarkers.push({ id, line });\n }\n};\n\n/** Scan a line outside fences and prompts: an anchor host, a heading, or prose. */\nconst scanContentLine = (\n line: string,\n state: HeadingScanState,\n slugger: GithubSlugger,\n headings: Heading[],\n isRefDefined: (label: string) => boolean\n): void => {\n // Inline code is masked so a documented `<a id=\"…\">` isn't an anchor.\n state.anchorLines.push(line.replaceAll(INLINE_CODE, \"\"));\n const atx = line.match(ATX_HEADING);\n if (atx?.groups) {\n const depth = atx.groups.hashes?.length ?? 1;\n const text = (atx.groups.text ?? \"\").trim();\n pushHeading(\n headings,\n state,\n toHeading(depth, text, slugger, isRefDefined),\n state.line\n );\n noteCurlyMarker(text, state.line, state);\n state.paragraph = [];\n return;\n }\n const setext = line.match(SETEXT_UNDERLINE);\n if (setext?.groups && state.paragraph.length > 0) {\n // Setext wins over thematic break when it closes a paragraph (CommonMark);\n // a multi-line paragraph renders as one heading, soft breaks as spaces.\n const depth = setext.groups.marker?.startsWith(\"=\") ? 1 : 2;\n const text = state.paragraph.join(\" \").trim();\n // A setext heading's markers trail its last text line, just above the\n // underline — that is where a pin is appended.\n pushHeading(\n headings,\n state,\n toHeading(depth, text, slugger, isRefDefined),\n state.line - 1\n );\n noteCurlyMarker(text, state.paragraphStart, state);\n state.paragraph = [];\n return;\n }\n if (\n line.trim() === \"\" ||\n THEMATIC_BREAK.test(line) ||\n PARAGRAPH_INTERRUPT.test(line)\n ) {\n state.paragraph = [];\n return;\n }\n if (state.paragraph.length === 0) {\n state.paragraphStart = state.line;\n }\n state.paragraph.push(line.trim());\n};\n\n/** Scan one line for a heading, advancing the fence/paragraph state. */\nconst scanHeadingLine = (\n line: string,\n state: HeadingScanState,\n slugger: GithubSlugger,\n headings: Heading[],\n isRefDefined: (label: string) => boolean\n): void => {\n const next = nextFenceState(line, state.fence);\n // Skip fence delimiter lines themselves and anything inside a fence. A fence\n // also ends any open paragraph, so no underline can reach across it.\n if (state.fence !== null || next !== null) {\n state.fence = next;\n state.paragraph = [];\n return;\n }\n // Prompt tags may be indented arbitrarily (MDX has no indented code\n // blocks), so they match against the trimmed line. A tag line can't also\n // be a heading, so each just updates the state and moves on, same as a\n // fence delimiter line. Outside a prompt, a `</Prompt>` line is plain text.\n const trimmed = line.trimStart();\n if (state.promptTag) {\n finishPromptTag(trimmed, 0, state);\n state.paragraph = [];\n return;\n }\n if (PROMPT_OPEN.test(trimmed)) {\n finishPromptTag(trimmed, \"<Prompt\".length, state);\n state.paragraph = [];\n return;\n }\n if (state.promptDepth > 0) {\n if (PROMPT_CLOSE.test(line)) {\n state.promptDepth -= 1;\n }\n state.paragraph = [];\n return;\n }\n scanContentLine(line, state, slugger, headings, isRefDefined);\n};\n\n/** Where a heading's text ends in the scanned text, for appending a marker. */\nexport interface HeadingSite {\n /** 1-based line (of the text passed to `scanBody`) that any trailing marker ends. */\n line: number;\n /** True when the heading already pins its id with `[#id]`/`{#id}`. */\n pinned: boolean;\n}\n\n/** Everything one walk over a body yields for the anchor index. */\nexport interface BodyScan {\n /**\n * Raw HTML element ids (`<a id=\"…\">`) outside headings, fences, inline\n * code, comments, and `<Prompt>` regions — fragment-link targets that\n * `blume validate` accepts alongside heading slugs. Deduplicated, in\n * document order.\n */\n anchors: string[];\n /** Headings whose trailing `{#id}` marker is unescaped, for `.mdx` pages. */\n curlyMarkers: CurlyMarker[];\n headings: Heading[];\n /** Index-aligned with `headings`: where each one's markers would go. */\n sites: HeadingSite[];\n}\n\n/**\n * Scan a body for its headings, explicit HTML anchors, and unescaped `{#id}`\n * markers in one fence-aware walk (the same walk `extractHeadings` exposes for\n * headings alone).\n */\nexport const scanBody = (body: string): BodyScan => {\n const headings: Heading[] = [];\n const slugger = new GithubSlugger();\n const state: HeadingScanState = {\n anchorLines: [],\n curlyMarkers: [],\n fence: null,\n line: 0,\n paragraph: [],\n paragraphStart: 0,\n promptDepth: 0,\n promptTag: false,\n sites: [],\n };\n\n const { lines, offset } = linesWithoutFrontMatter(body);\n const definedLabels = refDefinitionLabels(lines);\n const isRefDefined = (label: string): boolean =>\n definedLabels.has(label.toLowerCase());\n for (const [index, line] of lines.entries()) {\n state.line = index + offset + 1;\n scanHeadingLine(line, state, slugger, headings, isRefDefined);\n }\n\n const anchors = new Set<string>();\n // Replaced with a space rather than removed: dropping a comment outright\n // can splice its neighbors into a new `<!--` (`<!-<!-- x -->->`), and a\n // space keeps a tag from fusing with an `id=` that only a comment separated.\n const markup = state.anchorLines.join(\"\\n\").replaceAll(HTML_COMMENT, \" \");\n for (const match of markup.matchAll(HTML_ID)) {\n const id = match.groups?.quoted ?? match.groups?.jsx ?? match.groups?.bare;\n if (id !== undefined) {\n anchors.add(id);\n }\n }\n return {\n anchors: [...anchors],\n curlyMarkers: state.curlyMarkers,\n headings,\n sites: state.sites,\n };\n};\n\nexport const extractHeadings = (body: string): Heading[] =>\n scanBody(body).headings;\n\n// The label admits one level of nested brackets so an image-wrapped link\n// (`[](/target)`) matches as the *outer* link — with a flat\n// `[^\\]]*` label the match stopped at the image's `]` and the outer target was\n// never seen. The target admits one level of balanced parens so a Wikipedia-\n// style URL (`/wiki/Foo_(bar)`) isn't truncated at its first `)`.\nconst MD_LINK =\n /\\[(?<label>(?:[^[\\]]|\\[[^\\]]*\\])*)\\]\\((?<target>(?:[^()\\s]|\\([^()\\s]*\\))+)(?<title>\\s+\"[^\"]*\")?\\)/gu;\n// An image inside a link label; its target was matched (and so validated) as a\n// link of its own before labels admitted nesting, and still should be.\nexport const MD_IMAGE =\n /!\\[[^\\]]*\\]\\((?<target>(?:[^()\\s]|\\([^()\\s]*\\))+)(?<title>\\s+\"[^\"]*\")?\\)/gu;\n\n/** Column (0-based, within `matched`) where a link/image match's target starts. */\nexport const targetOffsetIn = (\n matched: string,\n target: string,\n title: string | undefined\n): number => matched.length - 1 - (title?.length ?? 0) - target.length;\n\n/**\n * Extract link targets from a markdown body for later validation, recording the\n * 1-based line/column of each target. Skips fenced code blocks and inline code.\n * `lineOffset` shifts every recorded line: the body is frontmatter-stripped, so\n * diagnostics that point into the raw file must add the stripped block's height.\n */\n/** Scan one line for link targets; returns the next fenced-block state. */\nconst scanLinkLine = (\n line: string,\n lineNumber: number,\n fence: FenceState,\n links: PageLink[]\n): FenceState => {\n const next = nextFenceState(line, fence);\n // Skip fence delimiter lines themselves and anything inside a fence.\n if (fence !== null || next !== null) {\n return next;\n }\n // Blank out inline code spans (`[label](/x)` shown as syntax, not a link)\n // with same-length padding so recorded columns stay accurate.\n const masked = line.replaceAll(INLINE_CODE, (span) =>\n \" \".repeat(span.length)\n );\n for (const match of masked.matchAll(MD_LINK)) {\n const target = match.groups?.target;\n if (target === undefined || match.index === undefined) {\n continue;\n }\n // Locate the target by arithmetic from the match end rather than searching\n // for its text — a label that contains the same text (e.g. `[/a/b](/a/b)`)\n // would otherwise report the column inside the label.\n const targetOffset = targetOffsetIn(match[0], target, match.groups?.title);\n const entry: PageLink = {\n column: match.index + targetOffset + 1,\n line: lineNumber,\n target,\n };\n // `MD_LINK` matches the `[label](target)` tail of an image embed too; the\n // preceding `!` is what marks the target as going through the image\n // pipeline rather than resolving as a site route.\n if (masked[match.index - 1] === \"!\") {\n entry.image = true;\n }\n links.push(entry);\n // An image nested in the label (`[](/target)`) carries its\n // own target; surface it too so a missing image is still caught.\n const label = match[0].slice(0, targetOffset - \"](\".length);\n for (const image of label.matchAll(MD_IMAGE)) {\n const imageTarget = image.groups?.target;\n if (imageTarget === undefined || image.index === undefined) {\n continue;\n }\n links.push({\n column:\n match.index +\n image.index +\n targetOffsetIn(image[0], imageTarget, image.groups?.title) +\n 1,\n image: true,\n line: lineNumber,\n target: imageTarget,\n });\n }\n }\n return next;\n};\n\nexport const extractLinks = (body: string, lineOffset = 0): PageLink[] => {\n const links: PageLink[] = [];\n let fence: FenceState = null;\n let lineNumber = lineOffset;\n\n for (const line of body.split(\"\\n\")) {\n lineNumber += 1;\n fence = scanLinkLine(line, lineNumber, fence, links);\n }\n\n return links;\n};\n\n// Double-quoted strings hold JSX attribute values and JSON in `{...}` props; a\n// `<Tag>` written inside prose there (e.g. an \"Astro <Font> integration\" note)\n// isn't a real usage. Single quotes are left alone so prose apostrophes don't\n// swallow a real tag between two words.\nconst DOUBLE_QUOTED = /\"[^\"]*\"/gu;\nconst JSX_OPEN = /<(?<tag>[A-Z][A-Za-z0-9]*)/gu;\n\n/**\n * Capitalized JSX component tags used in an `.mdx` body (`<Callout>`,\n * `<Tree.File>` → `Tree`). Skips fenced code, inline code, and double-quoted\n * strings so code samples and prose don't count. Powers the missing-component\n * diagnostic.\n */\n/** Scan one line for JSX component tags; returns the next fenced-block state. */\nconst scanTagLine = (\n line: string,\n fence: FenceState,\n tags: Set<string>\n): FenceState => {\n const next = nextFenceState(line, fence);\n // Skip fence delimiter lines themselves and anything inside a fence.\n if (fence !== null || next !== null) {\n return next;\n }\n const clean = line.replaceAll(INLINE_CODE, \"\").replaceAll(DOUBLE_QUOTED, \"\");\n for (const match of clean.matchAll(JSX_OPEN)) {\n const tag = match.groups?.tag;\n if (tag) {\n tags.add(tag);\n }\n }\n return next;\n};\n\nexport const extractComponentTags = (body: string): string[] => {\n const tags = new Set<string>();\n let fence: FenceState = null;\n for (const line of body.split(\"\\n\")) {\n fence = scanTagLine(line, fence, tags);\n }\n return [...tags];\n};\n\n/**\n * Height of the frontmatter block stripped from `raw` to produce `body` (0\n * when the raw text is unknown or nothing was stripped). Link positions are\n * extracted from the stripped body, but diagnostics point into the raw\n * document — recorded lines must shift by this offset to match it.\n */\nexport const strippedLineOffset = (\n raw: string | undefined,\n body: string\n): number =>\n raw ? Math.max(0, raw.split(\"\\n\").length - body.split(\"\\n\").length) : 0;\n\n/**\n * Map links extracted from include-expanded text back to the file and raw\n * line each expanded line came from, so a broken link inside a partial is\n * reported against the partial. Links whose origin is the page's own source\n * carry no `file` override (origins already hold raw-file lines).\n */\nconst remapExpandedLinks = (\n links: PageLink[],\n origins: { file: string; line: number }[],\n sourcePath: string | undefined\n): PageLink[] =>\n links.map((link) => {\n const origin = origins[link.line - 1];\n if (!origin) {\n return link;\n }\n const remapped: PageLink = { ...link, line: origin.line };\n if (origin.file !== sourcePath) {\n remapped.file = origin.file;\n }\n return remapped;\n });\n\n/** The entry's transitively included files, when the scan expanded any. */\nconst entryIncludes = (entry: SourceEntry): string[] | undefined =>\n entry.expanded && entry.expanded.includes.length > 0\n ? entry.expanded.includes\n : undefined;\n\n/**\n * Extract an entry's links for validation. When the scan expanded includes,\n * extraction runs over the expanded text (origins already hold raw-file\n * lines); otherwise over the stripped body, shifted by the stripped front\n * matter block's height.\n */\nconst entryLinks = (entry: SourceEntry): PageLink[] =>\n entry.expanded\n ? remapExpandedLinks(\n extractLinks(entry.expanded.text),\n entry.expanded.origins,\n entry.sourcePath\n )\n : extractLinks(\n entry.body.text,\n strippedLineOffset(entry.raw, entry.body.text)\n );\n\n/**\n * Diagnostics for `{#id}` heading markers in an `.mdx` page. The MDX parser\n * reads a bare `{…}` as a JSX expression, so the page fails to compile —\n * reported here, at the marker's source line, instead of as a raw acorn error\n * at render time. An included `.md` partial is spliced into the including\n * page and parsed in *its* format (see `markdown/include.ts`), so a partial's\n * markers count too and are reported against the partial via the expansion's\n * origins. `.md` pages render the marker as text and get no diagnostic.\n */\nconst curlyMarkerDiagnostics = (\n entry: SourceEntry,\n markers: CurlyMarker[],\n sourceName: string\n): Diagnostic[] =>\n markers.map(({ id, line }) => {\n const origin = entry.expanded?.origins[line - 1];\n const page = entry.sourcePath ?? `${sourceName}:${entry.ref}`;\n const inPartial = origin !== undefined && origin.file !== entry.sourcePath;\n return {\n code: \"BLUME_MDX_CURLY_ANCHOR\",\n file: origin?.file ?? page,\n line:\n origin?.line ?? line + strippedLineOffset(entry.raw, entry.body.text),\n message: inPartial\n ? `\\`{#${id}}\\` is a JSX expression once this partial is included in ${page} (.mdx), so that page fails to compile.`\n : `\\`{#${id}}\\` is a JSX expression in .mdx, so this page fails to compile.`,\n severity: \"error\",\n suggestion: `Write \\`[#${id}]\\` or escape it as \\`\\\\{#${id}\\\\}\\` — both pin the same anchor in .md and .mdx.`,\n };\n });\n\nconst deriveTitle = (\n meta: PageMeta,\n headings: Heading[],\n id: string\n): string => {\n if (meta.title) {\n return meta.title;\n }\n const firstHeading = headings.find((h) => h.depth === 1) ?? headings[0];\n if (firstHeading) {\n return firstHeading.text;\n }\n const base = id.split(\"/\").pop() ?? id;\n return titleCase(stripNumericPrefix(base.replace(extname(base), \"\")));\n};\n\n/** Strip habitual leading/trailing slashes (`/getting-started`, `guides/`). */\nconst trimSlashes = (value: string): string => trimChar(value, \"/\");\n\n/** Whether a raw frontmatter value is a string (e.g. the `type` override). */\nexport const isStringValue = (\n value: SourceEntry[\"data\"][string]\n): value is string => typeof value === \"string\";\n\n/** Mount a source-relative path under the source's route prefix. */\nconst withPrefix = (prefix: string | undefined, path: string): string => {\n const clean = prefix ? trimSlashes(prefix) : \"\";\n return clean ? `${clean}/${path}` : path;\n};\n\n/** What a route resolution needs from the owning source and the config. */\nexport type RouteContext = Pick<NormalizeContext, \"i18n\" | \"versions\"> & {\n /** The source's route prefix (`NormalizeContext[\"source\"][\"prefix\"]`). */\n prefix?: string;\n};\n\n/** Where an entry's ref places it once its directories are read off. */\nexport interface EntryPlacement {\n /**\n * The locale codes the entry publishes in: `[\"\"]` without i18n, one code for\n * a placed file, every configured code for a shared `$` file.\n */\n locales: string[];\n /** The ref with its version and locale directories stripped, prefix-less. */\n navPath: string;\n /** The version snapshot the entry belongs to (`\"\"` for current). */\n version: string;\n}\n\n/**\n * Read the version and locale directories off an entry's ref. The version is\n * detected first: a snapshot directory is outermost on disk\n * (`v1.0/fr/page.mdx`), so the locale parser must see a version-stripped ref.\n * The current version is `\"\"` and lives at the root. Locale placement comes\n * from the ref (a leading dir, or a filename suffix under the `dot` parser),\n * never the slug — the slug is the logical, locale-agnostic path within a\n * locale. A shared `$` file maps to every locale; a source without i18n\n * placement maps to one.\n */\nexport const placeEntryRef = (\n ref: string,\n ext: string,\n ctx: Pick<RouteContext, \"i18n\" | \"versions\">\n): EntryPlacement => {\n const { version, rest } = ctx.versions\n ? detectVersionRef(ref, ctx.versions)\n : { rest: ref, version: \"\" };\n const { navPath, locales } = ctx.i18n\n ? localePlacement(rest, ext, ctx.i18n)\n : { locales: [\"\"], navPath: rest };\n return { locales, navPath, version };\n};\n\n/** Everything `normalizeEntry` derives from an entry's ref and slug. */\nexport interface EntryRoute extends Pick<\n EntryPlacement,\n \"locales\" | \"version\"\n> {\n groups: string[];\n /**\n * The version-prefixed, locale-agnostic route — the translation key. Pass it\n * through {@link localizedRoute} for the route one locale publishes at.\n */\n logicalRoute: string;\n /** The prefixed, locale- and version-stripped nav path. */\n navPath: string;\n segments: string[];\n /** The version-agnostic mapped route. */\n versionKey: string;\n}\n\n/**\n * The canonical route resolution, shared by {@link normalizeEntry} and any\n * source that must predict the route an entry will publish at — the Obsidian\n * source turns `[[Note]]` into a real href, and a second derivation of a route\n * is a second answer.\n *\n * A frontmatter `slug` wins, then the adapter-supplied `entry.slug` (the typed\n * SPI's \"logical route input; defaults to ref if omitted\"), then the ref. The\n * extension is re-appended so `mapRoute`'s extname strip can't eat a dotted\n * slug segment (`v1.2`). A slug that trims to nothing falls back. The version\n * prefixes the mapped route *after* `mapRoute` runs: the mapped route is the\n * version-agnostic key, the config id is prepended verbatim (never\n * numeric-prefix-stripped), a frontmatter `slug` gets versionized so snapshots\n * can't collide with the live page, and `translationKey` becomes\n * version-specific for free. `basePath` is not applied here — it is outermost,\n * after locale prefixing — so the result reads `{locale?}/{prefix?}/…`.\n */\nexport const resolveEntryRoute = (\n entry: Pick<SourceEntry, \"ref\" | \"slug\">,\n ext: string,\n frontmatterSlug: string | undefined,\n ctx: RouteContext\n): EntryRoute => {\n const { locales, navPath, version } = placeEntryRef(entry.ref, ext, ctx);\n const slugInput = frontmatterSlug ?? entry.slug;\n const slug = slugInput ? trimSlashes(slugInput) : \"\";\n const routeInput = withPrefix(ctx.prefix, slug ? `${slug}${ext}` : navPath);\n const { segments, groups, route: versionKey } = mapRoute(routeInput);\n return {\n groups,\n locales,\n logicalRoute: versionizeRoute(versionKey, version),\n navPath: withPrefix(ctx.prefix, navPath),\n segments,\n version,\n versionKey,\n };\n};\n\n/** The route a logical route publishes at in one locale, base path excluded. */\nexport const localizedRoute = (\n logicalRoute: string,\n locale: string,\n i18n: ResolvedI18nConfig | undefined\n): string => (i18n ? localizeRoute(logicalRoute, locale, i18n) : logicalRoute);\n\n/** A custom-key validation failure, lowered to a joinable diagnostic path. */\ninterface CustomKeyIssue {\n message: string;\n path: (string | number)[];\n}\n\n/** The validated custom keys (if any survived) plus every failure found. */\ninterface CustomKeyValidation {\n custom?: PageRecord[\"custom\"];\n issues: CustomKeyIssue[];\n}\n\n/** Whether a Standard Schema path segment is the wrapped `{ key }` form. */\nconst isKeyCarrier = (\n segment: PropertyKey | { readonly key: PropertyKey }\n): segment is { readonly key: PropertyKey } =>\n typeof segment === \"object\" && segment !== null;\n\nconst isSymbolKey = (key: PropertyKey): key is symbol =>\n typeof key === \"symbol\";\n\n/** Lower a Standard Schema path segment (`key` or `{ key }`) for joining. */\nconst segmentKey = (\n segment: PropertyKey | { readonly key: PropertyKey }\n): string | number => {\n const key = isKeyCarrier(segment) ? segment.key : segment;\n return isSymbolKey(key) ? String(key) : key;\n};\n\n/**\n * Validate the opt-in custom frontmatter keys (`frontmatter.extend` and\n * `content.types.<type>.frontmatter`) through the Standard Schema contract —\n * the consumer's own Zod (any version), Valibot, or ArkType, never Blume's\n * bundled zod (see `standard-schema.ts`). Every declared key is checked,\n * absent ones included, so a required schema enforces its key on every page\n * it applies to. Async schemas are rejected with a diagnostic: this funnel is\n * synchronous, and frontmatter validation has no business awaiting I/O.\n */\nconst validateCustomKeys = (\n data: SourceEntry[\"data\"],\n extend: FrontmatterExtend\n): CustomKeyValidation => {\n const custom: NonNullable<PageRecord[\"custom\"]> = {};\n const issues: CustomKeyIssue[] = [];\n for (const [key, schema] of Object.entries(extend)) {\n const outcome = schema[\"~standard\"].validate(data[key]);\n if (outcome instanceof Promise) {\n issues.push({\n message: \"Async schemas are not supported for custom frontmatter keys.\",\n path: [key],\n });\n continue;\n }\n if (outcome.issues !== undefined) {\n issues.push(\n ...outcome.issues.map((issue) => ({\n message: issue.message,\n path: [key, ...(issue.path ?? []).map(segmentKey)],\n }))\n );\n continue;\n }\n // Preserve the validated (schema-output) value; skip keys that are absent\n // and stay absent, so `.optional()` extras don't materialize as undefined.\n if (outcome.value !== undefined || Object.hasOwn(data, key)) {\n custom[key] = outcome.value;\n }\n }\n return {\n custom: Object.keys(custom).length > 0 ? custom : undefined,\n issues,\n };\n};\n\n/**\n * Parse an entry's frontmatter: built-in keys through the strict page schema,\n * custom keys (`frontmatter.extend` plus the page type's\n * `content.types.<type>.frontmatter`) through their user-supplied schemas.\n * The custom keys are carved out before the strict parse, so the page schema\n * stays strict for everything else and unknown-key typo catching is unchanged\n * — a key declared only for some other type stays unknown here.\n * Returns diagnostics instead of meta when either side rejects.\n */\nconst parseEntryMeta = (\n entry: SourceEntry,\n ctx: NormalizeContext\n):\n | { meta: PageMeta; custom?: PageRecord[\"custom\"]; diagnostics?: never }\n | { meta?: never; diagnostics: Diagnostic[] } => {\n // Resolved the same way `contentType` is after parsing (`meta.type` falling\n // back to `defaultType`); a non-string `type` fails the strict parse below,\n // so which per-type map was merged for that entry never matters.\n const entryType = isStringValue(entry.data.type)\n ? entry.data.type\n : ctx.defaultType;\n const typeExtend = ctx.typeFrontmatter?.[entryType];\n // Config validation rejects a key declared both site-wide and per-type, so\n // this merge never has to pick a winner.\n const extend =\n ctx.frontmatterExtend || typeExtend\n ? { ...ctx.frontmatterExtend, ...typeExtend }\n : undefined;\n const known = extend\n ? Object.fromEntries(\n Object.entries(entry.data).filter(\n ([key]) => !Object.hasOwn(extend, key)\n )\n )\n : entry.data;\n\n const result = pageMetaSchema.safeParse(known);\n const customResult = extend ? validateCustomKeys(entry.data, extend) : null;\n\n if (result.success && (customResult?.issues.length ?? 0) === 0) {\n return { custom: customResult?.custom, meta: result.data };\n }\n\n // Source text lets the error carry a line/column into the frontmatter block:\n // `entry.raw` for non-filesystem sources, else the file itself (read only on\n // this rare error path, so filesystem entries stay cheap in the happy path).\n const source =\n entry.raw ??\n (entry.sourcePath && existsSync(entry.sourcePath)\n ? readFileSync(entry.sourcePath, \"utf-8\")\n : undefined);\n const location = {\n code: \"BLUME_FRONTMATTER_INVALID\",\n file: entry.sourcePath ?? `${ctx.source.name}:${entry.ref}`,\n source,\n };\n return {\n diagnostics: [\n ...(result.success ? [] : diagnosticsFromZod(result.error, location)),\n ...(customResult\n ? diagnosticsFromIssues(customResult.issues, location)\n : []),\n ],\n };\n};\n\n/** The per-locale page records and diagnostics from one source entry. */\nexport interface NormalizedEntry {\n pages: PageRecord[];\n diagnostics: Diagnostic[];\n}\n\n/**\n * Normalize one source entry into per-locale `PageRecord`s. This is the single\n * funnel every adapter's entries pass through, so route mapping, heading/link\n * extraction, and meta validation are identical regardless of origin.\n */\nexport const normalizeEntry = (\n entry: SourceEntry,\n ctx: NormalizeContext\n): NormalizedEntry => {\n const { format } = entry.body;\n const ext = format === \"mdx\" ? \".mdx\" : \".md\";\n\n const parsed = parseEntryMeta(entry, ctx);\n if (parsed.diagnostics) {\n return { diagnostics: parsed.diagnostics, pages: [] };\n }\n\n const { meta } = parsed;\n\n // Top-level `hidden`/`noindex` are accepted as shorthands for their nested\n // equivalents — the schema declares them, so silently ignoring them would\n // strand authors with no diagnostic.\n if (meta.hidden) {\n meta.sidebar.hidden = true;\n }\n if (meta.noindex) {\n meta.seo.noindex = true;\n }\n\n const {\n groups,\n locales,\n logicalRoute,\n navPath,\n segments,\n version,\n versionKey,\n } = resolveEntryRoute(entry, ext, meta.slug, {\n i18n: ctx.i18n,\n prefix: ctx.source.prefix,\n versions: ctx.versions,\n });\n // Extraction runs on the include-expanded body when the scan expanded one,\n // so a partial's headings anchor-index and TOC under every including page\n // and its components register for the runtime import map.\n const bodyText = entry.expanded?.text ?? entry.body.text;\n const { anchors, curlyMarkers, headings } = scanBody(bodyText);\n const { staged } = ctx.source;\n\n const base = {\n anchors,\n body: staged ? { format, text: entry.raw ?? entry.body.text } : undefined,\n collection: staged ? \"staged\" : undefined,\n componentsUsed:\n format === \"mdx\" ? extractComponentTags(bodyText) : undefined,\n contentType: meta.type ?? ctx.defaultType,\n custom: parsed.custom,\n description: meta.description,\n editUrl: entry.editUrl,\n entryId: staged ? `${ctx.source.name}/${entry.ref}` : undefined,\n format,\n groups,\n headings,\n id: `${ctx.source.name}:${entry.ref}`,\n includes: entryIncludes(entry),\n lastModified: meta.lastModified ?? entry.lastModified,\n links: entryLinks(entry),\n meta,\n navPath,\n segments,\n source: { name: ctx.source.name, ref: entry.ref },\n sourcePath: entry.sourcePath,\n title: deriveTitle(meta, headings, navPath),\n translationKey: logicalRoute,\n version,\n versionKey,\n } satisfies Omit<PageRecord, \"locale\" | \"route\">;\n\n // One record per locale this entry maps to (one normally; every locale for a\n // shared `$` file). All share the same id, source ref, and translation key.\n // `basePath` is applied outermost — after locale prefixing — so the route\n // reads `{basePath}/{locale?}/{prefix?}/…`; `navPath` and `translationKey`\n // stay base-less so the nav tree and translation matching are unaffected.\n const pages = locales.map((locale) => ({\n ...base,\n locale,\n route: withBasePath(\n ctx.basePath ?? \"\",\n localizedRoute(logicalRoute, locale, ctx.i18n)\n ),\n }));\n\n return {\n diagnostics:\n format === \"mdx\"\n ? curlyMarkerDiagnostics(entry, curlyMarkers, ctx.source.name)\n : [],\n pages,\n };\n};\n",
|
|
19
|
-
"/**\n * Trailing heading markers, matching Fumadocs' syntax so migrated content works\n * verbatim: `## Heading [#custom-id]` pins the anchor id, `## Heading [!toc]`\n * keeps the heading on the page but out of the table of contents, and\n * `## Heading [toc]` shows it only in the table of contents. Markers chain in\n * any order (`## Heading [toc] [#id]`). The `{#id}` pin of Pandoc, kramdown,\n * and Markdown-based specification toolchains is accepted as an equivalent of\n * `[#id]` — in `.md` verbatim, in `.mdx` only as the escape `\\{#id\\}` (a bare\n * `{…}` there is a JSX expression; the scan-time scanner reports one as\n * `BLUME_MDX_CURLY_ANCHOR`). Both pipelines resolve escapes before parsing\n * markers, so the escaped and bare spellings reach this parser identically.\n *\n * Both heading pipelines share this parser — the render-time hast plugin\n * (`markdown/heading-anchors.ts`) and the scan-time source scanner\n * (`core/sources/normalize.ts`) — so the rendered `id`, the TOC entry, and the\n * anchor index `blume validate` checks against always agree.\n */\n\nimport type GithubSlugger from \"github-slugger\";\n\n/**\n * Frontmatter key carrying the slugs of `[!toc]` headings out of a render. The\n * hast plugin pushes onto it and the generated page template reads it back via\n * `remarkPluginFrontmatter` to filter the TOC.\n */\nexport const TOC_HIDDEN_KEY = \"__blumeTocHidden\";\n\n/**\n * Register a pinned `[#id]` with the document slugger, the way\n * `GithubSlugger#slug` registers the slugs it returns — so a later heading\n * whose auto-slug collides disambiguates (`setup` → `setup-1`) instead of\n * silently duplicating the anchor id. An id that is already taken is left\n * untouched (the pin still uses it verbatim; duplicate pins are the author's\n * explicit choice).\n */\nexport const occupySlug = (slugger: GithubSlugger, id: string): void => {\n slugger.occurrences[id] ??= 0;\n};\n\n/** One trailing marker: `[#id]`, `{#id}`, `[toc]`, or `[!toc]`. */\nconst MARKER =\n /\\s*(?:\\{#(?<curlyId>[^\\s}]+)\\}|\\[(?:#(?<id>[^\\s\\]]+)|(?<hide>!)?toc)\\])\\s*$/u;\n\nexport interface HeadingMarkers {\n /** Author-pinned anchor id from `[#id]` or `{#id}`, used verbatim (never re-slugged). */\n id?: string;\n /** The heading text with every trailing marker stripped. */\n text: string;\n /** `[!toc]` hides the heading from the TOC; `[toc]` shows it only there. */\n toc?: \"hide\" | \"only\";\n}\n\n/**\n * Split trailing markers off a heading's text. Only markers at the very end of\n * the heading count (mid-text brackets are ordinary prose); when a marker kind\n * repeats, the last one written wins.\n *\n * `isRefDefined` reports whether a link-reference definition exists for a\n * bracket's label (`toc`, `!toc`, `#id`): a defined bracket is a CommonMark\n * shortcut link, not a marker — the renderer sees a resolved `<a>` element\n * there and strips nothing at or before it, so the parse stops the same way.\n * The render-time hast plugin never passes it (resolved links are already\n * elements by the time it runs); the raw-source scanner does.\n */\nexport const parseHeadingMarkers = (\n text: string,\n isRefDefined?: (label: string) => boolean\n): HeadingMarkers => {\n let remaining = text;\n let id: string | undefined;\n let toc: HeadingMarkers[\"toc\"];\n for (\n let match = MARKER.exec(remaining);\n match?.groups;\n match = MARKER.exec(remaining)\n ) {\n const explicitId = match.groups.curlyId ?? match.groups.id;\n const label =\n explicitId === undefined\n ? `${match.groups.hide ?? \"\"}toc`\n : `#${explicitId}`;\n // A `{#id}` is never a shortcut link, whatever the definitions say.\n if (match.groups.curlyId === undefined && isRefDefined?.(label)) {\n break;\n }\n remaining = remaining.slice(0, match.index);\n if (explicitId === undefined) {\n // Stripping runs right-to-left, so keeping the first capture of each\n // kind makes the rightmost occurrence win.\n toc ??= match.groups.hide === undefined ? \"only\" : \"hide\";\n } else {\n id ??= explicitId;\n }\n }\n return { id, text: remaining, toc };\n};\n",
|
|
20
|
-
"import { execFileSync } from \"node:child_process\";\n\nimport { 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 union into `{ enabled, source }`. */\nexport const resolveLastModifiedConfig = (\n value: boolean | { type: \"git\" | \"frontmatter\" }\n): ResolvedLastModified => {\n if (value === false) {\n return { enabled: false, source: \"git\" };\n }\n if (value === true) {\n return { enabled: true, source: \"git\" };\n }\n return { enabled: true, source: value.type };\n};\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 * 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 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,\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(gitRoot, 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",
|
|
21
|
-
"import { basename, dirname, relative } from \"pathe\";\nimport { glob } 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. */\nexport interface FolderMetaSource {\n root: string;\n prefix?: string;\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 const localeDirs = new Set(options.localeDirs);\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 glob(META_FILES, {\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 ignore: [\"**/node_modules/**\", \"**/.blume/**\", \"**/dist/**\"],\n onlyFiles: true,\n });\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 && localeDirs.has(localeHead) ? localeHead : \"\";\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",
|
|
22
|
-
"import { isAbsolute, join, resolve } from \"pathe\";\n\nimport { blumeReferences } from \"../../openapi/references.ts\";\nimport { openApiSource } from \"../../openapi/source.ts\";\nimport type { ContentSourceConfig, ResolvedConfig } from \"../schema.ts\";\nimport type { ProjectContext } from \"../types.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 { sanitySource } from \"./sanity.ts\";\nimport type { ContentSource, SourceContext } from \"./types.ts\";\n\n/** Allocate a unique, stable source name from a base (prefix or type). */\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\nconst sourceContext = (\n context: ProjectContext,\n name: string,\n runtime: SourceRuntime\n): SourceContext => ({\n assetsBaseUrl: `/blume-assets/${name}`,\n assetsDir: join(context.outDir, \"public\", \"blume-assets\", name),\n cacheDir: join(context.outDir, \"cache\", name),\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\nconst buildSource = (\n def: ContentSourceConfig,\n name: string,\n config: ResolvedConfig,\n context: ProjectContext,\n runtime: SourceRuntime\n): ContentSource => {\n if (def.type === \"filesystem\") {\n return filesystemSource({\n exclude: def.exclude,\n include: def.include,\n name,\n prefix: def.prefix,\n projectRoot: context.root,\n root: def.root,\n });\n }\n if (def.type === \"custom\") {\n // A user-provided instance manages its own context/caching; we only ensure\n // its name is unique across the project for id namespacing.\n return def.source.name === name ? def.source : { ...def.source, name };\n }\n if (def.type === \"sanity\") {\n return sanitySource(\n {\n apiVersion: def.apiVersion,\n dataset: def.dataset,\n fields: def.fields,\n name,\n pollInterval: def.pollInterval,\n prefix: def.prefix,\n projectId: def.projectId,\n query: def.query,\n },\n sourceContext(context, name, runtime)\n );\n }\n if (def.type === \"notion\") {\n return notionSource(\n {\n concurrency: def.concurrency,\n database: def.database,\n name,\n pollInterval: def.pollInterval,\n prefix: def.prefix,\n properties: def.properties,\n publishedValue: def.publishedValue,\n },\n sourceContext(context, name, runtime)\n );\n }\n if (def.type === \"obsidian\") {\n return obsidianSource(\n {\n defaultType: config.content.defaultType,\n exclude: def.exclude,\n frontmatterKeys: Object.keys(config.frontmatter.extend),\n i18n: config.i18n,\n name,\n prefix: def.prefix,\n typeFrontmatterKeys: typeFrontmatterKeys(config),\n vault: def.vault,\n versions: config.versions,\n },\n sourceContext(context, name, runtime)\n );\n }\n if (def.type === \"github-releases\") {\n return githubReleasesSource(\n {\n drafts: def.drafts,\n limit: def.limit,\n name,\n owner: def.owner,\n pollInterval: def.pollInterval,\n prefix: def.prefix,\n prereleases: def.prereleases,\n repo: def.repo,\n },\n sourceContext(context, name, runtime)\n );\n }\n return mdxRemoteSource(\n {\n files: def.files,\n github: def.github,\n include: def.include,\n name,\n pollInterval: def.pollInterval,\n prefix: def.prefix,\n url: def.url,\n },\n sourceContext(context, name, runtime)\n );\n};\n\n/** Resolve a source `root` against the project root (absolute passes through). */\nconst resolveRoot = (projectRoot: string, root: string): string =>\n isAbsolute(root) ? root : join(resolve(projectRoot), root);\n\n/**\n * The generated `docs` glob collection: its base directory and the include /\n * exclude globs applied under it. Astro's glob loader ids each entry by its path\n * relative to `base`, and a filesystem source ids each entry relative to its own\n * root — so the two only agree when the collection is rooted at that source. A\n * project with exactly one filesystem source therefore roots the collection at\n * *that* source (honoring a non-default `root`), rather than the global\n * `content.root`. With no sources (the implicit source) or several, the base\n * stays `content.root`; a second filesystem source rooted elsewhere can't share\n * one base and is caught by the entry-id guard in `scanProject`.\n */\nexport interface DocsCollection {\n base: string;\n include: string[];\n exclude: string[];\n}\n\nexport const resolveDocsCollection = (\n config: ResolvedConfig,\n context: ProjectContext\n): DocsCollection => {\n const filesystem = (config.content.sources ?? []).filter(\n (def) => def.type === \"filesystem\"\n );\n const only = filesystem.length === 1 ? filesystem[0] : undefined;\n if (only) {\n return {\n base: resolveRoot(context.root, only.root),\n exclude: only.exclude,\n include: only.include,\n };\n }\n return {\n base: context.contentRoot,\n exclude: config.content.exclude,\n include: config.content.include,\n };\n};\n\n/** The base name to allocate for a source config (before deduplication). */\nconst baseName = (def: ContentSourceConfig): string => {\n if (def.type === \"custom\") {\n return def.source.name;\n }\n return def.prefix ?? def.type;\n};\n\n/** The content sources declared by config (implicit filesystem when none). */\nconst contentSources = (\n config: ResolvedConfig,\n context: ProjectContext,\n runtime: SourceRuntime\n): ContentSource[] => {\n const defs = config.content.sources;\n if (!defs || defs.length === 0) {\n return [\n filesystemSource({\n exclude: config.content.exclude,\n include: config.content.include,\n name: \"filesystem\",\n projectRoot: context.root,\n root: config.content.root,\n }),\n ];\n }\n\n const nameFor = uniqueNamer();\n return defs.map((def) =>\n buildSource(def, nameFor(baseName(def)), config, context, runtime)\n );\n};\n\n/**\n * Build the ordered list of content sources for a project. With no\n * `content.sources` configured, the top-level `root`/`include`/`exclude` desugar\n * to a single implicit filesystem source, so existing projects are untouched.\n * A Blume-rendered OpenAPI reference contributes an internal staged source that\n * lowers each 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 sources = contentSources(config, context, runtime);\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",
|
|
23
|
-
"import { createHash } from \"node:crypto\";\nimport { mkdir, readFile, writeFile } from \"node:fs/promises\";\n\nimport { join } from \"pathe\";\n\nimport { BlumeError } from \"../diagnostics.ts\";\nimport type { Diagnostic } from \"../types.ts\";\nimport type { SourceEntry, SourceLoadResult } from \"./types.ts\";\n\n/**\n * Small, stable content hash for cache/HMR bookkeeping — and for staged asset\n * *filenames* (see sources/assets.ts and content-assets.ts), where a collision\n * silently serves the wrong file. 64 bits of SHA-256 keeps those names\n * collision-safe at any realistic asset count; the old 31-bit DJB2 hash had a\n * ~46k-item birthday bound.\n */\nexport const hashText = (text: string): string =>\n createHash(\"sha256\").update(text).digest(\"hex\").slice(0, 16);\n\n/** A stable digest of a source's entries, for change detection while polling. */\nexport const entriesDigest = (entries: SourceEntry[]): string =>\n hashText(\n entries\n .map((entry) => `${entry.ref}:${entry.hash ?? hashText(entry.body.text)}`)\n .join(\"|\")\n );\n\n/**\n * Build an opt-in polling watcher for a remote source: re-`load()` on an\n * interval and fire `onChange` only when the entry digest changes, so a remote\n * source can hot-reload in dev without refetching the world on every keystroke.\n *\n * `load` must fetch fresh (bypassing the cache-first dev path) — polling the\n * cache-first loader would serve the identical snapshot on every tick and\n * never observe a remote change. `seed` (the source's regular, cache-first\n * loader) establishes the baseline digest from what dev actually served, so a\n * remote change landing before the first tick still fires.\n */\nexport const pollingWatch =\n (\n load: () => Promise<SourceLoadResult>,\n intervalSeconds: number,\n seed?: () => Promise<SourceLoadResult>\n ): ((onChange: () => void) => () => void) =>\n (onChange) => {\n let last = \"\";\n if (seed) {\n const seedBaseline = async (): Promise<void> => {\n try {\n const { entries } = await seed();\n last ||= entriesDigest(entries);\n } catch {\n // Fall back to first-tick seeding.\n }\n };\n void seedBaseline();\n }\n const tick = async (): Promise<void> => {\n try {\n const { entries } = await load();\n const next = entriesDigest(entries);\n if (last && next !== last) {\n onChange();\n }\n last = next;\n } catch {\n // Ignore transient poll failures; the cache keeps serving last-known-good.\n }\n };\n const timer = setInterval(() => {\n void tick();\n }, intervalSeconds * 1000);\n return () => clearInterval(timer);\n };\n\n/** A per-source snapshot of the last successful fetch, for offline tolerance. */\nexport interface SnapshotCache {\n read: () => Promise<SourceEntry[]>;\n write: (entries: SourceEntry[]) => Promise<void>;\n}\n\n/** Build a JSON snapshot cache under `<cacheDir>/entries.json`. */\nexport const snapshotCache = (cacheDir: string): SnapshotCache => {\n const file = join(cacheDir, \"entries.json\");\n return {\n read: async () => {\n try {\n // SAFETY: the snapshot file is only ever written by `write` below, from\n // a `SourceEntry[]` via JSON.stringify; a corrupt file lands in the\n // catch and reads as empty.\n return JSON.parse(await readFile(file, \"utf-8\")) as SourceEntry[];\n } catch {\n return [];\n }\n },\n write: async (entries) => {\n try {\n await mkdir(cacheDir, { recursive: true });\n await writeFile(file, `${JSON.stringify(entries)}\\n`, \"utf-8\");\n } catch {\n // Cache is best-effort; a write failure must not fail the build.\n }\n },\n };\n};\n\n/**\n * Run a remote `fetchEntries`, caching the result. When `refresh` is false and a\n * snapshot exists, serve it without fetching (cache-first dev). On fetch failure,\n * serve the last-known-good snapshot with a warning so a CMS/network outage\n * doesn't fail the build; if there is no snapshot either, surface a hard error.\n */\nexport const loadWithCache = async (\n name: string,\n cache: SnapshotCache,\n fetchEntries: () => Promise<SourceEntry[]>,\n refresh = true\n): Promise<SourceLoadResult> => {\n if (!refresh) {\n const cached = await cache.read();\n if (cached.length > 0) {\n return { diagnostics: [], entries: cached };\n }\n }\n try {\n const entries = await fetchEntries();\n await cache.write(entries);\n return { diagnostics: [], entries };\n } catch (error) {\n const fallback = await cache.read();\n if (fallback.length > 0) {\n // SAFETY: everything thrown on this path is an Error — fetch rejects\n // with a TypeError and the source adapters throw Error instances.\n const diagnostic: Diagnostic = {\n code: \"BLUME_SOURCE_OFFLINE\",\n message: `Source \"${name}\" could not be fetched (${(error as Error).message}); served ${fallback.length} cached entries.`,\n severity: \"warning\",\n };\n return { diagnostics: [diagnostic], entries: fallback };\n }\n // SAFETY: same invariant as above — `fetchEntries` failures are Errors.\n throw new BlumeError({\n code: \"BLUME_SOURCE_FETCH_FAILED\",\n message: `Source \"${name}\" failed to load and no cache is available: ${(error as Error).message}`,\n severity: \"error\",\n });\n }\n};\n",
|
|
24
|
-
"import type { Document, OperationObject } from \"@scalar/openapi-types/3.1\";\n\nimport type { AsyncApiAction, AsyncApiDocument } from \"./asyncapi.ts\";\n// Type-only, so the import can't cycle at runtime (graphql.ts imports the\n// collector from here).\nimport type { GraphqlDocument, GraphqlMember } from \"./graphql.ts\";\nimport type { ReferenceKind } from \"./references.ts\";\nimport { slugify } from \"./references.ts\";\n\n// The slug rules live with the reference resolver so operation routes and\n// reference-source tokens can never drift apart; re-exported for existing\n// importers.\nexport { slugify } from \"./references.ts\";\n\n/**\n * Blume's own API reference model, shared by both spec kinds. OpenAPI specs\n * are parsed and upgraded to 3.1, AsyncAPI specs normalized to 3.x (see\n * `parse.ts`), with internal `$ref`s left intact — the document stays\n * JSON-serializable (a fully dereferenced graph can be circular), and the schema\n * components resolve refs against `document.components.schemas` at render time.\n * Each operation is flattened into an {@link ApiOperationRef} with a real,\n * per-operation route so it becomes a first-class Blume page.\n */\n\n/** A normalized OpenAPI 3.1 document, internal `$ref`s intact. */\nexport type ApiDocument = Document;\n\n/** The HTTP methods an OpenAPI path item may declare, in spec order. */\nexport const HTTP_METHODS = [\n \"get\",\n \"put\",\n \"post\",\n \"delete\",\n \"options\",\n \"head\",\n \"patch\",\n \"trace\",\n] as const;\n\nexport type HttpMethod = (typeof HTTP_METHODS)[number];\n\n/** Group used for operations that declare no tag. */\nconst UNTAGGED = \"Operations\";\n\n/** A lower-case letter or digit followed by a capital: `addPet`, `v2List`. */\nconst CASE_BOUNDARY = /(?<before>[\\p{Ll}\\p{N}])(?<capital>\\p{Lu})/gu;\n/** The last capital of an acronym before a capitalized word: `HTTPResponse`. */\nconst ACRONYM_BOUNDARY = /(?<acronym>\\p{Lu})(?<word>\\p{Lu}\\p{Ll})/gu;\n\n/**\n * Hyphenate an identifier's word boundaries before slugifying, so a camelCase\n * `operationId` keeps its words in the URL (`getHTTPResponse` ->\n * `get-http-response`) instead of collapsing once `slugify` lowercases it.\n * OpenAPI generators reuse operation ids as SDK method names, so camelCase is\n * the norm there; GraphQL field and type names and AsyncAPI operation ids go\n * through the same rule so every spec kind derives routes alike. An id that\n * is already kebab-case has no boundaries to split and passes through as is.\n */\nconst splitIdentifier = (identifier: string): string =>\n identifier\n .replace(CASE_BOUNDARY, \"$<before>-$<capital>\")\n .replace(ACRONYM_BOUNDARY, \"$<acronym>-$<word>\");\n\n/** A stable, URL-safe key for an operation: its `operationId`, else method+path. */\nexport const operationKey = (\n method: string,\n path: string,\n operationId?: string\n): string => {\n const fromId = operationId ? slugify(splitIdentifier(operationId)) : \"\";\n return fromId || slugify(`${method}-${path}`);\n};\n\n/** One operation, flattened out of its document and mapped to a route. */\nexport interface ApiOperationRef {\n /** Stable key, unique within a spec; matches the MDX `<Operation id>`. */\n key: string;\n /**\n * HTTP method (OpenAPI), `send`/`receive` action (AsyncAPI), or the member\n * kind — a root-field operation kind or a named-type kind (GraphQL).\n */\n method: HttpMethod | AsyncApiAction | GraphqlMember;\n /**\n * Templated path, e.g. `/pets/{id}` — the channel address (AsyncAPI), or\n * the root field / type name (GraphQL).\n */\n path: string;\n /** Full site route for this operation's page, e.g. `/reference/pet/add-pet`. */\n route: string;\n /** Display tag name (first tag; `Operations` or the channel address when untagged). */\n tag: string;\n tagSlug: string;\n summary: string;\n description: string;\n /** The `operationId` (OpenAPI) or the `operations` map key (AsyncAPI). */\n operationId?: string;\n deprecated: boolean;\n /** The channel the operation acts on (AsyncAPI only). */\n channelId?: string;\n}\n\n/** A tag/section, in first-seen order. */\nexport interface ApiTagRef {\n slug: string;\n name: string;\n description: string;\n}\n\n/** Everything the runtime needs for one spec, serialized into `blume:openapi`. */\nexport interface ApiSpecData {\n /** Which front-end parsed the spec (and which components render it). */\n kind: ReferenceKind;\n /** Unique token used as the `<Operation source>` and the data-module key. */\n slug: string;\n /** Base route the spec's operations hang off, e.g. `/reference`. */\n route: string;\n label: string;\n title: string;\n version: string;\n description: string;\n document: ApiDocument | AsyncApiDocument | GraphqlDocument;\n /**\n * URL of the live GraphQL endpoint the playground and code samples target\n * (GraphQL only; OpenAPI documents carry their servers in the document).\n */\n endpoint?: string;\n /** Operations keyed by {@link ApiOperationRef.key}. */\n operations: Record<string, ApiOperationRef>;\n tags: ApiTagRef[];\n /** Code-sample languages to render per operation. */\n codeSamples: string[];\n /** Whether nested schema rows start expanded. */\n expandSchemas: boolean;\n /**\n * The \"Try it\" playground: whether operation pages render it, and the\n * resolved proxy the Send button targets — `false` for direct requests, a\n * URL string otherwise (the built-in `/_api-proxy` route already carries\n * the site `basePath`).\n */\n playground: { enabled: boolean; proxy: string | false };\n}\n\n/** The generated `blume:openapi` module: specs keyed by {@link ApiSpecData.slug}. */\nexport type OpenApiData = Record<string, ApiSpecData>;\n\n/**\n * The spec a `<Operation source>` / `<ApiOverview source>` names, or nothing.\n * `blume:openapi` crosses a JSON boundary as a plain object, so a lookup must\n * be an own-property one: `source=\"toString\"` would otherwise resolve to the\n * inherited function, which is truthy and carries no `operations`, and every\n * consumer would throw where it means to decline. Shared by the components\n * and the agent-surface serializers so they miss the same way.\n */\nexport const specOf = (\n specs: OpenApiData,\n source: string\n): ApiSpecData | undefined =>\n Object.hasOwn(specs, source) ? specs[source] : undefined;\n\n/** The operation an `<Operation id>` names within its spec, or nothing. */\nexport const operationOf = (\n spec: ApiSpecData,\n id: string\n): ApiOperationRef | undefined =>\n Object.hasOwn(spec.operations, id) ? spec.operations[id] : undefined;\n\n/** The addresses an API overview lists, and what to call them. */\nexport interface SpecAddresses {\n /** `Base URL` (OpenAPI), `Servers` (AsyncAPI) or `Endpoint` (GraphQL). */\n label: string;\n addresses: string[];\n}\n\n/**\n * Where the API lives, flattened into one list for the overview page. OpenAPI\n * declares `servers` as an array of URLs; AsyncAPI as a named map of\n * host/protocol/pathname; a GraphQL schema names no server, so its configured\n * live endpoint stands in.\n */\nexport const specAddresses = (spec: ApiSpecData): SpecAddresses => {\n const addresses: string[] = [];\n if (spec.kind === \"graphql\") {\n if (spec.endpoint) {\n addresses.push(spec.endpoint);\n }\n return { addresses, label: \"Endpoint\" };\n }\n if (spec.kind === \"asyncapi\") {\n // SAFETY: an `asyncapi` spec's document is the AsyncAPI shape (`parse.ts`\n // routes each kind to its own parser).\n const servers = (spec.document as AsyncApiDocument).servers ?? {};\n for (const server of Object.values(servers)) {\n if (server?.host) {\n addresses.push(\n `${server.protocol ? `${server.protocol}://` : \"\"}${server.host}${server.pathname ?? \"\"}`\n );\n }\n }\n return { addresses, label: \"Servers\" };\n }\n // Hand-written specs sometimes declare `servers` as a bare object; degrade\n // to no addresses instead of throwing mid-build.\n // SAFETY: the remaining kind is OpenAPI, whose document declares `servers`\n // as an array of server objects; the array check below guards a spec that\n // wrote something else there.\n const declared = (spec.document as { servers?: { url?: string }[] }).servers;\n for (const server of Array.isArray(declared) ? declared : []) {\n if (server.url) {\n addresses.push(server.url);\n }\n }\n return { addresses, label: \"Base URL\" };\n};\n\n// The runtime object check stands guard because the document was parsed from\n// arbitrary YAML/JSON: a spec can put a scalar where the type promises an\n// operation object.\nconst isOperation = (\n value: OperationObject | undefined\n): value is OperationObject => typeof value === \"object\" && value !== null;\n\n/** A declared tag whose `name` really is a string at runtime, type aside. */\nconst hasTagName = (tag: SpecTag): tag is SpecTag =>\n typeof tag.name === \"string\";\n\n/**\n * Assign each distinct tag name a unique slug. `slugify` can collapse\n * different names onto one value — any two punctuation-only tags (`!!!`,\n * `???`) both fall through to the `operations` fallback — and a shared slug\n * silently merges the tags' routes, sidebar groups, and overview sections.\n * Collisions gain `-2`, `-3`, … in first-seen order. Shared with the AsyncAPI\n * extractor (`asyncapi.ts`), whose untagged fallback groups are channel\n * addresses.\n */\nexport const tagSlugger = (): ((name: string) => string) => {\n const assigned = new Map<string, string>();\n const taken = new Set<string>();\n return (name) => {\n const existing = assigned.get(name);\n if (existing) {\n return existing;\n }\n const base = slugify(name) || \"operations\";\n let slug = base;\n for (let suffix = 2; taken.has(slug); suffix += 1) {\n slug = `${base}-${suffix}`;\n }\n taken.add(slug);\n assigned.set(name, slug);\n return slug;\n };\n};\n\n/** An operation before the collector assigns its unique key and route. */\ntype CollectedOperation = Omit<ApiOperationRef, \"route\" | \"tagSlug\">;\n\n/** A document's declared tag entry (`tags[n]`). */\ntype SpecTag = NonNullable<ApiDocument[\"tags\"]>[number];\n\n/** The flattened output both extractors produce. */\nexport interface CollectedOperations {\n operations: ApiOperationRef[];\n tags: ApiTagRef[];\n}\n\n/** The collector handle: feed operations in, read the flattened output out. */\nexport interface OperationCollector {\n add: (entry: CollectedOperation) => void;\n finish: () => CollectedOperations;\n}\n\n/** {@link CollectedOperations} plus anything the extractor had to skip. */\nexport interface ExtractedOperations extends CollectedOperations {\n warnings: string[];\n}\n\n/**\n * The collector behind both extractors (OpenAPI here, AsyncAPI in\n * `asyncapi.ts`): first-seen tag ordering, key de-duplication (a repeated key\n * gains its method/action as a suffix), and the shared route template — so\n * URL shape and slug rules can never drift between the two spec kinds.\n */\nexport const operationCollector = (\n baseRoute: string,\n tagMeta: ReadonlyMap<string, string>\n): OperationCollector => {\n const operations: ApiOperationRef[] = [];\n const tagOrder: string[] = [];\n const tagsSeen = new Set<string>();\n const seen = new Set<string>();\n const slugForTag = tagSlugger();\n\n const add = (entry: CollectedOperation): void => {\n const tagSlug = slugForTag(entry.tag);\n if (!tagsSeen.has(entry.tag)) {\n tagsSeen.add(entry.tag);\n tagOrder.push(entry.tag);\n }\n let { key } = entry;\n while (seen.has(key)) {\n key = `${key}-${entry.method}`;\n }\n seen.add(key);\n operations.push({\n ...entry,\n key,\n // A root-mounted reference (`route: \"/\"`) must not emit `//tag/key`.\n route: `${baseRoute === \"/\" ? \"\" : baseRoute}/${tagSlug}/${key}`,\n tagSlug,\n });\n };\n\n const finish = (): CollectedOperations => ({\n operations,\n tags: tagOrder.map((name) => ({\n description: tagMeta.get(name) ?? \"\",\n name,\n // The same slugger instance, so every tag resolves to the slug its\n // operations were routed under.\n slug: slugForTag(name),\n })),\n });\n\n return { add, finish };\n};\n\n/**\n * Flatten a 3.1 document into a route-mapped operation list and its ordered\n * tags. Operations inherit the first tag they declare; keys are de-duplicated so\n * a repeated `operationId` still yields distinct routes. `warnings` reports\n * anything skipped (a `$ref` path item), so missing operations aren't silent.\n */\nexport const extractOperations = (\n document: ApiDocument,\n baseRoute: string\n): ExtractedOperations => {\n const warnings: string[] = [];\n const tagMeta = new Map(\n (document.tags ?? [])\n .filter(hasTagName)\n .map((tag) => [tag.name, tag.description ?? \"\"])\n );\n const collector = operationCollector(baseRoute, tagMeta);\n\n for (const [path, item] of Object.entries(document.paths ?? {})) {\n // A parsed spec can carry a null path item despite the type; skip it.\n if (!item) {\n continue;\n }\n if (\"$ref\" in item) {\n warnings.push(\n `Path \"${path}\" is a $ref to a shared path item; referenced path items are not resolved, so its operations are missing from the reference. Inline the path item under \"paths\" to render it.`\n );\n continue;\n }\n for (const method of HTTP_METHODS) {\n const operation = item[method];\n if (!isOperation(operation)) {\n continue;\n }\n collector.add({\n deprecated: operation.deprecated ?? false,\n description: operation.description ?? \"\",\n key: operationKey(method, path, operation.operationId),\n method,\n operationId: operation.operationId,\n path,\n summary: operation.summary ?? \"\",\n tag: operation.tags?.[0] ?? UNTAGGED,\n });\n }\n }\n\n return { ...collector.finish(), warnings };\n};\n\n/** Resolve the operation object for a ref out of its (OpenAPI) document. */\nexport const operationObject = (\n spec: ApiSpecData,\n ref: ApiOperationRef\n): OperationObject | undefined => {\n // Only OpenAPI refs carry HTTP methods; the AsyncAPI counterpart is\n // `asyncApiOperationObject` in `asyncapi.ts`.\n const method = HTTP_METHODS.find((candidate) => candidate === ref.method);\n // SAFETY: only OpenAPI specs route their refs through this resolver\n // (AsyncAPI documents go to `asyncApiOperationObject`), so the spec's\n // document is the OpenAPI shape.\n const document = spec.document as ApiDocument;\n const item = document.paths?.[ref.path];\n const operation = method === undefined ? undefined : item?.[method];\n return isOperation(operation) ? operation : undefined;\n};\n",
|
|
25
|
-
"import type { ApiOperationRef, ApiTagRef } from \"./model.ts\";\nimport { operationCollector, operationKey } from \"./model.ts\";\n\n/**\n * Blume's own AsyncAPI model — the second front-end of the API reference\n * pipeline. Specs are normalized to AsyncAPI 3.x at parse time (see\n * `parseAsyncApiSpec` in `parse.ts`), so this module only handles one shape:\n * top-level `operations` with `action: send | receive` pointing at `channels`.\n * Internal `$ref`s are deliberately left in place, mirroring `model.ts` — the\n * components resolve them lazily, which keeps circular schemas serializable\n * and lets type labels keep their `#/components/schemas/<name>` names.\n * Browser-safe: no Node imports (the components import from here).\n */\n\n/** The two AsyncAPI 3.x operation actions, from the application's perspective. */\nexport const ASYNCAPI_ACTIONS = [\"send\", \"receive\"] as const;\n\nexport type AsyncApiAction = (typeof ASYNCAPI_ACTIONS)[number];\n\n/**\n * A value carried by a parsed spec document: JSON-compatible data whose exact\n * position in the document is not modeled (extension fields, bindings,\n * protocol-specific extras). `undefined` covers absent optional members.\n */\nexport type AsyncApiSpecValue =\n | string\n | number\n | boolean\n | null\n | undefined\n | AsyncApiSpecValue[]\n | { [key: string]: AsyncApiSpecValue };\n\n/** A permissive view of an AsyncAPI reference object. */\nexport interface AsyncApiRefLike {\n $ref?: string;\n [key: string]: AsyncApiSpecValue;\n}\n\n/** A permissive view of an AsyncAPI 3.x channel — only the fields we render. */\nexport interface AsyncApiChannelObject {\n address?: string | null;\n title?: string;\n summary?: string;\n description?: string;\n messages?: Record<string, AsyncApiRefLike>;\n parameters?: Record<string, AsyncApiRefLike>;\n servers?: AsyncApiRefLike[];\n bindings?: Record<string, Record<string, AsyncApiSpecValue>>;\n [key: string]: AsyncApiSpecValue;\n}\n\n/** A permissive view of an AsyncAPI 3.x operation — only the fields we render. */\nexport interface AsyncApiOperationObject {\n action?: string;\n channel?: AsyncApiRefLike;\n title?: string;\n summary?: string;\n description?: string;\n deprecated?: boolean;\n tags?: { name?: string; description?: string }[];\n security?: AsyncApiRefLike[];\n messages?: AsyncApiRefLike[];\n bindings?: Record<string, Record<string, AsyncApiSpecValue>>;\n [key: string]: AsyncApiSpecValue;\n}\n\n/** A permissive view of an AsyncAPI 3.x server object. */\nexport interface AsyncApiServerObject {\n host?: string;\n protocol?: string;\n pathname?: string;\n description?: string;\n security?: AsyncApiRefLike[];\n [key: string]: AsyncApiSpecValue;\n}\n\n/** A normalized AsyncAPI 3.x document, internal `$ref`s intact. */\nexport interface AsyncApiDocument {\n asyncapi?: string;\n info?: {\n title?: string;\n version?: string;\n description?: string;\n tags?: { name?: string; description?: string }[];\n [key: string]: AsyncApiSpecValue;\n };\n defaultContentType?: string;\n servers?: Record<string, AsyncApiServerObject>;\n channels?: Record<string, AsyncApiChannelObject>;\n operations?: Record<string, AsyncApiOperationObject>;\n components?: Record<string, Record<string, AsyncApiSpecValue>>;\n [key: string]: AsyncApiSpecValue;\n}\n\n/** Decode a JSON-pointer token: `user~1signedup` -> `user/signedup`. */\nconst unescapePointer = (token: string): string =>\n token.replaceAll(\"~1\", \"/\").replaceAll(\"~0\", \"~\");\n\nconst CHANNEL_REF = /^#\\/channels\\/(?<id>.+)$/u;\n\nconst isString = (value: AsyncApiSpecValue): value is string =>\n typeof value === \"string\";\n\n/** The channel id an operation's `channel.$ref` points at, if resolvable. */\nexport const channelIdOf = (channel?: AsyncApiRefLike): string | undefined => {\n const id = CHANNEL_REF.exec(channel?.$ref ?? \"\")?.groups?.id;\n return id === undefined ? undefined : unescapePointer(id);\n};\n\n/**\n * A channel's display address. AsyncAPI 3.x allows `address: null` (unknown at\n * design time) — fall back to the channel id so the operation still shows\n * where it lives.\n */\nexport const channelAddress = (\n channelId: string,\n channel?: AsyncApiChannelObject\n): string => {\n const address = channel?.address;\n return isString(address) && address !== \"\" ? address : channelId;\n};\n\nconst isObject = (\n value: AsyncApiSpecValue\n): value is Record<string, AsyncApiSpecValue> =>\n typeof value === \"object\" && value !== null && !Array.isArray(value);\n\nconst COMPONENT_SECTION_REF =\n /^#\\/components\\/(?<section>channels|operations)\\/(?<name>[^/]+)$/u;\n\n/**\n * Inline top-level `channels`/`operations` entries declared as Reference\n * Objects (`{ $ref: \"#/components/channels/…\" }` — the spec's reuse pattern)\n * by replacing them with their components target. Runs before trait merging\n * so an inlined operation's traits merge exactly like an inline one's.\n * Unresolvable refs stay in place; the extractor reports them.\n */\nconst inlineComponentRefs = (document: AsyncApiDocument): void => {\n const maps: [Record<string, AsyncApiSpecValue> | undefined, string][] = [\n [document.channels, \"channels\"],\n [document.operations, \"operations\"],\n ];\n for (const [map, section] of maps) {\n if (!isObject(map)) {\n continue;\n }\n const table = document.components?.[section];\n for (const [id, entry] of Object.entries(map)) {\n if (!isObject(entry) || !isString(entry.$ref)) {\n continue;\n }\n const groups = COMPONENT_SECTION_REF.exec(entry.$ref)?.groups;\n if (groups?.section !== section) {\n continue;\n }\n const resolved = isObject(table)\n ? table[unescapePointer(groups.name ?? \"\")]\n : undefined;\n // A shallow copy per alias: trait merging mutates its node in place\n // (fields written, `traits` deleted), and two entries pointing at the\n // same component — or the component table itself — must not share it.\n if (isObject(resolved) && !isString(resolved.$ref)) {\n map[id] = { ...resolved };\n }\n }\n }\n};\n\nconst TRAIT_REF =\n /^#\\/components\\/(?<section>operationTraits|messageTraits)\\/(?<name>[^/]+)$/u;\n\n/**\n * Merge `traits` into their operation/message objects, in declaration order,\n * with the object's own properties taking precedence (the spec's merge rule;\n * applied shallowly, which covers the fields traits carry in practice —\n * bindings, security, tags, headers). Runs once at parse time so the\n * serialized document and every downstream consumer are trait-free; the\n * `traits` key itself is dropped. Unresolvable trait `$ref`s are skipped.\n */\nexport const applyAsyncApiTraits = (\n document: AsyncApiDocument\n): AsyncApiDocument => {\n const resolveTrait = (\n trait: AsyncApiSpecValue\n ): Record<string, AsyncApiSpecValue> | undefined => {\n if (!isObject(trait)) {\n return undefined;\n }\n if (!isString(trait.$ref)) {\n return trait;\n }\n const groups = TRAIT_REF.exec(trait.$ref)?.groups;\n const section = groups?.section\n ? document.components?.[groups.section]\n : undefined;\n const resolved = section?.[groups?.name ?? \"\"];\n return isObject(resolved) ? resolved : undefined;\n };\n\n const mergeTraits = (node: Record<string, AsyncApiSpecValue>): void => {\n const { traits } = node;\n if (!Array.isArray(traits)) {\n return;\n }\n delete node.traits;\n const merged: Record<string, AsyncApiSpecValue> = {};\n for (const trait of traits) {\n Object.assign(merged, resolveTrait(trait));\n }\n for (const [key, value] of Object.entries(merged)) {\n if (!(key in node)) {\n node[key] = value;\n }\n }\n };\n\n for (const operation of Object.values(document.operations ?? {})) {\n if (isObject(operation)) {\n mergeTraits(operation);\n }\n }\n const channelMaps = [\n ...Object.values(document.channels ?? {}),\n // Reusable channels under components carry messages too; their traits\n // must merge the same way so a resolved component channel renders alike.\n ...Object.values(document.components?.channels ?? {}),\n ];\n const messageMaps = [\n ...channelMaps.map((channel) =>\n isObject(channel) ? channel.messages : undefined\n ),\n document.components?.messages,\n ];\n for (const messages of messageMaps) {\n if (!isObject(messages)) {\n continue;\n }\n for (const message of Object.values(messages)) {\n // A channel message that is itself a `$ref` resolves to a components\n // message, which this loop also visits — don't merge through the ref.\n if (isObject(message) && !isString(message.$ref)) {\n mergeTraits(message);\n }\n }\n }\n return document;\n};\n\n/**\n * The parse-time normalization pass: component channel/operation refs inlined,\n * then traits merged. The serialized document and every downstream consumer\n * see plain channel/operation objects, trait-free.\n */\nexport const normalizeAsyncApiDocument = (\n document: AsyncApiDocument\n): AsyncApiDocument => {\n inlineComponentRefs(document);\n return applyAsyncApiTraits(document);\n};\n\n/**\n * Resolve where one operation renders from: its action and its declared\n * channel. A string return is the warning explaining why the operation can't\n * appear in the reference.\n */\nconst operationSite = (\n id: string,\n operation: AsyncApiOperationObject,\n channels: Record<string, AsyncApiChannelObject>\n): { action: AsyncApiAction; channelId: string; address: string } | string => {\n const action = ASYNCAPI_ACTIONS.find((a) => a === operation.action);\n if (!action) {\n return `Operation \"${id}\" declares no send/receive action; it is missing from the reference.`;\n }\n const channelId = channelIdOf(operation.channel);\n const channel = channelId === undefined ? undefined : channels[channelId];\n // A channel entry that is still a bare `$ref` survived normalization — its\n // components target doesn't exist — so it renders nothing useful either.\n if (channelId === undefined || !isObject(channel) || isString(channel.$ref)) {\n return `Operation \"${id}\" references a channel that isn't declared under \"channels\"; it is missing from the reference.`;\n }\n return { action, address: channelAddress(channelId, channel), channelId };\n};\n\n/** The route-mapped operations, ordered tags, and skip warnings of one document. */\nexport interface AsyncApiOperationCatalog {\n operations: ApiOperationRef[];\n tags: ApiTagRef[];\n warnings: string[];\n}\n\n/**\n * Flatten a normalized AsyncAPI 3.x document into a route-mapped operation\n * list and its ordered tags — the AsyncAPI counterpart of `extractOperations`\n * in `model.ts`, built on the same `operationCollector`. Operations group by\n * their first tag; untagged operations fall back to their channel address, so\n * a spec with no tags still gets one sidebar group per channel. Keys come from\n * the operation id (the `operations` map key), which the official 2.x\n * converter synthesizes deterministically (`<channel>.publish` /\n * `<channel>.subscribe`) — so a 2.x spec and its converter-upgraded 3.x form\n * yield identical URLs. An id that slugifies to nothing falls back to\n * action + address, the same scheme `operationKey` uses for method + path.\n */\nexport const extractAsyncApiOperations = (\n document: AsyncApiDocument,\n baseRoute: string\n): AsyncApiOperationCatalog => {\n const warnings: string[] = [];\n const tagMeta = new Map(\n (document.info?.tags ?? [])\n .filter(\n (tag): tag is { name: string; description?: string } =>\n typeof tag?.name === \"string\"\n )\n .map((tag): [string, string] => [tag.name, tag.description ?? \"\"])\n );\n const collector = operationCollector(baseRoute, tagMeta);\n const channels = document.channels ?? {};\n\n for (const [id, operation] of Object.entries(document.operations ?? {})) {\n if (!isObject(operation)) {\n continue;\n }\n // An operation entry still carrying a `$ref` is one normalization could\n // not inline; name the real problem instead of the missing-action one.\n if (isString(operation.$ref)) {\n warnings.push(\n `Operation \"${id}\" is a reference that doesn't resolve to a components operation; it is missing from the reference.`\n );\n continue;\n }\n const site = operationSite(id, operation, channels);\n if (isString(site)) {\n warnings.push(site);\n continue;\n }\n const { action, address, channelId } = site;\n const tag = operation.tags?.find(\n (candidate): candidate is { name: string; description?: string } =>\n typeof candidate?.name === \"string\"\n )?.name;\n collector.add({\n channelId,\n deprecated: operation.deprecated === true,\n description: operation.description ?? \"\",\n key: operationKey(action, address, id),\n method: action,\n operationId: id,\n path: address,\n summary: operation.title ?? operation.summary ?? \"\",\n tag: tag ?? address,\n });\n }\n\n return { ...collector.finish(), warnings };\n};\n\n/** Resolve the operation object for a ref out of its (AsyncAPI) document. */\nexport const asyncApiOperationObject = (\n document: AsyncApiDocument,\n ref: ApiOperationRef\n): AsyncApiOperationObject | undefined => {\n const operation =\n ref.operationId === undefined\n ? undefined\n : document.operations?.[ref.operationId];\n return isObject(operation) ? operation : undefined;\n};\n",
|
|
26
|
-
"import type { ApiOperationRef, ExtractedOperations } from \"./model.ts\";\nimport { operationCollector, operationKey } from \"./model.ts\";\n\n/**\n * Blume's own GraphQL model — the third front-end of the API reference\n * pipeline. Schemas are lowered to this JSON-serializable document at parse\n * time (see `buildGraphqlDocument` in `graphql-build.ts`, kept separate so\n * `graphql`-js stays out of this module's import graph), so the components\n * render entirely from plain data, mirroring how `model.ts` keeps OpenAPI\n * documents serializable. Browser-safe: no Node imports (the components\n * import from here).\n */\n\n/** The three GraphQL operation kinds, each a sidebar group of root fields. */\nexport const GRAPHQL_OPERATION_KINDS = [\n \"query\",\n \"mutation\",\n \"subscription\",\n] as const;\n\nexport type GraphqlOperationKind = (typeof GRAPHQL_OPERATION_KINDS)[number];\n\n/** The named-type kinds that get reference pages, in sidebar-group order. */\nexport const GRAPHQL_TYPE_KINDS = [\n \"object\",\n \"input\",\n \"enum\",\n \"interface\",\n \"union\",\n \"scalar\",\n] as const;\n\nexport type GraphqlTypeKind = (typeof GRAPHQL_TYPE_KINDS)[number];\n\n/** Everything a GraphQL operation ref's `method` can carry. */\nexport type GraphqlMember = GraphqlOperationKind | GraphqlTypeKind;\n\n/** Sidebar/overview group label for each member kind. */\nexport const GRAPHQL_GROUP_LABELS = {\n enum: \"Enums\",\n input: \"Input Objects\",\n interface: \"Interfaces\",\n mutation: \"Mutations\",\n object: \"Objects\",\n query: \"Queries\",\n scalar: \"Scalars\",\n subscription: \"Subscriptions\",\n union: \"Unions\",\n} satisfies Record<GraphqlMember, string>;\n\n/**\n * A type expression as it appears in the schema: the rendered display form\n * (`[String!]!`) plus the named type at its core (`String`), so components can\n * print the exact wrapper shape and still link to the named type's page.\n */\nexport interface GraphqlTypeRef {\n display: string;\n name: string;\n}\n\n/** An argument or input-object field. */\nexport interface GraphqlInputValue {\n name: string;\n description: string;\n type: GraphqlTypeRef;\n /** Default value printed as a GraphQL literal (`10`, `\"asc\"`, `[1, 2]`). */\n default?: string;\n deprecationReason?: string;\n}\n\n/** An output field of an object or interface type (root fields included). */\nexport interface GraphqlField {\n name: string;\n description: string;\n type: GraphqlTypeRef;\n args: GraphqlInputValue[];\n deprecationReason?: string;\n}\n\n/** One member of an enum type. */\nexport interface GraphqlEnumValue {\n name: string;\n description: string;\n deprecationReason?: string;\n}\n\n/** One named type of the schema, flattened to the fields the reference renders. */\nexport interface GraphqlTypeDef {\n name: string;\n kind: GraphqlTypeKind;\n description: string;\n /** Output fields (object and interface types). */\n fields?: GraphqlField[];\n /** Input fields (input object types). */\n inputFields?: GraphqlInputValue[];\n /** Members (enum types). */\n enumValues?: GraphqlEnumValue[];\n /** Member type names (unions) or implementing object types (interfaces). */\n possibleTypes?: string[];\n /** Interfaces this object/interface type implements. */\n interfaces?: string[];\n /** `specifiedBy` URL (custom scalars). */\n specifiedByUrl?: string;\n /**\n * A spec-defined scalar (`String`, `Int`, …): kept for type refs to resolve\n * against, but never given a reference page of its own.\n */\n builtIn?: boolean;\n}\n\n/** A normalized GraphQL schema, lowered to serializable data at parse time. */\nexport interface GraphqlDocument {\n /** Mirrors the OpenAPI `info` block so `source.ts` reads one shape. */\n info?: { title?: string; version?: string; description?: string };\n /** Root operation type names (`Query`, `Mutation`, `Subscription`). */\n roots: Partial<Record<GraphqlOperationKind, string>>;\n /** Named types by name, spec-defined scalars included, introspection types excluded. */\n types: Record<string, GraphqlTypeDef>;\n}\n\n/** Whether an operation ref's method is a root-field kind (vs a type page). */\nexport const isGraphqlOperationKind = (\n method: string\n): method is GraphqlOperationKind =>\n GRAPHQL_OPERATION_KINDS.some((kind) => kind === method);\n\n/** The SDL keyword that declares each named-type kind: `type Pet`, `enum Status`. */\nconst GRAPHQL_TYPE_KEYWORDS = {\n enum: \"enum\",\n input: \"input\",\n interface: \"interface\",\n object: \"type\",\n scalar: \"scalar\",\n union: \"union\",\n} satisfies Record<GraphqlTypeKind, string>;\n\n/**\n * A GraphQL member in the schema's own notation — `query pets` for a root\n * field, `type Pet` / `input PetInput` / `enum Status` for a named type. The\n * rendered page shows the same pair as a kind badge beside the name; the\n * text form spells the kind the way SDL does, so a reader who knows GraphQL\n * and not Blume's badges reads it right.\n */\nexport const graphqlSignature = (\n operation: Pick<ApiOperationRef, \"method\" | \"path\">\n): string => {\n // SAFETY: the GraphQL extractor only ever assigns member kinds as the\n // method (see `extractGraphqlOperations`).\n const member = operation.method as GraphqlMember;\n const keyword = isGraphqlOperationKind(member)\n ? member\n : GRAPHQL_TYPE_KEYWORDS[member];\n return `${keyword} ${operation.path}`;\n};\n\n// Deterministic name order for type pages, independent of schema declaration\n// order and of the platform's collation (localeCompare varies across ICU\n// builds; codepoint order does not).\nconst byName = (a: GraphqlTypeDef, b: GraphqlTypeDef): number =>\n a.name < b.name ? -1 : 1;\n\n/**\n * Flatten a GraphQL document into a route-mapped page list and its ordered\n * groups — the GraphQL counterpart of `extractOperations` in `model.ts`, built\n * on the same `operationCollector`. Root fields become operation pages grouped\n * as Queries/Mutations/Subscriptions; every other named type becomes a type\n * page grouped by kind (Objects, Input Objects, …). Spec-defined scalars and\n * the root types themselves get no page — the roots' fields ARE the operation\n * pages, and `String`/`Int`/… document nothing.\n */\nexport const extractGraphqlOperations = (\n document: GraphqlDocument,\n baseRoute: string\n): ExtractedOperations => {\n const collector = operationCollector(baseRoute, new Map());\n const rootNames = new Set(Object.values(document.roots));\n\n for (const kind of GRAPHQL_OPERATION_KINDS) {\n const rootName = document.roots[kind];\n const root = rootName === undefined ? undefined : document.types[rootName];\n for (const field of root?.fields ?? []) {\n collector.add({\n deprecated: field.deprecationReason !== undefined,\n description: field.description,\n // `operationKey` slug rules keep GraphQL URLs consistent with the\n // other spec kinds; its fallback covers a name that slugifies empty.\n key: operationKey(kind, field.name, field.name),\n method: kind,\n operationId: field.name,\n path: field.name,\n summary: \"\",\n tag: GRAPHQL_GROUP_LABELS[kind],\n });\n }\n }\n\n for (const kind of GRAPHQL_TYPE_KINDS) {\n const members = Object.values(document.types)\n .filter(\n (type) =>\n type.kind === kind && !(type.builtIn || rootNames.has(type.name))\n )\n .toSorted(byName);\n for (const type of members) {\n collector.add({\n deprecated: false,\n description: type.description,\n key: operationKey(kind, type.name, type.name),\n method: kind,\n operationId: type.name,\n path: type.name,\n summary: \"\",\n tag: GRAPHQL_GROUP_LABELS[kind],\n });\n }\n }\n\n return { ...collector.finish(), warnings: [] };\n};\n\n/** Resolve the root field behind an operation-kind ref, if it still exists. */\nexport const graphqlRootField = (\n document: GraphqlDocument,\n ref: ApiOperationRef\n): GraphqlField | undefined => {\n if (!isGraphqlOperationKind(ref.method)) {\n return undefined;\n }\n const rootName = document.roots[ref.method];\n const root = rootName === undefined ? undefined : document.types[rootName];\n return root?.fields?.find((field) => field.name === ref.operationId);\n};\n\n/** Resolve the named type behind a type-page ref, if it still exists. */\nexport const graphqlTypeDef = (\n document: GraphqlDocument,\n ref: ApiOperationRef\n): GraphqlTypeDef | undefined => {\n const type = document.types[ref.operationId ?? \"\"];\n return type && type.kind === ref.method ? type : undefined;\n};\n",
|
|
27
|
-
"import { mkdir, readFile, writeFile } from \"node:fs/promises\";\nimport { setTimeout as sleep } from \"node:timers/promises\";\n\nimport type { AsyncAPIDocument as ConverterDocument } from \"@asyncapi/converter\";\nimport { convert } from \"@asyncapi/converter\";\nimport { normalize, upgrade } from \"@scalar/openapi-parser\";\nimport pRetry, { AbortError } from \"p-retry\";\nimport { isAbsolute, join } from \"pathe\";\n\nimport { hashText } from \"../core/sources/cache.ts\";\nimport type { AsyncApiDocument } from \"./asyncapi.ts\";\nimport { normalizeAsyncApiDocument } from \"./asyncapi.ts\";\nimport { buildGraphqlDocument } from \"./graphql-build.ts\";\nimport type { GraphqlDocument } from \"./graphql.ts\";\nimport type { ApiDocument } from \"./model.ts\";\n\n/**\n * Spec loading and normalization. Blume reuses Scalar's parser\n * (`@scalar/openapi-parser`) to read a spec (YAML or JSON), then upgrade Swagger\n * 2.0 / OpenAPI 3.0 documents to 3.1 so the renderer only handles one shape.\n * Internal `$ref`s are deliberately left in place (see `model.ts`).\n *\n * Remote (`http(s)`) specs are fetched defensively — bounded per attempt, retried\n * on transient failures, proxy-aware, and cached on disk — mirroring the\n * resilience the external link checker (`core/links.ts`) and the Notion source\n * (`core/sources/notion.ts`) already have. A bare `fetch` is the classic \"curl\n * works but the build doesn't\" gap: it ignores `*_PROXY`, has no timeout, and\n * dies on a single cold-start blip.\n */\n\nconst URL_SPEC = /^https?:\\/\\//u;\n\nconst FETCH_TIMEOUT_MS = 15_000;\nconst MAX_ATTEMPTS = 3;\nconst BASE_BACKOFF_MS = 500;\n// Honor Retry-After only up to a sane ceiling: a server answering with\n// `Retry-After: 3600` must not stall a build for an hour per attempt.\nconst MAX_RETRY_WAIT_MS = 10_000;\nconst SECOND_MS = 1000;\n// Worth another try: request timeout, too-early, rate-limited, and the 5xx range.\nconst RETRYABLE_STATUS = new Set([408, 425, 429, 500, 502, 503, 504]);\nconst USER_AGENT = \"blume (+https://github.com/haydenbleasel/blume)\";\nconst PROXY_ENV_VARS = [\n \"HTTP_PROXY\",\n \"http_proxy\",\n \"HTTPS_PROXY\",\n \"https_proxy\",\n \"ALL_PROXY\",\n \"all_proxy\",\n];\n\nexport interface ParsedSpec {\n document: ApiDocument;\n warnings: string[];\n}\n\n/**\n * The spec was read successfully but its contents aren't an OpenAPI document\n * (an empty file, or YAML that parses to a scalar or list — say, a README\n * pointed at by mistake). Kept distinct from read/fetch failures so callers\n * can suggest fixing the file instead of checking reachability.\n */\nexport class InvalidSpecError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"InvalidSpecError\";\n }\n}\n\n/** Where and whether to cache a remote spec's text between runs. */\nexport interface SpecFetchOptions {\n /** Dir for a last-good on-disk copy of a remote spec (offline fallback). */\n cacheDir?: string;\n /**\n * Re-fetch even when a cached copy exists. Builds/sync refresh; dev is\n * cache-first for fast, offline-tolerant restarts (see `SourceContext`).\n */\n refresh?: boolean;\n}\n\n/**\n * Route Node's global `fetch` through an HTTP(S) proxy the first time a remote\n * spec is fetched with one configured. Node's built-in `fetch` ignores `*_PROXY`\n * on its own; undici's env proxy agent, installed on the shared global-dispatcher\n * symbol, wires it in without replacing `fetch` (so tests can still stub it).\n * Best-effort and lazy: no proxy env means no undici import at all, and an\n * unavailable undici just leaves the direct connection in place.\n */\nlet proxyInstalled = false;\nconst ensureProxyDispatcher = async (): Promise<void> => {\n // Only memoize a successful install: with no proxy configured we cheaply\n // re-check each time, so a proxy set later in the process still takes effect.\n if (proxyInstalled || !PROXY_ENV_VARS.some((name) => process.env[name])) {\n return;\n }\n try {\n const { EnvHttpProxyAgent, setGlobalDispatcher } = await import(\"undici\");\n setGlobalDispatcher(new EnvHttpProxyAgent());\n proxyInstalled = true;\n } catch {\n // No proxy support available; fall back to a direct connection.\n }\n};\n\n/**\n * `Retry-After` in ms when the server sent a sane one, else undefined. RFC\n * 9110 allows both forms: delta-seconds (`120`) and an HTTP-date (`Wed, 21\n * Oct 2015 07:28:00 GMT`); the date form arrives from CDN rate limiters and\n * was previously ignored.\n */\nconst retryAfterMs = (response: Response): number | undefined => {\n const header = response.headers.get(\"retry-after\");\n if (!header) {\n return undefined;\n }\n const seconds = Number(header);\n if (Number.isFinite(seconds)) {\n return seconds > 0 ? seconds * SECOND_MS : undefined;\n }\n const delta = Date.parse(header) - Date.now();\n return Number.isFinite(delta) && delta > 0 ? delta : undefined;\n};\n\n/** One fetch attempt, normalized: the body text, or a (maybe-retryable) error. */\ntype Attempt =\n | { text: string }\n | { error: Error; retryable: boolean; retryAfter?: number };\n\nconst attemptFetch = async (spec: string): Promise<Attempt> => {\n try {\n const response = await fetch(spec, {\n headers: {\n accept: \"application/json, application/yaml, text/yaml, */*\",\n \"user-agent\": USER_AGENT,\n },\n redirect: \"follow\",\n signal: AbortSignal.timeout(FETCH_TIMEOUT_MS),\n });\n if (response.ok) {\n return { text: await response.text() };\n }\n return {\n error: new Error(`${spec} -> ${response.status} ${response.statusText}`),\n retryAfter: retryAfterMs(response),\n retryable: RETRYABLE_STATUS.has(response.status),\n };\n } catch (error) {\n // Network error, DNS/TLS failure, or an aborted (timed-out) request — all\n // transient by nature, so worth a retry.\n return {\n error: error instanceof Error ? error : new Error(String(error)),\n retryable: true,\n };\n }\n};\n\n/**\n * A retryable failure, wrapped in a plain Error p-retry never special-cases:\n * it refuses to retry a non-network `TypeError`, and the underlying error's\n * type is the server's choice, not ours. The message is the underlying\n * error's, so the exhaustion throw still reads `spec -> 503 Service\n * Unavailable`.\n */\ntype RetryableFetchError = Error & { retryAfter?: number };\n\nconst retryableFetchError = (\n error: Error,\n retryAfter?: number\n): RetryableFetchError => {\n const wrapper: RetryableFetchError = new Error(error.message, {\n cause: error,\n });\n wrapper.name = \"RetryableFetchError\";\n wrapper.retryAfter = retryAfter;\n return wrapper;\n};\n\n/** Fetch a remote spec's text, retrying transient failures with backoff. */\nconst fetchSpecText = async (spec: string): Promise<string> => {\n await ensureProxyDispatcher();\n return await pRetry(\n async () => {\n const attempt = await attemptFetch(spec);\n if (\"text\" in attempt) {\n return attempt.text;\n }\n if (!attempt.retryable) {\n // AbortError stops retrying and rethrows the original untouched.\n throw new AbortError(attempt.error);\n }\n throw retryableFetchError(attempt.error, attempt.retryAfter);\n },\n {\n factor: 2,\n maxTimeout: MAX_RETRY_WAIT_MS,\n minTimeout: BASE_BACKOFF_MS,\n // A sane `Retry-After` replaces the exponential backoff rather than\n // stacking on it: p-retry's own (capped) delay still runs after this\n // hook, so only the difference is slept here.\n onFailedAttempt: async (context) => {\n // SAFETY: every retryable throw above is a RetryableFetchError; any\n // other error reaching this hook reads an absent retryAfter.\n const { retryAfter } = context.error as RetryableFetchError;\n if (retryAfter !== undefined && context.retriesLeft > 0) {\n await sleep(\n Math.max(\n 0,\n Math.min(retryAfter, MAX_RETRY_WAIT_MS) - context.retryDelay\n )\n );\n }\n },\n retries: MAX_ATTEMPTS - 1,\n }\n );\n};\n\nconst cacheFileFor = (cacheDir: string, spec: string): string =>\n join(cacheDir, `spec-${hashText(spec)}.cache`);\n\nconst readCache = async (file: string): Promise<string | undefined> => {\n try {\n return await readFile(file, \"utf-8\");\n } catch {\n return undefined;\n }\n};\n\nconst writeCache = async (\n dir: string,\n file: string,\n text: string\n): Promise<void> => {\n try {\n await mkdir(dir, { recursive: true });\n await writeFile(file, text, \"utf-8\");\n } catch {\n // Best-effort cache; a write failure must not fail the load.\n }\n};\n\n/** Read a spec's raw text from an `http(s)` URL or a local (project-relative) path. */\nconst readSpecText = async (\n spec: string,\n root: string,\n options: SpecFetchOptions\n): Promise<{ text: string; warnings: string[] }> => {\n if (!URL_SPEC.test(spec)) {\n const absolute = isAbsolute(spec) ? spec : join(root, spec);\n return { text: await readFile(absolute, \"utf-8\"), warnings: [] };\n }\n\n const cacheFile = options.cacheDir\n ? cacheFileFor(options.cacheDir, spec)\n : undefined;\n\n // Cache-first in dev: serve the last-good snapshot without touching the network.\n if (cacheFile && options.refresh === false) {\n const cached = await readCache(cacheFile);\n if (cached !== undefined) {\n return { text: cached, warnings: [] };\n }\n }\n\n try {\n const text = await fetchSpecText(spec);\n if (options.cacheDir && cacheFile) {\n await writeCache(options.cacheDir, cacheFile, text);\n }\n return { text, warnings: [] };\n } catch (error) {\n // A transient outage falls back to the last good fetch, with a warning.\n if (cacheFile) {\n const cached = await readCache(cacheFile);\n if (cached !== undefined) {\n // SAFETY: fetchSpecText throws only Error instances — attemptFetch\n // wraps every non-Error throw in an Error.\n return {\n text: cached,\n warnings: [\n `Could not fetch ${spec} (${(error as Error).message}); using the last cached copy.`,\n ],\n };\n }\n }\n throw error;\n }\n};\n\n/**\n * Read, normalize, and upgrade a spec to an OpenAPI 3.1 document. Throws when the\n * spec can't be read and no cache is available; callers turn that into a source\n * diagnostic (an error in build, a warning in dev) rather than a hard failure so\n * a broken spec doesn't take down the whole build.\n */\n/**\n * A parsed mapping is the only shape the renderer can treat as a document:\n * `normalize` yields undefined for anything that isn't a YAML/JSON mapping\n * (empty file, scalar, list) and `upgrade(undefined)` a null specification.\n */\nconst isApiDocument = <Value>(value: Value): value is Value & ApiDocument =>\n typeof value === \"object\" && value !== null;\n\nexport const parseSpec = async (\n spec: string,\n root: string,\n options: SpecFetchOptions = {}\n): Promise<ParsedSpec> => {\n const { text, warnings } = await readSpecText(spec, root, options);\n const normalized = normalize(text);\n const { specification } = upgrade(normalized);\n // Reject a non-mapping here so the renderer never sees a non-document.\n if (!isApiDocument(specification)) {\n throw new InvalidSpecError(\n `${spec} is not a valid OpenAPI document (expected a YAML or JSON object).`\n );\n }\n return { document: specification, warnings };\n};\n\nexport interface ParsedAsyncApiSpec {\n document: AsyncApiDocument;\n warnings: string[];\n}\n\n/**\n * Read and normalize a spec to an AsyncAPI 3.x document — the AsyncAPI mirror\n * of {@link parseSpec}. 1.x/2.x documents are lifted to 3.0 with the official\n * `@asyncapi/converter` (channels + operations with `send`/`receive` actions),\n * so the extractor and components only ever handle one shape; `$ref`s stay\n * intact, matching the OpenAPI path. Error semantics match `parseSpec`: an\n * unreadable spec throws, a readable non-AsyncAPI document throws\n * {@link InvalidSpecError}, and callers lower both into source diagnostics.\n */\n/**\n * An object carrying a non-empty `asyncapi` version string — the only input\n * the converter and extractor can key on. `normalize` yields undefined for\n * non-mapping input, which fails the object check here.\n */\nconst isAsyncApiDocument = <Value>(\n value: Value\n): value is Value & AsyncApiDocument & { asyncapi: string } =>\n typeof value === \"object\" &&\n value !== null &&\n \"asyncapi\" in value &&\n typeof value.asyncapi === \"string\" &&\n value.asyncapi !== \"\";\n\nexport const parseAsyncApiSpec = async (\n spec: string,\n root: string,\n options: SpecFetchOptions = {}\n): Promise<ParsedAsyncApiSpec> => {\n const { text, warnings } = await readSpecText(spec, root, options);\n const normalized = normalize(text);\n if (!isAsyncApiDocument(normalized)) {\n throw new InvalidSpecError(\n `${spec} is not a valid AsyncAPI document (expected a YAML or JSON object with an \\`asyncapi\\` version field).`\n );\n }\n const version = normalized.asyncapi;\n let document: AsyncApiDocument = normalized;\n if (!version.startsWith(\"3.\")) {\n // The converter reports lossy conversions (e.g. a 2.x parameter schema\n // that 3.0 can't express) through console.warn — capture those as spec\n // warnings instead of letting them leak into CLI output.\n const captured: string[] = [];\n const originalWarn = console.warn;\n console.warn = (...args: unknown[]) => {\n captured.push(args.map(String).join(\" \"));\n };\n try {\n // SAFETY: the converter accepts any pre-3.0 AsyncAPI object and returns\n // the 3.0 shape the extractor consumes; the two packages just declare\n // the document type differently.\n document = convert(\n document as ConverterDocument,\n \"3.0.0\"\n ) as AsyncApiDocument;\n } catch (error) {\n // An unconvertible document (say, an unknown `asyncapi` version) is a\n // content problem, not a network one — same class as a non-document.\n // SAFETY: @asyncapi/converter throws Error instances for bad input.\n throw new InvalidSpecError(\n `${spec} could not be converted to AsyncAPI 3.0 (${(error as Error).message}).`\n );\n } finally {\n console.warn = originalWarn;\n }\n warnings.push(\n ...captured.map(\n (message) => `Converting ${spec} to AsyncAPI 3.0: ${message}`\n )\n );\n }\n return { document: normalizeAsyncApiDocument(document), warnings };\n};\n\nexport interface ParsedGraphqlSpec {\n document: GraphqlDocument;\n warnings: string[];\n}\n\n/**\n * Read and lower a GraphQL schema — SDL text or an introspection JSON result —\n * to the serializable document the reference renders from; the GraphQL mirror\n * of {@link parseSpec}. Error semantics match the other kinds: an unreadable\n * spec throws the read/fetch error, while readable-but-invalid schema text\n * (SDL syntax errors, validation failures, JSON that isn't an introspection\n * result) throws {@link InvalidSpecError} so callers suggest fixing the file\n * rather than checking reachability.\n */\nexport const parseGraphqlSpec = async (\n spec: string,\n root: string,\n options: SpecFetchOptions = {}\n): Promise<ParsedGraphqlSpec> => {\n const { text, warnings } = await readSpecText(spec, root, options);\n try {\n return { document: buildGraphqlDocument(text), warnings };\n } catch (error) {\n // SAFETY: `graphql`-js throws GraphQLError (an Error) for syntax and\n // validation failures, and `buildGraphqlDocument` throws plain Errors for\n // non-introspection JSON; only the message is surfaced.\n throw new InvalidSpecError(\n `${spec} is not a valid GraphQL schema (${(error as Error).message.trim()})`\n );\n }\n};\n",
|
|
28
|
-
"import type {\n GraphQLArgument,\n GraphQLEnumType,\n GraphQLField,\n GraphQLInputField,\n GraphQLInputObjectType,\n GraphQLInterfaceType,\n GraphQLNamedType,\n GraphQLObjectType,\n GraphQLScalarType,\n GraphQLSchema,\n GraphQLUnionType,\n} from \"graphql\";\nimport {\n buildClientSchema,\n buildSchema,\n getNamedType,\n isEnumType,\n isInputObjectType,\n isInterfaceType,\n isObjectType,\n isSpecifiedScalarType,\n isUnionType,\n print,\n} from \"graphql\";\n\nimport type {\n GraphqlDocument,\n GraphqlEnumValue,\n GraphqlField as ModelField,\n GraphqlInputValue,\n GraphqlTypeDef,\n GraphqlTypeRef,\n} from \"./graphql.ts\";\n\n/**\n * Lower schema text — SDL or an introspection result — into the serializable\n * {@link GraphqlDocument} the reference renders from. This is the only module\n * that imports `graphql`-js, and it runs Node-side at parse time only (see\n * `parseGraphqlSpec` in `parse.ts`), so the parser never reaches the generated\n * runtime or the browser. Throws plain Errors for unreadable schemas; the\n * caller wraps them as content problems.\n */\n\n/** A type expression flattened to its display form and core named type. */\nconst typeRef = (\n type: GraphQLField<unknown, unknown>[\"type\"] | GraphQLInputField[\"type\"]\n): GraphqlTypeRef => ({\n display: String(type),\n name: getNamedType(type).name,\n});\n\n/**\n * An argument's or input field's default, printed as a GraphQL literal. Both\n * SDL- and introspection-built schemas carry the default as a const AST node\n * (`default.literal`); a schema constructed programmatically with a coerced\n * `value` instead never reaches this module (it only builds from text).\n */\nconst defaultLiteral = (\n input: GraphQLArgument | GraphQLInputField\n): string | undefined => {\n const literal = input.default?.literal;\n return literal ? print(literal) : undefined;\n};\n\nconst inputValue = (\n input: GraphQLArgument | GraphQLInputField\n): GraphqlInputValue => {\n const value: GraphqlInputValue = {\n description: input.description ?? \"\",\n name: input.name,\n type: typeRef(input.type),\n };\n const printed = defaultLiteral(input);\n if (printed !== undefined) {\n value.default = printed;\n }\n // `deprecationReason` is null on non-deprecated members in some builds;\n // absent means \"not deprecated\" downstream, so both collapse to absent.\n if (\n input.deprecationReason !== null &&\n input.deprecationReason !== undefined\n ) {\n value.deprecationReason = input.deprecationReason;\n }\n return value;\n};\n\nconst outputField = (field: GraphQLField<unknown, unknown>): ModelField => {\n const value: ModelField = {\n args: field.args.map(inputValue),\n description: field.description ?? \"\",\n name: field.name,\n type: typeRef(field.type),\n };\n if (\n field.deprecationReason !== null &&\n field.deprecationReason !== undefined\n ) {\n value.deprecationReason = field.deprecationReason;\n }\n return value;\n};\n\nconst enumValues = (type: GraphQLEnumType): GraphqlEnumValue[] =>\n type.getValues().map((value) => {\n const member: GraphqlEnumValue = {\n description: value.description ?? \"\",\n name: value.name,\n };\n if (\n value.deprecationReason !== null &&\n value.deprecationReason !== undefined\n ) {\n member.deprecationReason = value.deprecationReason;\n }\n return member;\n });\n\nconst objectDef = (\n type: GraphQLObjectType | GraphQLInterfaceType,\n kind: \"object\" | \"interface\"\n): GraphqlTypeDef => {\n const interfaces = type.getInterfaces().map((iface) => iface.name);\n const def: GraphqlTypeDef = {\n description: type.description ?? \"\",\n fields: Object.values(type.getFields()).map(outputField),\n kind,\n name: type.name,\n };\n if (interfaces.length > 0) {\n def.interfaces = interfaces;\n }\n return def;\n};\n\nconst inputDef = (type: GraphQLInputObjectType): GraphqlTypeDef => ({\n description: type.description ?? \"\",\n inputFields: Object.values(type.getFields()).map(inputValue),\n kind: \"input\",\n name: type.name,\n});\n\nconst unionDef = (type: GraphQLUnionType): GraphqlTypeDef => ({\n description: type.description ?? \"\",\n kind: \"union\",\n name: type.name,\n possibleTypes: type.getTypes().map((member) => member.name),\n});\n\nconst scalarDef = (type: GraphQLScalarType): GraphqlTypeDef => {\n const def: GraphqlTypeDef = {\n description: type.description ?? \"\",\n kind: \"scalar\",\n name: type.name,\n };\n if (type.specifiedByURL !== null && type.specifiedByURL !== undefined) {\n def.specifiedByUrl = type.specifiedByURL;\n }\n if (isSpecifiedScalarType(type)) {\n def.builtIn = true;\n }\n return def;\n};\n\nconst typeDef = (\n type: GraphQLNamedType,\n schema: GraphQLSchema\n): GraphqlTypeDef => {\n if (isObjectType(type)) {\n return objectDef(type, \"object\");\n }\n if (isInterfaceType(type)) {\n const def = objectDef(type, \"interface\");\n // Implementations are indexed on the schema, not the type — captured here\n // so the type page can list \"implemented by\" without a schema in hand.\n const implementers = schema\n .getImplementations(type)\n .objects.map((object) => object.name);\n if (implementers.length > 0) {\n def.possibleTypes = implementers;\n }\n return def;\n }\n if (isInputObjectType(type)) {\n return inputDef(type);\n }\n if (isEnumType(type)) {\n return {\n description: type.description ?? \"\",\n enumValues: enumValues(type),\n kind: \"enum\",\n name: type.name,\n };\n }\n if (isUnionType(type)) {\n return unionDef(type);\n }\n // SAFETY: the named-type union is exhaustive — after the object, interface,\n // input, enum, and union guards, only scalars remain.\n return scalarDef(type as GraphQLScalarType);\n};\n\n/** Lower a built schema to the serializable document. */\nconst documentOf = (schema: GraphQLSchema): GraphqlDocument => {\n const types: Record<string, GraphqlTypeDef> = {};\n for (const [name, type] of Object.entries(schema.getTypeMap())) {\n // Introspection meta-types (`__Schema`, `__Type`, …) are machinery, not API.\n if (name.startsWith(\"__\")) {\n continue;\n }\n types[name] = typeDef(type, schema);\n }\n const roots: GraphqlDocument[\"roots\"] = {};\n const query = schema.getQueryType();\n const mutation = schema.getMutationType();\n const subscription = schema.getSubscriptionType();\n if (query) {\n roots.query = query.name;\n }\n if (mutation) {\n roots.mutation = mutation.name;\n }\n if (subscription) {\n roots.subscription = subscription.name;\n }\n return {\n info: { description: schema.description ?? \"\" },\n roots,\n types,\n };\n};\n\n/**\n * A parsed JSON value carrying an introspection result's `__schema` key —\n * possibly under the standard `{ data: … }` GraphQL response envelope. The\n * generic-guard shape mirrors the document checks in `parse.ts`; the real\n * validation is `buildClientSchema`'s, which throws a descriptive error on\n * anything malformed inside `__schema`.\n */\nconst hasSchemaKey = <Value>(\n value: Value\n): value is Value & { __schema: object } =>\n typeof value === \"object\" && value !== null && \"__schema\" in value;\n\n/** A parsed JSON value shaped like a GraphQL response envelope. */\nconst hasDataEnvelope = <Value>(\n value: Value\n): value is Value & { data: unknown } =>\n typeof value === \"object\" && value !== null && \"data\" in value;\n\n/** The introspection result inside a parsed JSON value, if it carries one. */\nconst introspectionOf = <Value>(parsed: Value): object | undefined => {\n if (hasSchemaKey(parsed)) {\n return parsed;\n }\n return hasDataEnvelope(parsed) && hasSchemaKey(parsed.data)\n ? parsed.data\n : undefined;\n};\n\n/**\n * Build the document from schema text: an introspection JSON result (the raw\n * `{ __schema }` shape or a `{ data: { __schema } }` response envelope) or\n * SDL. Anything else throws — JSON that isn't an introspection result names\n * that problem directly instead of earning SDL syntax errors for a `{`.\n */\nexport const buildGraphqlDocument = (text: string): GraphqlDocument => {\n let parsed: unknown;\n let isJson = true;\n try {\n parsed = JSON.parse(text);\n } catch {\n isJson = false;\n }\n if (isJson) {\n const introspection = introspectionOf(parsed);\n if (!introspection) {\n throw new Error(\n \"the JSON document is not a GraphQL introspection result (no `__schema` key).\"\n );\n }\n // SAFETY: the guard above only establishes the `__schema` key;\n // `buildClientSchema` validates the full introspection shape itself and\n // throws a descriptive error on anything malformed.\n return documentOf(\n buildClientSchema(\n introspection as Parameters<typeof buildClientSchema>[0]\n )\n );\n }\n return documentOf(buildSchema(text));\n};\n",
|
|
29
|
-
"import type { Nodes } from \"mdast\";\nimport { fromMarkdown } from \"mdast-util-from-markdown\";\nimport { toString as mdastToString } from \"mdast-util-to-string\";\nimport stringWidth from \"string-width\";\n\nimport { apiNamePhrase } from \"../core/api-name.ts\";\nimport { columnsPrefix } from \"../core/text-width.ts\";\nimport type { GraphqlMember } from \"./graphql.ts\";\nimport { isGraphqlOperationKind } from \"./graphql.ts\";\nimport type { ApiOperationRef, ApiSpecData } from \"./model.ts\";\nimport type { ReferenceSource } from \"./references.ts\";\nimport { operationSignature } from \"./signature.ts\";\n\n/**\n * Lower a parsed spec into MDX for the staged content source. Each operation and\n * the spec overview become a thin MDX page: the frontmatter carries the\n * searchable `title` (so operations flow into Blume's search, OG, and llms.txt),\n * the operation/overview **description is emitted as markdown in the body** so it\n * renders parsed (links, formatting) and is indexed, and the structured UI is\n * deferred to a Blume-owned component (`<Operation>` / `<ApiOverview>`). The\n * catch-all renders the frontmatter title as the page `<h1>`, so the components\n * omit their own top heading.\n */\n\n// Neutralize the few characters MDX treats specially (`{` expressions, `<`\n// JSX) so an arbitrary spec description can be embedded in the body verbatim\n// without breaking compilation. They render as their literal selves. `>` is\n// deliberately not escaped: it isn't MDX-special on its own, and escaping it\n// turns a `> Note:` blockquote into literal \"> Note:\" text.\nconst MDX_UNSAFE = /[<{}]/gu;\nconst ENTITIES = new Map([\n [\"<\", \"<\"],\n [\"{\", \"{\"],\n [\"}\", \"}\"],\n]);\n// MDX also parses lines starting with `import`/`export` as ESM (\"import the\n// SDK…\" is common spec prose). Entity-escape the keyword's first letter so the\n// construct can't match; it still renders as the literal word.\nconst MDX_ESM_KEYWORD = /^(?<keyword>import|export)\\b/gmu;\nconst escapeProse = (text: string): string =>\n text\n .replace(MDX_UNSAFE, (char) => ENTITIES.get(char) ?? char)\n .replace(\n MDX_ESM_KEYWORD,\n (keyword) => `&#${keyword.codePointAt(0)};${keyword.slice(1)}`\n );\n\n/**\n * The source offset ranges of code constructs — inline spans and fences —\n * that MDX treats as literal (entities are NOT decoded inside them, so\n * escaping there would render `/pets/{petId}` verbatim). The ranges\n * come from a CommonMark parse rather than fence-emulating regexes: the\n * parser is the authority on equal-length backtick pairing, longer tilde\n * closers, unclosed fences running to EOF, and fences nested in blockquotes —\n * each of which the replaced regexes had to re-derive (two with a recorded\n * bug history in this file).\n *\n * One CommonMark construct is deliberately *not* masked: indented code. MDX\n * disables indented code blocks, so a 4-space-indented sample is a paragraph\n * whose braces genuinely need escaping; fence-or-backtick is told apart from\n * indentation by the construct's first character.\n */\nconst codeSpans = (text: string): [number, number][] => {\n const spans: [number, number][] = [];\n const collect = (node: Nodes): void => {\n if (node.type === \"inlineCode\" || node.type === \"code\") {\n // fromMarkdown always stamps positions; -1 is an unreachable guard.\n const start = node.position?.start.offset ?? -1;\n const end = node.position?.end.offset ?? -1;\n const head = text.slice(Math.max(start, 0), Math.max(end, 0)).trimStart();\n if (\n start >= 0 &&\n (node.type === \"inlineCode\" ||\n head.startsWith(\"`\") ||\n head.startsWith(\"~\"))\n ) {\n spans.push([start, end]);\n }\n return;\n }\n if (\"children\" in node) {\n for (const child of node.children) {\n collect(child);\n }\n }\n };\n collect(fromMarkdown(text));\n return spans;\n};\n\n/** Escape MDX-special syntax in prose while leaving code verbatim. */\nconst mdxSafe = (text: string): string => {\n let out = \"\";\n let cursor = 0;\n for (const [start, end] of codeSpans(text)) {\n out += escapeProse(text.slice(cursor, start));\n out += text.slice(start, end);\n cursor = end;\n }\n return out + escapeProse(text.slice(cursor));\n};\n\n/**\n * Frontmatter emitted for one operation or overview page. Boolean flags are\n * assigned only when set, so absent keys stay absent in the staged MDX.\n */\nexport interface RenderedPageData {\n ai?: { exclude: boolean };\n deprecated?: boolean;\n search?: { exclude?: boolean; tags?: string[] };\n seo: { description: string; noindex?: boolean };\n sidebar: { badge?: string; label: string };\n title: string;\n type?: string;\n}\n\n/** Frontmatter + body for one operation or overview page. */\nexport interface RenderedPage {\n data: RenderedPageData;\n body: string;\n}\n\n// Meta descriptions. A page that sets none falls back to the site-wide default,\n// so a spec's pages would otherwise all ship one identical description — what\n// search engines treat as duplicate content. These go in `seo.description`, not\n// `description`: the prose already renders in the body, and a `description`\n// frontmatter field would print it a second time as the page subtitle.\nconst META_DESCRIPTION_MAX = 160;\nconst WHITESPACE = /\\s+/gu;\nconst TRAILING_WORD = /\\s+\\S*$/u;\n\n/**\n * Flatten markdown prose to its first paragraph as single-line plain text,\n * via a real parse (`mdast-util-to-string`). The regex strip this replaces\n * was lossy on literal prose — `snake_case` → `snakecase`, `C#` → `C` — and\n * these strings ship as `seo.description` meta tags. A description with no\n * paragraph (say, only a heading or list) falls back to its first block.\n */\nconst plainProse = (markdown: string): string => {\n const tree = fromMarkdown(markdown);\n const first =\n tree.children.find((node) => node.type === \"paragraph\") ?? tree.children[0];\n return first ? mdastToString(first).replace(WHITESPACE, \" \").trim() : \"\";\n};\n\n/** Cap `text` at `max` display columns, cutting on a word boundary. */\nconst clip = (text: string, max: number): string => {\n if (max <= 0) {\n return \"\";\n }\n if (stringWidth(text) <= max) {\n return text;\n }\n const head = columnsPrefix(text, max - 1);\n const onWordBoundary = head.replace(TRAILING_WORD, \"\");\n // One very long token — an endpoint path has no spaces — would be dropped\n // whole, leaving a stub. Hard-cut it instead of losing it.\n return `${stringWidth(onWordBoundary) >= max / 2 ? onWordBoundary : head}…`;\n};\n\nconst apiName = (spec: ApiSpecData): string => spec.title || spec.label;\n/** The spec's name as \"the ___\" in generated prose (see `apiNamePhrase`). */\nconst apiPhrase = (spec: ApiSpecData): string => apiNamePhrase(apiName(spec));\n\n/** Human phrase for each GraphQL page kind, for meta descriptions. */\nconst GRAPHQL_MEMBER_PHRASES = {\n enum: \"enum type\",\n input: \"input object type\",\n interface: \"interface type\",\n mutation: \"mutation\",\n object: \"object type\",\n query: \"query\",\n scalar: \"scalar type\",\n subscription: \"subscription\",\n union: \"union type\",\n} satisfies Record<GraphqlMember, string>;\n\n/**\n * The spec's own prose for the operation, followed by the endpoint it documents\n * — so every operation page carries a distinct, self-describing meta\n * description even when the spec's summaries are terse. With the suffix\n * switched off (`seoDescriptionSuffix: false`, for sites whose prose isn't\n * English) the description is the prose alone, or the page `title` — a\n * language-neutral `GET /pets`, channel, or field name — when the operation\n * has no prose at all, so no page ships an empty description.\n */\nconst operationDescription = (\n spec: ApiSpecData,\n operation: ApiOperationRef,\n options: { suffix: boolean; title: string }\n): string => {\n if (!options.suffix) {\n return clip(\n plainProse(operation.description || operation.summary) || options.title,\n META_DESCRIPTION_MAX\n );\n }\n // AsyncAPI operations act on a channel, not an HTTP endpoint; GraphQL pages\n // document a root field or a named type.\n let subject: string;\n if (spec.kind === \"asyncapi\") {\n subject = `${operation.method} operation on ${operation.path}`;\n } else if (spec.kind === \"graphql\") {\n // SAFETY: the GraphQL extractor only ever assigns member kinds as the\n // method (see `extractGraphqlOperations`).\n subject = `${operation.path} ${GRAPHQL_MEMBER_PHRASES[operation.method as GraphqlMember]}`;\n } else {\n subject = `${operation.method.toUpperCase()} ${operation.path} endpoint`;\n }\n const suffix = `Reference for the ${subject} in the ${apiPhrase(spec)}.`;\n const prose = clip(\n plainProse(operation.description || operation.summary),\n META_DESCRIPTION_MAX - stringWidth(suffix) - 1\n );\n return clip([prose, suffix].filter(Boolean).join(\" \"), META_DESCRIPTION_MAX);\n};\n\n/** Prepend a markdown description (if any) above a component invocation. */\nconst withDescription = (description: string, component: string): string =>\n description.trim()\n ? `${mdxSafe(description.trim())}\\n\\n${component}`\n : component;\n\nexport const operationMdx = (\n spec: ApiSpecData,\n operation: ApiOperationRef,\n reference?: Pick<\n ReferenceSource,\n \"includeInLlms\" | \"includeInSearch\" | \"noindex\" | \"seoDescriptionSuffix\"\n >\n): RenderedPage => {\n const method = operation.method.toUpperCase();\n const graphql = spec.kind === \"graphql\";\n // A GraphQL page IS its field/type — `query pets` would double the badge the\n // page already renders; the other kinds title an endpoint or channel action.\n const fallbackTitle = graphql\n ? operation.path\n : operationSignature(spec, operation);\n const title = operation.summary || fallbackTitle;\n // Skip the body description when it only repeats the summary (the `<h1>`) —\n // common in specs that set summary and description to the same string.\n const description =\n operation.description.trim() === operation.summary.trim()\n ? \"\"\n : operation.description;\n const flags: Pick<RenderedPageData, \"ai\" | \"deprecated\"> = {};\n if (reference?.includeInLlms === false) {\n flags.ai = { exclude: true };\n }\n if (operation.deprecated) {\n flags.deprecated = true;\n }\n const searchFlags: Pick<\n NonNullable<RenderedPageData[\"search\"]>,\n \"exclude\"\n > = {};\n if (reference?.includeInSearch === false) {\n searchFlags.exclude = true;\n }\n const seo: RenderedPageData[\"seo\"] = {\n description: operationDescription(spec, operation, {\n suffix: reference?.seoDescriptionSuffix !== false,\n title,\n }),\n };\n if (reference?.noindex) {\n seo.noindex = true;\n }\n const sidebar: RenderedPageData[\"sidebar\"] = {\n label: operation.summary || operation.path,\n };\n // GraphQL operation kinds badge like HTTP methods, but a type page's kind\n // already heads its sidebar group (\"Objects\", \"Enums\", …) — an `OBJECT`\n // badge on every row would only repeat it, so type pages get none. The\n // uppercased method is likewise an internal token on GraphQL pages, so\n // their search tags carry only the group name.\n if (!graphql || isGraphqlOperationKind(operation.method)) {\n sidebar.badge = method;\n }\n const tags = graphql ? [operation.tag] : [operation.tag, method];\n return {\n body: withDescription(\n description,\n `<Operation source=\"${spec.slug}\" id=\"${operation.key}\" />`\n ),\n data: {\n ...flags,\n search: { ...searchFlags, tags },\n seo,\n sidebar,\n title,\n // Signals the two-column API layout (request panel instead of the TOC).\n type: \"openapi-operation\",\n },\n };\n};\n\nexport const overviewMdx = (\n spec: ApiSpecData,\n reference?: Pick<\n ReferenceSource,\n \"includeInLlms\" | \"includeInSearch\" | \"noindex\"\n >\n): RenderedPage => {\n // Tag sections: declared tags in spec order, then any tag an operation\n // references that isn't declared under `tags`. The section headings are\n // emitted as real markdown `##` (not markup inside a component) so the\n // markdown pipeline gives them ids, permalink anchors, and table-of-contents\n // entries; only the operation-link list defers to a component.\n const operations = Object.values(spec.operations);\n // Dedupe by slug: two declared tags that slugify identically (`Store` and\n // `store`) must render one section, not the same operation list twice.\n const sections: typeof spec.tags = [];\n const known = new Set<string>();\n for (const tag of spec.tags) {\n if (!known.has(tag.slug)) {\n known.add(tag.slug);\n sections.push(tag);\n }\n }\n for (const operation of operations) {\n if (!known.has(operation.tagSlug)) {\n known.add(operation.tagSlug);\n sections.push({\n description: \"\",\n name: operation.tag,\n slug: operation.tagSlug,\n });\n }\n }\n const tagSections: string[] = [];\n for (const tag of sections) {\n if (!operations.some((operation) => operation.tagSlug === tag.slug)) {\n continue;\n }\n const description = tag.description.trim()\n ? [mdxSafe(tag.description.trim())]\n : [];\n tagSections.push(\n [\n `## ${mdxSafe(tag.name)}`,\n ...description,\n `<ApiTagOperations source=\"${spec.slug}\" tag=\"${tag.slug}\" />`,\n ].join(\"\\n\\n\")\n );\n }\n const flags: Pick<RenderedPageData, \"ai\" | \"search\"> = {};\n if (reference?.includeInLlms === false) {\n flags.ai = { exclude: true };\n }\n if (reference?.includeInSearch === false) {\n flags.search = { exclude: true };\n }\n const seo: RenderedPageData[\"seo\"] = {\n description:\n clip(plainProse(spec.description), META_DESCRIPTION_MAX) ||\n `${apiPhrase(spec)} reference.`,\n };\n if (reference?.noindex) {\n seo.noindex = true;\n }\n return {\n body: [\n withDescription(\n spec.description,\n `<ApiOverview source=\"${spec.slug}\" />`\n ),\n ...tagSections,\n ].join(\"\\n\\n\"),\n data: {\n ...flags,\n seo,\n sidebar: { label: \"Overview\" },\n title: apiName(spec),\n },\n };\n};\n",
|
|
30
|
-
"/**\n * Whether a name already reads as an API's name: it contains `API` or `APIs`\n * as a whole word, anywhere. Word-bounded on both sides, so \"OpenAPI\" and\n * \"AsyncAPI\" are product names rather than API names and still take the\n * suffix; unanchored at the end, so \"Petstore API v2\" is left alone too.\n */\nconst NAMES_AN_API = /\\bAPIs?\\b/iu;\n\n/**\n * `name` as the phrase \"the ___\" in generated prose — the spec title or\n * reference label, followed by ` API` unless it already says so. Keeps\n * \"Petstore\" reading as \"Petstore API\" without turning \"Petstore API\" into\n * \"Petstore API API\".\n */\nexport const apiNamePhrase = (name: string): string => {\n const trimmed = name.trim();\n return NAMES_AN_API.test(trimmed) ? trimmed : `${trimmed} API`;\n};\n",
|
|
31
|
-
"import stringWidth from \"string-width\";\n\n/**\n * The longest prefix of `text` that renders within `max` display columns\n * without cutting inside a grapheme cluster. Columns are `string-width`'s: a\n * fullwidth or wide character counts 2, a combining mark 0, everything else 1\n * — the same measure `blume audit` grades titles and meta descriptions with,\n * so text cut here stays inside the audit's thresholds for every script, not\n * just Latin. Grapheme segmentation is rule-based (UAX #29), so unlike word\n * segmentation it does not drift across ICU builds, and it can never split a\n * surrogate pair or halve an emoji sequence.\n */\nexport const columnsPrefix = (text: string, max: number): string => {\n let end = 0;\n let used = 0;\n const graphemes = new Intl.Segmenter(undefined, { granularity: \"grapheme\" });\n for (const { index, segment } of graphemes.segment(text)) {\n const width = stringWidth(segment);\n if (used + width > max) {\n break;\n }\n used += width;\n end = index + segment.length;\n }\n return text.slice(0, end);\n};\n",
|
|
32
|
-
"import { graphqlSignature } from \"./graphql.ts\";\nimport type { ApiOperationRef, ApiSpecData } from \"./model.ts\";\n\n/**\n * The one-line name of what an operation page documents, in the notation of\n * its spec kind: `GET /pets/{id}` for an HTTP endpoint, `SEND user/signup`\n * for an AsyncAPI channel action, `query pets` or `type Pet` for a GraphQL\n * member. The page titles an untitled operation with it and the agent\n * surfaces (`<route>.md`, llms-full.txt, MCP `get_page`) downlevel\n * `<Operation>` to it, so the two can never disagree about the endpoint.\n */\nexport const operationSignature = (\n spec: Pick<ApiSpecData, \"kind\">,\n operation: Pick<ApiOperationRef, \"method\" | \"path\">\n): string =>\n spec.kind === \"graphql\"\n ? graphqlSignature(operation)\n : `${operation.method.toUpperCase()} ${operation.path}`;\n",
|
|
33
|
-
"import { withBasePath } from \"../core/base-path.ts\";\nimport matter from \"../core/frontmatter.ts\";\nimport type { FolderMeta } from \"../core/schema.ts\";\nimport { hashText } from \"../core/sources/cache.ts\";\nimport type {\n ContentSource,\n SourceContext,\n SourceEntry,\n SourceLoadResult,\n} from \"../core/sources/types.ts\";\nimport type { Diagnostic } from \"../core/types.ts\";\nimport { extractAsyncApiOperations } from \"./asyncapi.ts\";\nimport type { AsyncApiDocument } from \"./asyncapi.ts\";\nimport { extractGraphqlOperations } from \"./graphql.ts\";\nimport type { GraphqlDocument } from \"./graphql.ts\";\nimport { extractOperations } from \"./model.ts\";\nimport type {\n ApiDocument,\n ApiOperationRef,\n ApiSpecData,\n ApiTagRef,\n OpenApiData,\n} from \"./model.ts\";\nimport {\n InvalidSpecError,\n parseAsyncApiSpec,\n parseGraphqlSpec,\n parseSpec,\n} from \"./parse.ts\";\nimport type { ReferenceSource } from \"./references.ts\";\nimport { operationMdx, overviewMdx } from \"./render-mdx.ts\";\nimport type { RenderedPage } from \"./render-mdx.ts\";\n\n/**\n * The staged content source behind Blume's own API reference renderer (OpenAPI\n * and AsyncAPI alike). Each configured spec is parsed once here, then lowered\n * into one MDX page per operation plus an overview page — so operations become\n * first-class Blume pages (real routes, sidebar, search, i18n, OG) and the\n * parsed documents are handed to the generated `blume:openapi` module for the\n * UI components to render. The source keeps its historical `openapi` name for\n * both kinds — downstream consumers (`ai.llmsTxt.openapi`, the llms noindex\n * exemption) key on it as \"the generated API reference source\".\n */\n\n/** A content source that also exposes the specs it parsed during `load()`. */\nexport interface OpenApiContentSource extends ContentSource {\n readonly kind: \"openapi-source\";\n /** Parsed spec data, populated by `load()`; `{}` before the first load. */\n openApiData: () => OpenApiData;\n}\n\n/** Narrow a content source to the OpenAPI source (to read its parsed specs). */\nexport const isOpenApiSource = (\n source: ContentSource\n): source is OpenApiContentSource =>\n \"kind\" in source && source.kind === \"openapi-source\";\n\n/** Route (`/reference/pet/add-pet`) to a staged content ref, without extension. */\nconst routeToRef = (route: string): string => route.replace(/^\\/+/u, \"\");\n\n/** Human label per spec kind, so one kind's failure never reads as another's. */\nconst KIND_LABELS = {\n asyncapi: \"AsyncAPI\",\n graphql: \"GraphQL\",\n openapi: \"OpenAPI\",\n} satisfies Record<ReferenceSource[\"kind\"], string>;\n\n/** Diagnostic-code prefix per spec kind. */\nconst CODE_PREFIXES = {\n asyncapi: \"BLUME_ASYNCAPI\",\n graphql: \"BLUME_GRAPHQL\",\n openapi: \"BLUME_OPENAPI\",\n} satisfies Record<ReferenceSource[\"kind\"], string>;\n\n/**\n * Parse-level warning code per kind: an offline cache fallback for any kind,\n * plus lossy 2.x→3.0 conversion notes for AsyncAPI — hence the broader codes\n * there and for GraphQL (OpenAPI keeps its historical one).\n */\nconst SPEC_WARNING_CODES = {\n asyncapi: \"BLUME_ASYNCAPI_SPEC_WARNING\",\n graphql: \"BLUME_GRAPHQL_SPEC_WARNING\",\n openapi: \"BLUME_OPENAPI_STALE\",\n} satisfies Record<ReferenceSource[\"kind\"], string>;\n\n/**\n * Extract-level skip code per kind. OpenAPI keeps its historical code (the\n * only extract warning it emits is the unresolved $ref path item); the\n * GraphQL extractor currently skips nothing, so its code is reserved.\n */\nconst SKIPPED_CODES = {\n asyncapi: \"BLUME_ASYNCAPI_SKIPPED_OPERATION\",\n graphql: \"BLUME_GRAPHQL_SKIPPED\",\n openapi: \"BLUME_OPENAPI_REF_PATH_ITEM\",\n} satisfies Record<ReferenceSource[\"kind\"], string>;\n\n/** `_EMPTY` diagnostic suggestion per kind. */\nconst EMPTY_SUGGESTIONS = {\n asyncapi:\n \"Check the spec points at an AsyncAPI document with `channels` and `operations`.\",\n graphql:\n \"Check the spec points at a GraphQL schema (SDL or introspection JSON) whose root types declare fields.\",\n openapi:\n \"Check the spec points at an OpenAPI document with operations under `paths`.\",\n} satisfies Record<ReferenceSource[\"kind\"], string>;\n\n/** `_UNAVAILABLE` suggestion for a readable-but-invalid spec, per kind. */\nconst INVALID_SUGGESTIONS = {\n asyncapi:\n \"Point the spec at an AsyncAPI document (a YAML or JSON file with an object at the top level).\",\n graphql:\n \"Point the spec at a GraphQL schema — SDL text or an introspection JSON result.\",\n openapi:\n \"Point the spec at an OpenAPI document (a YAML or JSON file with an object at the top level).\",\n} satisfies Record<ReferenceSource[\"kind\"], string>;\n\nconst toEntry = (rendered: RenderedPage, ref: string): SourceEntry => {\n const raw = matter.stringify(`${rendered.body}\\n`, rendered.data);\n return {\n body: { format: \"mdx\", text: rendered.body },\n // Spread so the named frontmatter shape satisfies the open metadata\n // dictionary every source entry carries.\n data: { ...rendered.data },\n hash: hashText(raw),\n raw,\n ref,\n };\n};\n\n/** All staged entries for one spec: operations first, overview last. */\nconst specEntries = (\n spec: ApiSpecData,\n operations: ApiOperationRef[],\n reference: ReferenceSource\n): SourceEntry[] => {\n const entries = operations.map((operation) =>\n toEntry(\n operationMdx(spec, operation, reference),\n `${routeToRef(operation.route)}.mdx`\n )\n );\n // Overview last so an operation sets the section's routePath before the index\n // page is inserted (the group's routePath is derived from its first child).\n // A root-mounted reference refs `index.mdx`, not `/index.mdx`.\n const base = routeToRef(spec.route);\n entries.push(\n toEntry(\n overviewMdx(spec, reference),\n base ? `${base}/index.mdx` : \"index.mdx\"\n )\n );\n return entries;\n};\n\n/**\n * Label each tag's sidebar group with the spec's own tag name. The group label\n * is otherwise re-humanized from the tag's route slug (split on hyphens,\n * title-cased), which mangles authored casing and symbols — `OAuth2` →\n * \"Oauth2\", `Größe` → \"Größe\" only by luck of the slug. Keys are the tag\n * directories under the reference route, the same group paths `meta.ts` files\n * use, so user-authored meta still overrides these.\n */\nconst tagFolderMeta = (\n spec: ApiSpecData,\n tags: { slug: string; name: string }[]\n): Record<string, FolderMeta> => {\n const base = routeToRef(spec.route);\n return Object.fromEntries(\n tags.map((tag) => [\n base ? `${base}/${tag.slug}` : tag.slug,\n { title: tag.name },\n ])\n );\n};\n\ninterface LoadedSpec {\n slug: string;\n spec: ApiSpecData;\n entries: SourceEntry[];\n /** Sidebar-group labels for the spec's tag directories. */\n folderMeta: Record<string, FolderMeta>;\n /** Non-fatal notes from the load (e.g. an offline cache fallback). */\n diagnostics: Diagnostic[];\n}\n\n/** One parsed spec, whichever front-end read it — the kind dispatch seam. */\ninterface ParsedReference {\n document: ApiDocument | AsyncApiDocument | GraphqlDocument;\n warnings: string[];\n operations: ApiOperationRef[];\n tags: ApiTagRef[];\n extractWarnings: string[];\n}\n\nconst parseReference = async (\n reference: ReferenceSource,\n ctx: SourceContext\n): Promise<ParsedReference> => {\n const options = { cacheDir: ctx.cacheDir, refresh: ctx.refresh };\n if (reference.kind === \"asyncapi\") {\n const { document, warnings } = await parseAsyncApiSpec(\n reference.spec,\n ctx.projectRoot,\n options\n );\n const extracted = extractAsyncApiOperations(document, reference.route);\n return {\n document,\n extractWarnings: extracted.warnings,\n operations: extracted.operations,\n tags: extracted.tags,\n warnings,\n };\n }\n if (reference.kind === \"graphql\") {\n const { document, warnings } = await parseGraphqlSpec(\n reference.spec,\n ctx.projectRoot,\n options\n );\n const extracted = extractGraphqlOperations(document, reference.route);\n return {\n document,\n extractWarnings: extracted.warnings,\n operations: extracted.operations,\n tags: extracted.tags,\n warnings,\n };\n }\n const { document, warnings } = await parseSpec(\n reference.spec,\n ctx.projectRoot,\n options\n );\n const extracted = extractOperations(document, reference.route);\n return {\n document,\n extractWarnings: extracted.warnings,\n operations: extracted.operations,\n tags: extracted.tags,\n warnings,\n };\n};\n\nexport const openApiSource = (\n references: ReferenceSource[],\n ctx: SourceContext\n): OpenApiContentSource => {\n let parsed: OpenApiData = {};\n\n const loadReference = async (\n reference: ReferenceSource\n ): Promise<LoadedSpec | Diagnostic> => {\n // Human label and diagnostic-code prefix for the spec's kind, so an\n // AsyncAPI failure never reads as an OpenAPI one.\n const kindLabel = KIND_LABELS[reference.kind];\n const codePrefix = CODE_PREFIXES[reference.kind];\n try {\n const { document, warnings, operations, tags, extractWarnings } =\n await parseReference(reference, ctx);\n const info = document.info ?? { title: reference.label, version: \"\" };\n // The playground proxy resolves here, not client-side: `true` selects\n // the built-in `/_api-proxy` route (mounted under the site `basePath`,\n // like every served URL this module emits), a non-empty string is an\n // external proxy used verbatim, and anything else (`false`, `\"\"`)\n // means the Send button fetches the API directly.\n const { proxy: configuredProxy } = reference.display.playground;\n let proxy: string | false = false;\n if (configuredProxy === true) {\n proxy = withBasePath(reference.basePath, \"/_api-proxy\");\n } else if (configuredProxy !== false && configuredProxy !== \"\") {\n proxy = configuredProxy;\n }\n const spec: ApiSpecData = {\n codeSamples: reference.display.codeSamples,\n description: info.description ?? \"\",\n document,\n expandSchemas: reference.display.expandSchemas,\n kind: reference.kind,\n label: reference.label,\n // Operation pages flow through the content pipeline, which mounts them\n // under the site-wide `basePath` (staged entry refs below stay\n // base-less); serialize the served route so components link to the\n // pages' real URLs.\n operations: Object.fromEntries(\n operations.map((operation) => [\n operation.key,\n {\n ...operation,\n route: withBasePath(reference.basePath, operation.route),\n },\n ])\n ),\n playground: { enabled: reference.display.playground.enabled, proxy },\n route: reference.route,\n slug: reference.slug,\n tags,\n title: info.title ?? reference.label,\n version: info.version ?? \"\",\n };\n // Only GraphQL references carry a live endpoint (a schema names no\n // server); assigned separately so the key stays absent otherwise.\n if (reference.endpoint !== undefined) {\n spec.endpoint = reference.endpoint;\n }\n return {\n diagnostics: [\n ...warnings.map((message) => ({\n code: SPEC_WARNING_CODES[reference.kind],\n message,\n severity: \"warning\" as const,\n })),\n ...extractWarnings.map((message) => ({\n code: SKIPPED_CODES[reference.kind],\n message: `In ${kindLabel} spec \"${reference.spec}\": ${message}`,\n severity: \"warning\" as const,\n })),\n // A document with no operations (say, a config file that happens to\n // parse as YAML) would otherwise build a nav tab onto an empty\n // reference with no hint why.\n ...(operations.length === 0\n ? [\n {\n code: `${codePrefix}_EMPTY`,\n message: `${kindLabel} spec \"${reference.spec}\" for ${reference.route} declares no operations; its API reference is empty.`,\n severity: \"warning\" as const,\n suggestion: EMPTY_SUGGESTIONS[reference.kind],\n },\n ]\n : []),\n ],\n entries: specEntries(spec, operations, reference),\n folderMeta: tagFolderMeta(spec, tags),\n slug: reference.slug,\n spec,\n };\n } catch (error) {\n return {\n code: `${codePrefix}_UNAVAILABLE`,\n // SAFETY: spec loading fails with Error instances (fetch, read, and\n // parse errors alike); only the message is read for the diagnostic.\n message: `Could not load ${kindLabel} spec \"${reference.spec}\" for ${reference.route} (${(error as Error).message}); its reference pages were skipped.`,\n // A configured-but-unloadable spec ships a dead nav tab (a 404 route),\n // so fail loudly in build (blocks under --strict) while staying a warning\n // in dev so offline work still runs.\n severity: ctx.mode === \"build\" ? \"error\" : \"warning\",\n // A readable-but-invalid file is a content problem, not a network one;\n // only point at reachability for actual fetch/read failures.\n suggestion:\n error instanceof InvalidSpecError\n ? INVALID_SUGGESTIONS[reference.kind]\n : \"Check the spec URL/path is reachable from the build environment; behind a proxy, set HTTP(S)_PROXY.\",\n };\n }\n };\n\n const load = async (): Promise<SourceLoadResult> => {\n const results = await Promise.all(references.map(loadReference));\n const entries: SourceEntry[] = [];\n // Route collisions recorded while deduping (see `blumeReferences`): a\n // dropped source loses a whole spec's pages, so warn even when the kept\n // spec loads cleanly.\n const diagnostics: Diagnostic[] = references.flatMap((reference) =>\n (reference.collisions ?? []).map((message) => ({\n code: \"BLUME_OPENAPI_ROUTE_COLLISION\",\n message,\n severity: \"warning\" as const,\n }))\n );\n const data: OpenApiData = {};\n const folderMeta: Record<string, FolderMeta> = {};\n for (const result of results) {\n if (\"severity\" in result) {\n diagnostics.push(result);\n continue;\n }\n data[result.slug] = result.spec;\n entries.push(...result.entries);\n Object.assign(folderMeta, result.folderMeta);\n diagnostics.push(...result.diagnostics);\n }\n parsed = data;\n return { diagnostics, entries, folderMeta };\n };\n\n return {\n kind: \"openapi-source\",\n load,\n name: \"openapi\",\n openApiData: () => parsed,\n staged: true,\n };\n};\n",
|
|
34
|
-
"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 { 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 const entries = await Promise.all(\n files.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 suggestion: `Create a \"${options.root}\" folder with at least one .md or .mdx file, or set content.root in blume.config.ts.`,\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 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",
|
|
35
|
-
"import type { WatchListener } from \"node:fs\";\n\n/**\n * Directory segments that are never authored content: VCS, dependency trees,\n * Blume's own generated project and build output, and framework/deploy caches.\n * Both the content scan and the dev watcher skip these unconditionally, on top\n * of the user's `content.exclude`.\n *\n * The scan needs them because a broadly-scoped `content.root` — `\".\"` or an app\n * dir that also holds `node_modules`/`dist`, the common shape when migrating a\n * docs app that lives at the repo or app root — would otherwise glob thousands\n * of stray markdown files out of dependencies and build artifacts. `content.root`\n * defaults to `docs/` where this rarely bites, but any wider root hits it.\n *\n * The watcher needs them because a recursive `fs.watch` rooted at the project\n * also sees Blume's own `.blume/` output, which the dev server rewrites on every\n * render (e.g. `.blume/.astro/data-store.json`). Left unfiltered, each such write\n * re-triggers a rescan + runtime regeneration whose writes land back under\n * `.blume/` and fire the watcher again: a self-sustaining loop that stalls page\n * renders and floods the console. `fs.watch` has no ignore option, so we filter\n * by the changed path in the callback.\n */\nexport const BLUME_IGNORE_DIRS = [\n \".blume\",\n // The isolated `blume check --isolated` runtime. A sibling of `.blume`, it is\n // written while a dev server runs; without this the content-layer watcher (or\n // a `.`-rooted fs.watch) would treat its generation as a content change and\n // reload — the very thing `--isolated` promises not to do.\n \".blume-verify\",\n \".cache\",\n \".git\",\n \".next\",\n \".turbo\",\n \".vercel\",\n \"dist\",\n \"node_modules\",\n];\n\n/**\n * Baseline scan-ignore globs applied to every filesystem source, unioned with\n * the user's `content.exclude`. Kept in sync with the watcher via the shared\n * {@link BLUME_IGNORE_DIRS} so `load()` and `watch()` never disagree on what is\n * content. `**\\/<dir>/**` matches the directory at the content root or nested.\n */\nexport const baselineScanIgnore = (): string[] =>\n BLUME_IGNORE_DIRS.map((dir) => `**/${dir}/**`);\n\n/**\n * Alias retained for the dev watcher's call site and its tests; the watcher and\n * the scan share the same never-content directory set.\n */\nexport const BLUME_WATCH_IGNORE_DIRS = BLUME_IGNORE_DIRS;\n\n/** Extract single-segment ignore dirs (`foo`) from `foo/**`-style excludes. */\nexport const excludeDirSegments = (patterns: readonly string[]): string[] =>\n patterns\n .map((pattern) => /^(?<dir>[^*/]+)\\/\\*\\*$/u.exec(pattern)?.groups?.dir)\n .filter((dir): dir is string => dir !== undefined);\n\n/**\n * Build a recursive-watch listener that fires `onChange` for content changes but\n * ignores events whose path crosses an ignored directory segment — one in\n * `ignoreDirs`, or one `ignoreSegment` rejects, for a scan whose skip rule is\n * a shape (every dot-name) rather than a list. A missing `filename` — rare;\n * the platform couldn't name the changed path — falls through to `onChange`\n * rather than silently dropping a real edit. Exported for testing.\n */\nexport const ignoringWatchListener = (\n onChange: () => void,\n ignoreDirs: Iterable<string> = BLUME_WATCH_IGNORE_DIRS,\n ignoreSegment: (segment: string) => boolean = () => false\n): WatchListener<string> => {\n const ignore = new Set(ignoreDirs);\n return (_event, filename) => {\n if (\n filename !== null &&\n filename\n .split(/[/\\\\]/u)\n .some((segment) => ignore.has(segment) || ignoreSegment(segment))\n ) {\n return;\n }\n onChange();\n };\n};\n",
|
|
36
|
-
"import { 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 { columnsPrefix } from \"../text-width.ts\";\nimport {\n hashText,\n loadWithCache,\n pollingWatch,\n snapshotCache,\n} from \"./cache.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\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/** 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): 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 const body = (release.body ?? \"\").replaceAll(\"\\r\\n\", \"\\n\").trim();\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 const fetchPage = async (page: number): Promise<GithubRelease[]> => {\n const url = `${base}/repos/${options.owner}/${options.repo}/releases?per_page=${PER_PAGE}&page=${page}`;\n const res = await doFetch(url, { headers: githubHeaders() });\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 };\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(releaseToEntry);\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 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 };\n};\n",
|
|
37
|
-
"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 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\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 res = await doFetch(treeUrl, { headers: githubHeaders(treeUrl) });\n if (!res.ok) {\n throw new Error(`${treeUrl} -> ${res.status}`);\n }\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 = (await res.json()) 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 res = await doFetch(item.fetchUrl, {\n headers: githubHeaders(item.fetchUrl),\n });\n if (!res.ok) {\n throw new Error(`${item.fetchUrl} -> ${res.status}`);\n }\n const text = await res.text();\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 settled = await Promise.all(\n refs.map(async (ref) => {\n try {\n return await fetchEntry(ref);\n } catch (error) {\n skipped.push({\n code: \"BLUME_SOURCE_FETCH_FAILED\",\n // SAFETY: fetch and decode failures throw Error instances;\n // only the message is read for the skip diagnostic.\n message: `Source \"${options.name}\" skipped \"${ref.ref}\" (${(error as Error).message}); 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 if (refs.length > 0 && entries.length === 0) {\n skipped.length = 0;\n throw new Error(`all ${refs.length} remote file(s) failed to fetch`);\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 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 };\n};\n",
|
|
38
|
-
"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 type { Diagnostic } from \"../types.ts\";\nimport { materializeAssets } from \"./assets.ts\";\nimport {\n hashText,\n loadWithCache,\n pollingWatch,\n snapshotCache,\n} from \"./cache.ts\";\nimport { 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\nconst richToMarkdown = (rich: NotionRichText[] = []): string =>\n rich\n .map((node) => {\n let text = node.plain_text;\n if (node.annotations?.code) {\n text = `\\`${text}\\``;\n }\n if (node.annotations?.bold) {\n text = `**${text}**`;\n }\n if (node.annotations?.italic) {\n text = `*${text}*`;\n }\n if (node.annotations?.strikethrough) {\n text = `~~${text}~~`;\n }\n return node.href ? `[${text}](${node.href})` : text;\n })\n .join(\"\");\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` maps lower-cased HTTP\n // header names to their values; a missing header yields `NaN` and falls\n // back to exponential backoff.\n const retryAfter = Number(\n (error as { headers?: Record<string, string> }).headers?.[\"retry-after\"]\n );\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\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.\n const media = isYouTubeUrl(url)\n ? `<YouTube${title} url=${JSON.stringify(url)} />`\n : `<video controls src=${JSON.stringify(url)} />`;\n return caption\n ? `<Frame caption=${jsxString(caption)}>\\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 = 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 return `\\`\\`\\`${data.language ?? \"\"}\\n${text}\\n\\`\\`\\``;\n }\n case \"image\": {\n const url = data.external?.url ?? data.file?.url;\n return 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 // 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 = async (): Promise<NotionClientLike> => {\n if (options.client) {\n return options.client;\n }\n let Client: new (config: { auth?: string }) => NotionClientLike;\n try {\n // The module is widened first because the SDK's response unions are\n // broader than the NotionClientLike slice, so a direct assertion is\n // rejected as non-overlapping.\n const sdk: unknown = await import(\"@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 = [richToMarkdown(blockField(block)), await children(block)]\n .filter(Boolean)\n .join(\"\\n\\n\");\n return `<Callout>\\n${body}\\n</Callout>`;\n }\n if (block.type === \"toggle\") {\n const title = jsxString(richToMarkdown(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 const title = richToMarkdown(titleProperty(page)?.title);\n if (title) {\n data.title = title;\n }\n const description = richToMarkdown(\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 = richToMarkdown(\n page.properties[props.slug ?? \"Slug\"]?.rich_text\n );\n // Path-aware: a `guides/setup` slug keeps its `/` (per-segment slugging)\n // instead of mashing into `guidessetup`.\n const slug = slugifyPath(slugProp || title) || 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 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 = await 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 };\n};\n",
|
|
39
|
-
"/**\n * Helpers for the `<YouTube>` content component. Kept in a sibling `.ts` (like\n * `diff.ts`/`github-info.ts`) so the id parsing and embed-URL building are pure,\n * unit-testable functions — the `.astro` file stays a thin presentational shell.\n */\n\n// A YouTube video id is 11 characters of [A-Za-z0-9_-].\nconst BARE_ID = /^[\\w-]{11}$/u;\n\n// Pull the id out of any common YouTube URL: youtu.be/<id>, watch?v=<id>,\n// /embed/<id>, /shorts/<id>, /live/<id>.\nconst URL_ID =\n /(?:youtu\\.be\\/|\\/embed\\/|\\/shorts\\/|\\/live\\/|[?&]v=)(?<id>[\\w-]{11})/u;\n\n/**\n * Resolve a YouTube video id from either a bare id or a full URL. Returns `null`\n * when nothing that looks like an id can be found, so the component can render\n * nothing rather than a broken embed.\n */\nexport const parseYouTubeId = (input: string): string | null => {\n const value = input.trim();\n if (!value) {\n return null;\n }\n if (BARE_ID.test(value)) {\n return value;\n }\n return URL_ID.exec(value)?.groups?.id ?? null;\n};\n\n/**\n * Build a privacy-enhanced (`youtube-nocookie.com`) embed URL, optionally\n * starting at `start` seconds.\n */\nexport const youtubeEmbedUrl = (\n id: string,\n options: { start?: number } = {}\n): string => {\n const base = `https://www.youtube-nocookie.com/embed/${id}`;\n const { start } = options;\n if (start && start > 0) {\n const params = new URLSearchParams({ start: String(Math.floor(start)) });\n return `${base}?${params.toString()}`;\n }\n return base;\n};\n",
|
|
40
|
-
"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\nconst MD_IMAGE = /!\\[(?<alt>[^\\]]*)\\]\\((?<url>[^)\\s]+)\\)/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 SAFE_EXT = /^\\.[a-z0-9]+$/iu;\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 for a URL whose path carries none, keyed by the media type the\n// server reports. A static host serves by extension, so a `.png` holding JPEG\n// bytes is mislabeled and a `.png` holding a video is refused by strict\n// players — the response is the source of truth, not the reference kind.\nconst EXT_BY_MIME = new Map([\n [\"image/avif\", \".avif\"],\n [\"image/gif\", \".gif\"],\n [\"image/jpeg\", \".jpg\"],\n [\"image/png\", \".png\"],\n [\"image/svg+xml\", \".svg\"],\n [\"image/webp\", \".webp\"],\n [\"video/mp4\", \".mp4\"],\n [\"video/ogg\", \".ogv\"],\n [\"video/quicktime\", \".mov\"],\n [\"video/webm\", \".webm\"],\n]);\nconst UNKNOWN_EXT = \".bin\";\n// Response types that are never a media file. A pasted Vimeo/Loom/Wistia link\n// is a video block in Notion whose URL is a watch page (200 text/html), and an\n// API endpoint answers JSON or XML; `image/svg+xml` is media and stays.\nconst NON_MEDIA_TYPE =\n /^(?:text\\/|application\\/(?:[\\w.-]+\\+)?(?:json|xml|javascript))/u;\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 /** Abort a download that hasn't completed within this many milliseconds. */\n timeoutMs?: number;\n}\n\n/** The extension in a URL's path, or null when it carries none we'd trust. */\nconst extFromUrl = (url: string): string | null => {\n const clean = url.split(\"?\")[0] ?? url;\n const ext = extname(clean);\n return SAFE_EXT.test(ext) ? ext.toLowerCase() : null;\n};\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 an extension-less asset is not fetched on every\n * poll. Only a lone candidate counts: query-less URLs can collide, and picking\n * between two files would be a guess.\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}.`) && !name.endsWith(PART_SUFFIX)\n );\n return candidates.length === 1 ? (candidates[0] ?? null) : null;\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 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 urls = new Set<string>();\n for (const pattern of [MD_IMAGE, HTML_VIDEO_SRC]) {\n for (const match of masked.matchAll(pattern)) {\n const url = match.groups?.url;\n if (url && REMOTE.test(url)) {\n urls.add(url);\n }\n }\n }\n\n /** Fetch one asset into the asset dir and return its file name. */\n const download = 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 urlExt = extFromUrl(url);\n // The name is known up front whenever the path has an extension (every\n // Notion upload does), 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 = urlExt ? null : 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, so refuse what is never media.\n const type = mediaType(res);\n if (NON_MEDIA_TYPE.test(type)) {\n throw new Error(`responded with ${type}, not a media file`);\n }\n if (!res.body) {\n throw new Error(\"empty response body\");\n }\n const file = `${stem}${urlExt ?? EXT_BY_MIME.get(type) ?? UNKNOWN_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, and two pages fetching the same asset at once (the gate bounds\n // concurrency, it doesn't dedupe) never write into each other's file —\n // both publish identical bytes and the last rename wins.\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 const rewrites = new Map<string, string>();\n await Promise.all(\n [...urls].map(async (url) => {\n try {\n const file = await limit(() => download(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(MD_IMAGE, (match, alt, url) => {\n const local = rewrites.get(url);\n return local ? `` : match;\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",
|
|
41
|
-
"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} 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 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 link inside a\n * heading is rewritten here before the other notes' anchors are known, so its\n * own anchor is left off; that only moves the manifest id of a heading that\n * itself holds a heading link, which the docs already flag as unaddressable.\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\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 text = transformBody(note.content.trim(), index, pair.self, unresolved);\n const raw = matter.stringify(`${text}\\n`, merged);\n return {\n body: { format: \"md\", text },\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 { content, data } = matter(await readFile(absPath, \"utf-8\"));\n return {\n absPath,\n content,\n data,\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 const files = await walkVault(vaultDir, vaultDir, exclude);\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: unresolvedDiagnostics(options.name, unresolved),\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 prefix: options.prefix,\n read,\n staged: true,\n validate,\n watch,\n };\n};\n",
|
|
42
|
-
"import { join } from \"pathe\";\n\nimport { BlumeError } from \"../diagnostics.ts\";\nimport matter from \"../frontmatter.ts\";\nimport {\n hashText,\n loadWithCache,\n pollingWatch,\n snapshotCache,\n} from \"./cache.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 value in a fetched document: arbitrary JSON the GROQ query returned. */\ntype SanityValue =\n | string\n | number\n | boolean\n | null\n | SanityValue[]\n | { [key: string]: SanityValue };\n\n/** A document as the GROQ query returns it. */\ninterface SanityDocument {\n [key: string]: SanityValue;\n}\n\n/** The frontmatter fields this adapter maps from a document. */\ninterface SanityFrontmatter {\n description?: string;\n title?: string;\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 */\nconst isTraversable = (\n value: SanityValue | undefined\n): value is SanityDocument => typeof value === \"object\" && value !== null;\n\n/** Resolve a dot path (`slug.current`) against a document. */\nconst getPath = (\n doc: SanityDocument,\n path: string\n): SanityValue | undefined => {\n let current: SanityValue | undefined = doc;\n for (const key of path.split(\".\")) {\n if (isTraversable(current)) {\n current = current[key];\n } else {\n return;\n }\n }\n return current;\n};\n\nconst isStringValue = (value: SanityValue | undefined): value is string =>\n typeof value === \"string\";\n\nconst asString = (value: SanityValue | undefined): string | undefined =>\n isStringValue(value) ? value : undefined;\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 = async (\n options: SanitySourceOptions,\n preview: boolean\n): Promise<SanityClientLike> => {\n if (options.client) {\n return options.client;\n }\n let createClient: (config: SanityClientConfig) => SanityClientLike;\n try {\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 } = (await import(\"@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 const raw = matter.stringify(markdown, data);\n return {\n body: { format: \"md\", 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}.md`,\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 = await 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 };\n};\n",
|
|
43
|
-
"/**\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 */\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 /** Custom block-type serializers, keyed by `_type`; return Markdown/MDX. */\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// Markdown/raw-HTML structure characters. Portable Text spans are *plain\n// text* — formatting arrives as marks, never as syntax in the text — so a\n// literal `*`, `_`, `[`, backtick, `~`, or `<` typed in the CMS must render\n// as itself. Unescaped, it opened emphasis or a code span mid-paragraph, and\n// `<` let CMS prose inject raw HTML into the rendered page. CommonMark\n// backslash-escapes every ASCII punctuation character, so `\\*` is always the\n// literal asterisk.\nconst MARKDOWN_SPECIALS = /[\\\\`*_[\\]~<]/gu;\n\nconst escapeText = (text: string): string =>\n text.replaceAll(MARKDOWN_SPECIALS, String.raw`\\$&`);\n\n/** Wrap a span's text in Markdown for its marks (decorators + link defs). */\nconst renderSpan = (\n span: PortableTextSpan,\n defs: Map<string, PortableTextMarkDef>\n): string => {\n // Code spans stay verbatim: their text is literal inside the backticks,\n // and backslash escapes would render as backslashes.\n const isCode = span.marks?.includes(\"code\") ?? false;\n let text = isCode ? (span.text ?? \"\") : escapeText(span.text ?? \"\");\n if (!span.marks || span.marks.length === 0) {\n return text;\n }\n // Decorators wrap inline; a link def wraps last so its label keeps emphasis.\n let link: PortableTextMarkDef | undefined;\n for (const mark of span.marks) {\n switch (mark) {\n case \"strong\": {\n text = `**${text}**`;\n break;\n }\n case \"em\": {\n text = `*${text}*`;\n break;\n }\n case \"code\": {\n text = `\\`${text}\\``;\n break;\n }\n case \"strike-through\": {\n text = `~~${text}~~`;\n break;\n }\n default: {\n const def = defs.get(mark);\n if (def?._type === \"link\") {\n link = def;\n }\n }\n }\n }\n return link?.href ? `[${text}](${link.href})` : text;\n};\n\n/** Render the inline children of a block to a single Markdown string. */\nconst renderChildren = (block: PortableTextBlock): string => {\n const defs = new Map(\n (block.markDefs ?? []).map((def) => [def._key, def] as const)\n );\n return (block.children ?? []).map((span) => renderSpan(span, defs)).join(\"\");\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\nconst renderBlock = (\n block: PortableTextBlock,\n options: PortableTextOptions\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 ? `` : \"\";\n }\n if (block._type !== \"block\") {\n return `<!-- unsupported Portable Text block: ${block._type} -->`;\n }\n\n const inline = renderChildren(block);\n if (block.listItem) {\n const indent = \" \".repeat(Math.max(0, (block.level ?? 1) - 1));\n const marker = block.listItem === \"number\" ? \"1.\" : \"-\";\n return `${indent}${marker} ${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 lines = blocks.map((block) => renderBlock(block, options));\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"
|
|
44
|
-
],
|
|
45
|
-
"mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;AAeA,IAAM,gBAAgB,IAAI,IACxB,CAAC,GAAG,OAAO,KAAK,QAAQ,GAAG,IAAI,EAAE,IAAI,CAAC,SAAS,KAAK,YAAY,CAAC,CACnE;AAGO,IAAM,cAAc,CACzB,WAEA,OAAO,SAAS;AAGX,IAAM,cAAc,CAAC,SAC1B,KAAK,QAAQ,IAAI,CAAC,WAAW,OAAO,IAAI;AAcnC,IAAM,wBAAwB,CACnC,SACkB;AAAA,EAClB,IAAI,KAAK,mBAAmB,MAAM;AAAA,IAChC,OAAO;AAAA,EACT;AAAA,EACA,OAAO,KAAK,kBAAkB,KAAK;AAAA;AAe9B,IAAM,eAAe,CAAC,MAAc,SACzC,SAAS,KAAK,iBAAiB,KAAK,0BAA0B,KAAK,IAAI;AAMlE,IAAM,gBAAgB,CAC3B,cACA,MACA,SACW;AAAA,EACX,MAAM,SAAS,aAAa,MAAM,IAAI;AAAA,EACtC,IAAI,CAAC,QAAQ;AAAA,IACX,OAAO;AAAA,EACT;AAAA,EACA,OAAO,iBAAiB,MAAM,SAAS,GAAG,SAAS;AAAA;AAS9C,IAAM,eAAe,CAAC,OAAiB,SAA6B;AAAA,EAIzE,MAAM,QAAQ,MAAM,IAAI,YAAY;AAAA,EACpC,MAAM,UAAU,KAAK,QAAQ,KAC3B,CAAC,WACC,OAAO,SAAS,KAAK,iBAAiB,OAAO,KAAK,YAAY,MAAM,KACxE;AAAA,EACA,IAAI,SAAS;AAAA,IACX,OAAO,EAAE,QAAQ,QAAQ,MAAM,MAAM,MAAM,MAAM,CAAC,EAAE;AAAA,EACtD;AAAA,EACA,OAAO,EAAE,QAAQ,KAAK,eAAe,MAAM,MAAM;AAAA;AAa5C,IAAM,kBAAkB,CAC7B,KACA,KACA,SACG;AAAA,EACH,MAAM,OAAO,IAAI,MAAM,GAAG,IAAI,SAAS,IAAI,MAAM;AAAA,EAOjD,IAAI,KAAK,SAAS,IAAI,GAAG;AAAA,IACvB,MAAM,SAAS,GAAG,KAAK,MAAM,GAAG,EAAE,IAAI;AAAA,IACtC,OAAO;AAAA,MACL,SAAS,KAAK,QAAQ,IAAI,CAAC,WAAW,OAAO,IAAI;AAAA,MACjD,SACE,KAAK,WAAW,QACZ,aAAa,OAAO,MAAM,GAAG,GAAG,IAAI,EAAE,KAAK,KAAK,GAAG,IACnD;AAAA,IACR;AAAA,EACF;AAAA,EAEA,IAAI,KAAK,WAAW,OAAO;AAAA,IACzB,MAAM,UAAU,KAAK,YAAY,GAAG;AAAA,IAKpC,IAAI,UAAU,KAAK,YAAY,GAAG,GAAG;AAAA,MAGnC,MAAM,SAAS,KAAK,MAAM,UAAU,CAAC,EAAE,YAAY;AAAA,MACnD,MAAM,UAAU,KAAK,QAAQ,KAC3B,CAAC,WAAW,OAAO,KAAK,YAAY,MAAM,MAC5C;AAAA,MACA,IAAI,SAAS;AAAA,QACX,OAAO;AAAA,UACL,SAAS,CAAC,QAAQ,IAAI;AAAA,UACtB,SAAS,GAAG,KAAK,MAAM,GAAG,OAAO,IAAI;AAAA,QACvC;AAAA,MACF;AAAA,IACF;AAAA,IACA,OAAO,EAAE,SAAS,CAAC,KAAK,aAAa,GAAG,SAAS,IAAI;AAAA,EACvD;AAAA,EAEA,QAAQ,QAAQ,SAAS,aAAa,IAAI,MAAM,GAAG,GAAG,IAAI;AAAA,EAC1D,OAAO,EAAE,SAAS,CAAC,MAAM,GAAG,SAAS,KAAK,KAAK,GAAG,EAAE;AAAA;AAc/C,IAAM,mBAAmB,CAC9B,KACA,KACA,QACA,SACW;AAAA,EACX,IAAI,KAAK,WAAW,OAAO;AAAA,IACzB,IAAI,OAAO,IAAI,MAAM,GAAG,IAAI,SAAS,IAAI,MAAM;AAAA,IAC/C,MAAM,UAAU,KAAK,YAAY,GAAG;AAAA,IACpC,IAAI,UAAU,KAAK,YAAY,GAAG,GAAG;AAAA,MACnC,MAAM,SAAS,KAAK,MAAM,UAAU,CAAC,EAAE,YAAY;AAAA,MACnD,IAAI,KAAK,QAAQ,KAAK,CAAC,UAAU,MAAM,KAAK,YAAY,MAAM,MAAM,GAAG;AAAA,QACrE,OAAO,KAAK,MAAM,GAAG,OAAO;AAAA,MAC9B;AAAA,IACF;AAAA,IACA,OAAO,GAAG,QAAQ,SAAS;AAAA,EAC7B;AAAA,EACA,OAAO,GAAG,UAAU;AAAA;AASf,IAAM,kBAAkB,CAC7B,OACA,MACA,aACiB;AAAA,EACjB,MAAM,aAAa,IAAI,IACrB,KAAK,QAAQ,IAAI,CAAC,WAAW,OAAO,KAAK,YAAY,CAAC,CACxD;AAAA,EACA,MAAM,cAAc,IAAI,IAAI,UAAU,SAAS,IAAI,CAAC,YAAY,QAAQ,EAAE,CAAC;AAAA,EAC3E,MAAM,OAAO,IAAI;AAAA,EACjB,MAAM,cAA4B,CAAC;AAAA,EACnC,WAAW,QAAQ,OAAO;AAAA,IAKxB,MAAM,QAAQ,KAAK,OAAO,IAAI,MAAM,GAAG;AAAA,IACvC,MAAM,SACJ,MAAM,MAAM,YAAY,IAAI,MAAM,EAAE,IAAI,MAAM,KAAK,MAAM,KACxD,YAAY;AAAA,IACf,IACE,SACA,CAAC,KAAK,IAAI,KAAK,KACf,cAAc,IAAI,KAAK,KACvB,CAAC,WAAW,IAAI,KAAK,GACrB;AAAA,MACA,KAAK,IAAI,KAAK;AAAA,MACd,YAAY,KAAK;AAAA,QACf,MAAM;AAAA,QACN,SAAS,WAAW,qCAAqC,6DAA6D,KAAK,8BAA8B;AAAA,QACzJ,UAAU;AAAA,QACV,YAAY,gBAAgB;AAAA,MAC9B,CAAC;AAAA,IACH;AAAA,EACF;AAAA,EACA,OAAO;AAAA;;;ACvNT,IAAM,sBAAsB,CAAC,YAAoB,WAC/C,GAAG,iBAAmB;AAQxB,IAAM,uBAAuB,CAC3B,OACA,YACA,WACuB;AAAA,EACvB,MAAM,MAAM,oBAAoB,YAAY,MAAM;AAAA,EAClD,MAAM,WAAW,MAAM,IAAI,GAAG;AAAA,EAC9B,IAAI,UAAU;AAAA,IACZ,OAAO;AAAA,EACT;AAAA,EACA,MAAM,OAA2B,CAAC;AAAA,EAClC,MAAM,IAAI,KAAK,IAAI;AAAA,EACnB,OAAO;AAAA;AAIF,IAAM,mBAAmB;AAQzB,IAAM,mBAAmB,CAC9B,MACA,WAEA,CAAC,KAAK,KAAK,OAAO,YACjB,CAAC,KAAK,KAAK,QAAQ,UAAU,OAAO,OAAO,SAAS;AAavD,IAAM,mBAAmB,CAAC,SACxB,KAAK,KAAK,IAAI,eAAe,KAAK;AAEpC,IAAM,sBAAsB,CAC1B,OACA,MACA,iBACA,UACA,2BACyB;AAAA,EACzB,MAAM,WAAW,sBAAsB,IAAI;AAAA,EAC3C,IAAI,CAAC,UAAU;AAAA,IACb,OAAO,CAAC;AAAA,EACV;AAAA,EACA,MAAM,gBAAgB,IAAI,IACxB,MAAM,MAAM,QAAQ,CAAC,SACnB,KAAK,WAAW,WAAW,CAAC,CAAC,KAAK,gBAAgB,IAAI,CAAU,IAAI,CAAC,CACvE,CACF;AAAA,EACA,MAAM,SAA+B,CAAC;AAAA,EACtC,aAAa,UAAU,KAAK,SAAS;AAAA,IACnC,IAAI,SAAS,UAAU;AAAA,MACrB;AAAA,IACF;AAAA,IACA,MAAM,UAAU,IAAI,IAClB,MAAM,MAAM,QAAQ,CAAC,SACnB,KAAK,WAAW,OAAO,CAAC,KAAK,cAAc,IAAI,CAAC,CAClD,CACF;AAAA,IACA,YAAY,KAAK,WAAW,eAAe;AAAA,MACzC,IAAI,QAAQ,IAAI,GAAG,GAAG;AAAA,QACpB;AAAA,MACF;AAAA,MACA,MAAM,OAAO,aAAa,UAAU,cAAc,KAAK,MAAM,IAAI,CAAC;AAAA,MAKlE,MAAM,oBAAwC,yBAC1C,qBAAqB,wBAAwB,OAAO,YAAY,IAAI,IACpE,CAAC;AAAA,MACL,IAAI,wBAAwB;AAAA,QAC1B,kBAAkB,KAAK,EAAE,MAAM,SAAS,OAAO,QAAQ,CAAC;AAAA,MAC1D;AAAA,MACA,OAAO,KAAK;AAAA,QACV,YAAY,gBAAgB,IAAI,GAAG,KAAK,CAAC;AAAA,QACzC,YAAY,OAAO,cAAc;AAAA,QACjC,aAAa,OAAO;AAAA,QACpB,aAAa,iBAAiB,MAAM;AAAA,QACpC,OAAO,OAAO,KAAK;AAAA,QACnB,SAAS,OAAO;AAAA,QAChB,SAAS,OAAO,WAAW,OAAO,OAAO;AAAA,QACzC,UAAU;AAAA,QACV,QAAQ,OAAO,KAAK,QAAQ;AAAA,QAC5B,IAAI,OAAO;AAAA,QACX,WAAW;AAAA,QACX,cAAc,OAAO;AAAA,QACrB,QAAQ;AAAA,QACR;AAAA,QACA,QAAQ,OAAO;AAAA,QACf,YAAY,OAAO;AAAA,QACnB,OAAO,OAAO;AAAA,QACd,SAAS,OAAO;AAAA,QAChB;AAAA,MACF,CAAC;AAAA,IACH;AAAA,EACF;AAAA,EACA,OAAO;AAAA;AAIF,IAAM,gBAAgB,CAAC,YAIT;AAAA,EACnB,QAAQ,SAAS,QAAQ,UAAU;AAAA,EACnC,MAAM,gBAAgB,OAAO,OAAO,aAAa;AAAA,EACjD,QAAQ,SAAS;AAAA,EAIjB,MAAM,kBAAkB,IAAI;AAAA,EAC5B,IAAI,MAAM;AAAA,IACR,WAAW,QAAQ,MAAM,OAAO;AAAA,MAC9B,MAAM,OAAO,gBAAgB,IAAI,KAAK,cAAc,KAAK,CAAC;AAAA,MAC1D,KAAK,KAAK,EAAE,QAAQ,KAAK,QAAQ,MAAM,KAAK,MAAM,CAAC;AAAA,MACnD,gBAAgB,IAAI,KAAK,gBAAgB,IAAI;AAAA,IAC/C;AAAA,EACF;AAAA,EAMA,MAAM,yBAAyB,OAAO,WAClC,IAAI,MACJ;AAAA,EACJ,IAAI,wBAAwB;AAAA,IAC1B,WAAW,QAAQ,MAAM,OAAO;AAAA,MAC9B,qBACE,wBACA,KAAK,YACL,KAAK,MACP,EAAE,KAAK,EAAE,MAAM,KAAK,OAAO,SAAS,KAAK,QAAQ,CAAC;AAAA,IACpD;AAAA,EACF;AAAA,EAEA,MAAM,SAA+B,MAAM,MAAM,IAAI,CAAC,UAAU;AAAA,IAC9D,YAAY,gBAAgB,IAAI,KAAK,cAAc,KAAK,CAAC;AAAA,IACzD,YAAY,KAAK,cAAc;AAAA,IAC/B,aAAa,KAAK;AAAA,IAClB,aAAa,iBAAiB,IAAI;AAAA,IAClC,OAAO,KAAK,KAAK;AAAA,IACjB,SAAS,KAAK;AAAA,IACd,SAAS,KAAK,WAAW,KAAK,OAAO;AAAA,IACrC,QAAQ,KAAK,KAAK,QAAQ;AAAA,IAC1B,IAAI,KAAK;AAAA,IACT,WAAW,iBAAiB,iBAAiB,MAAM,MAAM;AAAA,IACzD,cAAc,KAAK;AAAA,IACnB,QAAQ,KAAK;AAAA,IACb,MAAM,KAAK;AAAA,IACX,QAAQ,KAAK;AAAA,IACb,YAAY,KAAK;AAAA,IACjB,OAAO,KAAK;AAAA,IACZ,SAAS,KAAK;AAAA,IACd,mBAAmB,yBACf,qBACE,wBACA,KAAK,YACL,KAAK,MACP,IACA,CAAC;AAAA,EACP,EAAE;AAAA,EAEF,IAAI,MAAM;AAAA,IACR,OAAO,KACL,GAAG,oBACD,OACA,MACA,iBACA,OAAO,UACP,sBACF,CACF;AAAA,EACF;AAAA,EAKA,IAAI,0BAA0B,OAAO,UAAU;AAAA,IAC7C,MAAM,OAAO,IAAI,IACf,OAAO,SAAS,SAAS,IAAI,CAAC,SAAS,UAAU,CAAC,QAAQ,IAAI,QAAQ,CAAC,CAAC,CAC1E;AAAA,IACA,WAAW,QAAQ,uBAAuB,OAAO,GAAG;AAAA,MAClD,KAAK,KACH,CAAC,GAAG,OAAO,KAAK,IAAI,EAAE,OAAO,KAAK,MAAM,KAAK,IAAI,EAAE,OAAO,KAAK,EACjE;AAAA,IACF;AAAA,EACF;AAAA,EAEA,OAAO,KAAK,CAAC,GAAG,MAAM,EAAE,KAAK,cAAc,EAAE,IAAI,CAAC;AAAA,EAElD,OAAO;AAAA,IACL,cAAc,gBAAgB;AAAA,IAC9B,aAAa,QAAQ;AAAA,IACrB,QAAQ,OAAO,WAAW;AAAA,IAC1B,aAAa,QAAQ;AAAA,IACrB;AAAA,IACA,SAAS;AAAA,EACX;AAAA;;;AChPF,uBAAS,yBAAY;;;ACMrB,IAAM,aACJ;AAEF,IAAM,aAAa;AAGZ,IAAM,cAAc,CAAC,UAA2B,WAAW,KAAK,KAAK;AAGrE,IAAM,cAAc,CAAC,UAA2B,WAAW,KAAK,KAAK;AAGrE,IAAM,cAAc,CAAC,UAC1B,YAAY,KAAK,KAAK,YAAY,KAAK;;;ACVzC;AAGA;AAMA,IAAM,cAAc,cAAc,YAAY,GAAG;AAGjD,IAAM,UAAU,CAAC,QAA6B,YAAY,GAAG;AAE7D,IAAM,OAAO;AAAA,EACX,QAAQ,QAAQ,iCAAiC;AACnD;AAGA,IAAM,cAAc;AAGpB,IAAM,cAAc;AAAA,EAClB,QAAQ;AACV;AAQA,IAAM,WAAW,CAAI,KAAwB,QAC3C,OAAO,OAAO,KAAK,GAAG,IAAI,IAAI,OAAO;AAWvC,IAAM,YAAY,CAAC,SACjB,KACG,KAAK,EACL,YAAY,EACZ,WAAW,YAAY,GAAG;AAE/B,IAAM,UAAU,CAAC,SAAiB,aAA0C;AAAA,EAC1E,MAAM,MAAM,SAAS,MAAM,OAAO;AAAA,EAClC,MAAM,OAAO,OAAO,YAAY,KAAK,QAAQ;AAAA,EAC7C,IAAI,CAAC,MAAM;AAAA,IACT,OAAO;AAAA,EACT;AAAA,EACA,QAAQ,YAAY,SAAS,UAAU,MAAM,EAAE,QAAQ,OAAO,CAAC;AAAA,EAC/D,OAAO,EAAE,MAAM,MAAM,UAAU,SAAS,WAAW,QAAQ;AAAA;AAOtD,IAAM,cAAc,CAAC,SAAsC;AAAA,EAChE,MAAM,aAAa,UAAU,IAAI;AAAA,EACjC,MAAM,QAAQ,WAAW,QAAQ,GAAG;AAAA,EACpC,IAAI,QAAQ,GAAG;AAAA,IACb,MAAM,UAAU,SAAS,aAAa,WAAW,MAAM,GAAG,KAAK,CAAC;AAAA,IAChE,OAAO,UAAU,QAAQ,SAAS,WAAW,MAAM,QAAQ,CAAC,CAAC,IAAI;AAAA,EACnE;AAAA,EACA,OAAO,QAAQ,aAAa,UAAU;AAAA;AASjC,IAAM,UAAU,CAAC,SAA0B,YAAY,IAAI,MAAM;;;AC7ExE,IAAM,mBACJ;AAGF,IAAM,eAAe,CAAC,UACpB,MAAM,QAAQ,CAAC,SACb,KAAK,SAAS,UAAU,CAAC,MAAM,GAAG,aAAa,KAAK,QAAQ,CAAC,IAAI,CAAC,IAAI,CACxE;AAGF,IAAM,eAAe,CACnB,eACsC;AAAA,EACtC,MAAM,QAA2C,CAAC;AAAA,EAClD,MAAM,OAAO,CAAC,MAA0B,UAAwB;AAAA,IAC9D,IAAI,MAAM;AAAA,MACR,MAAM,KAAK,EAAE,MAAM,MAAM,CAAC;AAAA,IAC5B;AAAA;AAAA,EAEF,WAAW,OAAO,WAAW,MAAM;AAAA,IACjC,KAAK,IAAI,MAAM,QAAQ,IAAI,QAAQ;AAAA,IACnC,WAAW,QAAQ,IAAI,SAAS,CAAC,GAAG;AAAA,MAClC,KAAK,KAAK,MAAM,aAAa,KAAK,QAAQ;AAAA,IAC5C;AAAA,EACF;AAAA,EACA,WAAW,YAAY,WAAW,WAAW;AAAA,IAC3C,WAAW,QAAQ,SAAS,OAAO;AAAA,MACjC,KAAK,KAAK,MAAM,aAAa,KAAK,QAAQ;AAAA,IAC5C;AAAA,EACF;AAAA,EACA,WAAW,QAAQ,WAAW,UAAU;AAAA,IACtC,KAAK,KAAK,MAAM,kBAAkB,KAAK,QAAQ;AAAA,EACjD;AAAA,EACA,MAAM,WAAW,CAAC,WAAW,OAAO;AAAA,EACpC,WAAW,WAAW,UAAU;AAAA,IAC9B,WAAW,QAAQ,aAAa,OAAO,GAAG;AAAA,MACxC,KAAK,KAAK,MAAM,IAAI,KAAK,QAAQ;AAAA,IACnC;AAAA,EACF;AAAA,EACA,OAAO;AAAA;AAIT,IAAM,yBAAyB,CAC7B,OACA,eACiB;AAAA,EACjB,MAAM,OAAO,IAAI;AAAA,EACjB,MAAM,cAA4B,CAAC;AAAA,EACnC,aAAa,MAAM,WAAW,OAAO;AAAA,IACnC,IAAI,YAAY,IAAI,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,GAAG;AAAA,MACxD;AAAA,IACF;AAAA,IACA,KAAK,IAAI,IAAI;AAAA,IAGb,MAAM,UAAU,KAAK,UAAU,EAAE,WAAW,GAAG,IAC3C,gBAAgB,UAAU,sEAC1B,iBAAiB,UAAU;AAAA,IAC/B,YAAY,KAAK;AAAA,MACf,MAAM;AAAA,MACN;AAAA,MACA,UAAU;AAAA,MACV;AAAA,IACF,CAAC;AAAA,EACH;AAAA,EACA,OAAO;AAAA;AAIF,IAAM,mBAAmB,CAAC,eAC/B,uBAAuB,aAAa,UAAU,GAAG,gBAAgB;AAM5D,IAAM,6BAA6B,CACxC,YACiB;AAAA,EACjB,MAAM,QAAQ,QAAQ,QAAQ,CAAC,SAC7B,KAAK,OACD,CAAC,EAAE,MAAM,KAAK,MAAM,OAAO,iBAAiB,KAAK,SAAS,CAAC,IAC3D,CAAC,CACP;AAAA,EACA,OAAO,uBAAuB,OAAO,gBAAgB;AAAA;AAIvD,IAAM,kBAAkB,CAAC,QAAqB,SAC5C,OAAO,IAAI,IAAI,KAAK,CAAC,GAAG,MAAM,EAAE,KAAK,CAAC,UAAU,MAAM,WAAW,GAAG,OAAO,CAAC;AAQvE,IAAM,qBAAqB,CAChC,YACA,WACiB;AAAA,EACjB,MAAM,UAA6C;AAAA,IACjD,GAAG,WAAW,KAAK,IAAI,CAAC,SAAS,EAAE,OAAO,IAAI,OAAO,MAAM,IAAI,KAAK,EAAE;AAAA,IACtE,GAAG,WAAW,UAAU,QAAQ,CAAC,aAC/B,SAAS,MAAM,IAAI,CAAC,UAAU,EAAE,OAAO,KAAK,OAAO,MAAM,KAAK,KAAK,EAAE,CACvE;AAAA,IACA,GAAG,WAAW,SAAS,IAAI,CAAC,UAAU;AAAA,MACpC,OAAO,KAAK;AAAA,MACZ,MAAM,KAAK;AAAA,IACb,EAAE;AAAA,IACF,IAAI,WAAW,WAAW,CAAC,GAAG,IAAI,CAAC,YAAY;AAAA,MAC7C,OAAO,OAAO;AAAA,MACd,MAAM,OAAO;AAAA,IACf,EAAE;AAAA,IACF,GAAI,WAAW,MACX,CAAC,EAAE,OAAO,WAAW,IAAI,OAAO,MAAM,WAAW,IAAI,KAAK,CAAC,IAC3D,CAAC;AAAA,EACP;AAAA,EACA,MAAM,cAA4B,CAAC;AAAA,EACnC,MAAM,OAAO,IAAI;AAAA,EACjB,aAAa,OAAO,UAAU,SAAS;AAAA,IAGrC,IAAI,CAAC,eAAe,IAAI,KAAK,KAAK,WAAW,IAAI,KAAK,KAAK,IAAI,IAAI,GAAG;AAAA,MACpE;AAAA,IACF;AAAA,IACA,IAAI,CAAC,gBAAgB,QAAQ,KAAK,MAAM,GAAG,EAAE,MAAM,IAAI,GAAG;AAAA,MACxD,KAAK,IAAI,IAAI;AAAA,MACb,YAAY,KAAK;AAAA,QACf,MAAM;AAAA,QACN,SAAS,qBAAqB,oBAAoB;AAAA,QAClD,UAAU;AAAA,QACV,YAAY;AAAA,MACd,CAAC;AAAA,IACH;AAAA,EACF;AAAA,EACA,OAAO;AAAA;AAIT,IAAM,4BAA4B,CAAC,eAAyC;AAAA,EAC1E,MAAM,cAA4B,CAAC;AAAA,EACnC,MAAM,aAAa,CAAC,OAAkB,UAAwB;AAAA,IAC5D,MAAM,SAAS,IAAI;AAAA,IACnB,WAAW,QAAQ,OAAO;AAAA,MACxB,OAAO,IAAI,KAAK,QAAQ,OAAO,IAAI,KAAK,KAAK,KAAK,KAAK,CAAC;AAAA,IAC1D;AAAA,IACA,YAAY,OAAO,UAAU,QAAQ;AAAA,MACnC,IAAI,QAAQ,GAAG;AAAA,QACb,YAAY,KAAK;AAAA,UACf,MAAM;AAAA,UACN,SAAS,4BAA4B,kBAAkB,eAAe;AAAA,UACtE,UAAU;AAAA,UACV,YAAY;AAAA,QACd,CAAC;AAAA,MACH;AAAA,IACF;AAAA,IACA,WAAW,QAAQ,OAAO;AAAA,MACxB,IAAI,KAAK,SAAS,SAAS;AAAA,QACzB,WAAW,KAAK,UAAU,UAAU,KAAK,QAAQ;AAAA,MACnD;AAAA,IACF;AAAA;AAAA,EAEF,MAAM,WAAkD;AAAA,IACtD,EAAE,OAAO,WAAW,SAAS,OAAO,mBAAmB;AAAA,EACzD;AAAA,EACA,aAAa,OAAO,WAAW,UAAU;AAAA,IACvC,WAAW,OAAO,KAAK;AAAA,EACzB;AAAA,EACA,OAAO;AAAA;AAIT,IAAM,6BAA6B,CACjC,YACA,UACiB;AAAA,EACjB,MAAM,SAAS,IAAI,IACjB,MAAM,QAAQ,CAAC,SAAU,KAAK,KAAK,QAAQ,SAAS,CAAC,KAAK,EAAE,IAAI,CAAC,CAAE,CACrE;AAAA,EACA,IAAI,OAAO,SAAS,GAAG;AAAA,IACrB,OAAO,CAAC;AAAA,EACV;AAAA,EACA,MAAM,WAAW,CAAC,WAAW,OAAO;AAAA,EACpC,MAAM,cAA4B,CAAC;AAAA,EACnC,MAAM,OAAO,IAAI;AAAA,EACjB,WAAW,WAAW,UAAU;AAAA,IAC9B,WAAW,QAAQ,aAAa,OAAO,GAAG;AAAA,MACxC,IACE,KAAK,SAAS,UACd,OAAO,IAAI,KAAK,MAAM,KACtB,CAAC,KAAK,IAAI,KAAK,MAAM,GACrB;AAAA,QACA,KAAK,IAAI,KAAK,MAAM;AAAA,QACpB,YAAY,KAAK;AAAA,UACf,MAAM;AAAA,UACN,SAAS,SAAS,KAAK;AAAA,UACvB,UAAU;AAAA,UACV,YACE;AAAA,QACJ,CAAC;AAAA,MACH;AAAA,IACF;AAAA,EACF;AAAA,EACA,OAAO;AAAA;AAUF,IAAM,uBAAuB,CAClC,YACA,UACiB;AAAA,EACjB,GAAG,0BAA0B,UAAU;AAAA,EACvC,GAAG,2BAA2B,YAAY,KAAK;AACjD;;;ACzOA;AAmBA,IAAM,iBAAiB;AACvB,IAAM,eAAe;AACrB,IAAM,aAAa;AAOZ,IAAM,cAAc,CAAC,OAAe,SACzC,SAAS,OAAO,UAAU,QAAQ,MAAM,WAAW,GAAG,OAAO;AAaxD,IAAM,YAAY,CAAC,KAAa,SACrC,YAAY,MAAM,IAAI,IAAI;AAE5B,IAAM,WAAW,CAAC,YAChB,QACG,QAAQ,gBAAgB,EAAE,EAC1B,MAAM,UAAU,EAChB,OAAO,OAAO,EACd,IAAI,CAAC,SAAS,KAAK,OAAO,CAAC,EAAE,YAAY,IAAI,KAAK,MAAM,CAAC,CAAC,EAC1D,KAAK,GAAG;AAEb,IAAM,eAAe,CAAC,YAA4B;AAAA,EAChD,MAAM,QAAQ,QAAQ,MAAM,cAAc,GAAG,QAAQ;AAAA,EACrD,OAAO,QAAQ,KAAK,MAAM,OAAO,KAAK,CAAC,IAAI,OAAO;AAAA;AAIpD,IAAM,aAAa,CAAC,QAAwB;AAAA,EAC1C,MAAM,QAAQ,IAAI,MAAM,YAAY,GAAG,QAAQ;AAAA,EAC/C,QAAQ,SAAS,KAAK,QAAQ,gBAAgB,EAAE;AAAA;AAQlD,IAAM,cAAc,CAAC,SACnB,KAAK,QAAQ,gBAAgB,EAAE,MAAM;AAGvC,IAAM,SAAS,CAAC,aACd,SAAS,QAAQ,QAAQ,QAAQ,GAAG,EAAE;AAQxC,IAAM,UAAU,CAAC,YACf,OAAO,QAAQ,MAAM,GAAG,EAAE,GAAG,EAAE,KAAK,OAAO;AA4C7C,IAAM,cAAc,CAClB,KACA,MACA,OACA,WACkB;AAAA,EAClB,UAAU,CAAC;AAAA,EACX,OAAO,IAAI;AAAA,EACX;AAAA,EACA,MAAM;AAAA,EACN;AAAA,EACA;AAAA,EACA;AACF;AAEA,IAAM,cAAc,CAClB,QACA,eACiB;AAAA,EACjB,MAAM,WAAW,OAAO,MAAM,IAAI,UAAU;AAAA,EAC5C,IAAI,UAAU;AAAA,IACZ,OAAO;AAAA,EACT;AAAA,EACA,MAAM,OAAO,OAAO,OAAO,GAAG,OAAO,QAAQ,eAAe;AAAA,EAC5D,MAAM,QAAQ,YACZ,WAAW,UAAU,GACrB,MACA,SAAS,WAAW,MAAM,YAAY,GAAG,QAAQ,SAAS,UAAU,GACpE,aAAa,UAAU,CACzB;AAAA,EACA,OAAO,MAAM,IAAI,YAAY,KAAK;AAAA,EAClC,OAAO,SAAS,KAAK,KAAK;AAAA,EAC1B,OAAO;AAAA;AAST,IAAM,YAAY,CAAC,MAAkB,aAAgC;AAAA,EACnE,IAAI,KAAK,KAAK,QAAQ,UAAU,WAAW;AAAA,IACzC,OAAO,EAAE,OAAO,KAAK,KAAK,QAAQ,OAAO,iBAAiB,KAAK;AAAA,EACjE;AAAA,EACA,IAAI,YAAY,OAAO,QAAQ,CAAC,GAAG;AAAA,IACjC,OAAO,EAAE,OAAO,OAAO,mBAAmB,iBAAiB,MAAM;AAAA,EACnE;AAAA,EAMA,IAAI,KAAK,gBAAgB,aAAa;AAAA,IACpC,MAAM,MAAM,KAAK,KAAK,QAAQ,KAAK,KAAK,WAAW;AAAA,IACnD,MAAM,OAAO,MAAM,KAAK,MAAM,GAAG,IAAI,OAAO;AAAA,IAC5C,IAAI,CAAC,OAAO,MAAM,IAAI,GAAG;AAAA,MACvB,OAAO,EAAE,OAAO,CAAC,MAAM,iBAAiB,MAAM;AAAA,IAChD;AAAA,EACF;AAAA,EAGA,MAAM,QAAQ,aAAa,QAAQ;AAAA,EACnC,OAAO;AAAA,IACL;AAAA,IACA,iBAAiB,KAAK,gBAAgB,eAAe,OAAO,SAAS,KAAK;AAAA,EAC5E;AAAA;AAQF,IAAM,UAAU,CAAC,MAAc,eAA+B;AAAA,EAC5D,IAAI,CAAC,YAAY;AAAA,IACf,OAAO;AAAA,EACT;AAAA,EACA,OAAO,OAAO,GAAG,cAAc,SAAS;AAAA;AAO1C,IAAM,kBAAkB,CACtB,OACA,YACA,YACA,YACA,kBACA,cACA,eACS;AAAA,EAKT,MAAM,QAAQ,WAAW,IAAI,MAAM,IAAI;AAAA,EAIvC,MAAM,OACJ,WAAW,IAAI,QAAQ,MAAM,MAAM,UAAU,CAAC,KAC9C,WAAW,IAAI,QAAQ,MAAM,MAAM,gBAAgB,CAAC;AAAA,EAItD,MAAM,UAAU,aAAa,IAAI,MAAM,IAAI,KAAK,MAAM;AAAA,EACtD,IAAI,MAAM;AAAA,IACR,MAAM,QAAQ,KAAK,SAAS,MAAM;AAAA,IAClC,MAAM,OAAO,KAAK,QAAQ,MAAM;AAAA,IAChC,MAAM,QAAQ,KAAK,SAAS,MAAM;AAAA,IAClC,MAAM,YAAY,KAAK,aAAa,MAAM;AAAA,IAE1C,IAAI,KAAK,OAAO;AAAA,MACd,MAAM,OAAO,IAAI,IAAI,KAAK,MAAM,IAAI,CAAC,KAAK,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;AAAA,MACzD,WAAW,SAAS,MAAM,UAAU;AAAA,QAClC,MAAM,WAAW,KAAK,IAAI,MAAM,GAAG;AAAA,QACnC,IAAI,aAAa,WAAW;AAAA,UAC1B,MAAM,QAAQ;AAAA,UACd,IAAI,MAAM,SAAS,QAAQ;AAAA,YACzB,MAAM,kBAAkB;AAAA,UAC1B;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAAA,EAEA,WAAW,SAAS,MAAM,UAAU;AAAA,IAClC,IAAI,MAAM,SAAS,SAAS;AAAA,MAC1B,gBACE,OACA,YACA,YACA,YACA,kBACA,cACA,UACF;AAAA,IACF;AAAA,EACF;AAAA;AAwBF,IAAM,+BAA+B,CACnC,MACA,YACA,YACA,YACA,YACA,qBAC2B;AAAA,EAC3B,IAAI,CAAC,KAAK,KAAK,SAAS,KAAK,YAAY,eAAe,IAAI;AAAA,IAC1D;AAAA,EACF;AAAA,EACA,MAAM,OACJ,WAAW,IAAI,QAAQ,YAAY,UAAU,CAAC,KAC9C,WAAW,IAAI,QAAQ,YAAY,gBAAgB,CAAC;AAAA,EACtD,IAAI,CAAC,MAAM,SAAS,KAAK,UAAU,KAAK,OAAO;AAAA,IAC7C;AAAA,EACF;AAAA,EACA,OAAO;AAAA,IACL,MAAM;AAAA,IACN,MAAM,KAAK,cAAc,KAAK;AAAA,IAC9B,SAAS,eAAe,KAAK,uBAAuB,KAAK,2CAA2C,KAAK,+FAA+F,KAAK;AAAA,IAC7M,UAAU;AAAA,IACV,YAAY,kDAAkD,KAAK;AAAA,EACrE;AAAA;AAqBF,IAAM,mCAAmC,CACvC,OACA,oBACiB;AAAA,EACjB,MAAM,cAA4B,CAAC;AAAA,EACnC,WAAW,QAAQ,OAAO;AAAA,IACxB,IAAI,CAAC,KAAK,KAAK,QAAQ,SAAS;AAAA,MAC9B;AAAA,IACF;AAAA,IACA,MAAM,UAAU,YAAY,QAAQ,KAAK,OAAO,CAAC;AAAA,IACjD,IAAI,iBAAiB;AAAA,MACnB,YAAY,KAAK;AAAA,QACf,MAAM;AAAA,QACN,MAAM,KAAK,cAAc,KAAK;AAAA,QAC9B,SAAS,IAAI,KAAK;AAAA,QAClB,UAAU;AAAA,QACV,YACE;AAAA,MACJ,CAAC;AAAA,MACD;AAAA,IACF;AAAA,IACA,IAAI,WAAW,CAAC,KAAK,QAAQ,SAAS,GAAG,GAAG;AAAA,MAC1C,YAAY,KAAK;AAAA,QACf,MAAM;AAAA,QACN,MAAM,KAAK,cAAc,KAAK;AAAA,QAC9B,SAAS,IAAI,KAAK;AAAA,QAClB,UAAU;AAAA,QACV,YACE;AAAA,MACJ,CAAC;AAAA,MACD;AAAA,IACF;AAAA,IACA,IAAI,SAAS;AAAA,MACX;AAAA,IACF;AAAA,IACA,YAAY,KAAK;AAAA,MACf,MAAM;AAAA,MACN,MAAM,KAAK,cAAc,KAAK;AAAA,MAC9B,SAAS,IAAI,KAAK;AAAA,MAClB,UAAU;AAAA,MACV,YACE;AAAA,IACJ,CAAC;AAAA,EACH;AAAA,EACA,OAAO;AAAA;AAIT,IAAM,kBAAkB,CAAC,SACvB,KAAK,SAAS,WAAW,KAAK;AAWhC,IAAM,4BAA4B,CAAC,UAAuC;AAAA,EACxE,MAAM,UAAU,IAAI;AAAA,EACpB,WAAW,QAAQ,OAAO;AAAA,IACxB,IAAI,CAAC,OAAO,SAAS,KAAK,KAAK,KAAK,CAAC,gBAAgB,IAAI,GAAG;AAAA,MAC1D;AAAA,IACF;AAAA,IACA,MAAM,OAAO,QAAQ,IAAI,KAAK,KAAK;AAAA,IACnC,IAAI,MAAM;AAAA,MACR,KAAK,KAAK,IAAI;AAAA,IAChB,EAAO;AAAA,MACL,QAAQ,IAAI,KAAK,OAAO,CAAC,IAAI,CAAC;AAAA;AAAA,EAElC;AAAA,EACA,MAAM,cAA4B,CAAC;AAAA,EACnC,YAAY,OAAO,SAAS,SAAS;AAAA,IACnC,IAAI,KAAK,SAAS,GAAG;AAAA,MACnB,MAAM,QAAQ,KAAK,IAAI,CAAC,SAAS,IAAI,KAAK,QAAQ;AAAA,MAClD,MAAM,OACJ,MAAM,SAAS,IACX,GAAG,MAAM,MAAM,GAAG,EAAE,EAAE,KAAK,IAAI,UAAU,MAAM,GAAG,EAAE,MACpD,MAAM,KAAK,OAAO;AAAA,MACxB,MAAM,OAAO,KAAK,SAAS,IAAI,aAAa;AAAA,MAI5C,MAAM,OAAO,KAAK,KAChB,CAAC,SAA8B,KAAK,SAAS,MAC/C,GAAG;AAAA,MACH,YAAY,KAAK;AAAA,QACf,MAAM;AAAA,QACN;AAAA,QACA,SAAS,GAAG,QAAQ,sBAAsB;AAAA,QAC1C,UAAU;AAAA,QACV,YACE;AAAA,MACJ,CAAC;AAAA,IACH;AAAA,EACF;AAAA,EACA,OAAO;AAAA;AAGT,IAAM,YAAY,CAAC,OAAsB,gBAAoC;AAAA,EAC3E,YAAY,KAAK,GAAG,0BAA0B,KAAK,CAAC;AAAA,EACpD,MAAM,KAAK,CAAC,GAAG,MAAM;AAAA,IACnB,IAAI,EAAE,UAAU,EAAE,OAAO;AAAA,MACvB,OAAO,EAAE,QAAQ,EAAE;AAAA,IACrB;AAAA,IACA,OAAO,EAAE,MAAM,cAAc,EAAE,KAAK;AAAA,GACrC;AAAA,EACD,WAAW,QAAQ,OAAO;AAAA,IACxB,IAAI,KAAK,SAAS,SAAS;AAAA,MACzB,UAAU,KAAK,UAAU,WAAW;AAAA,IACtC;AAAA,EACF;AAAA;AAcF,IAAM,aAAa,CACjB,OACA,SACA,UACS;AAAA,EACT,MAAM,SAAS,MAAM,OAAO,CAAC,SAAS,KAAK,SAAS,OAAO;AAAA,EAC3D,IAAI,SAAS,OAAO,KAAK,CAAC,WAAW,MAAM,WAAW,aAAa,MAAM,GAAG;AAAA,IAC1E,MAAM,QAAQ,MAAM,OAAO,CAAC,SAAS,KAAK,SAAS,MAAM;AAAA,IACzD,MAAM,OAAO,GAAG,MAAM,QAAQ,GAAG,OAAO,GAAG,MAAM;AAAA,EACnD;AAAA,EACA,WAAW,SAAS,QAAQ;AAAA,IAC1B,WAAW,MAAM,UAAU,SAAS,KAAK;AAAA,EAC3C;AAAA;AASF,IAAM,mBAAmB,CACvB,OACA,UACA,YACS;AAAA,EACT,WAAW,QAAQ,OAAO;AAAA,IACxB,IAAI,KAAK,SAAS,SAAS;AAAA,MACzB;AAAA,IACF;AAAA,IACA,IAAI,KAAK,cAAc,aAAa,SAAS,IAAI,KAAK,SAAS,GAAG;AAAA,MAChE,WAAW,KAAK,UAAU,SAAS,IAAI;AAAA,IACzC;AAAA,IACA,iBAAiB,KAAK,UAAU,UAAU,OAAO;AAAA,EACnD;AAAA;AAGF,IAAM,YAAY,CAAC,MAAmB,YAAqC;AAAA,EACzE,IAAI,KAAK,SAAS,QAAQ;AAAA,IACxB,OAAO;AAAA,MACL,OAAO,KAAK;AAAA,MACZ,YAAY,KAAK,cAAc;AAAA,MAC/B,aAAa,KAAK;AAAA,MAClB,MAAM,KAAK;AAAA,MACX,MAAM;AAAA,MACN,OAAO,KAAK;AAAA,MACZ,QAAQ,KAAK;AAAA,MACb,OAAO,KAAK;AAAA,IACd;AAAA,EACF;AAAA,EACA,OAAO;AAAA,IACL,UAAU,KAAK,SAAS,IAAI,CAAC,UAAU,UAAU,OAAO,OAAO,CAAC;AAAA,IAChE,WAAW,KAAK;AAAA,IAChB,SAAS,KAAK,WAAW;AAAA,IACzB,MAAM,KAAK;AAAA,IACX,MAAM;AAAA,IACN,OAAO,KAAK;AAAA,IACZ,MAAM,KAAK;AAAA,IACX,OAAO,KAAK;AAAA,EACd;AAAA;AAIF,IAAM,yBAAyB,CAC7B,OACA,YACA,YACA,YACA,kBACA,SACA,UACA,cAA4B,CAAC,MACf;AAAA,EACd,MAAM,OAAO,YAAY,IAAI,IAAI,IAAI,CAAC;AAAA,EAItC,MAAM,eAAe,IAAI;AAAA,EAKzB,MAAM,aAAa,IAAI;AAAA,EAEvB,WAAW,QAAQ,OAAO;AAAA,IAExB,MAAM,QAAQ,KAAK,QAAQ,MAAM,GAAG;AAAA,IACpC,MAAM,WAAW,MAAM,GAAG,EAAE,KAAK,KAAK;AAAA,IACtC,MAAM,OAAO,OAAO,QAAQ;AAAA,IAC5B,MAAM,OAAO,MAAM,MAAM,GAAG,EAAE;AAAA,IAI9B,IAAI,YAAY,IAAI,GAAG;AAAA,MACrB,MAAM,aAAa,6BACjB,MACA,KAAK,KAAK,GAAG,GACb,YACA,YACA,YACA,gBACF;AAAA,MACA,IAAI,YAAY;AAAA,QACd,YAAY,KAAK,UAAU;AAAA,MAC7B;AAAA,MAKA,IAAI,KAAK,KAAK,QAAQ,WAAW,CAAC,KAAK,UAAU;AAAA,QAC/C,aAAa,IAAI,KAAK,KAAK,GAAG,GAAG,KAAK,KAAK,QAAQ,OAAO;AAAA,MAC5D;AAAA,MACA,IAAI,KAAK,SAAS,GAAG;AAAA,QACnB,WAAW,IAAI,KAAK,KAAK,GAAG,GAAG,KAAK,KAAK;AAAA,MAC3C;AAAA,IACF;AAAA,IAEA,IAAI,KAAK,KAAK,QAAQ,QAAQ;AAAA,MAC5B;AAAA,IACF;AAAA,IASA,MAAM,gBAAgB,KAAK,MAAM,MAAM,GAAG,EAAE,OAAO,OAAO;AAAA,IAC1D,MAAM,cAAc,YAAY,IAAI,IAChC,gBACA,cAAc,MAAM,GAAG,EAAE;AAAA,IAC7B,MAAM,gBAAgB,KAAK,OAAO,CAAC,QAAQ,CAAC,aAAa,KAAK,GAAG,CAAC,EAAE;AAAA,IACpE,MAAM,SAAS,KAAK,IAAI,GAAG,YAAY,SAAS,aAAa;AAAA,IAE7D,IAAI,SAAS;AAAA,IACb,IAAI,WAAW;AAAA,IACf,WAAW,OAAO,MAAM;AAAA,MACtB,SAAS,YAAY,QAAQ,GAAG;AAAA,MAChC,IAAI,CAAC,aAAa,KAAK,GAAG,GAAG;AAAA,QAC3B,YAAY;AAAA,MACd;AAAA,MACA,OAAO,cAAc,IAAI,YAAY,MAAM,GAAG,QAAQ,EAAE,KAAK,GAAG;AAAA,IAClE;AAAA,IAEA,QAAQ,OAAO,oBAAoB,UAAU,MAAM,QAAQ;AAAA,IAC3D,OAAO,SAAS,KAAK;AAAA,MACnB,OAAO,KAAK,KAAK,QAAQ;AAAA,MACzB,YAAY,KAAK,KAAK,cAAc;AAAA,MACpC,aAAa,KAAK;AAAA,MAClB,MAAM,KAAK;AAAA,MACX,MAAM,KAAK,KAAK,QAAQ;AAAA,MACxB,KAAK,WAAW,IAAI;AAAA,MACpB,MAAM;AAAA,MACN,OAAO,KAAK,KAAK,QAAQ,SAAS,KAAK;AAAA,MACvC;AAAA,MACA;AAAA,MACA,QAAQ,KAAK;AAAA,MACb,OAAO,KAAK;AAAA,IACd,CAAC;AAAA,EACH;AAAA,EAEA,gBACE,MACA,YACA,YACA,YACA,kBACA,cACA,UACF;AAAA,EACA,UAAU,KAAK,UAAU,WAAW;AAAA,EACpC,WAAW,KAAK,UAAU,SAAS,IAAI;AAAA,EACvC,iBAAiB,KAAK,UAAU,UAAU,OAAO;AAAA,EACjD,OAAO,KAAK,SAAS,IAAI,CAAC,UAAU,UAAU,OAAO,OAAO,CAAC;AAAA;AAG/D,IAAM,eAAe,CAAC,QAAwB;AAAA,EAC5C,IAAI,QAAQ,SAAS;AAAA,IACnB,OAAO;AAAA,EACT;AAAA,EACA,MAAM,YAAY,IAAI,WAAW,GAAG,IAAI,MAAM,IAAI;AAAA,EAIlD,MAAM,aAAa,UAAU,QAAQ,SAAS,EAAE;AAAA,EAChD,MAAM,UAAU,WAAW,SAAS,QAAQ,IACxC,WAAW,MAAM,GAAG,CAAC,SAAS,MAAM,IACpC;AAAA,EAEJ,OAAO,YAAY,KAAK,MAAM;AAAA;AAGhC,IAAM,cAAc,CAClB,KACA,SACA,aACuB;AAAA,EACvB,IAAI,CAAC,KAAK;AAAA,IACR;AAAA,EACF;AAAA,EACA,MAAM,aAAa,aAAa,GAAG;AAAA,EAGnC,OAAO,QAAQ,IAAI,UAAU,GAAG,SAAS,aAAa,UAAU,UAAU;AAAA;AAI5E,IAAM,YAAY,CAAC,SACjB,OAAO,SAAS;AAOlB,IAAM,mBAAmB,CACvB,MACA,SACA,aACmB;AAAA,EACnB,IAAI,UAAU,IAAI,GAAG;AAAA,IACnB,MAAM,OAAO,QAAQ,IAAI,aAAa,IAAI,CAAC;AAAA,IAC3C,IAAI,CAAC,MAAM;AAAA,MACT,OAAO;AAAA,IACT;AAAA,IACA,OAAO;AAAA,MACL,OAAO,KAAK,KAAK,QAAQ;AAAA,MACzB,YAAY,KAAK,KAAK,cAAc;AAAA,MACpC,aAAa,KAAK;AAAA,MAClB,MAAM,KAAK,KAAK,QAAQ;AAAA,MACxB,MAAM;AAAA,MACN,OAAO,KAAK,KAAK,QAAQ,SAAS,KAAK;AAAA,MACvC,QAAQ,KAAK;AAAA,MACb,OAAO,KAAK;AAAA,IACd;AAAA,EACF;AAAA,EAEA,IAAI,KAAK,MAAM;AAAA,IACb,MAAM,OAAO,QAAQ,IAAI,aAAa,KAAK,IAAI,CAAC;AAAA,IAChD,OAAO;AAAA,MACL,OAAO,KAAK;AAAA,MACZ,YAAY,MAAM,KAAK,cAAc;AAAA,MACrC,MAAM,KAAK;AAAA,MACX,MAAM;AAAA,MACN,OAAO,KAAK;AAAA,MACZ,QAAQ,MAAM,MAAM;AAAA,MACpB,OAAO,MAAM,SAAS,aAAa,UAAU,aAAa,KAAK,IAAI,CAAC;AAAA,IACtE;AAAA,EACF;AAAA,EAEA,IAAI,KAAK,MAAM;AAAA,IACb,OAAO;AAAA,MACL,OAAO,KAAK;AAAA,MACZ,MAAM,KAAK;AAAA,MACX,MAAM;AAAA,MACN,OAAO,KAAK;AAAA,MACZ,QAAQ;AAAA,MACR,OAAO,aAAa,UAAU,KAAK,IAAI;AAAA,IACzC;AAAA,EACF;AAAA,EAEA,OAAO;AAAA;AAIT,IAAM,qBAAqB,CACzB,OACA,SACA,SACA,aACc;AAAA,EACd,MAAM,QAAmB,CAAC;AAAA,EAC1B,WAAW,QAAQ,OAAO;AAAA,IACxB,IAAI,CAAC,UAAU,IAAI,KAAK,KAAK,OAAO;AAAA,MAClC,MAAM,KAAK;AAAA,QACT,OAAO,KAAK;AAAA,QACZ,UAAU,mBAAmB,KAAK,OAAO,SAAS,SAAS,QAAQ;AAAA,QACnE,WAAW,KAAK;AAAA,QAChB,WAAW,KAAK;AAAA,QAChB,SAAS,KAAK,WAAW;AAAA,QACzB,MAAM,KAAK;AAAA,QACX,MAAM;AAAA,QACN,OAAO,KAAK;AAAA,QACZ,OAAO,YAAY,KAAK,MAAM,SAAS,QAAQ;AAAA,MACjD,CAAC;AAAA,MACD;AAAA,IACF;AAAA,IACA,MAAM,OAAO,iBAAiB,MAAM,SAAS,QAAQ;AAAA,IACrD,IAAI,MAAM;AAAA,MACR,MAAM,KAAK,IAAI;AAAA,IACjB;AAAA,EACF;AAAA,EACA,OAAO;AAAA;AAST,IAAM,iBAAiB,CAAC,SAAoB,SAAyB;AAAA,EACnE,IAAI;AAAA,EACJ,MAAM,OAAO,CAAC,UAA8B;AAAA,IAC1C,WAAW,QAAQ,OAAO;AAAA,MACxB,QAAQ,UAAU;AAAA,MAClB,IAAI,UAAU,MAAM;AAAA,QAClB,OAAO;AAAA,MACT;AAAA,MACA,IACE,UAAU,aACV,UAAU,aACV,MAAM,WAAW,GAAG,OAAO,GAC3B;AAAA,QACA,QAAQ;AAAA,MACV;AAAA,MACA,IAAI,KAAK,SAAS,WAAW,KAAK,KAAK,QAAQ,GAAG;AAAA,QAChD,OAAO;AAAA,MACT;AAAA,IACF;AAAA,IACA,OAAO;AAAA;AAAA,EAET,OAAO,KAAK,OAAO,IAAI,OAAQ,SAAS;AAAA;AAU1C,IAAM,eAAe,CAAC,MAAgB,YACpC,KAAK,IAAI,CAAC,QAAQ;AAAA,EAChB,MAAM,OAAO,IAAI,QAAQ,eAAe,SAAS,IAAI,IAAI;AAAA,EACzD,OAAO,SAAS,IAAI,OAAO,MAAM,KAAK,KAAK,KAAK;AAAA,CACjD;AAiBH,IAAM,kBAAkB,CACtB,UACA,YAOc;AAAA,EACd,MAAM,UAAU,QAAQ,WAAW,CAAC;AAAA,EACpC,MAAM,MAAM,QAAQ,OAAO;AAAA,EAC3B,MAAM,WAAW,QAAQ,YAAY,CAAC;AAAA,EACtC,MAAM,YAAY,QAAQ,aAAa,CAAC;AAAA,EACxC,MAAM,OAAO,QAAQ,QAAQ,CAAC;AAAA,EAC9B,IAAI,CAAC,UAAU;AAAA,IACb,OAAO,EAAE,SAAS,KAAK,UAAU,WAAW,KAAK;AAAA,EACnD;AAAA,EACA,MAAM,aAAa,CAA6B,UAAgB;AAAA,OAC3D;AAAA,IACH,MAAM,aAAa,UAAU,KAAK,IAAI;AAAA,EACxC;AAAA,EACA,MAAM,aAAa,CAA6B,UAAgB;AAAA,OAC3D;AAAA,IACH,MAAM,aAAa,UAAU,KAAK,IAAI;AAAA,EACxC;AAAA,EACA,OAAO;AAAA,IACL,SAAS,QAAQ,IAAI,UAAU;AAAA,IAC/B,KAAK,MAAM,WAAW,GAAG,IAAI;AAAA,IAC7B,UAAU,SAAS,IAAI,UAAU;AAAA,IACjC,WAAW,UAAU,IAAI,CAAC,cAAc;AAAA,SACnC;AAAA,MACH,OAAO,SAAS,MAAM,IAAI,UAAU;AAAA,IACtC,EAAE;AAAA,IACF,MAAM,KAAK,IAAI,CAAC,QAAQ;AAAA,MACtB,MAAM,UAAkB;AAAA,WACnB;AAAA,QACH,OAAO,IAAI,OAAO,IAAI,UAAU;AAAA,QAChC,MAAM,aAAa,UAAU,IAAI,IAAI;AAAA,MACvC;AAAA,MACA,IAAI,IAAI,MAAM;AAAA,QACZ,QAAQ,OAAO,aAAa,UAAU,IAAI,IAAI;AAAA,MAChD;AAAA,MACA,OAAO;AAAA,KACR;AAAA,EACH;AAAA;AAIK,IAAM,kBAAkB,CAC7B,OACA,YAgDe;AAAA,EACf,MAAM,WAAW,QAAQ,YAAY;AAAA,EACrC,MAAM,UAAU,QAAQ,WAAW;AAAA,EACnC,MAAM,aAAa,QAAQ,cAAc;AAAA,EACzC,MAAM,mBAAmB,QAAQ,oBAAoB;AAAA,EACrD,MAAM,mBAAmB,QAAQ,oBAAoB,IAAI;AAAA,EAKzD,QAAQ,SAAS,KAAK,UAAU,WAAW,SAAS,gBAClD,UACA,OACF;AAAA,EACA,MAAM,UAAU,IAAI,IAClB,MAAM,IAAI,CAAC,SAAS;AAAA,IAClB,QAAQ,eAAe,KAAK,iBAAiB,KAAK;AAAA,IAClD;AAAA,EACF,CAAC,CACH;AAAA,EAKA,IAAI,YAAY,CAAC,QAAQ,cAAc;AAAA,IACrC,WAAW,QAAQ,OAAO;AAAA,MACxB,MAAM,OAAO,cAAc,UAAU,KAAK,KAAK;AAAA,MAC/C,IAAI,CAAC,QAAQ,IAAI,IAAI,GAAG;AAAA,QACtB,QAAQ,IAAI,MAAM,IAAI;AAAA,MACxB;AAAA,IACF;AAAA,EACF;AAAA,EAYA,MAAM,cAAc,aAAa,UAAU,QAAQ,iBAAiB,GAAG;AAAA,EAKvE,MAAM,cAAc,QAAQ,eAAe,CAAC;AAAA,EAC5C,YAAY,KACV,GAAG,iCAAiC,OAAO,QAAQ,QAAQ,OAAO,CAAC,CACrE;AAAA,EAEA,IAAI,QAAQ,SAAS;AAAA,IACnB,MAAM,UAAU,mBACd,QAAQ,SACR,SACA,SACA,QACF;AAAA,IACA,OAAO;AAAA,MACL;AAAA,MACA;AAAA,MACA;AAAA,MACA,MAAM;AAAA,MACN;AAAA,MACA;AAAA,MACA,MAAM,aAAa,MAAM,OAAO;AAAA,IAClC;AAAA,EACF;AAAA,EAEA,MAAM,UAAU,uBACd,OACA,QAAQ,YACR,kBACA,YACA,kBACA,SACA,IAAI,IACF,KAAK,QAAQ,CAAC,QAAS,UAAU,KAAK,WAAW,IAAI,CAAC,IAAI,CAAC,IAAI,IAAI,CAAE,CACvE,GACA,WACF;AAAA,EACA,OAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA,MAAM;AAAA,IACN;AAAA,IACA;AAAA,IACA,MAAM,aAAa,MAAM,OAAO;AAAA,EAClC;AAAA;;;ACt9BK,IAAM,eAAe;AAmDrB,IAAM,gBAAgB,CAC3B,OACA,aACoB;AAAA,EACpB,OAAO,SAAS;AAAA,EAChB,IAAI,SAAS,SAAS,SAAS,KAAK,CAAC,YAAY,QAAQ,OAAO,KAAK,GAAG;AAAA,IACtE,OAAO,EAAE,MAAM,MAAM,MAAM,CAAC,GAAG,SAAS,MAAM;AAAA,EAChD;AAAA,EACA,OAAO,EAAE,MAAM,OAAO,SAAS,GAAG;AAAA;AAQ7B,IAAM,mBAAmB,CAC9B,KACA,aACuB;AAAA,EACvB,QAAQ,SAAS,SAAS,cAAc,IAAI,MAAM,GAAG,GAAG,QAAQ;AAAA,EAChE,OAAO,EAAE,MAAM,KAAK,KAAK,GAAG,GAAG,QAAQ;AAAA;AAQlC,IAAM,kBAAkB,CAC7B,cACA,YACW;AAAA,EACX,IAAI,CAAC,SAAS;AAAA,IACZ,OAAO;AAAA,EACT;AAAA,EACA,OAAO,iBAAiB,MAAM,IAAI,YAAY,IAAI,UAAU;AAAA;AA0BvD,IAAM,sBAAsB,CACjC,OACA,aACiB;AAAA,EAKjB,MAAM,aAAa,IAAI,IAAI,SAAS,SAAS,IAAI,CAAC,YAAY,QAAQ,EAAE,CAAC;AAAA,EACzE,MAAM,OAAO,IAAI;AAAA,EACjB,MAAM,cAA4B,CAAC;AAAA,EACnC,WAAW,QAAQ,OAAO;AAAA,IAGxB,OAAO,SAAS,KAAK,OAAO,IAAI,MAAM,GAAG;AAAA,IACzC,IACE,SACA,CAAC,KAAK,IAAI,KAAK,KACf,aAAa,KAAK,KAAK,KACvB,CAAC,WAAW,IAAI,KAAK,GACrB;AAAA,MACA,KAAK,IAAI,KAAK;AAAA,MACd,YAAY,KAAK;AAAA,QACf,MAAM;AAAA,QACN,SAAS,WAAW,sCAAsC,6FAA6F;AAAA,QACvJ,UAAU;AAAA,QACV,YAAY,cAAc;AAAA,MAC5B,CAAC;AAAA,IACH;AAAA,EACF;AAAA,EACA,OAAO;AAAA;;;ACvIT,IAAM,gBAAgB,CAAC,UAAwB;AAAA,EAC7C,MAAM,SAAS,IAAI;AAAA,EACnB,MAAM,cAA4B,CAAC;AAAA,EACnC,WAAW,QAAQ,OAAO;AAAA,IACxB,MAAM,WAAW,OAAO,IAAI,KAAK,KAAK;AAAA,IACtC,IAAI,UAAU;AAAA,MACZ,YAAY,KAAK;AAAA,QACf,MAAM;AAAA,QACN,MAAM,KAAK,cAAc,KAAK;AAAA,QAC9B,SAAS,wBAAwB,KAAK,UAAU,gBAAgB,KAAK;AAAA,QACrE,UAAU;AAAA,QACV,YAAY;AAAA,MACd,CAAC;AAAA,MACD;AAAA,IACF;AAAA,IACA,OAAO,IAAI,KAAK,OAAO,KAAK,EAAE;AAAA,EAChC;AAAA,EACA,OAAO,EAAE,aAAa,OAAO;AAAA;AAQ/B,IAAM,iBAAiB,CACrB,MACA,MACA,UACA,eACA,MACA,aACiB;AAAA,EACjB,IAAI,EAAE,YAAY,SAAS,WAAW;AAAA,IACpC,OAAO;AAAA,EACT;AAAA,EACA,MAAM,UAAU,IAAI,IAAI,KAAK,IAAI,CAAC,SAAS,KAAK,cAAc,CAAC;AAAA,EAC/D,MAAM,SAAuB,CAAC;AAAA,EAC9B,YAAY,KAAK,WAAW,eAAe;AAAA,IACzC,IAAI,CAAC,QAAQ,IAAI,GAAG,GAAG;AAAA,MACrB,OAAO,KAAK;AAAA,WACP;AAAA,QACH,UAAU;AAAA,QACV,QAAQ;AAAA,QACR,OAAO,aAAa,UAAU,cAAc,KAAK,MAAM,IAAI,CAAC;AAAA,MAC9D,CAAC;AAAA,IACH;AAAA,EACF;AAAA,EACA,OAAO,CAAC,GAAG,MAAM,GAAG,MAAM;AAAA;AAS5B,IAAM,gBAAgB,CAAC,UACrB,OAAO,UAAU;AAEnB,IAAM,eAAe,CACnB,OACA,QACA,kBACW;AAAA,EACX,IAAI,cAAc,KAAK,GAAG;AAAA,IACxB,OAAO;AAAA,EACT;AAAA,EACA,OACE,MAAM,YACL,kBAAkB,YAAY,YAAY,MAAM,mBACjD,OAAO,OAAO,KAAK,EAAE,MACrB;AAAA;AAKJ,IAAM,mBAAmB,CACvB,MACA,QACA,mBAEC,QAAQ,CAAC,GAAG,IAAI,CAAC,SAAS;AAAA,KACtB;AAAA,EACH,OAAO,IAAI,OAAO,IAAI,CAAC,UAAU;AAAA,OAC5B;AAAA,IACH,OAAO,aAAa,KAAK,OAAO,QAAQ,aAAa;AAAA,EACvD,EAAE;AAAA,EACF,OAAO,aAAa,IAAI,OAAO,QAAQ,aAAa;AACtD,EAAE;AAQJ,IAAM,wBAAwB,CAC5B,MACA,OACA,UACA,eACA,SACA,MACA,aACA,UAAU,OACK;AAAA,EAQf,MAAM,eAAe,CAAC,SACpB,KAAK,WAAW,GAAG,KAAK,CAAC,KAAK,WAAW,IAAI,IACzC,cAAc,MAAM,MAAM,IAAI,IAC9B;AAAA,EACN,MAAM,OAAO,iBACX,QAAQ,WAAW,MACnB,MACA,KAAK,aACP,EAAE,IAAI,CAAC,QAAQ;AAAA,IACb,MAAM,YAAY;AAAA,SACb;AAAA,MACH,OAAO,IAAI,OAAO,IAAI,CAAC,UAAU;AAAA,WAC5B;AAAA,QACH,MAAM,aAAa,KAAK,IAAI;AAAA,MAC9B,EAAE;AAAA,MACF,MAAM,aAAa,IAAI,IAAI;AAAA,IAC7B;AAAA,IACA,IAAI,UAAU,MAAM;AAAA,MAClB,UAAU,OAAO,aAAa,UAAU,IAAI;AAAA,IAC9C;AAAA,IACA,OAAO;AAAA,GACR;AAAA,EACD,MAAM,OAAO,MAAM,OAAO,CAAC,SAAS,KAAK,WAAW,IAAI;AAAA,EACxD,MAAM,cAAc,eAClB,MACA,MACA,UACA,eACA,MACA,QAAQ,YAAY,EACtB;AAAA,EAMA,MAAM,YACJ,KAAK,WAAW,SAAS,SAAS,KAAK,gBAAgB,OAAO;AAAA,EAIhE,MAAM,eAAe,CAA6B,UAAgB;AAAA,OAC7D;AAAA,IACH,MAAM,aAAa,KAAK,IAAI;AAAA,EAC9B;AAAA,EACA,QAAQ,SAAS,KAAK,aAAa,QAAQ;AAAA,EAE3C,OAAO,gBAAgB,aAAa;AAAA,IAClC,SAAS,SAAS,IAAI,YAAY;AAAA,IAClC,UAAU,QAAQ,YAAY;AAAA,IAC9B,KAAK,MAAM,aAAa,GAAG,IAAI;AAAA,IAC/B;AAAA,IACA,SAAS,QAAQ,WAAW,QAAQ;AAAA,IACpC,UAAU,UAAU,IAAI,YAAY;AAAA,IACpC,YAAY,QAAQ;AAAA,IAIpB,eAAe,cAAc,gBAAgB,KAAK,OAAO,GAAG,MAAM,IAAI;AAAA,IACtE,YAAY,CAAC,SAAS,SAAS,EAAE,OAAO,OAAO,EAAE,KAAK,GAAG;AAAA,IACzD,cAAc;AAAA,IACd,WAAW,QAAQ,WAAW;AAAA,IAC9B,kBAAkB,QAAQ;AAAA,IAG1B,kBAAkB;AAAA,IAIlB,SAAS,UAAU,YAAY,QAAQ,WAAW,QAAQ;AAAA,IAC1D;AAAA,EACF,CAAC;AAAA;AAQH,IAAM,sBAAsB,CAC1B,OACA,SACA,MACA,aACA,UAAU,OACP;AAAA,EAGH,MAAM,WAAW,sBAAsB,IAAI;AAAA,EAC3C,MAAM,gBAAgB,IAAI;AAAA,EAC1B,IAAI,UAAU;AAAA,IACZ,WAAW,QAAQ,OAAO;AAAA,MACxB,IAAI,KAAK,WAAW,UAAU;AAAA,QAC5B,cAAc,IAAI,KAAK,gBAAgB,IAAI;AAAA,MAC7C;AAAA,IACF;AAAA,EACF;AAAA,EAOA,MAAM,qBAAiD,CAAC;AAAA,EACxD,MAAM,OAAO,IAAI;AAAA,EACjB,aAAa,UAAU,KAAK,SAAS;AAAA,IACnC,MAAM,oBAAkC,CAAC;AAAA,IACzC,mBAAmB,QAAQ,sBACzB,MACA,OACA,UACA,eACA,SACA,MACA,mBACA,OACF;AAAA,IACA,WAAW,cAAc,mBAAmB;AAAA,MAC1C,MAAM,MAAM,GAAG,WAAW;AAAA,EAAS,WAAW,QAAQ;AAAA,EAAO,WAAW;AAAA,MACxE,IAAI,CAAC,KAAK,IAAI,GAAG,GAAG;AAAA,QAClB,KAAK,IAAI,GAAG;AAAA,QACZ,YAAY,KAAK,UAAU;AAAA,MAC7B;AAAA,IACF;AAAA,EACF;AAAA,EACA,MAAM,aAAyB,mBAAmB,KAAK,kBAAkB;AAAA,IACvE,UAAU,CAAC;AAAA,IACX,WAAW,CAAC;AAAA,IACZ,SAAS,CAAC;AAAA,IACV,MAAM,CAAC;AAAA,EACT;AAAA,EACA,OAAO,EAAE,YAAY,mBAAmB;AAAA;AAI1C,IAAM,yBAAyB,CAC7B,IACA,cACA,SACA,gBACG;AAAA,EACH,QAAQ,SAAS;AAAA,EACjB,IAAI,MAAM;AAAA,IACR,OAAO,oBAAoB,cAAc,SAAS,MAAM,aAAa,EAAE,EACpE;AAAA,EACL;AAAA,EACA,OAAO;AAAA,IACL,IAAI,gBAAgB,cAAc;AAAA,MAChC,SAAS,QAAQ,WAAW;AAAA,MAC5B,UAAU,QAAQ,YAAY;AAAA,MAC9B,KAAK,QAAQ,WAAW;AAAA,MACxB;AAAA,MACA,SAAS,QAAQ,WAAW,QAAQ;AAAA,MACpC,UAAU,QAAQ,WAAW;AAAA,MAC7B,YAAY,QAAQ;AAAA,MACpB,eAAe,gBAAgB,KAAK,EAAE;AAAA,MACtC,YAAY;AAAA,MACZ,WAAW,QAAQ,WAAW;AAAA,MAC9B,kBAAkB,QAAQ;AAAA,MAC1B,kBAAkB;AAAA,MAGlB,MAAM,iBAAiB,QAAQ,WAAW,MAAM,EAAE;AAAA,IACpD,CAAC;AAAA,EACH;AAAA;AAIK,IAAM,oBAAoB,CAC/B,OACA,YACiB;AAAA,EACjB,QAAQ,aAAa,WAAW,cAAc,KAAK;AAAA,EACnD,QAAQ,SAAS;AAAA,EAKjB,MAAM,eAAe,QAAQ,WACzB,MAAM,OAAO,CAAC,SAAS,KAAK,YAAY,EAAE,IAC1C;AAAA,EAIJ,IAAI,qBAAiD,CAAC;AAAA,EACtD,IAAI;AAAA,EACJ,IAAI,MAAM;AAAA,KACP,EAAE,YAAY,mBAAmB,IAAI,oBACpC,cACA,SACA,MACA,WACF;AAAA,EACF,EAAO;AAAA,IACL,aAAa,gBAAgB,cAAc;AAAA,MACzC,SAAS,QAAQ,WAAW;AAAA,MAC5B,UAAU,QAAQ,YAAY;AAAA,MAC9B,KAAK,QAAQ,WAAW;AAAA,MACxB;AAAA,MACA,SAAS,QAAQ,WAAW,QAAQ;AAAA,MACpC,UAAU,QAAQ,WAAW;AAAA,MAC7B,YAAY,QAAQ;AAAA,MACpB,WAAW,QAAQ,WAAW;AAAA,MAC9B,kBAAkB,QAAQ;AAAA,MAC1B,SAAS,QAAQ,WAAW,QAAQ;AAAA,MAGpC,MAAM,iBAAiB,QAAQ,WAAW,MAAM,EAAE;AAAA,IACpD,CAAC;AAAA;AAAA,EAGH,MAAM,sBAAkE,CAAC;AAAA,EACzE,aAAa,QAAQ,QAAQ,UAAU,YAAY,CAAC,GAAG;AAAA,IACrD,oBAAoB,MAAM,uBACxB,IACA,MAAM,OAAO,CAAC,SAAS,KAAK,YAAY,EAAE,GAC1C,SACA,WACF;AAAA,EACF;AAAA,EAKA,YAAY,KACV,GAAG,iBAAiB,UAAU,GAC9B,GAAG,qBAAqB,YAAY,YAAY,CAClD;AAAA,EAEA,OAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AAAA;;;AC/XF;AAEA,6BAAkB;;;ACUlB,IAAM,aAAa;AAUZ,IAAM,iBAAiB,CAAC,MAAc,UAAkC;AAAA,EAC7E,MAAM,UAAU,KAAK,UAAU;AAAA,EAC/B,MAAM,MAAM,QAAQ,MAAM,UAAU,GAAG,QAAQ;AAAA,EAC/C,IAAI,QAAQ,WAAW;AAAA,IACrB,OAAO;AAAA,EACT;AAAA,EACA,MAAM,OAAO,QAAQ,MAAM,IAAI,MAAM;AAAA,EACrC,MAAM,YAAY,IAAI,WAAW,GAAG,IAAK,MAAiB;AAAA,EAC1D,IAAI,UAAU,MAAM;AAAA,IAKlB,IAAI,cAAc,OAAO,KAAK,SAAS,GAAG,GAAG;AAAA,MAC3C,OAAO;AAAA,IACT;AAAA,IACA,OAAO,EAAE,WAAW,QAAQ,IAAI,OAAO;AAAA,EACzC;AAAA,EAGA,OAAO,MAAM,cAAc,aACzB,IAAI,UAAU,MAAM,UACpB,KAAK,KAAK,MAAM,KACd,OACA;AAAA;;;AC9CN;AACA;;;ACDA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAkBO,IAAM,cAAc,YAAY,SACrC,cACA,UACA,WACA,SACA,UACA,MACF;;;ADJA,IAAM,aAAa;AAAA,EAKjB,OAAO,CAAC,UACL,KAAK,OAAO,EAAE,QAAQ,YAAY,CAAC,KAAK,CAAC;AAAA,EAC5C,WAAW,CAAC,SAAkC,KAAK,IAAI;AACzD;AAEA,IAAM,iBAAiB,CACrB,aAKC;AAAA,KACI;AAAA,EACH,SAAS;AAAA,IACP,MAAM;AAAA,OACH,SAAS;AAAA,EACd;AACF;AAaF,IAAM,yBAAyB,CAAC,UAA2B;AAAA,EACzD,OAAO,QAAQ,IAAI,UAAU,MAAM,MAAM,UAAU,CAAC;AAAA,EACpD,IAAI,CAAC,aAAa,KAAK,KAAK,GAAG;AAAA,IAC7B,OAAO;AAAA,EACT;AAAA,EACA,IAAI,WAAW,aAAa,OAAO,KAAK,MAAM,IAAI;AAAA,IAChD,OAAO;AAAA,EACT;AAAA,EAGA,OAAO,CAAC,MAAM,SAAS;AAAA,MAAS,CAAC;AAAA;AAgBnC,IAAM,cAAc,CAAC,UAAiD;AAAA,EACpE,MAAM,OAAqB;AAAA,IACzB,SAAS;AAAA,IACT,MAAM,CAAC;AAAA,IACP,SAAS;AAAA,IACT,SAAS;AAAA,IACT,UAAU;AAAA,IACV,QAAQ;AAAA,IACR,MAAM;AAAA,IAEN,WAAW,MAAc;AAAA,EAC3B;AAAA,EACA,OAAO;AAAA;AAIT,IAAM,gBAAgB,CAAC,UACrB,OAAO,UAAU;AAMnB,IAAM,SAAS,OAAO,OACpB,CAAC,OAAoB,YACnB,cAAc,KAAK,KAAK,uBAAuB,KAAK,IAChD,YAAY,KAAK,IACjB,WAAW,OAAO,eAAe,OAAO,CAAC,GAC/C,YACA;AAAA,EACE,MAAM,CAAC,UAAuB,YAC5B,WAAW,KAAK,UAAU,eAAe,OAAO,CAAC;AAAA,EAKnD,WAAW,CACT,MACA,MACA,YAEA,WAAW,UACT,cAAc,IAAI,IAAI,EAAE,SAAS,KAAK,IAAI,MAC1C,MACA,eAAe,OAAO,CACxB;AACJ,CACF;AAEA,IAAe;;;AEnIf;AAEA;AACA,oBAAS;;;ACsBF,IAAM,iBAAiB;AAUvB,IAAM,aAAa,CAAC,SAAwB,OAAqB;AAAA,EACtE,QAAQ,YAAY,QAAQ;AAAA;AAI9B,IAAM,SACJ;AAuBK,IAAM,sBAAsB,CACjC,MACA,iBACmB;AAAA,EACnB,IAAI,YAAY;AAAA,EAChB,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,SACM,QAAQ,OAAO,KAAK,SAAS,EACjC,OAAO,QACP,QAAQ,OAAO,KAAK,SAAS,GAC7B;AAAA,IACA,MAAM,aAAa,MAAM,OAAO,WAAW,MAAM,OAAO;AAAA,IACxD,MAAM,QACJ,eAAe,YACX,GAAG,MAAM,OAAO,QAAQ,UACxB,IAAI;AAAA,IAEV,IAAI,MAAM,OAAO,YAAY,aAAa,eAAe,KAAK,GAAG;AAAA,MAC/D;AAAA,IACF;AAAA,IACA,YAAY,UAAU,MAAM,GAAG,MAAM,KAAK;AAAA,IAC1C,IAAI,eAAe,WAAW;AAAA,MAG5B,QAAQ,MAAM,OAAO,SAAS,YAAY,SAAS;AAAA,IACrD,EAAO;AAAA,MACL,OAAO;AAAA;AAAA,EAEX;AAAA,EACA,OAAO,EAAE,IAAI,MAAM,WAAW,IAAI;AAAA;;;ADxEpC,IAAM,kBAAiB;AACvB,IAAM,gBAAe;AACrB,IAAM,cAAa;AAGnB,IAAM,qBAAqB,CAAC,YAC1B,QAAQ,QAAQ,iBAAgB,EAAE;AAGpC,IAAM,aAAa,CAAC,YAClB,QAAQ,MAAM,aAAY,GAAG,QAAQ,SAAS;AAczC,IAAM,WAAU,CAAC,SACtB,KACG,UAAU,KAAK,EACf,YAAY,EACZ,KAAK,EACL,WAAW,4BAA4B,EAAE,EACzC,WAAW,YAAY,GAAG,EAC1B,WAAW,QAAQ,GAAG,EACtB,WAAW,WAAW,EAAE;AAQtB,IAAM,cAAc,CAAC,SAC1B,KAAK,MAAM,GAAG,EAAE,IAAI,QAAO,EAAE,OAAO,OAAO,EAAE,KAAK,GAAG;AAGvD,IAAM,YAAY,CAAC,UACjB,MACG,MAAM,WAAU,EAChB,OAAO,OAAO,EACd,IAAI,CAAC,SAAS,KAAK,OAAO,CAAC,EAAE,YAAY,IAAI,KAAK,MAAM,CAAC,CAAC,EAC1D,KAAK,GAAG;AAWb,IAAM,kBAAkB,CAAC,YACvB,QAAQ,WAAW,eAAe,EAAE;AAGtC,IAAM,kBAAkB,CACtB,MACA,UACA,WACS;AAAA,EAGT,IAAI,SAAS,IAAI;AAAA,IACf;AAAA,EACF;AAAA,EACA,MAAM,QAAQ,WAAW,IAAI;AAAA,EAC7B,IAAI,UAAU,MAAM;AAAA,IAClB,OAAO,KAAK,KAAK;AAAA,IACjB;AAAA,EACF;AAAA,EACA,MAAM,QAAQ,mBAAmB,IAAI;AAAA,EACrC,IAAI,UAAU,SAAS;AAAA,IACrB;AAAA,EACF;AAAA,EACA,MAAM,OAAO,gBAAgB,KAAK;AAAA,EAElC,IAAI,SAAS,IAAI;AAAA,IACf;AAAA,EACF;AAAA,EACA,SAAS,KAAK,IAAI;AAAA;AAepB,IAAM,WAAW,CAAC,iBAAsC;AAAA,EACtD,MAAM,aAAa,aAAa,MAC9B,GACA,aAAa,SAAS,SAAQ,YAAY,EAAE,MAC9C;AAAA,EACA,MAAM,WAAW,WAAW,MAAM,GAAG;AAAA,EAErC,MAAM,WAAqB,CAAC;AAAA,EAC5B,MAAM,SAAmB,CAAC;AAAA,EAE1B,WAAW,QAAQ,UAAU;AAAA,IAC3B,gBAAgB,MAAM,UAAU,MAAM;AAAA,EACxC;AAAA,EAEA,MAAM,QAAQ,SAAS,WAAW,IAAI,MAAM,IAAI,SAAS,KAAK,GAAG;AAAA,EACjE,OAAO,EAAE,QAAQ,OAAO,SAAS;AAAA;AAMnC,IAAM,cAAc;AAIpB,IAAM,mBAAmB;AAKzB,IAAM,sBAAsB;AAC5B,IAAM,iBACJ;AACF,IAAM,qBAAqB;AAY3B,IAAM,cAAc;AAGpB,IAAM,eAAe;AAWrB,IAAM,0BAA0B,CAAC,SAAiB;AAAA,EAChD,MAAM,QAAQ,KAAK,MAAM;AAAA,CAAI;AAAA,EAC7B,IAAI,CAAC,aAAa,KAAK,MAAM,MAAM,EAAE,GAAG;AAAA,IACtC,OAAO,EAAE,OAAO,QAAQ,EAAE;AAAA,EAC5B;AAAA,EAKA,KAAK,MAAM,MAAM,IAAI,KAAK,MAAM,IAAI;AAAA,IAClC,OAAO,EAAE,OAAO,QAAQ,EAAE;AAAA,EAC5B;AAAA,EACA,MAAM,QAAQ,MAAM,UAClB,CAAC,MAAM,UAAU,QAAQ,KAAK,mBAAmB,KAAK,IAAI,CAC5D;AAAA,EACA,OAAO,UAAU,KACb,EAAE,OAAO,QAAQ,EAAE,IACnB,EAAE,OAAO,MAAM,MAAM,QAAQ,CAAC,GAAG,QAAQ,QAAQ,EAAE;AAAA;AASzD,IAAM,oBACJ;AASF,IAAM,UACJ;AAIK,IAAM,eAAe;AACrB,IAAM,cAAc;AA4C3B,IAAM,kBAAkB,CACtB,MACA,OACA,UACS;AAAA,EACT,MAAM,MAAM,KAAK,QAAQ,KAAK,KAAK;AAAA,EACnC,IAAI,QAAQ,IAAI;AAAA,IACd,MAAM,YAAY;AAAA,IAClB;AAAA,EACF;AAAA,EACA,MAAM,YAAY;AAAA,EAClB,IAAI,KAAK,MAAM,OAAO,OAAO,KAAK,SAAS,aAAa,GAAG,GAAG;AAAA,IAC5D;AAAA,EACF;AAAA,EACA,MAAM,eAAe;AAAA;AAsBvB,IAAM,sBAAsB;AAa5B,IAAM,iBACJ;AAgBF,IAAM,sBAAsB,CAAC,UAA0C;AAAA,EACrE,MAAM,SAAS,IAAI;AAAA,EACnB,IAAI,QAAoB;AAAA,EACxB,YAAY,OAAO,SAAS,MAAM,QAAQ,GAAG;AAAA,IAC3C,MAAM,OAAO,eAAe,MAAM,KAAK;AAAA,IACvC,IAAI,UAAU,QAAQ,SAAS,MAAM;AAAA,MACnC,QAAQ;AAAA,MACR;AAAA,IACF;AAAA,IACA,MAAM,SAAS,KAAK,MAAM,cAAc,GAAG;AAAA,IAC3C,IAAI,QAAQ,UAAU,aAAa,OAAO,MAAM,KAAK,MAAM,IAAI;AAAA,MAC7D;AAAA,IACF;AAAA,IACA,MAAM,cAAc,OAAO,QAAQ;AAAA,IACnC,IAAI,gBAAgB,OAAO,MAAM,QAAQ,MAAM,IAAI,KAAK,MAAM,IAAI;AAAA,MAChE;AAAA,IACF;AAAA,IACA,OAAO,IAAI,OAAO,MAAM,KAAK,EAAE,WAAW,SAAS,GAAG,EAAE,YAAY,CAAC;AAAA,EACvE;AAAA,EACA,OAAO;AAAA;AAmBT,IAAM,YAAY,CAChB,OACA,KACA,SACA,iBACmB;AAAA,EACnB,MAAM,YAAY,IAAI,WAAW,qBAAqB,SAAS;AAAA,EAC/D,MAAM,UAAU,oBAAoB,WAAW,YAAY;AAAA,EAC3D,MAAM,OAAO,QAAQ,KAAK,KAAK;AAAA,EAC/B,IAAI,SAAS,OAAO,QAAQ,OAAO,aAAa,QAAQ,QAAQ,YAAY;AAAA,IAC1E,OAAO;AAAA,MACL,SAAS,EAAE,OAAO,MAAM,QAAQ,KAAK,SAAS,GAAG,MAAM,UAAU;AAAA,MACjE,QAAQ;AAAA,IACV;AAAA,EACF;AAAA,EACA,IAAI,QAAQ,OAAO,WAAW;AAAA,IAC5B,WAAW,SAAS,QAAQ,EAAE;AAAA,IAC9B,OAAO,EAAE,SAAS,EAAE,OAAO,MAAM,QAAQ,IAAI,KAAK,GAAG,QAAQ,KAAK;AAAA,EACpE;AAAA,EACA,OAAO,EAAE,SAAS,EAAE,OAAO,MAAM,QAAQ,KAAK,IAAI,GAAG,KAAK,GAAG,QAAQ,MAAM;AAAA;AAI7E,IAAM,cAAc,CAClB,UACA,OACA,SACA,SACS;AAAA,EACT,SAAS,KAAK,QAAQ,OAAO;AAAA,EAC7B,MAAM,MAAM,KAAK,EAAE,MAAM,QAAQ,QAAQ,OAAO,CAAC;AAAA;AAInD,IAAM,kBAAkB,CACtB,MACA,MACA,UACS;AAAA,EACT,MAAM,KAAK,KAAK,MAAM,iBAAiB,GAAG,QAAQ;AAAA,EAClD,IAAI,OAAO,WAAW;AAAA,IACpB,MAAM,aAAa,KAAK,EAAE,IAAI,KAAK,CAAC;AAAA,EACtC;AAAA;AAIF,IAAM,kBAAkB,CACtB,MACA,OACA,SACA,UACA,iBACS;AAAA,EAET,MAAM,YAAY,KAAK,KAAK,WAAW,aAAa,EAAE,CAAC;AAAA,EACvD,MAAM,MAAM,KAAK,MAAM,WAAW;AAAA,EAClC,IAAI,KAAK,QAAQ;AAAA,IACf,MAAM,QAAQ,IAAI,OAAO,QAAQ,UAAU;AAAA,IAC3C,MAAM,QAAQ,IAAI,OAAO,QAAQ,IAAI,KAAK;AAAA,IAC1C,YACE,UACA,OACA,UAAU,OAAO,MAAM,SAAS,YAAY,GAC5C,MAAM,IACR;AAAA,IACA,gBAAgB,MAAM,MAAM,MAAM,KAAK;AAAA,IACvC,MAAM,YAAY,CAAC;AAAA,IACnB;AAAA,EACF;AAAA,EACA,MAAM,SAAS,KAAK,MAAM,gBAAgB;AAAA,EAC1C,IAAI,QAAQ,UAAU,MAAM,UAAU,SAAS,GAAG;AAAA,IAGhD,MAAM,QAAQ,OAAO,OAAO,QAAQ,WAAW,GAAG,IAAI,IAAI;AAAA,IAC1D,MAAM,OAAO,MAAM,UAAU,KAAK,GAAG,EAAE,KAAK;AAAA,IAG5C,YACE,UACA,OACA,UAAU,OAAO,MAAM,SAAS,YAAY,GAC5C,MAAM,OAAO,CACf;AAAA,IACA,gBAAgB,MAAM,MAAM,gBAAgB,KAAK;AAAA,IACjD,MAAM,YAAY,CAAC;AAAA,IACnB;AAAA,EACF;AAAA,EACA,IACE,KAAK,KAAK,MAAM,MAChB,eAAe,KAAK,IAAI,KACxB,oBAAoB,KAAK,IAAI,GAC7B;AAAA,IACA,MAAM,YAAY,CAAC;AAAA,IACnB;AAAA,EACF;AAAA,EACA,IAAI,MAAM,UAAU,WAAW,GAAG;AAAA,IAChC,MAAM,iBAAiB,MAAM;AAAA,EAC/B;AAAA,EACA,MAAM,UAAU,KAAK,KAAK,KAAK,CAAC;AAAA;AAIlC,IAAM,kBAAkB,CACtB,MACA,OACA,SACA,UACA,iBACS;AAAA,EACT,MAAM,OAAO,eAAe,MAAM,MAAM,KAAK;AAAA,EAG7C,IAAI,MAAM,UAAU,QAAQ,SAAS,MAAM;AAAA,IACzC,MAAM,QAAQ;AAAA,IACd,MAAM,YAAY,CAAC;AAAA,IACnB;AAAA,EACF;AAAA,EAKA,MAAM,UAAU,KAAK,UAAU;AAAA,EAC/B,IAAI,MAAM,WAAW;AAAA,IACnB,gBAAgB,SAAS,GAAG,KAAK;AAAA,IACjC,MAAM,YAAY,CAAC;AAAA,IACnB;AAAA,EACF;AAAA,EACA,IAAI,YAAY,KAAK,OAAO,GAAG;AAAA,IAC7B,gBAAgB,SAAS,UAAU,QAAQ,KAAK;AAAA,IAChD,MAAM,YAAY,CAAC;AAAA,IACnB;AAAA,EACF;AAAA,EACA,IAAI,MAAM,cAAc,GAAG;AAAA,IACzB,IAAI,aAAa,KAAK,IAAI,GAAG;AAAA,MAC3B,MAAM,eAAe;AAAA,IACvB;AAAA,IACA,MAAM,YAAY,CAAC;AAAA,IACnB;AAAA,EACF;AAAA,EACA,gBAAgB,MAAM,OAAO,SAAS,UAAU,YAAY;AAAA;AAgCvD,IAAM,WAAW,CAAC,SAA2B;AAAA,EAClD,MAAM,WAAsB,CAAC;AAAA,EAC7B,MAAM,UAAU,IAAI;AAAA,EACpB,MAAM,QAA0B;AAAA,IAC9B,aAAa,CAAC;AAAA,IACd,cAAc,CAAC;AAAA,IACf,OAAO;AAAA,IACP,MAAM;AAAA,IACN,WAAW,CAAC;AAAA,IACZ,gBAAgB;AAAA,IAChB,aAAa;AAAA,IACb,WAAW;AAAA,IACX,OAAO,CAAC;AAAA,EACV;AAAA,EAEA,QAAQ,OAAO,WAAW,wBAAwB,IAAI;AAAA,EACtD,MAAM,gBAAgB,oBAAoB,KAAK;AAAA,EAC/C,MAAM,eAAe,CAAC,UACpB,cAAc,IAAI,MAAM,YAAY,CAAC;AAAA,EACvC,YAAY,OAAO,SAAS,MAAM,QAAQ,GAAG;AAAA,IAC3C,MAAM,OAAO,QAAQ,SAAS;AAAA,IAC9B,gBAAgB,MAAM,OAAO,SAAS,UAAU,YAAY;AAAA,EAC9D;AAAA,EAEA,MAAM,UAAU,IAAI;AAAA,EAIpB,MAAM,SAAS,MAAM,YAAY,KAAK;AAAA,CAAI,EAAE,WAAW,cAAc,GAAG;AAAA,EACxE,WAAW,SAAS,OAAO,SAAS,OAAO,GAAG;AAAA,IAC5C,MAAM,KAAK,MAAM,QAAQ,UAAU,MAAM,QAAQ,OAAO,MAAM,QAAQ;AAAA,IACtE,IAAI,OAAO,WAAW;AAAA,MACpB,QAAQ,IAAI,EAAE;AAAA,IAChB;AAAA,EACF;AAAA,EACA,OAAO;AAAA,IACL,SAAS,CAAC,GAAG,OAAO;AAAA,IACpB,cAAc,MAAM;AAAA,IACpB;AAAA,IACA,OAAO,MAAM;AAAA,EACf;AAAA;AAGK,IAAM,kBAAkB,CAAC,SAC9B,SAAS,IAAI,EAAE;AAOjB,IAAM,UACJ;AAGK,IAAM,WACX;AAGK,IAAM,iBAAiB,CAC5B,SACA,QACA,UACW,QAAQ,SAAS,KAAK,OAAO,UAAU,KAAK,OAAO;AAShE,IAAM,eAAe,CACnB,MACA,YACA,OACA,UACe;AAAA,EACf,MAAM,OAAO,eAAe,MAAM,KAAK;AAAA,EAEvC,IAAI,UAAU,QAAQ,SAAS,MAAM;AAAA,IACnC,OAAO;AAAA,EACT;AAAA,EAGA,MAAM,SAAS,KAAK,WAAW,aAAa,CAAC,SAC3C,IAAI,OAAO,KAAK,MAAM,CACxB;AAAA,EACA,WAAW,SAAS,OAAO,SAAS,OAAO,GAAG;AAAA,IAC5C,MAAM,SAAS,MAAM,QAAQ;AAAA,IAC7B,IAAI,WAAW,aAAa,MAAM,UAAU,WAAW;AAAA,MACrD;AAAA,IACF;AAAA,IAIA,MAAM,eAAe,eAAe,MAAM,IAAI,QAAQ,MAAM,QAAQ,KAAK;AAAA,IACzE,MAAM,QAAkB;AAAA,MACtB,QAAQ,MAAM,QAAQ,eAAe;AAAA,MACrC,MAAM;AAAA,MACN;AAAA,IACF;AAAA,IAIA,IAAI,OAAO,MAAM,QAAQ,OAAO,KAAK;AAAA,MACnC,MAAM,QAAQ;AAAA,IAChB;AAAA,IACA,MAAM,KAAK,KAAK;AAAA,IAGhB,MAAM,QAAQ,MAAM,GAAG,MAAM,GAAG,eAAe,KAAK,MAAM;AAAA,IAC1D,WAAW,SAAS,MAAM,SAAS,QAAQ,GAAG;AAAA,MAC5C,MAAM,cAAc,MAAM,QAAQ;AAAA,MAClC,IAAI,gBAAgB,aAAa,MAAM,UAAU,WAAW;AAAA,QAC1D;AAAA,MACF;AAAA,MACA,MAAM,KAAK;AAAA,QACT,QACE,MAAM,QACN,MAAM,QACN,eAAe,MAAM,IAAI,aAAa,MAAM,QAAQ,KAAK,IACzD;AAAA,QACF,OAAO;AAAA,QACP,MAAM;AAAA,QACN,QAAQ;AAAA,MACV,CAAC;AAAA,IACH;AAAA,EACF;AAAA,EACA,OAAO;AAAA;AAGF,IAAM,eAAe,CAAC,MAAc,aAAa,MAAkB;AAAA,EACxE,MAAM,QAAoB,CAAC;AAAA,EAC3B,IAAI,QAAoB;AAAA,EACxB,IAAI,aAAa;AAAA,EAEjB,WAAW,QAAQ,KAAK,MAAM;AAAA,CAAI,GAAG;AAAA,IACnC,cAAc;AAAA,IACd,QAAQ,aAAa,MAAM,YAAY,OAAO,KAAK;AAAA,EACrD;AAAA,EAEA,OAAO;AAAA;AAOT,IAAM,gBAAgB;AACtB,IAAM,WAAW;AASjB,IAAM,cAAc,CAClB,MACA,OACA,SACe;AAAA,EACf,MAAM,OAAO,eAAe,MAAM,KAAK;AAAA,EAEvC,IAAI,UAAU,QAAQ,SAAS,MAAM;AAAA,IACnC,OAAO;AAAA,EACT;AAAA,EACA,MAAM,QAAQ,KAAK,WAAW,aAAa,EAAE,EAAE,WAAW,eAAe,EAAE;AAAA,EAC3E,WAAW,SAAS,MAAM,SAAS,QAAQ,GAAG;AAAA,IAC5C,MAAM,MAAM,MAAM,QAAQ;AAAA,IAC1B,IAAI,KAAK;AAAA,MACP,KAAK,IAAI,GAAG;AAAA,IACd;AAAA,EACF;AAAA,EACA,OAAO;AAAA;AAGF,IAAM,uBAAuB,CAAC,SAA2B;AAAA,EAC9D,MAAM,OAAO,IAAI;AAAA,EACjB,IAAI,QAAoB;AAAA,EACxB,WAAW,QAAQ,KAAK,MAAM;AAAA,CAAI,GAAG;AAAA,IACnC,QAAQ,YAAY,MAAM,OAAO,IAAI;AAAA,EACvC;AAAA,EACA,OAAO,CAAC,GAAG,IAAI;AAAA;AASV,IAAM,qBAAqB,CAChC,KACA,SAEA,MAAM,KAAK,IAAI,GAAG,IAAI,MAAM;AAAA,CAAI,EAAE,SAAS,KAAK,MAAM;AAAA,CAAI,EAAE,MAAM,IAAI;AAQxE,IAAM,qBAAqB,CACzB,OACA,SACA,eAEA,MAAM,IAAI,CAAC,SAAS;AAAA,EAClB,MAAM,SAAS,QAAQ,KAAK,OAAO;AAAA,EACnC,IAAI,CAAC,QAAQ;AAAA,IACX,OAAO;AAAA,EACT;AAAA,EACA,MAAM,WAAqB,KAAK,MAAM,MAAM,OAAO,KAAK;AAAA,EACxD,IAAI,OAAO,SAAS,YAAY;AAAA,IAC9B,SAAS,OAAO,OAAO;AAAA,EACzB;AAAA,EACA,OAAO;AAAA,CACR;AAGH,IAAM,gBAAgB,CAAC,UACrB,MAAM,YAAY,MAAM,SAAS,SAAS,SAAS,IAC/C,MAAM,SAAS,WACf;AAQN,IAAM,aAAa,CAAC,UAClB,MAAM,WACF,mBACE,aAAa,MAAM,SAAS,IAAI,GAChC,MAAM,SAAS,SACf,MAAM,UACR,IACA,aACE,MAAM,KAAK,MACX,mBAAmB,MAAM,KAAK,MAAM,KAAK,IAAI,CAC/C;AAWN,IAAM,yBAAyB,CAC7B,OACA,SACA,eAEA,QAAQ,IAAI,GAAG,IAAI,WAAW;AAAA,EAC5B,MAAM,SAAS,MAAM,UAAU,QAAQ,OAAO;AAAA,EAC9C,MAAM,OAAO,MAAM,cAAc,GAAG,cAAc,MAAM;AAAA,EACxD,MAAM,YAAY,WAAW,aAAa,OAAO,SAAS,MAAM;AAAA,EAChE,OAAO;AAAA,IACL,MAAM;AAAA,IACN,MAAM,QAAQ,QAAQ;AAAA,IACtB,MACE,QAAQ,QAAQ,OAAO,mBAAmB,MAAM,KAAK,MAAM,KAAK,IAAI;AAAA,IACtE,SAAS,YACL,OAAO,8DAA8D,gDACrE,OAAO;AAAA,IACX,UAAU;AAAA,IACV,YAAY,aAAa,+BAA+B;AAAA,EAC1D;AAAA,CACD;AAEH,IAAM,cAAc,CAClB,MACA,UACA,OACW;AAAA,EACX,IAAI,KAAK,OAAO;AAAA,IACd,OAAO,KAAK;AAAA,EACd;AAAA,EACA,MAAM,eAAe,SAAS,KAAK,CAAC,MAAM,EAAE,UAAU,CAAC,KAAK,SAAS;AAAA,EACrE,IAAI,cAAc;AAAA,IAChB,OAAO,aAAa;AAAA,EACtB;AAAA,EACA,MAAM,OAAO,GAAG,MAAM,GAAG,EAAE,IAAI,KAAK;AAAA,EACpC,OAAO,UAAU,mBAAmB,KAAK,QAAQ,SAAQ,IAAI,GAAG,EAAE,CAAC,CAAC;AAAA;AAItE,IAAM,cAAc,CAAC,UAA0B,SAAS,OAAO,GAAG;AAG3D,IAAM,gBAAgB,CAC3B,UACoB,OAAO,UAAU;AAGvC,IAAM,aAAa,CAAC,QAA4B,SAAyB;AAAA,EACvE,MAAM,QAAQ,SAAS,YAAY,MAAM,IAAI;AAAA,EAC7C,OAAO,QAAQ,GAAG,SAAS,SAAS;AAAA;AAgC/B,IAAM,gBAAgB,CAC3B,KACA,KACA,QACmB;AAAA,EACnB,QAAQ,SAAS,SAAS,IAAI,WAC1B,iBAAiB,KAAK,IAAI,QAAQ,IAClC,EAAE,MAAM,KAAK,SAAS,GAAG;AAAA,EAC7B,QAAQ,SAAS,YAAY,IAAI,OAC7B,gBAAgB,MAAM,KAAK,IAAI,IAAI,IACnC,EAAE,SAAS,CAAC,EAAE,GAAG,SAAS,KAAK;AAAA,EACnC,OAAO,EAAE,SAAS,SAAS,QAAQ;AAAA;AAsC9B,IAAM,oBAAoB,CAC/B,OACA,KACA,iBACA,QACe;AAAA,EACf,QAAQ,SAAS,SAAS,YAAY,cAAc,MAAM,KAAK,KAAK,GAAG;AAAA,EACvE,MAAM,YAAY,mBAAmB,MAAM;AAAA,EAC3C,MAAM,OAAO,YAAY,YAAY,SAAS,IAAI;AAAA,EAClD,MAAM,aAAa,WAAW,IAAI,QAAQ,OAAO,GAAG,OAAO,QAAQ,OAAO;AAAA,EAC1E,QAAQ,UAAU,QAAQ,OAAO,eAAe,SAAS,UAAU;AAAA,EACnE,OAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA,cAAc,gBAAgB,YAAY,OAAO;AAAA,IACjD,SAAS,WAAW,IAAI,QAAQ,OAAO;AAAA,IACvC;AAAA,IACA;AAAA,IACA;AAAA,EACF;AAAA;AAIK,IAAM,iBAAiB,CAC5B,cACA,QACA,SACY,OAAO,cAAc,cAAc,QAAQ,IAAI,IAAI;AAejE,IAAM,eAAe,CACnB,YAEA,OAAO,YAAY,YAAY,YAAY;AAE7C,IAAM,cAAc,CAAC,QACnB,OAAO,QAAQ;AAGjB,IAAM,cAAa,CACjB,YACoB;AAAA,EACpB,MAAM,MAAM,aAAa,OAAO,IAAI,QAAQ,MAAM;AAAA,EAClD,OAAO,YAAY,GAAG,IAAI,OAAO,GAAG,IAAI;AAAA;AAY1C,IAAM,qBAAqB,CACzB,MACA,WACwB;AAAA,EACxB,MAAM,SAA4C,CAAC;AAAA,EACnD,MAAM,SAA2B,CAAC;AAAA,EAClC,YAAY,KAAK,WAAW,OAAO,QAAQ,MAAM,GAAG;AAAA,IAClD,MAAM,UAAU,OAAO,aAAa,SAAS,KAAK,IAAI;AAAA,IACtD,IAAI,mBAAmB,SAAS;AAAA,MAC9B,OAAO,KAAK;AAAA,QACV,SAAS;AAAA,QACT,MAAM,CAAC,GAAG;AAAA,MACZ,CAAC;AAAA,MACD;AAAA,IACF;AAAA,IACA,IAAI,QAAQ,WAAW,WAAW;AAAA,MAChC,OAAO,KACL,GAAG,QAAQ,OAAO,IAAI,CAAC,WAAW;AAAA,QAChC,SAAS,MAAM;AAAA,QACf,MAAM,CAAC,KAAK,IAAI,MAAM,QAAQ,CAAC,GAAG,IAAI,WAAU,CAAC;AAAA,MACnD,EAAE,CACJ;AAAA,MACA;AAAA,IACF;AAAA,IAGA,IAAI,QAAQ,UAAU,aAAa,OAAO,OAAO,MAAM,GAAG,GAAG;AAAA,MAC3D,OAAO,OAAO,QAAQ;AAAA,IACxB;AAAA,EACF;AAAA,EACA,OAAO;AAAA,IACL,QAAQ,OAAO,KAAK,MAAM,EAAE,SAAS,IAAI,SAAS;AAAA,IAClD;AAAA,EACF;AAAA;AAYF,IAAM,iBAAiB,CACrB,OACA,QAGiD;AAAA,EAIjD,MAAM,YAAY,cAAc,MAAM,KAAK,IAAI,IAC3C,MAAM,KAAK,OACX,IAAI;AAAA,EACR,MAAM,aAAa,IAAI,kBAAkB;AAAA,EAGzC,MAAM,SACJ,IAAI,qBAAqB,aACrB,KAAK,IAAI,sBAAsB,WAAW,IAC1C;AAAA,EACN,MAAM,QAAQ,SACV,OAAO,YACL,OAAO,QAAQ,MAAM,IAAI,EAAE,OACzB,EAAE,SAAS,CAAC,OAAO,OAAO,QAAQ,GAAG,CACvC,CACF,IACA,MAAM;AAAA,EAEV,MAAM,SAAS,eAAe,UAAU,KAAK;AAAA,EAC7C,MAAM,eAAe,SAAS,mBAAmB,MAAM,MAAM,MAAM,IAAI;AAAA,EAEvE,IAAI,OAAO,YAAY,cAAc,OAAO,UAAU,OAAO,GAAG;AAAA,IAC9D,OAAO,EAAE,QAAQ,cAAc,QAAQ,MAAM,OAAO,KAAK;AAAA,EAC3D;AAAA,EAKA,MAAM,SACJ,MAAM,QACL,MAAM,cAAc,WAAW,MAAM,UAAU,IAC5C,aAAa,MAAM,YAAY,OAAO,IACtC;AAAA,EACN,MAAM,WAAW;AAAA,IACf,MAAM;AAAA,IACN,MAAM,MAAM,cAAc,GAAG,IAAI,OAAO,QAAQ,MAAM;AAAA,IACtD;AAAA,EACF;AAAA,EACA,OAAO;AAAA,IACL,aAAa;AAAA,MACX,GAAI,OAAO,UAAU,CAAC,IAAI,mBAAmB,OAAO,OAAO,QAAQ;AAAA,MACnE,GAAI,eACA,sBAAsB,aAAa,QAAQ,QAAQ,IACnD,CAAC;AAAA,IACP;AAAA,EACF;AAAA;AAcK,IAAM,iBAAiB,CAC5B,OACA,QACoB;AAAA,EACpB,QAAQ,WAAW,MAAM;AAAA,EACzB,MAAM,MAAM,WAAW,QAAQ,SAAS;AAAA,EAExC,MAAM,SAAS,eAAe,OAAO,GAAG;AAAA,EACxC,IAAI,OAAO,aAAa;AAAA,IACtB,OAAO,EAAE,aAAa,OAAO,aAAa,OAAO,CAAC,EAAE;AAAA,EACtD;AAAA,EAEA,QAAQ,SAAS;AAAA,EAKjB,IAAI,KAAK,QAAQ;AAAA,IACf,KAAK,QAAQ,SAAS;AAAA,EACxB;AAAA,EACA,IAAI,KAAK,SAAS;AAAA,IAChB,KAAK,IAAI,UAAU;AAAA,EACrB;AAAA,EAEA;AAAA,IACE;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,MACE,kBAAkB,OAAO,KAAK,KAAK,MAAM;AAAA,IAC3C,MAAM,IAAI;AAAA,IACV,QAAQ,IAAI,OAAO;AAAA,IACnB,UAAU,IAAI;AAAA,EAChB,CAAC;AAAA,EAID,MAAM,WAAW,MAAM,UAAU,QAAQ,MAAM,KAAK;AAAA,EACpD,QAAQ,SAAS,cAAc,aAAa,SAAS,QAAQ;AAAA,EAC7D,QAAQ,WAAW,IAAI;AAAA,EAEvB,MAAM,OAAO;AAAA,IACX;AAAA,IACA,MAAM,SAAS,EAAE,QAAQ,MAAM,MAAM,OAAO,MAAM,KAAK,KAAK,IAAI;AAAA,IAChE,YAAY,SAAS,WAAW;AAAA,IAChC,gBACE,WAAW,QAAQ,qBAAqB,QAAQ,IAAI;AAAA,IACtD,aAAa,KAAK,QAAQ,IAAI;AAAA,IAC9B,QAAQ,OAAO;AAAA,IACf,aAAa,KAAK;AAAA,IAClB,SAAS,MAAM;AAAA,IACf,SAAS,SAAS,GAAG,IAAI,OAAO,QAAQ,MAAM,QAAQ;AAAA,IACtD;AAAA,IACA;AAAA,IACA;AAAA,IACA,IAAI,GAAG,IAAI,OAAO,QAAQ,MAAM;AAAA,IAChC,UAAU,cAAc,KAAK;AAAA,IAC7B,cAAc,KAAK,gBAAgB,MAAM;AAAA,IACzC,OAAO,WAAW,KAAK;AAAA,IACvB;AAAA,IACA;AAAA,IACA;AAAA,IACA,QAAQ,EAAE,MAAM,IAAI,OAAO,MAAM,KAAK,MAAM,IAAI;AAAA,IAChD,YAAY,MAAM;AAAA,IAClB,OAAO,YAAY,MAAM,UAAU,OAAO;AAAA,IAC1C,gBAAgB;AAAA,IAChB;AAAA,IACA;AAAA,EACF;AAAA,EAOA,MAAM,QAAQ,QAAQ,IAAI,CAAC,YAAY;AAAA,OAClC;AAAA,IACH;AAAA,IACA,OAAO,aACL,IAAI,YAAY,IAChB,eAAe,cAAc,QAAQ,IAAI,IAAI,CAC/C;AAAA,EACF,EAAE;AAAA,EAEF,OAAO;AAAA,IACL,aACE,WAAW,QACP,uBAAuB,OAAO,cAAc,IAAI,OAAO,IAAI,IAC3D,CAAC;AAAA,IACP;AAAA,EACF;AAAA;;;AJzpCF,IAAM,qBAAqB,IAAI,IAAI,CAAC,OAAO,MAAM,CAAC;AAelD,IAAM,oBACJ;AAGF,IAAM,oBACJ;AAMK,IAAM,uBAAuB,CAAC,SACnC,KAAK,SAAS,UAAU;AAI1B,IAAM,gBAAgB;AAItB,IAAM,oBAAmB;AAGzB,IAAM,qBAAqB;AAAA,EACzB,IAAI,EAAE,OAAO,OAAO,MAAM,OAAO;AAAA,EACjC,KAAK,EAAE,OAAO,OAAO,MAAM,MAAM;AACnC;AAQA,IAAM,sBAAsB,CAC1B,MACA,YACA,cACY;AAAA,EACZ,IAAI,SAAS;AAAA,EACb,IAAI,SAAS;AAAA,EACb,UAAS;AAAA,IACP,MAAM,SAAS,SAAS,WAAW,QAAQ,WAAW;AAAA,IACtD,MAAM,QAAQ,KAAK,QAAQ,QAAQ,MAAM;AAAA,IACzC,IAAI,UAAU,IAAI;AAAA,MAChB,OAAO;AAAA,IACT;AAAA,IACA,SAAS,CAAC;AAAA,IACV,SAAS,QAAQ,OAAO;AAAA,EAC1B;AAAA;AAsBK,IAAM,wBAAwB,CACnC,SAC4B;AAAA,EAC5B,MAAM,QAAQ,kBAAkB,KAAK,IAAI;AAAA,EACzC,IAAI,CAAC,OAAO,QAAQ;AAAA,IAClB,OAAO;AAAA,EACT;AAAA,EACA,MAAM,aAAgC,CAAC;AAAA,EACvC,WAAW,SAAS,MAAM,OAAO,SAAS,IAAI,SAAS,iBAAiB,GAAG;AAAA,IACzE,MAAM,OAAO,KAAK,QAAQ;AAAA,IAC1B,MAAM,QAAQ,KAAK,QAAQ,UAAU,KAAK,QAAQ;AAAA,IAClD,IAAI,SAAS,UAAU,OAAO;AAAA,MAC5B,WAAW,OAAO;AAAA,IACpB;AAAA,IACA,IAAI,SAAS,UAAU,OAAO;AAAA,MAC5B,WAAW,OAAO;AAAA,IACpB;AAAA,EACF;AAAA,EACA,OAAO,EAAE,YAAY,QAAQ,MAAM,OAAO,UAAU,GAAG;AAAA;AASlD,IAAM,mBAAmB,CAAC,SAC/B,cAAc,KAAK,IAAI,IAAI,OAAO,sBAAsB,KAAK,KAAK,CAAC;AAwCrE,IAAM,kBACJ;AAEF,IAAM,eAAe,CACnB,MACA,SACA,MACA,MACA,gBACgB,EAAE,MAAM,MAAM,MAAM,SAAS,UAAU,SAAS,WAAW;AAO7E,IAAM,cAAc,CAAC,MAAc,SAA0B;AAAA,EAC3D,MAAM,MAAM,SAAS,MAAM,IAAI;AAAA,EAC/B,OAAO,QAAQ,QAAQ,IAAI,WAAW,KAAK;AAAA;AAY7C,IAAM,qBAAqB,CACzB,QACA,UACA,KACA,SAC6C;AAAA,EAC7C,IAAI,OAAO,WAAW,GAAG,GAAG;AAAA,IAC1B,IAAI,CAAC,IAAI,aAAa;AAAA,MACpB,OAAO;AAAA,QACL,OAAO,aACL,8BACA,kBAAkB,oEAClB,UACA,MACA,oDACF;AAAA,MACF;AAAA,IACF;AAAA,IACA,MAAM,OAAO,KAAK,IAAI,aAAa,MAAM;AAAA,IACzC,IAAI,YAAY,IAAI,aAAa,IAAI,GAAG;AAAA,MACtC,OAAO;AAAA,QACL,OAAO,aACL,8BACA,kBAAkB,6CAClB,UACA,MACA,sFACF;AAAA,MACF;AAAA,IACF;AAAA,IACA,OAAO,EAAE,KAAK;AAAA,EAChB;AAAA,EACA,MAAM,OAAO,QAAQ,QAAQ,QAAQ,GAAG,MAAM;AAAA,EAC9C,IAAI,IAAI,eAAe,YAAY,IAAI,aAAa,IAAI,GAAG;AAAA,IACzD,OAAO;AAAA,MACL,OAAO,aACL,8BACA,kBAAkB,6CAClB,UACA,MACA,oGACF;AAAA,IACF;AAAA,EACF;AAAA,EACA,OAAO,EAAE,KAAK;AAAA;AAUhB,IAAM,kBAAkB,CACtB,MACA,SACA,UACW;AAAA,EACX,MAAM,SAAS,KAAK,WAAW,aAAa,CAAC,SAC3C,IAAI,OAAO,KAAK,MAAM,CACxB;AAAA,EACA,IAAI,MAAM;AAAA,EACV,IAAI,SAAS;AAAA,EACb,WAAW,SAAS,OAAO,SAAS,QAAQ,GAAG;AAAA,IAC7C,MAAM,SAAS,MAAM,QAAQ,UAAU;AAAA,IAGvC,IACE,OAAO,WAAW,GAAG,KACrB,OAAO,WAAW,GAAG,KACrB,IAAI,SAAS,MAAM,GACnB;AAAA,MACA;AAAA,IACF;AAAA,IACA,MAAM,UAAU,SAAS,OAAO,QAAQ,SAAS,MAAM,CAAC;AAAA,IACxD,MAAM,MAAM,QAAQ,WAAW,GAAG,IAAI,UAAU,KAAK;AAAA,IACrD,MAAM,UACH,MAAM,SAAS,KAChB,eAAe,MAAM,IAAI,QAAQ,MAAM,QAAQ,KAAK;AAAA,IACtD,OAAO,KAAK,MAAM,QAAQ,MAAM,IAAI;AAAA,IACpC,SAAS,SAAS,OAAO;AAAA,EAC3B;AAAA,EACA,OAAO,MAAM,KAAK,MAAM,MAAM;AAAA;AAIhC,IAAM,eAAe,CACnB,OACA,SACA,UACa;AAAA,EACb,IAAI,YAAY,OAAO;AAAA,IACrB,OAAO;AAAA,EACT;AAAA,EACA,IAAI,QAAoB;AAAA,EACxB,OAAO,MAAM,IAAI,CAAC,SAAS;AAAA,IACzB,MAAM,OAAO,eAAe,MAAM,KAAK;AAAA,IACvC,MAAM,UAAU,UAAU,QAAQ,SAAS;AAAA,IAC3C,QAAQ;AAAA,IACR,OAAO,UAAU,OAAO,gBAAgB,MAAM,SAAS,KAAK;AAAA,GAC7D;AAAA;AAKH,IAAM,iBAAiB,CACrB,SACA,MACA,eACa;AAAA,EACb,MAAM,OAAO,QAAQ,QAAQ,QAAQ,EAAE;AAAA,EACvC,IAAI,aAAa;AAAA,EACjB,WAAW,OAAO,KAAK,MAAM,MAAM,KAAK,CAAC,GAAG;AAAA,IAC1C,aAAa,KAAK,IAAI,YAAY,IAAI,MAAM;AAAA,EAC9C;AAAA,EACA,MAAM,QAAQ,IAAI,OAAO,KAAK,IAAI,GAAG,aAAa,CAAC,CAAC;AAAA,EACpD,MAAM,MAAM,SAAQ,IAAI;AAAA,EACxB,MAAM,OAAO,WAAW,SAAS,MAAM,IAAI,MAAM,CAAC,IAAI;AAAA,EACtD,MAAM,OAAO,WAAW,OAAO,IAAI,WAAW,SAAS;AAAA,EACvD,OAAO,CAAC,GAAG,QAAQ,OAAO,QAAQ,GAAG,KAAK,MAAM;AAAA,CAAI,GAAG,KAAK;AAAA;AAc9D,IAAM,kBAAkB,OACtB,WACA,UACA,MACA,KACA,UACmD;AAAA,EACnD,MAAM,WAAW,mBAAmB,UAAU,QAAQ,UAAU,KAAK,IAAI;AAAA,EACzE,IAAI,WAAW,UAAU;AAAA,IACvB,OAAO;AAAA,EACT;AAAA,EACA,QAAQ,SAAS;AAAA,EACjB,IAAI,MAAM,SAAS,IAAI,GAAG;AAAA,IACxB,OAAO;AAAA,MACL,OAAO,aACL,uBACA,qBAAqB,CAAC,GAAG,OAAO,IAAI,EACjC,IAAI,CAAC,UAAU,SAAS,IAAI,eAAe,QAAQ,IAAI,GAAG,KAAK,CAAC,EAChE,KAAK,MAAM,MACd,UACA,MACA,oDACF;AAAA,IACF;AAAA,EACF;AAAA,EACA,IAAI;AAAA,EACJ,IAAI;AAAA,IACF,UAAU,MAAM,SAAS,MAAM,OAAO;AAAA,IACtC,OAAO,OAAO;AAAA,IACd,OAAO;AAAA,MACL,OAAO,aACL,2BACA,kBAAkB,UAAU,mCAAmC,UAC7D,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,MAEvD,UACA,MACA,eACF;AAAA,IACF;AAAA;AAAA,EAEF,IAAI,SAAS,IAAI,IAAI;AAAA,EAErB,MAAM,YACJ,CAAC,UAAU,WAAW,QACtB,mBAAmB,IAAI,SAAQ,IAAI,EAAE,YAAY,CAAC;AAAA,EACpD,IAAI,CAAC,WAAW;AAAA,IACd,MAAM,QAAQ,eAAe,SAAS,MAAM,UAAU,UAAU;AAAA,IAGhE,OAAO;AAAA,MACL;AAAA,MACA,SAAS,MAAM,IAAI,CAAC,GAAG,OAAO,EAAE,MAAM,MAAM,MAAM,KAAK,IAAI,GAAG,CAAC,EAAE,EAAE;AAAA,IACrE;AAAA,EACF;AAAA,EAEA,MAAM,SAAS,oBAAO,OAAO;AAAA,EAC7B,MAAM,OAAO,OAAO;AAAA,EACpB,MAAM,iBAAiB,KAAK,IAC1B,GACA,QAAQ,MAAM;AAAA,CAAI,EAAE,SAAS,KAAK,MAAM;AAAA,CAAI,EAAE,MAChD;AAAA,EAIA,MAAM,WAAW,MAAM,YAAY,MAAM,MAAM,gBAAgB,KAAK;AAAA,IAClE,GAAG;AAAA,IACH;AAAA,EACF,CAAC;AAAA,EAID,MAAM,QAAQ,CAAC,GAAG,SAAS,KAAK;AAAA,EAChC,MAAM,UAAU,CAAC,GAAG,SAAS,OAAO;AAAA,EACpC,OAAO,MAAM,GAAG,EAAE,GAAG,KAAK,MAAM,IAAI;AAAA,IAClC,MAAM,IAAI;AAAA,IACV,QAAQ,IAAI;AAAA,EACd;AAAA,EACA,OAAO,MAAM,IAAI,KAAK,MAAM,IAAI;AAAA,IAC9B,MAAM,MAAM;AAAA,IACZ,QAAQ,MAAM;AAAA,EAChB;AAAA,EACA,OAAO;AAAA,IACL,OAAO,aAAa,OAAO,QAAQ,IAAI,GAAG,QAAQ,QAAQ,CAAC;AAAA,IAC3D;AAAA,EACF;AAAA;AAUF,IAAM,cAAc,OAClB,MACA,UACA,YACA,KACA,UAC2B;AAAA,EAC3B,MAAM,cAAc,KAAK,MAAM;AAAA,CAAI;AAAA,EACnC,MAAM,QAAQ,SAAQ,QAAQ,EAAE,YAAY,MAAM;AAAA,EAClD,MAAM,aAAa,mBAAmB,QAAQ,QAAQ;AAAA,EAOtD,IAAI,QAAoB;AAAA,EACxB,IAAI,YAAY;AAAA,EAChB,MAAM,aAAa,YAAY,IAAI,CAAC,MAAM,UAAU;AAAA,IAClD,IAAI,WAAW;AAAA,MACb,YAAY,oBAAoB,MAAM,YAAY,IAAI;AAAA,MACtD,OAAO;AAAA,IACT;AAAA,IACA,MAAM,OAAO,eAAe,MAAM,KAAK;AAAA,IACvC,MAAM,UAAU,UAAU,QAAQ,SAAS;AAAA,IAC3C,QAAQ;AAAA,IACR,IAAI,SAAS;AAAA,MACX,OAAO;AAAA,IACT;AAAA,IACA,YAAY,oBAAoB,MAAM,YAAY,KAAK;AAAA,IACvD,IAAI,aAAa,CAAC,qBAAqB,IAAI,GAAG;AAAA,MAC5C,OAAO;AAAA,IACT;AAAA,IACA,MAAM,YAAY,QACd,sBAAsB,KAAK,KAAK,CAAC,IACjC,iBAAiB,IAAI;AAAA,IAGzB,IACE,aACA,CAAC,SACD,kBAAiB,KAAK,YAAY,QAAQ,MAAM,EAAE,GAClD;AAAA,MACA,OAAO;AAAA,IACT;AAAA,IACA,OAAO;AAAA,GACR;AAAA,EAID,MAAM,aAAa,MAAM,QAAQ,IAC/B,WAAW,IAAI,CAAC,WAAW,UACzB,YACI,gBACE,WACA,UACA,aAAa,QAAQ,GACrB,KACA,KACF,IACA,IACN,CACF;AAAA,EAIA,MAAM,QAAkB,CAAC;AAAA,EACzB,MAAM,UAAwB,CAAC;AAAA,EAC/B,YAAY,OAAO,SAAS,YAAY,QAAQ,GAAG;AAAA,IACjD,MAAM,UAAU,aAAa,QAAQ;AAAA,IACrC,MAAM,WAAW,WAAW;AAAA,IAC5B,IAAI,CAAC,YAAY,WAAW,UAAU;AAAA,MACpC,IAAI,UAAU;AAAA,QACZ,IAAI,OAAO,KAAK,SAAS,KAAK;AAAA,MAChC;AAAA,MACA,MAAM,KAAK,IAAI;AAAA,MACf,QAAQ,KAAK,EAAE,MAAM,UAAU,MAAM,QAAQ,CAAC;AAAA,MAC9C;AAAA,IACF;AAAA,IACA,MAAM,MAAM,MAAY;AAAA,MACtB,MAAM,KAAK,EAAE;AAAA,MACb,QAAQ,KAAK,EAAE,MAAM,UAAU,MAAM,QAAQ,CAAC;AAAA;AAAA,IAEhD,IAAI,MAAM,GAAG,EAAE,GAAG,KAAK,GAAG;AAAA,MACxB,IAAI;AAAA,IACN;AAAA,IACA,MAAM,KAAK,GAAG,SAAS,KAAK;AAAA,IAC5B,QAAQ,KAAK,GAAG,SAAS,OAAO;AAAA,IAChC,IAAI,YAAY,QAAQ,IAAI,KAAK,GAAG;AAAA,MAClC,IAAI;AAAA,IACN;AAAA,EACF;AAAA,EACA,OAAO,EAAE,OAAO,QAAQ;AAAA;AAUnB,IAAM,iBAAiB,OAC5B,MACA,YAQ8B;AAAA,EAC9B,MAAM,MAAqB;AAAA,IACzB,aAAa,QAAQ;AAAA,IACrB,QAAQ,CAAC;AAAA,IACT,UAAU,IAAI;AAAA,EAChB;AAAA,EACA,QAAQ,OAAO,YAAY,MAAM,YAC/B,MACA,QAAQ,YACR,QAAQ,cAAc,GACtB,KACA,CAAC,QAAQ,UAAU,CACrB;AAAA,EACA,OAAO;AAAA,IACL,QAAQ,IAAI;AAAA,IACZ,UAAU,CAAC,GAAG,IAAI,QAAQ;AAAA,IAC1B;AAAA,IACA,MAAM,MAAM,KAAK;AAAA,CAAI;AAAA,EACvB;AAAA;AAwCK,IAAM,oBAAoB,CAC/B,UACG;AAAA,EACH,MAAM,QAAkC,CAAC;AAAA,EACzC,WAAW,QAAQ,OAAO;AAAA,IACxB,QAAQ,eAAe;AAAA,IACvB,IAAI,CAAC,YAAY;AAAA,MACf;AAAA,IACF;AAAA,IACA,WAAW,WAAW,KAAK,YAAY,CAAC,GAAG;AAAA,MACzC,MAAM,YAAY,MAAM,YAAY,CAAC;AAAA,MACrC,MAAM,WAAW;AAAA,MACjB,IAAI,CAAC,UAAU,SAAS,UAAU,GAAG;AAAA,QACnC,UAAU,KAAK,UAAU;AAAA,MAC3B;AAAA,IACF;AAAA,EACF;AAAA,EACA,OAAO;AAAA;;;AMnnBT;AAEA,qBAAS;AAiBT,IAAM,oBAAoB,IAAI,IAAI;AAAA,EAChC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAED,IAAM,SAAS,MACb,OAAO,YACL,OAAO,QAAQ,QAAQ,GAAG,EAAE,OAAO,EAAE,SAAS,CAAC,kBAAkB,IAAI,GAAG,CAAC,CAC3E;AAGK,IAAM,4BAA4B,CACvC,UACyB;AAAA,EACzB,IAAI,UAAU,OAAO;AAAA,IACnB,OAAO,EAAE,SAAS,OAAO,QAAQ,MAAM;AAAA,EACzC;AAAA,EACA,IAAI,UAAU,MAAM;AAAA,IAClB,OAAO,EAAE,SAAS,MAAM,QAAQ,MAAM;AAAA,EACxC;AAAA,EACA,OAAO,EAAE,SAAS,MAAM,QAAQ,MAAM,KAAK;AAAA;AAStC,IAAM,cAAc,CAAC,WAAwC;AAAA,EAClE,MAAM,QAAQ,IAAI;AAAA,EAClB,IAAI,UAAyB;AAAA,EAC7B,WAAW,QAAQ,OAAO,MAAM;AAAA,CAAI,GAAG;AAAA,IACrC,IAAI,KAAK,WAAW,MAAI,GAAG;AAAA,MACzB,UAAU,KAAK,MAAM,CAAC;AAAA,IACxB,EAAO,SAAI,QAAQ,WAAW,CAAC,MAAM,IAAI,IAAI,GAAG;AAAA,MAC9C,MAAM,IAAI,MAAM,OAAO;AAAA,IACzB;AAAA,EACF;AAAA,EACA,OAAO;AAAA;AASF,IAAM,oBAAoB,CAAC,SAAgC;AAAA,EAChE,IAAI;AAAA,IACF,OAAO,aAEL,OACA,CAAC,MAAM,MAAM,aAAa,iBAAiB,GAE3C,EAAE,UAAU,SAAS,KAAK,OAAO,GAAG,OAAO,CAAC,UAAU,QAAQ,QAAQ,EAAE,CAC1E,EAAE,KAAK;AAAA,IACP,MAAM;AAAA,IACN,OAAO;AAAA;AAAA;AAaJ,IAAM,uBAAuB,CAClC,MACA,cACA,aACA,mBACwB;AAAA,EAMxB,IAAI,YAAY,WAAW,KAAK,aAAa,WAAW,GAAG;AAAA,IACzD,OAAO,IAAI;AAAA,EACb;AAAA,EAGA,MAAM,UACJ,mBAAmB,YAAY,kBAAkB,IAAI,IAAI;AAAA,EAC3D,IAAI,YAAY,MAAM;AAAA,IACpB,OAAO,IAAI;AAAA,EACb;AAAA,EACA,IAAI;AAAA,IACF,MAAM,SAAS,aAEb,OACA;AAAA,MACE;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA,GAAG;AAAA,IACL,GACA,EAAE,UAAU,SAAS,KAAK,OAAO,GAAG,WAAW,MAAM,OAAO,KAAK,CACnE;AAAA,IACA,MAAM,aAAa,YAAY,MAAM;AAAA,IACrC,MAAM,SAAS,IAAI;AAAA,IACnB,WAAW,cAAc,aAAa;AAAA,MACpC,MAAM,MAAM,WAAW,IAAI,UAAS,SAAS,UAAU,CAAC;AAAA,MACxD,IAAI,KAAK;AAAA,QACP,OAAO,IAAI,YAAY,GAAG;AAAA,MAC5B;AAAA,IACF;AAAA,IACA,OAAO;AAAA,IACP,MAAM;AAAA,IACN,OAAO,IAAI;AAAA;AAAA;AASR,IAAM,yBAAyB,CAAC,SAA0B;AAAA,EAC/D,IAAI;AAAA,IACF,OACE,aAEE,OACA,CAAC,MAAM,MAAM,aAAa,yBAAyB,GAEnD;AAAA,MACE,UAAU;AAAA,MACV,KAAK,OAAO;AAAA,MACZ,OAAO,CAAC,UAAU,QAAQ,QAAQ;AAAA,IACpC,CACF,EAAE,KAAK,MAAM;AAAA,IAEf,MAAM;AAAA,IACN,OAAO;AAAA;AAAA;AAYJ,IAAM,6BAA6B,CACxC,MACA,iBACiB;AAAA,EACjB,IAAI,iBAAiB,KAAK,CAAC,uBAAuB,IAAI,GAAG;AAAA,IACvD,OAAO,CAAC;AAAA,EACV;AAAA,EACA,OAAO;AAAA,IACL;AAAA,MACE,MAAM;AAAA,MACN,SAAS,sEAAsE;AAAA,MAC/E,UAAU;AAAA,MACV,YACE;AAAA,IACJ;AAAA,EACF;AAAA;;;AClMF,8BAAmB,sBAAS;AAC5B;AASA,IAAM,aAAa;AAAA,EACjB;AAAA,EACA;AAAA,EACA;AAAA,EAEA;AAAA,EACA;AAAA,EACA;AACF;AAWA,IAAM,gBAAgB,CACpB,QAEA,OAAO,QAAQ;AAGjB,IAAM,cAAc,OAAO,QACzB,cAAc,GAAG,IAAI,MAAM,IAAI,IAAI;AAgBrC,IAAM,aAAa,CAAC,QAA4B,QAAwB;AAAA,EACtE,MAAM,QAAQ,SAAS,SAAS,QAAQ,GAAG,IAAI;AAAA,EAC/C,IAAI,CAAC,OAAO;AAAA,IACV,OAAO;AAAA,EACT;AAAA,EACA,OAAO,MAAM,GAAG,SAAS,QAAQ;AAAA;AAwB5B,IAAM,qBAAqB,OAChC,SACA,UAGI,CAAC,MAKD;AAAA,EACJ,MAAM,OAA2B,MAAM,QAAQ,OAAO,IAClD,UACA,CAAC,EAAE,MAAM,QAAQ,CAAC;AAAA,EACtB,MAAM,aAAa,IAAI,IAAI,QAAQ,UAAU;AAAA,EAC7C,MAAM,cAAc,IAAI,IAAI,QAAQ,WAAW;AAAA,EAE/C,MAAM,OAAO,mBAAmB;AAAA,EAChC,MAAM,OAAO,IAAI;AAAA,EACjB,MAAM,SAAS,IAAI;AAAA,EACnB,MAAM,cAA4B,CAAC;AAAA,EAInC,MAAM,YAAY,MAAM,QAAQ,IAC9B,KAAK,IAAI,OAAO,WAAW;AAAA,IACzB,MAAM,QAAQ,MAAM,KAAK,YAAY;AAAA,MACnC,UAAU;AAAA,MACV,KAAK,OAAO;AAAA,MAGZ,QAAQ,CAAC,sBAAsB,gBAAgB,YAAY;AAAA,MAC3D,WAAW;AAAA,IACb,CAAC;AAAA,IACD,MAAM,SAAS,MAAM,QAAQ,IAC3B,MAAM,IACJ,OACE,SAIG;AAAA,MACH,IAAI;AAAA,QACF,OAAO;AAAA,UACL;AAAA,UACA,IAAI;AAAA,UACJ,OAAO,MAAM,YAAY,MAAM,KAAK,IAAI,CAAC;AAAA,QAC3C;AAAA,QACA,OAAO,OAAO;AAAA,QAGd,OAAO,EAAE,OAAuB,MAAM,IAAI,MAAM;AAAA;AAAA,KAGtD,CACF;AAAA,IACA,OAAO,EAAE,QAAQ,OAAO;AAAA,GACzB,CACH;AAAA,EAEA,aAAa,QAAQ,YAAY,WAAW;AAAA,IAC1C,WAAW,SAAS,QAAQ;AAAA,MAC1B,MAAM,MAAM,UAAS,OAAO,MAAM,SAAQ,MAAM,IAAI,CAAC;AAAA,MAIrD,OAAO,SAAS,QAAQ,IAAI,MAAM,GAAG;AAAA,MACrC,MAAM,UAAU,QAAQ,YAAY,IAAI,IAAI,IAAI,OAAO;AAAA,MACvD,MAAM,eAAe,UAAU,OAAO,CAAC,QAAQ,IAAI,GAAG,IAAI;AAAA,MAC1D,OAAO,eAAe,cAAc;AAAA,MACpC,MAAM,SAAS,cAAc,WAAW,IAAI,UAAU,IAAI,aAAa;AAAA,MACvE,MAAM,QAAQ,SAAS,aAAa,cACjC,OAAO,OAAO,EACd,KAAK,GAAG;AAAA,MACX,MAAM,MAAM,CAAC,SAAS,QAAQ,WAAW,OAAO,QAAQ,IAAI,CAAC,EAC1D,OAAO,OAAO,EACd,KAAK,GAAG;AAAA,MAEX,IAAI,CAAC,MAAM,IAAI;AAAA,QACb,YAAY,KAAK;AAAA,UACf,MAAM;AAAA,UACN,MAAM,MAAM;AAAA,UACZ,SAAS,6BAA6B,MAAM,MAAM;AAAA,UAClD,UAAU;AAAA,QACZ,CAAC;AAAA,QACD;AAAA,MACF;AAAA,MAEA,MAAM,SAAS,iBAAiB,UAAU,MAAM,KAAK;AAAA,MACrD,IAAI,OAAO,SAAS;AAAA,QAClB,MAAM,SAAS,SAAS,MAAM,IAAI,EAAE,WAAW,SAAS,IACpD,SACA;AAAA,QACJ,OAAO,IAAI,KAAK,OAAO,IAAI;AAAA,MAC7B,EAAO;AAAA,QACL,YAAY,KACV,GAAG,mBAAmB,OAAO,OAAO;AAAA,UAClC,MAAM;AAAA,UACN,MAAM,MAAM;AAAA,QACd,CAAC,CACH;AAAA;AAAA,IAEJ;AAAA,EACF;AAAA,EAEA,OAAO,EAAE,aAAa,MAAM,OAAO;AAAA;;;AC1LrC,uBAAS,qBAAY,kBAAM;;;ACA3B;AACA,4BAAgB;AAEhB,iBAAS;AAaF,IAAM,WAAW,CAAC,SACvB,WAAW,QAAQ,EAAE,OAAO,IAAI,EAAE,OAAO,KAAK,EAAE,MAAM,GAAG,EAAE;AAGtD,IAAM,gBAAgB,CAAC,YAC5B,SACE,QACG,IAAI,CAAC,UAAU,GAAG,MAAM,OAAO,MAAM,QAAQ,SAAS,MAAM,KAAK,IAAI,GAAG,EACxE,KAAK,GAAG,CACb;AAaK,IAAM,eACX,CACE,MACA,iBACA,SAEF,CAAC,aAAa;AAAA,EACZ,IAAI,OAAO;AAAA,EACX,IAAI,MAAM;AAAA,IACR,MAAM,eAAe,YAA2B;AAAA,MAC9C,IAAI;AAAA,QACF,QAAQ,YAAY,MAAM,KAAK;AAAA,QAC/B,SAAS,cAAc,OAAO;AAAA,QAC9B,MAAM;AAAA;AAAA,IAIL,aAAa;AAAA,EACpB;AAAA,EACA,MAAM,OAAO,YAA2B;AAAA,IACtC,IAAI;AAAA,MACF,QAAQ,YAAY,MAAM,KAAK;AAAA,MAC/B,MAAM,OAAO,cAAc,OAAO;AAAA,MAClC,IAAI,QAAQ,SAAS,MAAM;AAAA,QACzB,SAAS;AAAA,MACX;AAAA,MACA,OAAO;AAAA,MACP,MAAM;AAAA;AAAA,EAIV,MAAM,QAAQ,YAAY,MAAM;AAAA,IACzB,KAAK;AAAA,KACT,kBAAkB,IAAI;AAAA,EACzB,OAAO,MAAM,cAAc,KAAK;AAAA;AAU7B,IAAM,gBAAgB,CAAC,aAAoC;AAAA,EAChE,MAAM,OAAO,MAAK,UAAU,cAAc;AAAA,EAC1C,OAAO;AAAA,IACL,MAAM,YAAY;AAAA,MAChB,IAAI;AAAA,QAIF,OAAO,KAAK,MAAM,MAAM,UAAS,MAAM,OAAO,CAAC;AAAA,QAC/C,MAAM;AAAA,QACN,OAAO,CAAC;AAAA;AAAA;AAAA,IAGZ,OAAO,OAAO,YAAY;AAAA,MACxB,IAAI;AAAA,QACF,MAAM,MAAM,UAAU,EAAE,WAAW,KAAK,CAAC;AAAA,QACzC,MAAM,UAAU,MAAM,GAAG,KAAK,UAAU,OAAO;AAAA,GAAO,OAAO;AAAA,QAC7D,MAAM;AAAA;AAAA,EAIZ;AAAA;AASK,IAAM,gBAAgB,OAC3B,MACA,OACA,cACA,UAAU,SACoB;AAAA,EAC9B,IAAI,CAAC,SAAS;AAAA,IACZ,MAAM,SAAS,MAAM,MAAM,KAAK;AAAA,IAChC,IAAI,OAAO,SAAS,GAAG;AAAA,MACrB,OAAO,EAAE,aAAa,CAAC,GAAG,SAAS,OAAO;AAAA,IAC5C;AAAA,EACF;AAAA,EACA,IAAI;AAAA,IACF,MAAM,UAAU,MAAM,aAAa;AAAA,IACnC,MAAM,MAAM,MAAM,OAAO;AAAA,IACzB,OAAO,EAAE,aAAa,CAAC,GAAG,QAAQ;AAAA,IAClC,OAAO,OAAO;AAAA,IACd,MAAM,WAAW,MAAM,MAAM,KAAK;AAAA,IAClC,IAAI,SAAS,SAAS,GAAG;AAAA,MAGvB,MAAM,aAAyB;AAAA,QAC7B,MAAM;AAAA,QACN,SAAS,WAAW,+BAAgC,MAAgB,oBAAoB,SAAS;AAAA,QACjG,UAAU;AAAA,MACZ;AAAA,MACA,OAAO,EAAE,aAAa,CAAC,UAAU,GAAG,SAAS,SAAS;AAAA,IACxD;AAAA,IAEA,MAAM,IAAI,WAAW;AAAA,MACnB,MAAM;AAAA,MACN,SAAS,WAAW,mDAAoD,MAAgB;AAAA,MACxF,UAAU;AAAA,IACZ,CAAC;AAAA;AAAA;;;ACrHE,IAAM,eAAe;AAAA,EAC1B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAKA,IAAM,WAAW;AAGjB,IAAM,gBAAgB;AAEtB,IAAM,mBAAmB;AAWzB,IAAM,kBAAkB,CAAC,eACvB,WACG,QAAQ,eAAe,sBAAsB,EAC7C,QAAQ,kBAAkB,oBAAoB;AAG5C,IAAM,eAAe,CAC1B,QACA,MACA,gBACW;AAAA,EACX,MAAM,SAAS,cAAc,QAAQ,gBAAgB,WAAW,CAAC,IAAI;AAAA,EACrE,OAAO,UAAU,QAAQ,GAAG,UAAU,MAAM;AAAA;AAmFvC,IAAM,SAAS,CACpB,OACA,WAEA,OAAO,OAAO,OAAO,MAAM,IAAI,MAAM,UAAU;AAG1C,IAAM,cAAc,CACzB,MACA,OAEA,OAAO,OAAO,KAAK,YAAY,EAAE,IAAI,KAAK,WAAW,MAAM;AAetD,IAAM,gBAAgB,CAAC,SAAqC;AAAA,EACjE,MAAM,YAAsB,CAAC;AAAA,EAC7B,IAAI,KAAK,SAAS,WAAW;AAAA,IAC3B,IAAI,KAAK,UAAU;AAAA,MACjB,UAAU,KAAK,KAAK,QAAQ;AAAA,IAC9B;AAAA,IACA,OAAO,EAAE,WAAW,OAAO,WAAW;AAAA,EACxC;AAAA,EACA,IAAI,KAAK,SAAS,YAAY;AAAA,IAG5B,MAAM,UAAW,KAAK,SAA8B,WAAW,CAAC;AAAA,IAChE,WAAW,UAAU,OAAO,OAAO,OAAO,GAAG;AAAA,MAC3C,IAAI,QAAQ,MAAM;AAAA,QAChB,UAAU,KACR,GAAG,OAAO,WAAW,GAAG,OAAO,gBAAgB,KAAK,OAAO,OAAO,OAAO,YAAY,IACvF;AAAA,MACF;AAAA,IACF;AAAA,IACA,OAAO,EAAE,WAAW,OAAO,UAAU;AAAA,EACvC;AAAA,EAMA,MAAM,WAAY,KAAK,SAA8C;AAAA,EACrE,WAAW,UAAU,MAAM,QAAQ,QAAQ,IAAI,WAAW,CAAC,GAAG;AAAA,IAC5D,IAAI,OAAO,KAAK;AAAA,MACd,UAAU,KAAK,OAAO,GAAG;AAAA,IAC3B;AAAA,EACF;AAAA,EACA,OAAO,EAAE,WAAW,OAAO,WAAW;AAAA;AAMxC,IAAM,cAAc,CAClB,UAC6B,OAAO,UAAU,YAAY,UAAU;AAGtE,IAAM,aAAa,CAAC,QAClB,OAAO,IAAI,SAAS;AAWf,IAAM,aAAa,MAAkC;AAAA,EAC1D,MAAM,WAAW,IAAI;AAAA,EACrB,MAAM,QAAQ,IAAI;AAAA,EAClB,OAAO,CAAC,SAAS;AAAA,IACf,MAAM,WAAW,SAAS,IAAI,IAAI;AAAA,IAClC,IAAI,UAAU;AAAA,MACZ,OAAO;AAAA,IACT;AAAA,IACA,MAAM,OAAO,QAAQ,IAAI,KAAK;AAAA,IAC9B,IAAI,OAAO;AAAA,IACX,SAAS,SAAS,EAAG,MAAM,IAAI,IAAI,GAAG,UAAU,GAAG;AAAA,MACjD,OAAO,GAAG,QAAQ;AAAA,IACpB;AAAA,IACA,MAAM,IAAI,IAAI;AAAA,IACd,SAAS,IAAI,MAAM,IAAI;AAAA,IACvB,OAAO;AAAA;AAAA;AAiCJ,IAAM,qBAAqB,CAChC,WACA,YACuB;AAAA,EACvB,MAAM,aAAgC,CAAC;AAAA,EACvC,MAAM,WAAqB,CAAC;AAAA,EAC5B,MAAM,WAAW,IAAI;AAAA,EACrB,MAAM,OAAO,IAAI;AAAA,EACjB,MAAM,aAAa,WAAW;AAAA,EAE9B,MAAM,MAAM,CAAC,UAAoC;AAAA,IAC/C,MAAM,UAAU,WAAW,MAAM,GAAG;AAAA,IACpC,IAAI,CAAC,SAAS,IAAI,MAAM,GAAG,GAAG;AAAA,MAC5B,SAAS,IAAI,MAAM,GAAG;AAAA,MACtB,SAAS,KAAK,MAAM,GAAG;AAAA,IACzB;AAAA,IACA,MAAM,QAAQ;AAAA,IACd,OAAO,KAAK,IAAI,GAAG,GAAG;AAAA,MACpB,MAAM,GAAG,OAAO,MAAM;AAAA,IACxB;AAAA,IACA,KAAK,IAAI,GAAG;AAAA,IACZ,WAAW,KAAK;AAAA,SACX;AAAA,MACH;AAAA,MAEA,OAAO,GAAG,cAAc,MAAM,KAAK,aAAa,WAAW;AAAA,MAC3D;AAAA,IACF,CAAC;AAAA;AAAA,EAGH,MAAM,SAAS,OAA4B;AAAA,IACzC;AAAA,IACA,MAAM,SAAS,IAAI,CAAC,UAAU;AAAA,MAC5B,aAAa,QAAQ,IAAI,IAAI,KAAK;AAAA,MAClC;AAAA,MAGA,MAAM,WAAW,IAAI;AAAA,IACvB,EAAE;AAAA,EACJ;AAAA,EAEA,OAAO,EAAE,KAAK,OAAO;AAAA;AAShB,IAAM,oBAAoB,CAC/B,UACA,cACwB;AAAA,EACxB,MAAM,WAAqB,CAAC;AAAA,EAC5B,MAAM,UAAU,IAAI,KACjB,SAAS,QAAQ,CAAC,GAChB,OAAO,UAAU,EACjB,IAAI,CAAC,QAAQ,CAAC,IAAI,MAAM,IAAI,eAAe,EAAE,CAAC,CACnD;AAAA,EACA,MAAM,YAAY,mBAAmB,WAAW,OAAO;AAAA,EAEvD,YAAY,MAAM,SAAS,OAAO,QAAQ,SAAS,SAAS,CAAC,CAAC,GAAG;AAAA,IAE/D,IAAI,CAAC,MAAM;AAAA,MACT;AAAA,IACF;AAAA,IACA,IAAI,UAAU,MAAM;AAAA,MAClB,SAAS,KACP,SAAS,mLACX;AAAA,MACA;AAAA,IACF;AAAA,IACA,WAAW,UAAU,cAAc;AAAA,MACjC,MAAM,YAAY,KAAK;AAAA,MACvB,IAAI,CAAC,YAAY,SAAS,GAAG;AAAA,QAC3B;AAAA,MACF;AAAA,MACA,UAAU,IAAI;AAAA,QACZ,YAAY,UAAU,cAAc;AAAA,QACpC,aAAa,UAAU,eAAe;AAAA,QACtC,KAAK,aAAa,QAAQ,MAAM,UAAU,WAAW;AAAA,QACrD;AAAA,QACA,aAAa,UAAU;AAAA,QACvB;AAAA,QACA,SAAS,UAAU,WAAW;AAAA,QAC9B,KAAK,UAAU,OAAO,MAAM;AAAA,MAC9B,CAAC;AAAA,IACH;AAAA,EACF;AAAA,EAEA,OAAO,KAAK,UAAU,OAAO,GAAG,SAAS;AAAA;;;ACtWpC,IAAM,mBAAmB,CAAC,QAAQ,SAAS;AAiFlD,IAAM,kBAAkB,CAAC,UACvB,MAAM,WAAW,MAAM,GAAG,EAAE,WAAW,MAAM,GAAG;AAElD,IAAM,cAAc;AAEpB,IAAM,WAAW,CAAC,UAChB,OAAO,UAAU;AAGZ,IAAM,cAAc,CAAC,YAAkD;AAAA,EAC5E,MAAM,KAAK,YAAY,KAAK,SAAS,QAAQ,EAAE,GAAG,QAAQ;AAAA,EAC1D,OAAO,OAAO,YAAY,YAAY,gBAAgB,EAAE;AAAA;AAQnD,IAAM,iBAAiB,CAC5B,WACA,YACW;AAAA,EACX,MAAM,UAAU,SAAS;AAAA,EACzB,OAAO,SAAS,OAAO,KAAK,YAAY,KAAK,UAAU;AAAA;AAGzD,IAAM,WAAW,CACf,UAEA,OAAO,UAAU,YAAY,UAAU,QAAQ,CAAC,MAAM,QAAQ,KAAK;AAErE,IAAM,wBACJ;AASF,IAAM,sBAAsB,CAAC,aAAqC;AAAA,EAChE,MAAM,OAAkE;AAAA,IACtE,CAAC,SAAS,UAAU,UAAU;AAAA,IAC9B,CAAC,SAAS,YAAY,YAAY;AAAA,EACpC;AAAA,EACA,YAAY,KAAK,YAAY,MAAM;AAAA,IACjC,IAAI,CAAC,SAAS,GAAG,GAAG;AAAA,MAClB;AAAA,IACF;AAAA,IACA,MAAM,QAAQ,SAAS,aAAa;AAAA,IACpC,YAAY,IAAI,UAAU,OAAO,QAAQ,GAAG,GAAG;AAAA,MAC7C,IAAI,CAAC,SAAS,KAAK,KAAK,CAAC,SAAS,MAAM,IAAI,GAAG;AAAA,QAC7C;AAAA,MACF;AAAA,MACA,MAAM,SAAS,sBAAsB,KAAK,MAAM,IAAI,GAAG;AAAA,MACvD,IAAI,QAAQ,YAAY,SAAS;AAAA,QAC/B;AAAA,MACF;AAAA,MACA,MAAM,WAAW,SAAS,KAAK,IAC3B,MAAM,gBAAgB,OAAO,QAAQ,EAAE,KACvC;AAAA,MAIJ,IAAI,SAAS,QAAQ,KAAK,CAAC,SAAS,SAAS,IAAI,GAAG;AAAA,QAClD,IAAI,MAAM,KAAK,SAAS;AAAA,MAC1B;AAAA,IACF;AAAA,EACF;AAAA;AAGF,IAAM,YACJ;AAUK,IAAM,sBAAsB,CACjC,aACqB;AAAA,EACrB,MAAM,eAAe,CACnB,UACkD;AAAA,IAClD,IAAI,CAAC,SAAS,KAAK,GAAG;AAAA,MACpB;AAAA,IACF;AAAA,IACA,IAAI,CAAC,SAAS,MAAM,IAAI,GAAG;AAAA,MACzB,OAAO;AAAA,IACT;AAAA,IACA,MAAM,SAAS,UAAU,KAAK,MAAM,IAAI,GAAG;AAAA,IAC3C,MAAM,UAAU,QAAQ,UACpB,SAAS,aAAa,OAAO,WAC7B;AAAA,IACJ,MAAM,WAAW,UAAU,QAAQ,QAAQ;AAAA,IAC3C,OAAO,SAAS,QAAQ,IAAI,WAAW;AAAA;AAAA,EAGzC,MAAM,cAAc,CAAC,SAAkD;AAAA,IACrE,QAAQ,WAAW;AAAA,IACnB,IAAI,CAAC,MAAM,QAAQ,MAAM,GAAG;AAAA,MAC1B;AAAA,IACF;AAAA,IACA,OAAO,KAAK;AAAA,IACZ,MAAM,SAA4C,CAAC;AAAA,IACnD,WAAW,SAAS,QAAQ;AAAA,MAC1B,OAAO,OAAO,QAAQ,aAAa,KAAK,CAAC;AAAA,IAC3C;AAAA,IACA,YAAY,KAAK,UAAU,OAAO,QAAQ,MAAM,GAAG;AAAA,MACjD,IAAI,EAAE,OAAO,OAAO;AAAA,QAClB,KAAK,OAAO;AAAA,MACd;AAAA,IACF;AAAA;AAAA,EAGF,WAAW,aAAa,OAAO,OAAO,SAAS,cAAc,CAAC,CAAC,GAAG;AAAA,IAChE,IAAI,SAAS,SAAS,GAAG;AAAA,MACvB,YAAY,SAAS;AAAA,IACvB;AAAA,EACF;AAAA,EACA,MAAM,cAAc;AAAA,IAClB,GAAG,OAAO,OAAO,SAAS,YAAY,CAAC,CAAC;AAAA,IAGxC,GAAG,OAAO,OAAO,SAAS,YAAY,YAAY,CAAC,CAAC;AAAA,EACtD;AAAA,EACA,MAAM,cAAc;AAAA,IAClB,GAAG,YAAY,IAAI,CAAC,YAClB,SAAS,OAAO,IAAI,QAAQ,WAAW,SACzC;AAAA,IACA,SAAS,YAAY;AAAA,EACvB;AAAA,EACA,WAAW,YAAY,aAAa;AAAA,IAClC,IAAI,CAAC,SAAS,QAAQ,GAAG;AAAA,MACvB;AAAA,IACF;AAAA,IACA,WAAW,WAAW,OAAO,OAAO,QAAQ,GAAG;AAAA,MAG7C,IAAI,SAAS,OAAO,KAAK,CAAC,SAAS,QAAQ,IAAI,GAAG;AAAA,QAChD,YAAY,OAAO;AAAA,MACrB;AAAA,IACF;AAAA,EACF;AAAA,EACA,OAAO;AAAA;AAQF,IAAM,4BAA4B,CACvC,aACqB;AAAA,EACrB,oBAAoB,QAAQ;AAAA,EAC5B,OAAO,oBAAoB,QAAQ;AAAA;AAQrC,IAAM,gBAAgB,CACpB,IACA,WACA,aAC4E;AAAA,EAC5E,MAAM,SAAS,iBAAiB,KAAK,CAAC,MAAM,MAAM,UAAU,MAAM;AAAA,EAClE,IAAI,CAAC,QAAQ;AAAA,IACX,OAAO,cAAc;AAAA,EACvB;AAAA,EACA,MAAM,YAAY,YAAY,UAAU,OAAO;AAAA,EAC/C,MAAM,UAAU,cAAc,YAAY,YAAY,SAAS;AAAA,EAG/D,IAAI,cAAc,aAAa,CAAC,SAAS,OAAO,KAAK,SAAS,QAAQ,IAAI,GAAG;AAAA,IAC3E,OAAO,cAAc;AAAA,EACvB;AAAA,EACA,OAAO,EAAE,QAAQ,SAAS,eAAe,WAAW,OAAO,GAAG,UAAU;AAAA;AAsBnE,IAAM,4BAA4B,CACvC,UACA,cAC6B;AAAA,EAC7B,MAAM,WAAqB,CAAC;AAAA,EAC5B,MAAM,UAAU,IAAI,KACjB,SAAS,MAAM,QAAQ,CAAC,GACtB,OACC,CAAC,QACC,OAAO,KAAK,SAAS,QACzB,EACC,IAAI,CAAC,QAA0B,CAAC,IAAI,MAAM,IAAI,eAAe,EAAE,CAAC,CACrE;AAAA,EACA,MAAM,YAAY,mBAAmB,WAAW,OAAO;AAAA,EACvD,MAAM,WAAW,SAAS,YAAY,CAAC;AAAA,EAEvC,YAAY,IAAI,cAAc,OAAO,QAAQ,SAAS,cAAc,CAAC,CAAC,GAAG;AAAA,IACvE,IAAI,CAAC,SAAS,SAAS,GAAG;AAAA,MACxB;AAAA,IACF;AAAA,IAGA,IAAI,SAAS,UAAU,IAAI,GAAG;AAAA,MAC5B,SAAS,KACP,cAAc,sGAChB;AAAA,MACA;AAAA,IACF;AAAA,IACA,MAAM,OAAO,cAAc,IAAI,WAAW,QAAQ;AAAA,IAClD,IAAI,SAAS,IAAI,GAAG;AAAA,MAClB,SAAS,KAAK,IAAI;AAAA,MAClB;AAAA,IACF;AAAA,IACA,QAAQ,QAAQ,SAAS,cAAc;AAAA,IACvC,MAAM,MAAM,UAAU,MAAM,KAC1B,CAAC,cACC,OAAO,WAAW,SAAS,QAC/B,GAAG;AAAA,IACH,UAAU,IAAI;AAAA,MACZ;AAAA,MACA,YAAY,UAAU,eAAe;AAAA,MACrC,aAAa,UAAU,eAAe;AAAA,MACtC,KAAK,aAAa,QAAQ,SAAS,EAAE;AAAA,MACrC,QAAQ;AAAA,MACR,aAAa;AAAA,MACb,MAAM;AAAA,MACN,SAAS,UAAU,SAAS,UAAU,WAAW;AAAA,MACjD,KAAK,OAAO;AAAA,IACd,CAAC;AAAA,EACH;AAAA,EAEA,OAAO,KAAK,UAAU,OAAO,GAAG,SAAS;AAAA;;;ACrVpC,IAAM,0BAA0B;AAAA,EACrC;AAAA,EACA;AAAA,EACA;AACF;AAKO,IAAM,qBAAqB;AAAA,EAChC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAQO,IAAM,uBAAuB;AAAA,EAClC,MAAM;AAAA,EACN,OAAO;AAAA,EACP,WAAW;AAAA,EACX,UAAU;AAAA,EACV,QAAQ;AAAA,EACR,OAAO;AAAA,EACP,QAAQ;AAAA,EACR,cAAc;AAAA,EACd,OAAO;AACT;AAyEO,IAAM,yBAAyB,CACpC,WAEA,wBAAwB,KAAK,CAAC,SAAS,SAAS,MAAM;AAGxD,IAAM,wBAAwB;AAAA,EAC5B,MAAM;AAAA,EACN,OAAO;AAAA,EACP,WAAW;AAAA,EACX,QAAQ;AAAA,EACR,QAAQ;AAAA,EACR,OAAO;AACT;AASO,IAAM,mBAAmB,CAC9B,cACW;AAAA,EAGX,MAAM,SAAS,UAAU;AAAA,EACzB,MAAM,UAAU,uBAAuB,MAAM,IACzC,SACA,sBAAsB;AAAA,EAC1B,OAAO,GAAG,WAAW,UAAU;AAAA;AAMjC,IAAM,SAAS,CAAC,GAAmB,MACjC,EAAE,OAAO,EAAE,OAAO,KAAK;AAWlB,IAAM,2BAA2B,CACtC,UACA,cACwB;AAAA,EACxB,MAAM,YAAY,mBAAmB,WAAW,IAAI,GAAK;AAAA,EACzD,MAAM,YAAY,IAAI,IAAI,OAAO,OAAO,SAAS,KAAK,CAAC;AAAA,EAEvD,WAAW,QAAQ,yBAAyB;AAAA,IAC1C,MAAM,WAAW,SAAS,MAAM;AAAA,IAChC,MAAM,OAAO,aAAa,YAAY,YAAY,SAAS,MAAM;AAAA,IACjE,WAAW,SAAS,MAAM,UAAU,CAAC,GAAG;AAAA,MACtC,UAAU,IAAI;AAAA,QACZ,YAAY,MAAM,sBAAsB;AAAA,QACxC,aAAa,MAAM;AAAA,QAGnB,KAAK,aAAa,MAAM,MAAM,MAAM,MAAM,IAAI;AAAA,QAC9C,QAAQ;AAAA,QACR,aAAa,MAAM;AAAA,QACnB,MAAM,MAAM;AAAA,QACZ,SAAS;AAAA,QACT,KAAK,qBAAqB;AAAA,MAC5B,CAAC;AAAA,IACH;AAAA,EACF;AAAA,EAEA,WAAW,QAAQ,oBAAoB;AAAA,IACrC,MAAM,UAAU,OAAO,OAAO,SAAS,KAAK,EACzC,OACC,CAAC,SACC,KAAK,SAAS,QAAQ,EAAE,KAAK,WAAW,UAAU,IAAI,KAAK,IAAI,EACnE,EACC,SAAS,MAAM;AAAA,IAClB,WAAW,QAAQ,SAAS;AAAA,MAC1B,UAAU,IAAI;AAAA,QACZ,YAAY;AAAA,QACZ,aAAa,KAAK;AAAA,QAClB,KAAK,aAAa,MAAM,KAAK,MAAM,KAAK,IAAI;AAAA,QAC5C,QAAQ;AAAA,QACR,aAAa,KAAK;AAAA,QAClB,MAAM,KAAK;AAAA,QACX,SAAS;AAAA,QACT,KAAK,qBAAqB;AAAA,MAC5B,CAAC;AAAA,IACH;AAAA,EACF;AAAA,EAEA,OAAO,KAAK,UAAU,OAAO,GAAG,UAAU,CAAC,EAAE;AAAA;;;ACzN/C,kBAAS,oBAAO,wBAAU;AAC1B,uBAAS;AAGT;AACA,sBAAS;AACT;AACA,6BAAqB;;;ACMrB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAgCA,IAAM,UAAU,CACd,UACoB;AAAA,EACpB,SAAS,OAAO,IAAI;AAAA,EACpB,MAAM,aAAa,IAAI,EAAE;AAC3B;AAQA,IAAM,iBAAiB,CACrB,UACuB;AAAA,EACvB,MAAM,UAAU,MAAM,SAAS;AAAA,EAC/B,OAAO,UAAU,MAAM,OAAO,IAAI;AAAA;AAGpC,IAAM,aAAa,CACjB,UACsB;AAAA,EACtB,MAAM,QAA2B;AAAA,IAC/B,aAAa,MAAM,eAAe;AAAA,IAClC,MAAM,MAAM;AAAA,IACZ,MAAM,QAAQ,MAAM,IAAI;AAAA,EAC1B;AAAA,EACA,MAAM,UAAU,eAAe,KAAK;AAAA,EACpC,IAAI,YAAY,WAAW;AAAA,IACzB,MAAM,UAAU;AAAA,EAClB;AAAA,EAGA,IACE,MAAM,sBAAsB,QAC5B,MAAM,sBAAsB,WAC5B;AAAA,IACA,MAAM,oBAAoB,MAAM;AAAA,EAClC;AAAA,EACA,OAAO;AAAA;AAGT,IAAM,cAAc,CAAC,UAAsD;AAAA,EACzE,MAAM,QAAoB;AAAA,IACxB,MAAM,MAAM,KAAK,IAAI,UAAU;AAAA,IAC/B,aAAa,MAAM,eAAe;AAAA,IAClC,MAAM,MAAM;AAAA,IACZ,MAAM,QAAQ,MAAM,IAAI;AAAA,EAC1B;AAAA,EACA,IACE,MAAM,sBAAsB,QAC5B,MAAM,sBAAsB,WAC5B;AAAA,IACA,MAAM,oBAAoB,MAAM;AAAA,EAClC;AAAA,EACA,OAAO;AAAA;AAGT,IAAM,aAAa,CAAC,SAClB,KAAK,UAAU,EAAE,IAAI,CAAC,UAAU;AAAA,EAC9B,MAAM,SAA2B;AAAA,IAC/B,aAAa,MAAM,eAAe;AAAA,IAClC,MAAM,MAAM;AAAA,EACd;AAAA,EACA,IACE,MAAM,sBAAsB,QAC5B,MAAM,sBAAsB,WAC5B;AAAA,IACA,OAAO,oBAAoB,MAAM;AAAA,EACnC;AAAA,EACA,OAAO;AAAA,CACR;AAEH,IAAM,YAAY,CAChB,MACA,SACmB;AAAA,EACnB,MAAM,aAAa,KAAK,cAAc,EAAE,IAAI,CAAC,UAAU,MAAM,IAAI;AAAA,EACjE,MAAM,MAAsB;AAAA,IAC1B,aAAa,KAAK,eAAe;AAAA,IACjC,QAAQ,OAAO,OAAO,KAAK,UAAU,CAAC,EAAE,IAAI,WAAW;AAAA,IACvD;AAAA,IACA,MAAM,KAAK;AAAA,EACb;AAAA,EACA,IAAI,WAAW,SAAS,GAAG;AAAA,IACzB,IAAI,aAAa;AAAA,EACnB;AAAA,EACA,OAAO;AAAA;AAGT,IAAM,WAAW,CAAC,UAAkD;AAAA,EAClE,aAAa,KAAK,eAAe;AAAA,EACjC,aAAa,OAAO,OAAO,KAAK,UAAU,CAAC,EAAE,IAAI,UAAU;AAAA,EAC3D,MAAM;AAAA,EACN,MAAM,KAAK;AACb;AAEA,IAAM,WAAW,CAAC,UAA4C;AAAA,EAC5D,aAAa,KAAK,eAAe;AAAA,EACjC,MAAM;AAAA,EACN,MAAM,KAAK;AAAA,EACX,eAAe,KAAK,SAAS,EAAE,IAAI,CAAC,WAAW,OAAO,IAAI;AAC5D;AAEA,IAAM,YAAY,CAAC,SAA4C;AAAA,EAC7D,MAAM,MAAsB;AAAA,IAC1B,aAAa,KAAK,eAAe;AAAA,IACjC,MAAM;AAAA,IACN,MAAM,KAAK;AAAA,EACb;AAAA,EACA,IAAI,KAAK,mBAAmB,QAAQ,KAAK,mBAAmB,WAAW;AAAA,IACrE,IAAI,iBAAiB,KAAK;AAAA,EAC5B;AAAA,EACA,IAAI,sBAAsB,IAAI,GAAG;AAAA,IAC/B,IAAI,UAAU;AAAA,EAChB;AAAA,EACA,OAAO;AAAA;AAGT,IAAM,UAAU,CACd,MACA,WACmB;AAAA,EACnB,IAAI,aAAa,IAAI,GAAG;AAAA,IACtB,OAAO,UAAU,MAAM,QAAQ;AAAA,EACjC;AAAA,EACA,IAAI,gBAAgB,IAAI,GAAG;AAAA,IACzB,MAAM,MAAM,UAAU,MAAM,WAAW;AAAA,IAGvC,MAAM,eAAe,OAClB,mBAAmB,IAAI,EACvB,QAAQ,IAAI,CAAC,WAAW,OAAO,IAAI;AAAA,IACtC,IAAI,aAAa,SAAS,GAAG;AAAA,MAC3B,IAAI,gBAAgB;AAAA,IACtB;AAAA,IACA,OAAO;AAAA,EACT;AAAA,EACA,IAAI,kBAAkB,IAAI,GAAG;AAAA,IAC3B,OAAO,SAAS,IAAI;AAAA,EACtB;AAAA,EACA,IAAI,WAAW,IAAI,GAAG;AAAA,IACpB,OAAO;AAAA,MACL,aAAa,KAAK,eAAe;AAAA,MACjC,YAAY,WAAW,IAAI;AAAA,MAC3B,MAAM;AAAA,MACN,MAAM,KAAK;AAAA,IACb;AAAA,EACF;AAAA,EACA,IAAI,YAAY,IAAI,GAAG;AAAA,IACrB,OAAO,SAAS,IAAI;AAAA,EACtB;AAAA,EAGA,OAAO,UAAU,IAAyB;AAAA;AAI5C,IAAM,aAAa,CAAC,WAA2C;AAAA,EAC7D,MAAM,QAAwC,CAAC;AAAA,EAC/C,YAAY,MAAM,SAAS,OAAO,QAAQ,OAAO,WAAW,CAAC,GAAG;AAAA,IAE9D,IAAI,KAAK,WAAW,IAAI,GAAG;AAAA,MACzB;AAAA,IACF;AAAA,IACA,MAAM,QAAQ,QAAQ,MAAM,MAAM;AAAA,EACpC;AAAA,EACA,MAAM,QAAkC,CAAC;AAAA,EACzC,MAAM,QAAQ,OAAO,aAAa;AAAA,EAClC,MAAM,WAAW,OAAO,gBAAgB;AAAA,EACxC,MAAM,eAAe,OAAO,oBAAoB;AAAA,EAChD,IAAI,OAAO;AAAA,IACT,MAAM,QAAQ,MAAM;AAAA,EACtB;AAAA,EACA,IAAI,UAAU;AAAA,IACZ,MAAM,WAAW,SAAS;AAAA,EAC5B;AAAA,EACA,IAAI,cAAc;AAAA,IAChB,MAAM,eAAe,aAAa;AAAA,EACpC;AAAA,EACA,OAAO;AAAA,IACL,MAAM,EAAE,aAAa,OAAO,eAAe,GAAG;AAAA,IAC9C;AAAA,IACA;AAAA,EACF;AAAA;AAUF,IAAM,eAAe,CACnB,UAEA,OAAO,UAAU,YAAY,UAAU,SAAQ,cAAc;AAG/D,IAAM,kBAAkB,CACtB,UAEA,OAAO,UAAU,YAAY,UAAU,SAAQ,UAAU;AAG3D,IAAM,kBAAkB,CAAQ,WAAsC;AAAA,EACpE,IAAI,aAAa,MAAM,GAAG;AAAA,IACxB,OAAO;AAAA,EACT;AAAA,EACA,OAAO,gBAAgB,MAAM,KAAK,aAAa,OAAO,IAAI,IACtD,OAAO,OACP;AAAA;AASC,IAAM,uBAAuB,CAAC,SAAkC;AAAA,EACrE,IAAI;AAAA,EACJ,IAAI,SAAS;AAAA,EACb,IAAI;AAAA,IACF,SAAS,KAAK,MAAM,IAAI;AAAA,IACxB,MAAM;AAAA,IACN,SAAS;AAAA;AAAA,EAEX,IAAI,QAAQ;AAAA,IACV,MAAM,gBAAgB,gBAAgB,MAAM;AAAA,IAC5C,IAAI,CAAC,eAAe;AAAA,MAClB,MAAM,IAAI,MACR,8EACF;AAAA,IACF;AAAA,IAIA,OAAO,WACL,kBACE,aACF,CACF;AAAA,EACF;AAAA,EACA,OAAO,WAAW,YAAY,IAAI,CAAC;AAAA;;;ADrQrC,IAAM,WAAW;AAEjB,IAAM,mBAAmB;AACzB,IAAM,eAAe;AACrB,IAAM,kBAAkB;AAGxB,IAAM,oBAAoB;AAC1B,IAAM,YAAY;AAElB,IAAM,mBAAmB,IAAI,IAAI,CAAC,KAAK,KAAK,KAAK,KAAK,KAAK,KAAK,GAAG,CAAC;AACpE,IAAM,aAAa;AACnB,IAAM,iBAAiB;AAAA,EACrB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAAA;AAaO,MAAM,yBAAyB,MAAM;AAAA,EAC1C,WAAW,CAAC,SAAiB;AAAA,IAC3B,MAAM,OAAO;AAAA,IACb,KAAK,OAAO;AAAA;AAEhB;AAqBA,IAAI,iBAAiB;AACrB,IAAM,wBAAwB,YAA2B;AAAA,EAGvD,IAAI,kBAAkB,CAAC,eAAe,KAAK,CAAC,SAAS,QAAQ,IAAI,KAAK,GAAG;AAAA,IACvE;AAAA,EACF;AAAA,EACA,IAAI;AAAA,IACF,QAAQ,mBAAmB,wBAAwB,MAAa;AAAA,IAChE,oBAAoB,IAAI,iBAAmB;AAAA,IAC3C,iBAAiB;AAAA,IACjB,MAAM;AAAA;AAWV,IAAM,eAAe,CAAC,aAA2C;AAAA,EAC/D,MAAM,SAAS,SAAS,QAAQ,IAAI,aAAa;AAAA,EACjD,IAAI,CAAC,QAAQ;AAAA,IACX;AAAA,EACF;AAAA,EACA,MAAM,UAAU,OAAO,MAAM;AAAA,EAC7B,IAAI,OAAO,SAAS,OAAO,GAAG;AAAA,IAC5B,OAAO,UAAU,IAAI,UAAU,YAAY;AAAA,EAC7C;AAAA,EACA,MAAM,QAAQ,KAAK,MAAM,MAAM,IAAI,KAAK,IAAI;AAAA,EAC5C,OAAO,OAAO,SAAS,KAAK,KAAK,QAAQ,IAAI,QAAQ;AAAA;AAQvD,IAAM,eAAe,OAAO,SAAmC;AAAA,EAC7D,IAAI;AAAA,IACF,MAAM,WAAW,MAAM,MAAM,MAAM;AAAA,MACjC,SAAS;AAAA,QACP,QAAQ;AAAA,QACR,cAAc;AAAA,MAChB;AAAA,MACA,UAAU;AAAA,MACV,QAAQ,YAAY,QAAQ,gBAAgB;AAAA,IAC9C,CAAC;AAAA,IACD,IAAI,SAAS,IAAI;AAAA,MACf,OAAO,EAAE,MAAM,MAAM,SAAS,KAAK,EAAE;AAAA,IACvC;AAAA,IACA,OAAO;AAAA,MACL,OAAO,IAAI,MAAM,GAAG,WAAW,SAAS,UAAU,SAAS,YAAY;AAAA,MACvE,YAAY,aAAa,QAAQ;AAAA,MACjC,WAAW,iBAAiB,IAAI,SAAS,MAAM;AAAA,IACjD;AAAA,IACA,OAAO,OAAO;AAAA,IAGd,OAAO;AAAA,MACL,OAAO,iBAAiB,QAAQ,QAAQ,IAAI,MAAM,OAAO,KAAK,CAAC;AAAA,MAC/D,WAAW;AAAA,IACb;AAAA;AAAA;AAaJ,IAAM,sBAAsB,CAC1B,OACA,eACwB;AAAA,EACxB,MAAM,UAA+B,IAAI,MAAM,MAAM,SAAS;AAAA,IAC5D,OAAO;AAAA,EACT,CAAC;AAAA,EACD,QAAQ,OAAO;AAAA,EACf,QAAQ,aAAa;AAAA,EACrB,OAAO;AAAA;AAIT,IAAM,gBAAgB,OAAO,SAAkC;AAAA,EAC7D,MAAM,sBAAsB;AAAA,EAC5B,OAAO,MAAM,OACX,YAAY;AAAA,IACV,MAAM,UAAU,MAAM,aAAa,IAAI;AAAA,IACvC,IAAI,UAAU,SAAS;AAAA,MACrB,OAAO,QAAQ;AAAA,IACjB;AAAA,IACA,IAAI,CAAC,QAAQ,WAAW;AAAA,MAEtB,MAAM,IAAI,WAAW,QAAQ,KAAK;AAAA,IACpC;AAAA,IACA,MAAM,oBAAoB,QAAQ,OAAO,QAAQ,UAAU;AAAA,KAE7D;AAAA,IACE,QAAQ;AAAA,IACR,YAAY;AAAA,IACZ,YAAY;AAAA,IAIZ,iBAAiB,OAAO,YAAY;AAAA,MAGlC,QAAQ,eAAe,QAAQ;AAAA,MAC/B,IAAI,eAAe,aAAa,QAAQ,cAAc,GAAG;AAAA,QACvD,MAAM,MACJ,KAAK,IACH,GACA,KAAK,IAAI,YAAY,iBAAiB,IAAI,QAAQ,UACpD,CACF;AAAA,MACF;AAAA;AAAA,IAEF,SAAS,eAAe;AAAA,EAC1B,CACF;AAAA;AAGF,IAAM,eAAe,CAAC,UAAkB,SACtC,MAAK,UAAU,QAAQ,SAAS,IAAI,SAAS;AAE/C,IAAM,YAAY,OAAO,SAA8C;AAAA,EACrE,IAAI;AAAA,IACF,OAAO,MAAM,UAAS,MAAM,OAAO;AAAA,IACnC,MAAM;AAAA,IACN;AAAA;AAAA;AAIJ,IAAM,aAAa,OACjB,KACA,MACA,SACkB;AAAA,EAClB,IAAI;AAAA,IACF,MAAM,OAAM,KAAK,EAAE,WAAW,KAAK,CAAC;AAAA,IACpC,MAAM,WAAU,MAAM,MAAM,OAAO;AAAA,IACnC,MAAM;AAAA;AAMV,IAAM,eAAe,OACnB,MACA,MACA,YACkD;AAAA,EAClD,IAAI,CAAC,SAAS,KAAK,IAAI,GAAG;AAAA,IACxB,MAAM,WAAW,WAAW,IAAI,IAAI,OAAO,MAAK,MAAM,IAAI;AAAA,IAC1D,OAAO,EAAE,MAAM,MAAM,UAAS,UAAU,OAAO,GAAG,UAAU,CAAC,EAAE;AAAA,EACjE;AAAA,EAEA,MAAM,YAAY,QAAQ,WACtB,aAAa,QAAQ,UAAU,IAAI,IACnC;AAAA,EAGJ,IAAI,aAAa,QAAQ,YAAY,OAAO;AAAA,IAC1C,MAAM,SAAS,MAAM,UAAU,SAAS;AAAA,IACxC,IAAI,WAAW,WAAW;AAAA,MACxB,OAAO,EAAE,MAAM,QAAQ,UAAU,CAAC,EAAE;AAAA,IACtC;AAAA,EACF;AAAA,EAEA,IAAI;AAAA,IACF,MAAM,OAAO,MAAM,cAAc,IAAI;AAAA,IACrC,IAAI,QAAQ,YAAY,WAAW;AAAA,MACjC,MAAM,WAAW,QAAQ,UAAU,WAAW,IAAI;AAAA,IACpD;AAAA,IACA,OAAO,EAAE,MAAM,UAAU,CAAC,EAAE;AAAA,IAC5B,OAAO,OAAO;AAAA,IAEd,IAAI,WAAW;AAAA,MACb,MAAM,SAAS,MAAM,UAAU,SAAS;AAAA,MACxC,IAAI,WAAW,WAAW;AAAA,QAGxB,OAAO;AAAA,UACL,MAAM;AAAA,UACN,UAAU;AAAA,YACR,mBAAmB,SAAU,MAAgB;AAAA,UAC/C;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,IACA,MAAM;AAAA;AAAA;AAeV,IAAM,gBAAgB,CAAQ,UAC5B,OAAO,UAAU,YAAY,UAAU;AAElC,IAAM,YAAY,OACvB,MACA,MACA,UAA4B,CAAC,MACL;AAAA,EACxB,QAAQ,MAAM,aAAa,MAAM,aAAa,MAAM,MAAM,OAAO;AAAA,EACjE,MAAM,aAAa,WAAU,IAAI;AAAA,EACjC,QAAQ,kBAAkB,QAAQ,UAAU;AAAA,EAE5C,IAAI,CAAC,cAAc,aAAa,GAAG;AAAA,IACjC,MAAM,IAAI,iBACR,GAAG,wEACL;AAAA,EACF;AAAA,EACA,OAAO,EAAE,UAAU,eAAe,SAAS;AAAA;AAsB7C,IAAM,qBAAqB,CACzB,UAEA,OAAO,UAAU,YACjB,UAAU,SACV,cAAc,UACd,OAAO,MAAM,aAAa,YAC1B,MAAM,aAAa;AAEd,IAAM,oBAAoB,OAC/B,MACA,MACA,UAA4B,CAAC,MACG;AAAA,EAChC,QAAQ,MAAM,aAAa,MAAM,aAAa,MAAM,MAAM,OAAO;AAAA,EACjE,MAAM,aAAa,WAAU,IAAI;AAAA,EACjC,IAAI,CAAC,mBAAmB,UAAU,GAAG;AAAA,IACnC,MAAM,IAAI,iBACR,GAAG,4GACL;AAAA,EACF;AAAA,EACA,MAAM,UAAU,WAAW;AAAA,EAC3B,IAAI,WAA6B;AAAA,EACjC,IAAI,CAAC,QAAQ,WAAW,IAAI,GAAG;AAAA,IAI7B,MAAM,WAAqB,CAAC;AAAA,IAC5B,MAAM,eAAe,QAAQ;AAAA,IAC7B,QAAQ,OAAO,IAAI,SAAoB;AAAA,MACrC,SAAS,KAAK,KAAK,IAAI,MAAM,EAAE,KAAK,GAAG,CAAC;AAAA;AAAA,IAE1C,IAAI;AAAA,MAIF,WAAW,QACT,UACA,OACF;AAAA,MACA,OAAO,OAAO;AAAA,MAId,MAAM,IAAI,iBACR,GAAG,gDAAiD,MAAgB,WACtE;AAAA,cACA;AAAA,MACA,QAAQ,OAAO;AAAA;AAAA,IAEjB,SAAS,KACP,GAAG,SAAS,IACV,CAAC,YAAY,cAAc,yBAAyB,SACtD,CACF;AAAA,EACF;AAAA,EACA,OAAO,EAAE,UAAU,0BAA0B,QAAQ,GAAG,SAAS;AAAA;AAiB5D,IAAM,mBAAmB,OAC9B,MACA,MACA,UAA4B,CAAC,MACE;AAAA,EAC/B,QAAQ,MAAM,aAAa,MAAM,aAAa,MAAM,MAAM,OAAO;AAAA,EACjE,IAAI;AAAA,IACF,OAAO,EAAE,UAAU,qBAAqB,IAAI,GAAG,SAAS;AAAA,IACxD,OAAO,OAAO;AAAA,IAId,MAAM,IAAI,iBACR,GAAG,uCAAwC,MAAgB,QAAQ,KAAK,IAC1E;AAAA;AAAA;;;AEzaJ;AACA,qBAAS;AACT;;;ACGA,IAAM,eAAe;AAQd,IAAM,gBAAgB,CAAC,SAAyB;AAAA,EACrD,MAAM,UAAU,KAAK,KAAK;AAAA,EAC1B,OAAO,aAAa,KAAK,OAAO,IAAI,UAAU,GAAG;AAAA;;;AChBnD;AAYO,IAAM,gBAAgB,CAAC,MAAc,QAAwB;AAAA,EAClE,IAAI,MAAM;AAAA,EACV,IAAI,OAAO;AAAA,EACX,MAAM,YAAY,IAAI,KAAK,UAAU,WAAW,EAAE,aAAa,WAAW,CAAC;AAAA,EAC3E,aAAa,OAAO,aAAa,UAAU,QAAQ,IAAI,GAAG;AAAA,IACxD,MAAM,QAAQ,YAAY,OAAO;AAAA,IACjC,IAAI,OAAO,QAAQ,KAAK;AAAA,MACtB;AAAA,IACF;AAAA,IACA,QAAQ;AAAA,IACR,MAAM,QAAQ,QAAQ;AAAA,EACxB;AAAA,EACA,OAAO,KAAK,MAAM,GAAG,GAAG;AAAA;;;ACbnB,IAAM,qBAAqB,CAChC,MACA,cAEA,KAAK,SAAS,YACV,iBAAiB,SAAS,IAC1B,GAAG,UAAU,OAAO,YAAY,KAAK,UAAU;;;AHYrD,IAAM,aAAa;AACnB,IAAM,WAAW,IAAI,IAAI;AAAA,EACvB,CAAC,KAAK,MAAM;AAAA,EACZ,CAAC,KAAK,QAAQ;AAAA,EACd,CAAC,KAAK,QAAQ;AAChB,CAAC;AAID,IAAM,kBAAkB;AACxB,IAAM,cAAc,CAAC,SACnB,KACG,QAAQ,YAAY,CAAC,SAAS,SAAS,IAAI,IAAI,KAAK,IAAI,EACxD,QACC,iBACA,CAAC,YAAY,KAAK,QAAQ,YAAY,CAAC,KAAK,QAAQ,MAAM,CAAC,GAC7D;AAiBJ,IAAM,YAAY,CAAC,SAAqC;AAAA,EACtD,MAAM,QAA4B,CAAC;AAAA,EACnC,MAAM,UAAU,CAAC,SAAsB;AAAA,IACrC,IAAI,KAAK,SAAS,gBAAgB,KAAK,SAAS,QAAQ;AAAA,MAEtD,MAAM,QAAQ,KAAK,UAAU,MAAM,UAAU;AAAA,MAC7C,MAAM,MAAM,KAAK,UAAU,IAAI,UAAU;AAAA,MACzC,MAAM,OAAO,KAAK,MAAM,KAAK,IAAI,OAAO,CAAC,GAAG,KAAK,IAAI,KAAK,CAAC,CAAC,EAAE,UAAU;AAAA,MACxE,IACE,SAAS,MACR,KAAK,SAAS,gBACb,KAAK,WAAW,GAAG,KACnB,KAAK,WAAW,GAAG,IACrB;AAAA,QACA,MAAM,KAAK,CAAC,OAAO,GAAG,CAAC;AAAA,MACzB;AAAA,MACA;AAAA,IACF;AAAA,IACA,IAAI,cAAc,MAAM;AAAA,MACtB,WAAW,SAAS,KAAK,UAAU;AAAA,QACjC,QAAQ,KAAK;AAAA,MACf;AAAA,IACF;AAAA;AAAA,EAEF,QAAQ,aAAa,IAAI,CAAC;AAAA,EAC1B,OAAO;AAAA;AAIT,IAAM,UAAU,CAAC,SAAyB;AAAA,EACxC,IAAI,MAAM;AAAA,EACV,IAAI,SAAS;AAAA,EACb,YAAY,OAAO,QAAQ,UAAU,IAAI,GAAG;AAAA,IAC1C,OAAO,YAAY,KAAK,MAAM,QAAQ,KAAK,CAAC;AAAA,IAC5C,OAAO,KAAK,MAAM,OAAO,GAAG;AAAA,IAC5B,SAAS;AAAA,EACX;AAAA,EACA,OAAO,MAAM,YAAY,KAAK,MAAM,MAAM,CAAC;AAAA;AA4B7C,IAAM,uBAAuB;AAC7B,IAAM,aAAa;AACnB,IAAM,gBAAgB;AAStB,IAAM,aAAa,CAAC,aAA6B;AAAA,EAC/C,MAAM,OAAO,aAAa,QAAQ;AAAA,EAClC,MAAM,QACJ,KAAK,SAAS,KAAK,CAAC,SAAS,KAAK,SAAS,WAAW,KAAK,KAAK,SAAS;AAAA,EAC3E,OAAO,QAAQ,cAAc,KAAK,EAAE,QAAQ,YAAY,GAAG,EAAE,KAAK,IAAI;AAAA;AAIxE,IAAM,OAAO,CAAC,MAAc,QAAwB;AAAA,EAClD,IAAI,OAAO,GAAG;AAAA,IACZ,OAAO;AAAA,EACT;AAAA,EACA,IAAI,aAAY,IAAI,KAAK,KAAK;AAAA,IAC5B,OAAO;AAAA,EACT;AAAA,EACA,MAAM,OAAO,cAAc,MAAM,MAAM,CAAC;AAAA,EACxC,MAAM,iBAAiB,KAAK,QAAQ,eAAe,EAAE;AAAA,EAGrD,OAAO,GAAG,aAAY,cAAc,KAAK,MAAM,IAAI,iBAAiB;AAAA;AAGtE,IAAM,UAAU,CAAC,SAA8B,KAAK,SAAS,KAAK;AAElE,IAAM,YAAY,CAAC,SAA8B,cAAc,QAAQ,IAAI,CAAC;AAG5E,IAAM,yBAAyB;AAAA,EAC7B,MAAM;AAAA,EACN,OAAO;AAAA,EACP,WAAW;AAAA,EACX,UAAU;AAAA,EACV,QAAQ;AAAA,EACR,OAAO;AAAA,EACP,QAAQ;AAAA,EACR,cAAc;AAAA,EACd,OAAO;AACT;AAWA,IAAM,uBAAuB,CAC3B,MACA,WACA,YACW;AAAA,EACX,IAAI,CAAC,QAAQ,QAAQ;AAAA,IACnB,OAAO,KACL,WAAW,UAAU,eAAe,UAAU,OAAO,KAAK,QAAQ,OAClE,oBACF;AAAA,EACF;AAAA,EAGA,IAAI;AAAA,EACJ,IAAI,KAAK,SAAS,YAAY;AAAA,IAC5B,UAAU,GAAG,UAAU,uBAAuB,UAAU;AAAA,EAC1D,EAAO,SAAI,KAAK,SAAS,WAAW;AAAA,IAGlC,UAAU,GAAG,UAAU,QAAQ,uBAAuB,UAAU;AAAA,EAClE,EAAO;AAAA,IACL,UAAU,GAAG,UAAU,OAAO,YAAY,KAAK,UAAU;AAAA;AAAA,EAE3D,MAAM,SAAS,qBAAqB,kBAAkB,UAAU,IAAI;AAAA,EACpE,MAAM,QAAQ,KACZ,WAAW,UAAU,eAAe,UAAU,OAAO,GACrD,uBAAuB,aAAY,MAAM,IAAI,CAC/C;AAAA,EACA,OAAO,KAAK,CAAC,OAAO,MAAM,EAAE,OAAO,OAAO,EAAE,KAAK,GAAG,GAAG,oBAAoB;AAAA;AAI7E,IAAM,kBAAkB,CAAC,aAAqB,cAC5C,YAAY,KAAK,IACb,GAAG,QAAQ,YAAY,KAAK,CAAC;AAAA;AAAA,EAAQ,cACrC;AAEC,IAAM,eAAe,CAC1B,MACA,WACA,cAIiB;AAAA,EACjB,MAAM,SAAS,UAAU,OAAO,YAAY;AAAA,EAC5C,MAAM,UAAU,KAAK,SAAS;AAAA,EAG9B,MAAM,gBAAgB,UAClB,UAAU,OACV,mBAAmB,MAAM,SAAS;AAAA,EACtC,MAAM,QAAQ,UAAU,WAAW;AAAA,EAGnC,MAAM,cACJ,UAAU,YAAY,KAAK,MAAM,UAAU,QAAQ,KAAK,IACpD,KACA,UAAU;AAAA,EAChB,MAAM,QAAqD,CAAC;AAAA,EAC5D,IAAI,WAAW,kBAAkB,OAAO;AAAA,IACtC,MAAM,KAAK,EAAE,SAAS,KAAK;AAAA,EAC7B;AAAA,EACA,IAAI,UAAU,YAAY;AAAA,IACxB,MAAM,aAAa;AAAA,EACrB;AAAA,EACA,MAAM,cAGF,CAAC;AAAA,EACL,IAAI,WAAW,oBAAoB,OAAO;AAAA,IACxC,YAAY,UAAU;AAAA,EACxB;AAAA,EACA,MAAM,MAA+B;AAAA,IACnC,aAAa,qBAAqB,MAAM,WAAW;AAAA,MACjD,QAAQ,WAAW,yBAAyB;AAAA,MAC5C;AAAA,IACF,CAAC;AAAA,EACH;AAAA,EACA,IAAI,WAAW,SAAS;AAAA,IACtB,IAAI,UAAU;AAAA,EAChB;AAAA,EACA,MAAM,UAAuC;AAAA,IAC3C,OAAO,UAAU,WAAW,UAAU;AAAA,EACxC;AAAA,EAMA,IAAI,CAAC,WAAW,uBAAuB,UAAU,MAAM,GAAG;AAAA,IACxD,QAAQ,QAAQ;AAAA,EAClB;AAAA,EACA,MAAM,OAAO,UAAU,CAAC,UAAU,GAAG,IAAI,CAAC,UAAU,KAAK,MAAM;AAAA,EAC/D,OAAO;AAAA,IACL,MAAM,gBACJ,aACA,sBAAsB,KAAK,aAAa,UAAU,SACpD;AAAA,IACA,MAAM;AAAA,SACD;AAAA,MACH,QAAQ,KAAK,aAAa,KAAK;AAAA,MAC/B;AAAA,MACA;AAAA,MACA;AAAA,MAEA,MAAM;AAAA,IACR;AAAA,EACF;AAAA;AAGK,IAAM,cAAc,CACzB,MACA,cAIiB;AAAA,EAMjB,MAAM,aAAa,OAAO,OAAO,KAAK,UAAU;AAAA,EAGhD,MAAM,WAA6B,CAAC;AAAA,EACpC,MAAM,QAAQ,IAAI;AAAA,EAClB,WAAW,OAAO,KAAK,MAAM;AAAA,IAC3B,IAAI,CAAC,MAAM,IAAI,IAAI,IAAI,GAAG;AAAA,MACxB,MAAM,IAAI,IAAI,IAAI;AAAA,MAClB,SAAS,KAAK,GAAG;AAAA,IACnB;AAAA,EACF;AAAA,EACA,WAAW,aAAa,YAAY;AAAA,IAClC,IAAI,CAAC,MAAM,IAAI,UAAU,OAAO,GAAG;AAAA,MACjC,MAAM,IAAI,UAAU,OAAO;AAAA,MAC3B,SAAS,KAAK;AAAA,QACZ,aAAa;AAAA,QACb,MAAM,UAAU;AAAA,QAChB,MAAM,UAAU;AAAA,MAClB,CAAC;AAAA,IACH;AAAA,EACF;AAAA,EACA,MAAM,cAAwB,CAAC;AAAA,EAC/B,WAAW,OAAO,UAAU;AAAA,IAC1B,IAAI,CAAC,WAAW,KAAK,CAAC,cAAc,UAAU,YAAY,IAAI,IAAI,GAAG;AAAA,MACnE;AAAA,IACF;AAAA,IACA,MAAM,cAAc,IAAI,YAAY,KAAK,IACrC,CAAC,QAAQ,IAAI,YAAY,KAAK,CAAC,CAAC,IAChC,CAAC;AAAA,IACL,YAAY,KACV;AAAA,MACE,MAAM,QAAQ,IAAI,IAAI;AAAA,MACtB,GAAG;AAAA,MACH,6BAA6B,KAAK,cAAc,IAAI;AAAA,IACtD,EAAE,KAAK;AAAA;AAAA,CAAM,CACf;AAAA,EACF;AAAA,EACA,MAAM,QAAiD,CAAC;AAAA,EACxD,IAAI,WAAW,kBAAkB,OAAO;AAAA,IACtC,MAAM,KAAK,EAAE,SAAS,KAAK;AAAA,EAC7B;AAAA,EACA,IAAI,WAAW,oBAAoB,OAAO;AAAA,IACxC,MAAM,SAAS,EAAE,SAAS,KAAK;AAAA,EACjC;AAAA,EACA,MAAM,MAA+B;AAAA,IACnC,aACE,KAAK,WAAW,KAAK,WAAW,GAAG,oBAAoB,KACvD,GAAG,UAAU,IAAI;AAAA,EACrB;AAAA,EACA,IAAI,WAAW,SAAS;AAAA,IACtB,IAAI,UAAU;AAAA,EAChB;AAAA,EACA,OAAO;AAAA,IACL,MAAM;AAAA,MACJ,gBACE,KAAK,aACL,wBAAwB,KAAK,UAC/B;AAAA,MACA,GAAG;AAAA,IACL,EAAE,KAAK;AAAA;AAAA,CAAM;AAAA,IACb,MAAM;AAAA,SACD;AAAA,MACH;AAAA,MACA,SAAS,EAAE,OAAO,WAAW;AAAA,MAC7B,OAAO,QAAQ,IAAI;AAAA,IACrB;AAAA,EACF;AAAA;;;AInUK,IAAM,kBAAkB,CAC7B,YAEA,UAAU,WAAU,OAAO,SAAS;AAGtC,IAAM,aAAa,CAAC,UAA0B,MAAM,QAAQ,SAAS,EAAE;AAGvE,IAAM,cAAc;AAAA,EAClB,UAAU;AAAA,EACV,SAAS;AAAA,EACT,SAAS;AACX;AAGA,IAAM,gBAAgB;AAAA,EACpB,UAAU;AAAA,EACV,SAAS;AAAA,EACT,SAAS;AACX;AAOA,IAAM,qBAAqB;AAAA,EACzB,UAAU;AAAA,EACV,SAAS;AAAA,EACT,SAAS;AACX;AAOA,IAAM,gBAAgB;AAAA,EACpB,UAAU;AAAA,EACV,SAAS;AAAA,EACT,SAAS;AACX;AAGA,IAAM,oBAAoB;AAAA,EACxB,UACE;AAAA,EACF,SACE;AAAA,EACF,SACE;AACJ;AAGA,IAAM,sBAAsB;AAAA,EAC1B,UACE;AAAA,EACF,SACE;AAAA,EACF,SACE;AACJ;AAEA,IAAM,UAAU,CAAC,UAAwB,QAA6B;AAAA,EACpE,MAAM,MAAM,oBAAO,UAAU,GAAG,SAAS;AAAA,GAAU,SAAS,IAAI;AAAA,EAChE,OAAO;AAAA,IACL,MAAM,EAAE,QAAQ,OAAO,MAAM,SAAS,KAAK;AAAA,IAG3C,MAAM,KAAK,SAAS,KAAK;AAAA,IACzB,MAAM,SAAS,GAAG;AAAA,IAClB;AAAA,IACA;AAAA,EACF;AAAA;AAIF,IAAM,cAAc,CAClB,MACA,YACA,cACkB;AAAA,EAClB,MAAM,UAAU,WAAW,IAAI,CAAC,cAC9B,QACE,aAAa,MAAM,WAAW,SAAS,GACvC,GAAG,WAAW,UAAU,KAAK,OAC/B,CACF;AAAA,EAIA,MAAM,OAAO,WAAW,KAAK,KAAK;AAAA,EAClC,QAAQ,KACN,QACE,YAAY,MAAM,SAAS,GAC3B,OAAO,GAAG,mBAAmB,WAC/B,CACF;AAAA,EACA,OAAO;AAAA;AAWT,IAAM,gBAAgB,CACpB,MACA,SAC+B;AAAA,EAC/B,MAAM,OAAO,WAAW,KAAK,KAAK;AAAA,EAClC,OAAO,OAAO,YACZ,KAAK,IAAI,CAAC,QAAQ;AAAA,IAChB,OAAO,GAAG,QAAQ,IAAI,SAAS,IAAI;AAAA,IACnC,EAAE,OAAO,IAAI,KAAK;AAAA,EACpB,CAAC,CACH;AAAA;AAsBF,IAAM,iBAAiB,OACrB,WACA,QAC6B;AAAA,EAC7B,MAAM,UAAU,EAAE,UAAU,IAAI,UAAU,SAAS,IAAI,QAAQ;AAAA,EAC/D,IAAI,UAAU,SAAS,YAAY;AAAA,IACjC,QAAQ,UAAU,aAAa,MAAM,kBACnC,UAAU,MACV,IAAI,aACJ,OACF;AAAA,IACA,MAAM,YAAY,0BAA0B,UAAU,UAAU,KAAK;AAAA,IACrE,OAAO;AAAA,MACL;AAAA,MACA,iBAAiB,UAAU;AAAA,MAC3B,YAAY,UAAU;AAAA,MACtB,MAAM,UAAU;AAAA,MAChB;AAAA,IACF;AAAA,EACF;AAAA,EACA,IAAI,UAAU,SAAS,WAAW;AAAA,IAChC,QAAQ,UAAU,aAAa,MAAM,iBACnC,UAAU,MACV,IAAI,aACJ,OACF;AAAA,IACA,MAAM,YAAY,yBAAyB,UAAU,UAAU,KAAK;AAAA,IACpE,OAAO;AAAA,MACL;AAAA,MACA,iBAAiB,UAAU;AAAA,MAC3B,YAAY,UAAU;AAAA,MACtB,MAAM,UAAU;AAAA,MAChB;AAAA,IACF;AAAA,EACF;AAAA,EACA,QAAQ,UAAU,aAAa,MAAM,UACnC,UAAU,MACV,IAAI,aACJ,OACF;AAAA,EACA,MAAM,YAAY,kBAAkB,UAAU,UAAU,KAAK;AAAA,EAC7D,OAAO;AAAA,IACL;AAAA,IACA,iBAAiB,UAAU;AAAA,IAC3B,YAAY,UAAU;AAAA,IACtB,MAAM,UAAU;AAAA,IAChB;AAAA,EACF;AAAA;AAGK,IAAM,gBAAgB,CAC3B,YACA,QACyB;AAAA,EACzB,IAAI,SAAsB,CAAC;AAAA,EAE3B,MAAM,gBAAgB,OACpB,cACqC;AAAA,IAGrC,MAAM,YAAY,YAAY,UAAU;AAAA,IACxC,MAAM,aAAa,cAAc,UAAU;AAAA,IAC3C,IAAI;AAAA,MACF,QAAQ,UAAU,UAAU,YAAY,MAAM,oBAC5C,MAAM,eAAe,WAAW,GAAG;AAAA,MACrC,MAAM,OAAO,SAAS,QAAQ,EAAE,OAAO,UAAU,OAAO,SAAS,GAAG;AAAA,MAMpE,QAAQ,OAAO,oBAAoB,UAAU,QAAQ;AAAA,MACrD,IAAI,QAAwB;AAAA,MAC5B,IAAI,oBAAoB,MAAM;AAAA,QAC5B,QAAQ,aAAa,UAAU,UAAU,aAAa;AAAA,MACxD,EAAO,SAAI,oBAAoB,SAAS,oBAAoB,IAAI;AAAA,QAC9D,QAAQ;AAAA,MACV;AAAA,MACA,MAAM,OAAoB;AAAA,QACxB,aAAa,UAAU,QAAQ;AAAA,QAC/B,aAAa,KAAK,eAAe;AAAA,QACjC;AAAA,QACA,eAAe,UAAU,QAAQ;AAAA,QACjC,MAAM,UAAU;AAAA,QAChB,OAAO,UAAU;AAAA,QAKjB,YAAY,OAAO,YACjB,WAAW,IAAI,CAAC,cAAc;AAAA,UAC5B,UAAU;AAAA,UACV;AAAA,eACK;AAAA,YACH,OAAO,aAAa,UAAU,UAAU,UAAU,KAAK;AAAA,UACzD;AAAA,QACF,CAAC,CACH;AAAA,QACA,YAAY,EAAE,SAAS,UAAU,QAAQ,WAAW,SAAS,MAAM;AAAA,QACnE,OAAO,UAAU;AAAA,QACjB,MAAM,UAAU;AAAA,QAChB;AAAA,QACA,OAAO,KAAK,SAAS,UAAU;AAAA,QAC/B,SAAS,KAAK,WAAW;AAAA,MAC3B;AAAA,MAGA,IAAI,UAAU,aAAa,WAAW;AAAA,QACpC,KAAK,WAAW,UAAU;AAAA,MAC5B;AAAA,MACA,OAAO;AAAA,QACL,aAAa;AAAA,UACX,GAAG,SAAS,IAAI,CAAC,aAAa;AAAA,YAC5B,MAAM,mBAAmB,UAAU;AAAA,YACnC;AAAA,YACA,UAAU;AAAA,UACZ,EAAE;AAAA,UACF,GAAG,gBAAgB,IAAI,CAAC,aAAa;AAAA,YACnC,MAAM,cAAc,UAAU;AAAA,YAC9B,SAAS,MAAM,mBAAmB,UAAU,UAAU;AAAA,YACtD,UAAU;AAAA,UACZ,EAAE;AAAA,UAIF,GAAI,WAAW,WAAW,IACtB;AAAA,YACE;AAAA,cACE,MAAM,GAAG;AAAA,cACT,SAAS,GAAG,mBAAmB,UAAU,aAAa,UAAU;AAAA,cAChE,UAAU;AAAA,cACV,YAAY,kBAAkB,UAAU;AAAA,YAC1C;AAAA,UACF,IACA,CAAC;AAAA,QACP;AAAA,QACA,SAAS,YAAY,MAAM,YAAY,SAAS;AAAA,QAChD,YAAY,cAAc,MAAM,IAAI;AAAA,QACpC,MAAM,UAAU;AAAA,QAChB;AAAA,MACF;AAAA,MACA,OAAO,OAAO;AAAA,MACd,OAAO;AAAA,QACL,MAAM,GAAG;AAAA,QAGT,SAAS,kBAAkB,mBAAmB,UAAU,aAAa,UAAU,UAAW,MAAgB;AAAA,QAI1G,UAAU,IAAI,SAAS,UAAU,UAAU;AAAA,QAG3C,YACE,iBAAiB,mBACb,oBAAoB,UAAU,QAC9B;AAAA,MACR;AAAA;AAAA;AAAA,EAIJ,MAAM,OAAO,YAAuC;AAAA,IAClD,MAAM,UAAU,MAAM,QAAQ,IAAI,WAAW,IAAI,aAAa,CAAC;AAAA,IAC/D,MAAM,UAAyB,CAAC;AAAA,IAIhC,MAAM,cAA4B,WAAW,QAAQ,CAAC,eACnD,UAAU,cAAc,CAAC,GAAG,IAAI,CAAC,aAAa;AAAA,MAC7C,MAAM;AAAA,MACN;AAAA,MACA,UAAU;AAAA,IACZ,EAAE,CACJ;AAAA,IACA,MAAM,OAAoB,CAAC;AAAA,IAC3B,MAAM,aAAyC,CAAC;AAAA,IAChD,WAAW,UAAU,SAAS;AAAA,MAC5B,IAAI,cAAc,QAAQ;AAAA,QACxB,YAAY,KAAK,MAAM;AAAA,QACvB;AAAA,MACF;AAAA,MACA,KAAK,OAAO,QAAQ,OAAO;AAAA,MAC3B,QAAQ,KAAK,GAAG,OAAO,OAAO;AAAA,MAC9B,OAAO,OAAO,YAAY,OAAO,UAAU;AAAA,MAC3C,YAAY,KAAK,GAAG,OAAO,WAAW;AAAA,IACxC;AAAA,IACA,SAAS;AAAA,IACT,OAAO,EAAE,aAAa,SAAS,WAAW;AAAA;AAAA,EAG5C,OAAO;AAAA,IACL,MAAM;AAAA,IACN;AAAA,IACA,MAAM;AAAA,IACN,aAAa,MAAM;AAAA,IACnB,QAAQ;AAAA,EACV;AAAA;;;ACvYF,uBAAS,sBAAY;AACrB,qBAAS;AAET,oBAAS,wBAAS,qBAAY,mBAAM,sBAAU;AAC9C,iBAAS;;;ACkBF,IAAM,oBAAoB;AAAA,EAC/B;AAAA,EAKA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAQO,IAAM,qBAAqB,MAChC,kBAAkB,IAAI,CAAC,QAAQ,MAAM,QAAQ;AAMxC,IAAM,0BAA0B;AAGhC,IAAM,qBAAqB,CAAC,aACjC,SACG,IAAI,CAAC,YAAY,0BAA0B,KAAK,OAAO,GAAG,QAAQ,GAAG,EACrE,OAAO,CAAC,QAAuB,QAAQ,SAAS;AAU9C,IAAM,wBAAwB,CACnC,UACA,aAA+B,yBAC/B,gBAA8C,MAAM,UAC1B;AAAA,EAC1B,MAAM,SAAS,IAAI,IAAI,UAAU;AAAA,EACjC,OAAO,CAAC,QAAQ,aAAa;AAAA,IAC3B,IACE,aAAa,QACb,SACG,MAAM,QAAQ,EACd,KAAK,CAAC,YAAY,OAAO,IAAI,OAAO,KAAK,cAAc,OAAO,CAAC,GAClE;AAAA,MACA;AAAA,IACF;AAAA,IACA,SAAS;AAAA;AAAA;;;AD7CN,IAAM,mBAAmB,CAC9B,YACqD;AAAA,EACrD,MAAM,cAAc,YAAW,QAAQ,IAAI,IACvC,QAAQ,OACR,MAAK,SAAQ,QAAQ,WAAW,GAAG,QAAQ,IAAI;AAAA,EAEnD,MAAM,OAAO,YAAuC;AAAA,IAClD,MAAM,QAAQ,MAAM,MAAK,QAAQ,SAAS;AAAA,MACxC,UAAU;AAAA,MACV,KAAK;AAAA,MAIL,QAAQ,CAAC,GAAG,QAAQ,SAAS,GAAG,mBAAmB,CAAC;AAAA,MACpD,WAAW;AAAA,IACb,CAAC;AAAA,IACD,MAAM,KAAK;AAAA,IAEX,MAAM,UAAU,MAAM,QAAQ,IAC5B,MAAM,IAAI,OAAO,SAA+B;AAAA,MAC9C,MAAM,SAAS,MAAM,UAAS,MAAM,OAAO;AAAA,MAC3C,MAAM,MAAM,SAAQ,IAAI,EAAE,YAAY;AAAA,MACtC,MAAM,SAAS,QAAQ,SAAS,QAAQ;AAAA,MACxC,MAAM,SAAS,oBAAO,MAAM;AAAA,MAC5B,OAAO;AAAA,QACL,MAAM,EAAE,QAAQ,MAAM,OAAO,QAAQ;AAAA,QACrC,MAAM,OAAO;AAAA,QAIb,KAAK;AAAA,QACL,KAAK,UAAS,aAAa,IAAI;AAAA,QAC/B,YAAY;AAAA,MACd;AAAA,KACD,CACH;AAAA,IAEA,OAAO,EAAE,aAAa,CAAC,GAAG,QAAQ;AAAA;AAAA,EAGpC,MAAM,WAAW,MAAY;AAAA,IAC3B,IAAI,CAAC,YAAW,WAAW,GAAG;AAAA,MAC5B,MAAM,IAAI,WAAW;AAAA,QACnB,MAAM,QAAQ,eAAe;AAAA,QAC7B,MAAM;AAAA,QACN,SAAS,2BAA2B,QAAQ;AAAA,QAC5C,UAAU;AAAA,QACV,YAAY,aAAa,QAAQ;AAAA,MACnC,CAAC;AAAA,IACH;AAAA;AAAA,EAOF,MAAM,kBAAkB,IAAI,IAAI;AAAA,IAC9B,GAAG;AAAA,IACH,GAAG,mBAAmB,QAAQ,OAAO;AAAA,EACvC,CAAC;AAAA,EAED,MAAM,QAAQ,CAAC,aAAuC;AAAA,IACpD,IAAI,CAAC,YAAW,WAAW,GAAG;AAAA,MAC5B,OAAO,MAAM;AAAA,IAGf;AAAA,IACA,MAAM,UAAU,QACd,aACA,EAAE,WAAW,KAAK,GAClB,sBAAsB,UAAU,eAAe,CACjD;AAAA,IACA,OAAO,MAAM,QAAQ,MAAM;AAAA;AAAA,EAG7B,OAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA,MAAM,QAAQ;AAAA,IACd,QAAQ,QAAQ;AAAA,IAChB,MAAM,CAAC,QACL,UAAS,MAAK,aAAa,GAAG,GAAG,OAAO;AAAA,IAC1C,QAAQ;AAAA,IACR;AAAA,IACA;AAAA,EACF;AAAA;;;AE3HF,yBAAS;AACT;AACA,qBAAS;AACT;AACA;AAsDA,IAAM,gBAAgB;AACtB,IAAM,WAAW;AAEjB,IAAM,YAAY;AAClB,IAAM,WAAW;AACjB,IAAM,cAAc;AAMpB,IAAM,kBAAkB;AACxB,IAAM,kBAAkB;AAKxB,IAAM,iBAAiB;AACvB,IAAM,cAAa;AACnB,IAAM,oBAAoB;AAG1B,IAAM,YAAY,IAAI,IAAI,CAAC,QAAQ,WAAW,QAAQ,eAAe,CAAC;AAUtE,IAAM,qBAAqB,CAAC,SAAqC;AAAA,EAC/D,MAAM,OAAO,cAAa,KAAK,WAAW,gBAAgB,SAAS,GAAG;AAAA,IACpE,YAAY,CAAC,IAAI,CAAC;AAAA,IAClB,iBAAiB,CAAC,gBAAgB,CAAC;AAAA,EACrC,CAAC;AAAA,EACD,MAAM,OAAO,KAAK,SACf,OAAO,CAAC,SAAS,CAAC,UAAU,IAAI,KAAK,IAAI,CAAC,EAG1C,IAAI,CAAC,SACJ,eAAc,MAAM,EAAE,aAAa,OAAO,iBAAiB,MAAM,CAAC,CACpE,EACC,KAAK,GAAG,EACR,WAAW,aAAY,GAAG,EAC1B,KAAK;AAAA,EACR,IAAI,CAAC,MAAM;AAAA,IACT;AAAA,EACF;AAAA,EACA,IAAI,aAAY,IAAI,KAAK,iBAAiB;AAAA,IACxC,OAAO;AAAA,EACT;AAAA,EAGA,MAAM,QAAQ,cAAc,MAAM,kBAAkB,CAAC;AAAA,EACrD,MAAM,WAAW,MAAM,YAAY,GAAG;AAAA,EACtC,MAAM,QACJ,aAAa,MAAM,aAAY,MAAM,MAAM,GAAG,QAAQ,CAAC,KAAK,kBACxD,MAAM,MAAM,GAAG,QAAQ,IACvB,OACJ,QAAQ,mBAAmB,EAAE;AAAA,EAC/B,OAAO,GAAG;AAAA;AAIZ,IAAM,aAAa,CAAC,QAClB,IAAI,YAAY,EAAE,WAAW,UAAU,GAAG,EAAE,WAAW,aAAa,EAAE;AAGxE,IAAM,gBAAgB,MAAe;AAAA,EACnC,MAAM,UAAU,IAAI,QAAQ,EAAE,QAAQ,8BAA8B,CAAC;AAAA,EACrE,MAAM,QAAQ,QAAQ,IAAI;AAAA,EAC1B,IAAI,OAAO;AAAA,IACT,QAAQ,IAAI,iBAAiB,UAAU,OAAO;AAAA,EAChD;AAAA,EACA,OAAO;AAAA;AAsBT,IAAM,iBAAiB,CAAC,YAAwC;AAAA,EAC9D,MAAM,UAAU,QAAQ,SAAS,QAAQ,WAAW,EAAE;AAAA,EACtD,MAAM,QAAQ,QAAQ,MAAM,KAAK,KAAK,QAAQ;AAAA,EAC9C,MAAM,OAAO,QAAQ,gBAAgB,QAAQ;AAAA,EAC7C,MAAM,WAAW,QAAQ,aAAa,eAAe;AAAA,EACrD,MAAM,QAAQ,QAAQ,QAAQ,IAAI,WAAW;AAAA,GAAQ;AAAA,CAAI,EAAE,KAAK;AAAA,EAIhE,MAAM,cAAc,mBAAmB,IAAI;AAAA,EAG3C,MAAM,OAA6B;AAAA,IACjC,WAAW,EAAE,UAAU,QAAQ;AAAA,IAC/B;AAAA,EACF;AAAA,EACA,IAAI,aAAa;AAAA,IACf,KAAK,MAAM,EAAE,YAAY;AAAA,EAC3B;AAAA,EACA,KAAK,QAAQ;AAAA,EACb,KAAK,OAAO;AAAA,EACZ,MAAM,MAAM,oBAAO,UAAU,GAAG;AAAA,GAAU,IAAI;AAAA,EAC9C,MAAM,cAAc,WAAW,QAAQ;AAAA,EACvC,MAAM,MAAM,GAAG,WAAW,QAAQ,QAAQ,KAAK;AAAA,EAC/C,OAAO;AAAA,IACL,MAAM,EAAE,QAAQ,MAAM,MAAM,KAAK;AAAA,IAGjC,MAAM,KAAK,KAAK;AAAA,IAChB,SAAS,QAAQ;AAAA,IACjB,MAAM,SAAS,GAAG;AAAA,IAClB,cAAc;AAAA,IACd;AAAA,IACA;AAAA,EACF;AAAA;AAUK,IAAM,uBAAuB,CAClC,SACA,QACkB;AAAA,EAClB,MAAM,UAAU,QAAQ,aAAa,WAAW;AAAA,EAChD,MAAM,QAAQ,QAAQ,WAAW,gBAAgB,QAAQ,QAAQ,EAAE;AAAA,EACnE,MAAM,MAAM,QAAQ,SAAS;AAAA,EAC7B,MAAM,QAAQ,cAAc,IAAI,QAAQ;AAAA,EACxC,IAAI,WAAW,IAAI;AAAA,EAEnB,MAAM,UAAU,CAAC,aACd,QAAQ,UAAU,CAAC,QAAQ,WAC3B,QAAQ,eAAe,CAAC,QAAQ;AAAA,EAEnC,MAAM,YAAY,OAAO,SAA2C;AAAA,IAClE,MAAM,MAAM,GAAG,cAAc,QAAQ,SAAS,QAAQ,0BAA0B,iBAAiB;AAAA,IACjG,MAAM,MAAM,MAAM,QAAQ,KAAK,EAAE,SAAS,cAAc,EAAE,CAAC;AAAA,IAC3D,IAAI,CAAC,IAAI,IAAI;AAAA,MACX,MAAM,IAAI,MAAM,GAAG,UAAU,IAAI,QAAQ;AAAA,IAC3C;AAAA,IAGA,OAAQ,MAAM,IAAI,KAAK;AAAA;AAAA,EAGzB,MAAM,gBAAgB,YAAsC;AAAA,IAC1D,MAAM,YAA6B,CAAC;AAAA,IACpC,IAAI,OAAO;AAAA,IACX,OAAO,UAAU,SAAS,KAAK;AAAA,MAE7B,MAAM,QAAQ,MAAM,UAAU,IAAI;AAAA,MAClC,UAAU,KAAK,GAAG,MAAM,OAAO,OAAO,CAAC;AAAA,MACvC,IAAI,MAAM,SAAS,UAAU;AAAA,QAC3B;AAAA,MACF;AAAA,MACA,QAAQ;AAAA,IACV;AAAA,IACA,OAAO,UAAU,MAAM,GAAG,GAAG;AAAA;AAAA,EAG/B,MAAM,OAAO,OACX,UAAU,IAAI,WAAW,SACK;AAAA,IAC9B,IAAI;AAAA,MACF,MAAM,SAAS,MAAM,cACnB,QAAQ,MACR,OACA,YAAY;AAAA,QACV,MAAM,WAAW,MAAM,cAAc;AAAA,QACrC,OAAO,SAAS,IAAI,cAAc;AAAA,SAEpC,OACF;AAAA,MACA,WAAW,IAAI,IAAI,OAAO,QAAQ,IAAI,CAAC,UAAU,CAAC,MAAM,KAAK,KAAK,CAAC,CAAC;AAAA,MACpE,OAAO;AAAA,MACP,OAAO,OAAO;AAAA,MAKd,WAAW,IAAI;AAAA,MAGf,OAAO;AAAA,QACL,aAAa;AAAA,UACX;AAAA,YACE,MAAM;AAAA,YACN,SAAS,WAAW,QAAQ,0CAA2C,MAAgB;AAAA,YACvF,UAAU;AAAA,UACZ;AAAA,QACF;AAAA,QACA,SAAS,CAAC;AAAA,MACZ;AAAA;AAAA;AAAA,EAIJ,MAAM,OAAO,OAAO,QAAiC;AAAA,IACnD,MAAM,SAAS,SAAS,IAAI,GAAG;AAAA,IAC/B,IAAI,QAAQ;AAAA,MACV,OAAO,OAAO,OAAO,OAAO,KAAK;AAAA,IACnC;AAAA,IACA,MAAM,MAAM,MAAM,MAAM,KAAK;AAAA,IAC7B,MAAM,QAAQ,IAAI,KAAK,CAAC,MAAM,EAAE,QAAQ,GAAG;AAAA,IAC3C,OAAO,OAAO,OAAO,OAAO,KAAK,QAAQ;AAAA;AAAA,EAG3C,OAAO;AAAA,IACL;AAAA,IACA,MAAM,QAAQ;AAAA,IACd,QAAQ,QAAQ;AAAA,IAChB;AAAA,IACA,QAAQ;AAAA,IACR,OAAO,QAAQ,eACX,aACE,MAAM,KAAK,IAAI,GACf,QAAQ,cACR,MAAM,KAAK,CACb,IACA;AAAA,EACN;AAAA;;;AC3SF;AAkDA,IAAM,eAAe,IAAI,IAAI,CAAC,kBAAkB,2BAA2B,CAAC;AAE5E,IAAM,iBAAgB,CAAC,QAAwC;AAAA,EAC7D,MAAM,QAAQ,QAAQ,IAAI;AAAA,EAC1B,IAAI,CAAC,OAAO;AAAA,IACV,OAAO,CAAC;AAAA,EACV;AAAA,EACA,IAAI,OAAO;AAAA,EACX,IAAI;AAAA,IACF,OAAO,IAAI,IAAI,GAAG,EAAE;AAAA,IACpB,MAAM;AAAA,IACN,OAAO,CAAC;AAAA;AAAA,EAEV,OAAO,aAAa,IAAI,IAAI,IAAI,EAAE,eAAe,UAAU,QAAQ,IAAI,CAAC;AAAA;AAS1E,IAAM,kBAAkB,OACtB,QACA,SACA,YACuD;AAAA,EACvD,QAAQ,OAAO,MAAM,QAAQ;AAAA,EAC7B,MAAM,OAAO,OAAO,KAAK,WAAW,aAAa,EAAE;AAAA,EACnD,MAAM,UAAU,gCAAgC,SAAS,kBAAkB;AAAA,EAC3E,MAAM,MAAM,MAAM,QAAQ,SAAS,EAAE,SAAS,eAAc,OAAO,EAAE,CAAC;AAAA,EACtE,IAAI,CAAC,IAAI,IAAI;AAAA,IACX,MAAM,IAAI,MAAM,GAAG,cAAc,IAAI,QAAQ;AAAA,EAC/C;AAAA,EAGA,MAAM,OAAQ,MAAM,IAAI,KAAK;AAAA,EAI7B,MAAM,SAAS,OAAO,GAAG,UAAU;AAAA,EACnC,MAAM,WAAW,UAAU,OAAO;AAAA,EAClC,MAAM,QAAQ,KAAK,QAAQ,CAAC,GAAG,QAAQ,CAAC,SAAS;AAAA,IAC/C,IAAI,EAAE,KAAK,SAAS,UAAU,KAAK,KAAK,WAAW,MAAM,IAAI;AAAA,MAC3D,OAAO,CAAC;AAAA,IACV;AAAA,IACA,MAAM,MAAM,KAAK,KAAK,MAAM,OAAO,MAAM;AAAA,IACzC,IAAI,CAAC,SAAS,GAAG,GAAG;AAAA,MAClB,OAAO,CAAC;AAAA,IACV;AAAA,IACA,OAAO;AAAA,MACL;AAAA,QACE,SAAS,sBAAsB,SAAS,aAAa,OAAO,SAAS;AAAA,QACrE,UAAU,qCAAqC,SAAS,QAAQ,OAAO,SAAS;AAAA,QAChF,KAAK;AAAA,MACP;AAAA,IACF;AAAA,GACD;AAAA,EAGD,OAAO,EAAE,MAAM,WAAW,KAAK,cAAc,KAAK;AAAA;AAQ7C,IAAM,kBAAkB,CAC7B,SACA,QACkB;AAAA,EAClB,MAAM,UAAU,QAAQ,aAAa,WAAW;AAAA,EAChD,MAAM,QAAQ,cAAc,IAAI,QAAQ;AAAA,EACxC,IAAI,WAAW,IAAI;AAAA,EAMnB,MAAM,mBAAmB,MAAY;AAAA,IACnC,IAAI,QAAQ,UAAW,QAAQ,SAAS,QAAQ,KAAM;AAAA,MACpD;AAAA,IACF;AAAA,IACA,MAAM,IAAI,WAAW;AAAA,MACnB,MAAM;AAAA,MACN,SAAS,WAAW,QAAQ;AAAA,MAC5B,UAAU;AAAA,IACZ,CAAC;AAAA;AAAA,EAGH,MAAM,YAAY,YAGZ;AAAA,IACJ,IAAI,QAAQ,QAAQ;AAAA,MAClB,OAAO,MAAM,gBAAgB,QAAQ,QAAQ,QAAQ,SAAS,OAAO;AAAA,IACvE;AAAA,IACA,MAAM,QAAQ,QAAQ,OAAO,IAAI,QAAQ,QAAQ,EAAE;AAAA,IACnD,MAAM,WAAW,UAAU,QAAQ,OAAO;AAAA,IAC1C,MAAM,QAAQ,QAAQ,SAAS,CAAC,GAAG,QAAQ,CAAC,QAC1C,SAAS,GAAG,IACR,CAAC,EAAE,SAAS,GAAG,QAAQ,OAAO,UAAU,GAAG,QAAQ,OAAO,IAAI,CAAC,IAC/D,CAAC,CACP;AAAA,IACA,OAAO,EAAE,MAAM,WAAW,MAAM;AAAA;AAAA,EAGlC,MAAM,aAAa,OAAO,SAA0C;AAAA,IAClE,MAAM,MAAM,MAAM,QAAQ,KAAK,UAAU;AAAA,MACvC,SAAS,eAAc,KAAK,QAAQ;AAAA,IACtC,CAAC;AAAA,IACD,IAAI,CAAC,IAAI,IAAI;AAAA,MACX,MAAM,IAAI,MAAM,GAAG,KAAK,eAAe,IAAI,QAAQ;AAAA,IACrD;AAAA,IACA,MAAM,OAAO,MAAM,IAAI,KAAK;AAAA,IAC5B,MAAM,SAAS,oBAAO,IAAI;AAAA,IAC1B,MAAM,SAAS,KAAK,IAAI,YAAY,EAAE,SAAS,MAAM,IAAI,QAAQ;AAAA,IACjE,OAAO;AAAA,MACL,MAAM,EAAE,QAAQ,MAAM,OAAO,QAAQ;AAAA,MACrC,MAAM,OAAO;AAAA,MACb,SAAS,KAAK;AAAA,MACd,MAAM,SAAS,IAAI;AAAA,MACnB,KAAK;AAAA,MACL,KAAK,KAAK;AAAA,IACZ;AAAA;AAAA,EAGF,MAAM,OAAO,OACX,UAAU,IAAI,WAAW,SACK;AAAA,IAC9B,iBAAiB;AAAA,IACjB,MAAM,UAAwB,CAAC;AAAA,IAC/B,MAAM,SAAS,MAAM,cACnB,QAAQ,MACR,OACA,YAAY;AAAA,MACV,QAAQ,MAAM,cAAc,MAAM,UAAU;AAAA,MAC5C,IAAI,WAAW;AAAA,QACb,QAAQ,KAAK;AAAA,UACX,MAAM;AAAA,UACN,SAAS,WAAW,QAAQ;AAAA,UAC5B,UAAU;AAAA,QACZ,CAAC;AAAA,MACH;AAAA,MACA,MAAM,UAAU,MAAM,QAAQ,IAC5B,KAAK,IAAI,OAAO,QAAQ;AAAA,QACtB,IAAI;AAAA,UACF,OAAO,MAAM,WAAW,GAAG;AAAA,UAC3B,OAAO,OAAO;AAAA,UACd,QAAQ,KAAK;AAAA,YACX,MAAM;AAAA,YAGN,SAAS,WAAW,QAAQ,kBAAkB,IAAI,SAAU,MAAgB;AAAA,YAC5E,UAAU;AAAA,UACZ,CAAC;AAAA,UACD,OAAO;AAAA;AAAA,OAEV,CACH;AAAA,MACA,MAAM,UAAU,QAAQ,OACtB,CAAC,UAAgC,UAAU,IAC7C;AAAA,MAIA,IAAI,KAAK,SAAS,KAAK,QAAQ,WAAW,GAAG;AAAA,QAC3C,QAAQ,SAAS;AAAA,QACjB,MAAM,IAAI,MAAM,OAAO,KAAK,uCAAuC;AAAA,MACrE;AAAA,MACA,OAAO;AAAA,OAET,OACF;AAAA,IACA,WAAW,IAAI,IAAI,OAAO,QAAQ,IAAI,CAAC,UAAU,CAAC,MAAM,KAAK,KAAK,CAAC,CAAC;AAAA,IACpE,OAAO;AAAA,SACF;AAAA,MACH,aAAa,CAAC,GAAG,OAAO,aAAa,GAAG,OAAO;AAAA,IACjD;AAAA;AAAA,EAGF,MAAM,OAAO,OAAO,QAAiC;AAAA,IACnD,MAAM,SAAS,SAAS,IAAI,GAAG;AAAA,IAC/B,IAAI,QAAQ;AAAA,MACV,OAAO,OAAO,OAAO,OAAO,KAAK;AAAA,IACnC;AAAA,IACA,MAAM,MAAM,MAAM,MAAM,KAAK;AAAA,IAC7B,MAAM,QAAQ,IAAI,KAAK,CAAC,MAAM,EAAE,QAAQ,GAAG;AAAA,IAC3C,OAAO,OAAO,OAAO,OAAO,KAAK,QAAQ;AAAA;AAAA,EAG3C,OAAO;AAAA,IACL;AAAA,IACA,MAAM,QAAQ;AAAA,IACd,QAAQ,QAAQ;AAAA,IAChB;AAAA,IACA,QAAQ;AAAA,IACR,OAAO,QAAQ,eACX,aACE,MAAM,KAAK,IAAI,GACf,QAAQ,cACR,MAAM,KAAK,CACb,IACA;AAAA,EACN;AAAA;;;AC/PF,uBAAS;AAET;AACA,iBAAS;;;ACIT,IAAM,UAAU;AAIhB,IAAM,SACJ;AAOK,IAAM,iBAAiB,CAAC,UAAiC;AAAA,EAC9D,MAAM,QAAQ,MAAM,KAAK;AAAA,EACzB,IAAI,CAAC,OAAO;AAAA,IACV,OAAO;AAAA,EACT;AAAA,EACA,IAAI,QAAQ,KAAK,KAAK,GAAG;AAAA,IACvB,OAAO;AAAA,EACT;AAAA,EACA,OAAO,OAAO,KAAK,KAAK,GAAG,QAAQ,MAAM;AAAA;;;AC3B3C;AACA;AAAA;AAAA,WAEE;AAAA;AAAA;AAAA;AAAA,eAIA;AAAA;AAGF,oBAAS,kBAAS;AAKlB,IAAM,YAAW;AAKjB,IAAM,iBACJ;AACF,IAAM,SAAS;AACf,IAAM,WAAW;AACjB,IAAM,mBACJ;AAGF,IAAM,cAAc;AAMpB,IAAM,cAAc,IAAI,IAAI;AAAA,EAC1B,CAAC,cAAc,OAAO;AAAA,EACtB,CAAC,aAAa,MAAM;AAAA,EACpB,CAAC,cAAc,MAAM;AAAA,EACrB,CAAC,aAAa,MAAM;AAAA,EACpB,CAAC,iBAAiB,MAAM;AAAA,EACxB,CAAC,cAAc,OAAO;AAAA,EACtB,CAAC,aAAa,MAAM;AAAA,EACpB,CAAC,aAAa,MAAM;AAAA,EACpB,CAAC,mBAAmB,MAAM;AAAA,EAC1B,CAAC,cAAc,OAAO;AACxB,CAAC;AACD,IAAM,cAAc;AAIpB,IAAM,iBACJ;AACF,IAAM,cAAc;AAGpB,IAAM,qBAAqB;AAmB3B,IAAM,aAAa,CAAC,QAA+B;AAAA,EACjD,MAAM,QAAQ,IAAI,MAAM,GAAG,EAAE,MAAM;AAAA,EACnC,MAAM,MAAM,SAAQ,KAAK;AAAA,EACzB,OAAO,SAAS,KAAK,GAAG,IAAI,IAAI,YAAY,IAAI;AAAA;AAIlD,IAAM,YAAY,CAAC,QACjB,IAAI,QAAQ,IAAI,cAAc,GAAG,MAAM,GAAG,EAAE,IAAI,KAAK,EAAE,YAAY,KAAK;AAE1E,IAAM,SAAS,OAAO,SAAmC;AAAA,EACvD,IAAI;AAAA,IACF,MAAM,OAAO,IAAI;AAAA,IACjB,OAAO;AAAA,IACP,MAAM;AAAA,IACN,OAAO;AAAA;AAAA;AAUX,IAAM,eAAe,OACnB,KACA,SAC2B;AAAA,EAC3B,IAAI;AAAA,EACJ,IAAI;AAAA,IACF,QAAQ,MAAM,QAAQ,GAAG;AAAA,IACzB,MAAM;AAAA,IACN,OAAO;AAAA;AAAA,EAET,MAAM,aAAa,MAAM,OACvB,CAAC,SAAS,KAAK,WAAW,GAAG,OAAO,KAAK,CAAC,KAAK,SAAS,WAAW,CACrE;AAAA,EACA,OAAO,WAAW,WAAW,IAAK,WAAW,MAAM,OAAQ;AAAA;AAYtD,IAAM,oBAAoB,OAC/B,UACA,QAC6D;AAAA,EAC7D,MAAM,UAAU,IAAI,aAAa,WAAW;AAAA,EAC5C,MAAM,QAAQ,IAAI,UAAU,CAAC,SAAS,KAAK;AAAA,EAC3C,MAAM,YAAY,IAAI,aAAa;AAAA,EACnC,MAAM,cAA4B,CAAC;AAAA,EAKnC,MAAM,SAAmB,CAAC;AAAA,EAC1B,MAAM,SAAS,SAAS,QAAQ,kBAAkB,CAAC,UAAU;AAAA,IAC3D,OAAO,KAAK,KAAK;AAAA,IACjB,OAAO,mBAAqB,OAAO,SAAS;AAAA,GAC7C;AAAA,EAED,MAAM,OAAO,IAAI;AAAA,EACjB,WAAW,WAAW,CAAC,WAAU,cAAc,GAAG;AAAA,IAChD,WAAW,SAAS,OAAO,SAAS,OAAO,GAAG;AAAA,MAC5C,MAAM,MAAM,MAAM,QAAQ;AAAA,MAC1B,IAAI,OAAO,OAAO,KAAK,GAAG,GAAG;AAAA,QAC3B,KAAK,IAAI,GAAG;AAAA,MACd;AAAA,IACF;AAAA,EACF;AAAA,EAGA,MAAM,WAAW,OAAO,QAAiC;AAAA,IAMvD,MAAM,OAAO,SAAS,IAAI,MAAM,GAAG,EAAE,MAAM,GAAG;AAAA,IAC9C,MAAM,SAAS,WAAW,GAAG;AAAA,IAI7B,IAAI,UAAW,MAAM,OAAO,MAAK,IAAI,WAAW,GAAG,OAAO,QAAQ,CAAC,GAAI;AAAA,MACrE,OAAO,GAAG,OAAO;AAAA,IACnB;AAAA,IACA,MAAM,YAAY,SAAS,OAAO,MAAM,aAAa,IAAI,WAAW,IAAI;AAAA,IACxE,IAAI,WAAW;AAAA,MACb,OAAO;AAAA,IACT;AAAA,IACA,MAAM,MAAM,MAAM,QAAQ,KAAK,EAAE,QAAQ,YAAY,QAAQ,SAAS,EAAE,CAAC;AAAA,IACzE,IAAI,CAAC,IAAI,IAAI;AAAA,MACX,MAAM,IAAI,MAAM,GAAG,IAAI,QAAQ;AAAA,IACjC;AAAA,IAGA,MAAM,OAAO,UAAU,GAAG;AAAA,IAC1B,IAAI,eAAe,KAAK,IAAI,GAAG;AAAA,MAC7B,MAAM,IAAI,MAAM,kBAAkB,wBAAwB;AAAA,IAC5D;AAAA,IACA,IAAI,CAAC,IAAI,MAAM;AAAA,MACb,MAAM,IAAI,MAAM,qBAAqB;AAAA,IACvC;AAAA,IACA,MAAM,OAAO,GAAG,OAAO,UAAU,YAAY,IAAI,IAAI,KAAK;AAAA,IAC1D,MAAM,SAAS,MAAK,IAAI,WAAW,IAAI;AAAA,IACvC,MAAM,OAAM,IAAI,WAAW,EAAE,WAAW,KAAK,CAAC;AAAA,IAQ9C,MAAM,OAAO,GAAG,UAAU,WAAW,IAAI;AAAA,IACzC,IAAI;AAAA,MACF,MAAM,WAAU,MAAM,IAAI,IAAI;AAAA,MAC9B,OAAO,OAAO;AAAA,MACd,MAAM,GAAG,MAAM,EAAE,OAAO,KAAK,CAAC;AAAA,MAC9B,MAAM;AAAA;AAAA,IAER,MAAM,OAAO,MAAM,MAAM;AAAA,IACzB,OAAO;AAAA;AAAA,EAGT,MAAM,WAAW,IAAI;AAAA,EACrB,MAAM,QAAQ,IACZ,CAAC,GAAG,IAAI,EAAE,IAAI,OAAO,QAAQ;AAAA,IAC3B,IAAI;AAAA,MACF,MAAM,OAAO,MAAM,MAAM,MAAM,SAAS,GAAG,CAAC;AAAA,MAC5C,SAAS,IAAI,KAAK,GAAG,IAAI,iBAAiB,MAAM;AAAA,MAChD,OAAO,OAAO;AAAA,MAGd,YAAY,KAAK;AAAA,QACf,MAAM;AAAA,QACN,SAAS,4BAA4B,QAAS,MAAgB;AAAA,QAC9D,UAAU;AAAA,MACZ,CAAC;AAAA;AAAA,GAEJ,CACH;AAAA,EAEA,MAAM,YAAY,OACf,WAAW,WAAU,CAAC,OAAO,KAAK,QAAQ;AAAA,IACzC,MAAM,QAAQ,SAAS,IAAI,GAAG;AAAA,IAC9B,OAAO,QAAQ,KAAK,QAAQ,WAAW;AAAA,GACxC,EACA,WAAW,gBAAgB,CAAC,OAAO,MAAM,KAAK,UAAU;AAAA,IACvD,MAAM,QAAQ,SAAS,IAAI,GAAG;AAAA,IAC9B,OAAO,QAAQ,GAAG,OAAO,QAAQ,UAAU;AAAA,GAC5C,EACA,WAAW,aAAa,CAAC,OAAO,UAAU,OAAO,OAAO,KAAK,MAAM,KAAK;AAAA,EAE3E,OAAO,EAAE,aAAa,UAAU,UAAU;AAAA;;;AFhF5C,IAAM,iBAAiB,CAAC,OAAyB,CAAC,MAChD,KACG,IAAI,CAAC,SAAS;AAAA,EACb,IAAI,OAAO,KAAK;AAAA,EAChB,IAAI,KAAK,aAAa,MAAM;AAAA,IAC1B,OAAO,KAAK;AAAA,EACd;AAAA,EACA,IAAI,KAAK,aAAa,MAAM;AAAA,IAC1B,OAAO,KAAK;AAAA,EACd;AAAA,EACA,IAAI,KAAK,aAAa,QAAQ;AAAA,IAC5B,OAAO,IAAI;AAAA,EACb;AAAA,EACA,IAAI,KAAK,aAAa,eAAe;AAAA,IACnC,OAAO,KAAK;AAAA,EACd;AAAA,EACA,OAAO,KAAK,OAAO,IAAI,SAAS,KAAK,UAAU;AAAA,CAChD,EACA,KAAK,EAAE;AAEZ,IAAM,iBAAiB,CACrB,UACgC,OAAO,UAAU;AAGnD,IAAM,YAAY,CAAC,UAAuD;AAAA,EACxE,MAAM,QAAQ,MAAM,MAAM;AAAA,EAC1B,OAAO,eAAe,KAAK,IAAI,QAAQ;AAAA;AAGzC,IAAM,aAAa,CAAC,UAClB,UAAU,KAAK,GAAG,aAAa,CAAC;AAElC,IAAM,eAAe;AACrB,IAAM,cAAc;AACpB,IAAM,gBAAgB;AACtB,IAAM,aAAY;AAClB,IAAM,sBAAsB;AAC5B,IAAM,6BAA6B;AAUnC,IAAM,kBAAkB,OACtB,MACA,UAAU,MACK;AAAA,EACf,IAAI;AAAA,IACF,OAAO,MAAM,KAAK;AAAA,IAClB,OAAO,OAAO;AAAA,IAId,QAAQ,WAAW;AAAA,IACnB,IAAI,WAAW,gBAAgB,YAAY,aAAa;AAAA,MACtD,MAAM;AAAA,IACR;AAAA,IAIA,MAAM,aAAa,OAChB,MAA+C,UAAU,cAC5D;AAAA,IACA,MAAM,OACJ,aAAa,IACT,aAAa,aACb,gBAAgB,KAAK,WAAW,IAAI,KAAK,OAAO;AAAA,IACtD,MAAM,OAAM,IAAI;AAAA,IAChB,OAAO,gBAAgB,MAAM,UAAU,CAAC;AAAA;AAAA;AAK5C,IAAM,aAAa,OACjB,MACA,QACA,MAAW,CAAC,MACK;AAAA,EACjB,MAAM,MAAM,MAAM,KAAK,MAAM;AAAA,EAC7B,MAAM,MAAM,CAAC,GAAG,KAAK,GAAG,IAAI,OAAO;AAAA,EACnC,OAAO,IAAI,YAAY,IAAI,cACvB,WAAW,MAAM,IAAI,aAAa,GAAG,IACrC;AAAA;AAGN,IAAM,cAAc,IAAI,IAAI;AAAA,EAC1B;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAGD,IAAM,aAAa,CAAC,UAClB,UAAU,aAAa,YAAY,IAAI,MAAM,IAAI;AAEnD,IAAM,cAAc,CAAC,OAAyB,CAAC,MAC7C,KAAK,IAAI,CAAC,SAAS,KAAK,UAAU,EAAE,KAAK,EAAE;AAQ7C,IAAM,YAAY,CAAC,UAA0B,IAAI,KAAK,UAAU,KAAK;AAErE,IAAM,eAAe;AAKrB,IAAM,eAAe,CAAC,QAAyB;AAAA,EAC7C,MAAM,OAAO,IAAI,MAAM,GAAG,GAAG,YAAY;AAAA,EACzC,OAAO,aAAa,KAAK,IAAI,KAAK,eAAe,GAAG,MAAM;AAAA;AAO5D,IAAM,cAAc,CAAC,SAAqC;AAAA,EACxD,MAAM,MAAM,KAAK,UAAU,OAAO,KAAK,MAAM;AAAA,EAC7C,IAAI,CAAC,KAAK;AAAA,IACR,OAAO;AAAA,EACT;AAAA,EACA,MAAM,UAAU,eAAe,KAAK,OAAO;AAAA,EAO3C,MAAM,QAAQ,UAAU,UAAU,UAAU,YAAY,KAAK,OAAO,CAAC,MAAM;AAAA,EAI3E,MAAM,QAAQ,aAAa,GAAG,IAC1B,WAAW,aAAa,KAAK,UAAU,GAAG,SAC1C,uBAAuB,KAAK,UAAU,GAAG;AAAA,EAC7C,OAAO,UACH,kBAAkB,UAAU,OAAO;AAAA,EAAO;AAAA,YAC1C;AAAA;AAIN,IAAM,aAAa,CAAC,UAAsC;AAAA,EACxD,MAAM,OAAO,UAAU,KAAK,KAAK,CAAC;AAAA,EAClC,MAAM,OAAO,eAAe,WAAW,KAAK,CAAC;AAAA,EAC7C,QAAQ,MAAM;AAAA,SACP,aAAa;AAAA,MAChB,OAAO;AAAA,IACT;AAAA,SACK,aAAa;AAAA,MAChB,OAAO,KAAK;AAAA,IACd;AAAA,SACK,aAAa;AAAA,MAChB,OAAO,MAAM;AAAA,IACf;AAAA,SACK,aAAa;AAAA,MAChB,OAAO,OAAO;AAAA,IAChB;AAAA,SACK,sBAAsB;AAAA,MACzB,OAAO,KAAK;AAAA,IACd;AAAA,SACK,sBAAsB;AAAA,MACzB,OAAO,MAAM;AAAA,IACf;AAAA,SACK,SAAS;AAAA,MACZ,OAAO,MAAM,KAAK,UAAU,MAAM,QAAQ;AAAA,IAC5C;AAAA,SACK,SAAS;AAAA,MACZ,OAAO,KAAK;AAAA,IACd;AAAA,SACK,WAAW;AAAA,MACd,OAAO;AAAA,IACT;AAAA,SACK,QAAQ;AAAA,MACX,OAAO,SAAS,KAAK,YAAY;AAAA,EAAO;AAAA;AAAA,IAC1C;AAAA,SACK,SAAS;AAAA,MACZ,MAAM,MAAM,KAAK,UAAU,OAAO,KAAK,MAAM;AAAA,MAC7C,OAAO,MAAM,KAAK,eAAe,KAAK,OAAO,MAAM,SAAS;AAAA,IAC9D;AAAA,SACK,SAAS;AAAA,MACZ,OAAO,YAAY,IAAI;AAAA,IACzB;AAAA,aACS;AAAA,MACP,OAAO;AAAA,IACT;AAAA;AAAA;AASG,IAAM,eAAe,CAC1B,SACA,QACkB;AAAA,EAClB,MAAM,QAAQ,QAAQ,cAAc,CAAC;AAAA,EAMrC,MAAM,QAAQ,OAAO,KAAK,IAAI,GAAG,QAAQ,eAAe,mBAAmB,CAAC;AAAA,EAI5E,MAAM,YAAY,OAAO,0BAA0B;AAAA,EAGnD,MAAM,aAAa,CAAI,SACrB,gBAAgB,MAAM,MAAM,IAAI,CAAC;AAAA,EACnC,MAAM,QAAQ,cACZ,KAAK,YAAY,MAAK,UAAU,SAAS,QAAQ,IAAI,CACvD;AAAA,EACA,MAAM,YACJ,KAAK,aAAa,MAAK,UAAU,UAAU,gBAAgB,QAAQ,IAAI;AAAA,EACzE,MAAM,gBAAgB,KAAK,iBAAiB,iBAAiB,QAAQ;AAAA,EACrE,IAAI,WAAW,IAAI;AAAA,EAEnB,MAAM,gBAAgB,YAAuC;AAAA,IAC3D,IAAI,QAAQ,QAAQ;AAAA,MAClB,OAAO,QAAQ;AAAA,IACjB;AAAA,IACA,IAAI;AAAA,IACJ,IAAI;AAAA,MAIF,MAAM,MAAe,MAAa;AAAA,OAIjC,EAAE,OAAO,IAAI;AAAA,MAGd,MAAM;AAAA,MACN,MAAM,IAAI,WAAW;AAAA,QACnB,MAAM;AAAA,QACN,SAAS,WAAW,QAAQ;AAAA,QAC5B,UAAU;AAAA,MACZ,CAAC;AAAA;AAAA,IAEH,OAAO,IAAI,OAAO,EAAE,MAAM,QAAQ,SAAS,QAAQ,IAAI,aAAa,CAAC;AAAA;AAAA,EAGvE,MAAM,aAAa,CACjB,QACA,YAEA,WAAW,CAAC,WACV,WAAW,MACT,OAAO,OAAO,SAAS,KAAK,EAAE,UAAU,SAAS,cAAc,OAAO,CAAC,CACzE,CACF;AAAA,EAIF,MAAM,kBAAkB,OACtB,QACA,OACA,WACoB;AAAA,IACpB,MAAM,WAAW,OAAO,WAAyC;AAAA,MAC/D,IAAI,CAAC,OAAO,cAAc;AAAA,QACxB,OAAO;AAAA,MACT;AAAA,MACA,OAAO,OAAO,QAAQ,MAAM,WAAW,QAAQ,OAAO,EAAE,CAAC;AAAA;AAAA,IAG3D,IAAI,MAAM,SAAS,WAAW;AAAA,MAC5B,MAAM,OAAO,CAAC,eAAe,WAAW,KAAK,CAAC,GAAG,MAAM,SAAS,KAAK,CAAC,EACnE,OAAO,OAAO,EACd,KAAK;AAAA;AAAA,CAAM;AAAA,MACd,OAAO;AAAA,EAAc;AAAA;AAAA,IACvB;AAAA,IACA,IAAI,MAAM,SAAS,UAAU;AAAA,MAC3B,MAAM,QAAQ,UAAU,eAAe,WAAW,KAAK,CAAC,CAAC;AAAA,MACzD,OAAO;AAAA,uBAAqC;AAAA,EAAW,MAAM,SAAS,KAAK;AAAA;AAAA;AAAA,IAC7E;AAAA,IACA,IAAI,MAAM,SAAS,eAAe;AAAA,MAChC,MAAM,OAAO,MAAM,WAAW,QAAQ,MAAM,EAAE;AAAA,MAC9C,MAAM,WAAW,MAAM,QAAQ,IAC7B,KAAK,IAAI,OAAO,QAAQ;AAAA,EAAa,MAAM,SAAS,GAAG;AAAA,UAAc,CACvE;AAAA,MACA,OAAO;AAAA,EAAc,SAAS,KAAK;AAAA,CAAI;AAAA;AAAA,IACzC;AAAA,IACA,OAAO,iCAAiC,MAAM;AAAA;AAAA,EAGhD,MAAM,eAAe,OACnB,QACA,WACoB;AAAA,IACpB,MAAM,QAAQ,MAAM,QAAQ,IAC1B,OAAO,IAAI,OAAO,UAAU;AAAA,MAC1B,MAAM,OAAO,WAAW,KAAK;AAAA,MAC7B,IAAI,SAAS,MAAM;AAAA,QACjB,OAAO,gBAAgB,QAAQ,OAAO,YAAY;AAAA,MACpD;AAAA,MAGA,IAAI,CAAC,MAAM,cAAc;AAAA,QACvB,OAAO;AAAA,MACT;AAAA,MACA,MAAM,SAAS,MAAM,aACnB,QACA,MAAM,WAAW,QAAQ,MAAM,EAAE,CACnC;AAAA,MACA,IAAI,CAAC,QAAQ;AAAA,QACX,OAAO;AAAA,MACT;AAAA,MACA,IAAI,WAAW,KAAK,GAAG;AAAA,QAGrB,MAAM,SAAS,IAAI,OACjB,MAAM,SAAS,uBAAuB,IAAI,CAC5C;AAAA,QACA,MAAM,WAAW,OACd,MAAM;AAAA,CAAI,EACV,IAAI,CAAC,SAAU,OAAO,GAAG,SAAS,SAAS,IAAK,EAChD,KAAK;AAAA,CAAI;AAAA,QACZ,OAAO,GAAG;AAAA,EAAS;AAAA,MACrB;AAAA,MAIA,OAAO,GAAG;AAAA;AAAA,EAAW;AAAA,KACtB,CACH;AAAA,IAGA,MAAM,QAAQ,OAAO,QAAQ,CAAC,OAAO,MAAM;AAAA,MACzC,MAAM,OAAO,MAAM,MAAM;AAAA,MACzB,OAAO,OAAO,CAAC,EAAE,OAAO,KAAK,CAAC,IAAI,CAAC;AAAA,KACpC;AAAA,IACD,OAAO,MACJ,IAAI,CAAC,MAAM,MAAM;AAAA,MAChB,IAAI,MAAM,GAAG;AAAA,QACX,OAAO,KAAK;AAAA,MACd;AAAA,MACA,MAAM,QAAQ,WAAW,MAAM,IAAI,IAAI,KAAK,KAAK,WAAW,KAAK,KAAK;AAAA,MACtE,OAAO,GAAG,QAAQ;AAAA,IAAO;AAAA;AAAA,IAAS,KAAK;AAAA,KACxC,EACA,KAAK,EAAE;AAAA;AAAA,EAGZ,MAAM,gBAAgB,CAAC,SAAiD;AAAA,IACtE,IAAI,MAAM,OAAO;AAAA,MACf,OAAO,KAAK,WAAW,MAAM;AAAA,IAC/B;AAAA,IACA,OAAO,OAAO,OAAO,KAAK,UAAU,EAAE,KAAK,CAAC,MAAM,EAAE,SAAS,OAAO;AAAA;AAAA,EAGtE,MAAM,UAAU,CAAC,SAA8B;AAAA,IAC7C,MAAM,OAAO,KAAK,WAAW,MAAM,UAAU;AAAA,IAC7C,MAAM,SAAS,MAAM,QAAQ,QAAQ,MAAM,QAAQ;AAAA,IAGnD,OAAO,QACL,UAAU,YAAY,QAAQ,kBAAkB,YAClD;AAAA;AAAA,EAGF,MAAM,UAAU,CAAC,SAAyC;AAAA,IACxD,MAAM,QAAQ,KAAK,WAAW,MAAM,SAAS,UAAU;AAAA,IACvD,OAAO,UAAU,OAAO,YAAY;AAAA;AAAA,EAGtC,MAAM,cAAc,CAAC,SAAqB;AAAA,IACxC,MAAM,OAA0B,CAAC;AAAA,IACjC,MAAM,QAAQ,eAAe,cAAc,IAAI,GAAG,KAAK;AAAA,IACvD,IAAI,OAAO;AAAA,MACT,KAAK,QAAQ;AAAA,IACf;AAAA,IACA,MAAM,cAAc,eAClB,KAAK,WAAW,MAAM,eAAe,gBAAgB,SACvD;AAAA,IACA,IAAI,aAAa;AAAA,MACf,KAAK,cAAc;AAAA,IACrB;AAAA,IACA,IAAI,QAAQ,IAAI,GAAG;AAAA,MACjB,KAAK,QAAQ;AAAA,IACf;AAAA,IACA,MAAM,QAAQ,QAAQ,IAAI;AAAA,IAC1B,IAAI,UAAU,WAAW;AAAA,MACvB,KAAK,UAAU,EAAE,MAAM;AAAA,IACzB;AAAA,IACA,MAAM,WAAW,eACf,KAAK,WAAW,MAAM,QAAQ,SAAS,SACzC;AAAA,IAGA,MAAM,OAAO,YAAY,YAAY,KAAK,KAAK,KAAK;AAAA,IACpD,OAAO,EAAE,MAAM,KAAK;AAAA;AAAA,EAGtB,MAAM,UAAU,OACd,QACA,SAC+D;AAAA,IAC/D,QAAQ,MAAM,SAAS,YAAY,IAAI;AAAA,IACvC,MAAM,MAAM,MAAM,aAAa,QAAQ,MAAM,WAAW,QAAQ,KAAK,EAAE,CAAC;AAAA,IACxE,MAAM,SAAS,MAAM,kBAAkB,KAAK;AAAA,MAC1C;AAAA,MACA;AAAA,MACA,WAAW,QAAQ;AAAA,MACnB,OAAO;AAAA,IACT,CAAC;AAAA,IACD,MAAM,MAAM,oBAAO,UAAU,OAAO,UAAU,IAAI;AAAA,IAClD,OAAO;AAAA,MACL,aAAa,OAAO;AAAA,MACpB,OAAO;AAAA,QACL,MAAM,EAAE,QAAQ,OAAO,MAAM,OAAO,SAAS;AAAA,QAC7C;AAAA,QACA,MAAM,SAAS,GAAG;AAAA,QAClB,cAAc,KAAK;AAAA,QACnB;AAAA,QACA,KAAK,GAAG;AAAA,MACV;AAAA,IACF;AAAA;AAAA,EAKF,MAAM,oBAAoB,OACxB,WACoB;AAAA,IACpB,MAAM,WAAW,MAAM,WAAW,MAChC,OAAO,UAAU,SAAS,EAAE,aAAa,QAAQ,SAAS,CAAC,CAC7D;AAAA,IACA,OAAO,cAAc,SAAS;AAAA,IAC9B,IAAI,CAAC,YAAY;AAAA,MACf,MAAM,IAAI,WAAW;AAAA,QACnB,MAAM;AAAA,QACN,SAAS,WAAW,QAAQ,2BAA2B,QAAQ;AAAA,QAC/D,UAAU;AAAA,MACZ,CAAC;AAAA,IACH;AAAA,IACA,OAAO,WAAW;AAAA;AAAA,EAKpB,MAAM,kBAAkB,CACtB,QACA,cACA,WAEA,WAAW,MACT,OAAO,YAAY,MAAM;AAAA,IACvB,gBAAgB;AAAA,IAChB,cAAc;AAAA,EAChB,CAAC,CACH;AAAA,EAEF,MAAM,OAAO,OACX,UAAU,KAAK,WAAW,SACI;AAAA,IAC9B,MAAM,mBAAiC,CAAC;AAAA,IACxC,MAAM,SAAS,MAAM,cACnB,QAAQ,MACR,OACA,YAAY;AAAA,MACV,MAAM,SAAS,MAAM,cAAc;AAAA,MACnC,MAAM,eAAe,MAAM,kBAAkB,MAAM;AAAA,MACnD,MAAM,QAAQ,MAAM,WAAW,CAAC,WAC9B,gBAAgB,QAAQ,cAAc,MAAM,CAC9C;AAAA,MACA,MAAM,QAAQ,MAAM,QAAQ,IAC1B,MAAM,IAAI,CAAC,SAAS,QAAQ,QAAQ,IAAI,CAAC,CAC3C;AAAA,MACA,WAAW,QAAQ,OAAO;AAAA,QACxB,iBAAiB,KAAK,GAAG,KAAK,WAAW;AAAA,MAC3C;AAAA,MACA,OAAO,MAAM,IAAI,CAAC,SAAS,KAAK,KAAK;AAAA,OAEvC,OACF;AAAA,IACA,WAAW,IAAI,IAAI,OAAO,QAAQ,IAAI,CAAC,UAAU,CAAC,MAAM,KAAK,KAAK,CAAC,CAAC;AAAA,IACpE,OAAO;AAAA,MACL,aAAa,CAAC,GAAG,OAAO,aAAa,GAAG,gBAAgB;AAAA,MACxD,SAAS,OAAO;AAAA,IAClB;AAAA;AAAA,EAGF,MAAM,OAAO,OAAO,QAAiC;AAAA,IACnD,MAAM,SAAS,SAAS,IAAI,GAAG;AAAA,IAC/B,IAAI,QAAQ;AAAA,MACV,OAAO,OAAO,OAAO,OAAO,KAAK;AAAA,IACnC;AAAA,IACA,MAAM,MAAM,MAAM,MAAM,KAAK;AAAA,IAC7B,OAAO,IAAI,KAAK,CAAC,MAAM,EAAE,QAAQ,GAAG,GAAG,OAAO;AAAA;AAAA,EAGhD,OAAO;AAAA,IACL;AAAA,IACA,MAAM,QAAQ;AAAA,IACd,QAAQ,QAAQ;AAAA,IAChB;AAAA,IACA,QAAQ;AAAA,IACR,OAAO,QAAQ,eACX,aACE,MAAM,KAAK,IAAI,GACf,QAAQ,cACR,MAAM,KAAK,CACb,IACA;AAAA,EACN;AAAA;;;AG/pBF,uBAAS,sBAAY;AAErB,oBAAS,sBAAS;AAElB,qBAAS,yBAAU,qBAAY,mBAAM,sBAAU;AA8D/C,IAAM,gBAAgB;AAEtB,IAAM,mBAAmB;AASzB,IAAM,WACJ;AAkBF,IAAM,WAAW,CAAC,UAChB,MAAM,UAAU,KAAK,EAAE,YAAY;AAGrC,IAAM,cAAc;AAMpB,IAAM,eAAe;AAQrB,IAAM,aAAa,CAAC,SAClB,SACE,KAAK,WAAW,aAAa,SAAS,EAAE,WAAW,cAAc,EAAE,CACrE;AASF,IAAM,uBAAuB,IAAI,IAAI;AAAA,EACnC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAGD,IAAM,iBAAiB,IAAI,IAAY,eAAe,MAAM,EAAE,OAAO;AAQrE,IAAM,eAAe,CAAC,YACpB,YAAY,QAAQ,QAAQ,eAAe,EAAE,CAAC;AA+ChD,IAAM,YAAY,OAA0B;AAAA,EAC1C,WAAW,CAAC;AAAA,EACZ,SAAS,CAAC;AAAA,EACV,OAAO,CAAC;AACV;AAUA,IAAM,cAAc,CAClB,QACA,MACA,SACW;AAAA,EACX,MAAM,MAAM,KAAK,QAAQ,MAAM;AAAA,EAC/B,MAAM,SAAS,OAAO,QAAQ,SAAS,GAAG,IAAI,MAAO,OAAO,QAAQ,MAAM;AAAA,EAC1E,OAAO,eAAe,OAAO,cAAc,QAAQ,IAAI;AAAA;AASzD,IAAM,eAAe,CAAC,QAA0B;AAAA,EAC9C,MAAM,WAAW,IAAI,QAAQ,eAAe,EAAE,EAAE,MAAM,GAAG;AAAA,EACzD,OAAO,SAAS,IAAI,CAAC,GAAG,SAAS,SAAS,SAAS,MAAM,IAAI,EAAE,KAAK,GAAG,CAAC,CAAC;AAAA;AAI3E,IAAM,eAAe;AASrB,IAAM,YAAY,CAAC,MAAc,OAAwB;AAAA,EACvD,IAAI,cAAc;AAAA,EAClB,SAAS,IAAI,KAAK,EAAG,KAAK,KAAK,KAAK,OAAO,MAAM,KAAK,GAAG;AAAA,IACvD,eAAe;AAAA,EACjB;AAAA,EACA,OAAO,cAAc,MAAM;AAAA;AAQ7B,IAAM,eAAe,CAAC,SAAgC;AAAA,EACpD,MAAM,OAAsB,CAAC;AAAA,EAC7B,WAAW,SAAS,KAAK,SAAS,YAAY,GAAG;AAAA,IAC/C,MAAM,UAAU,UAAU,MAAM,MAAM,KAAK;AAAA,IAC3C,MAAM,KAAK,UAAU,MAAM,QAAQ,IAAI,MAAM;AAAA,IAC7C,MAAM,SAAS,UAAU,MAAM,GAAG,SAAS,IAAI,MAAM,GAAG;AAAA,IACxD,IAAI,SAAS,GAAG;AAAA,MACd,KAAK,KAAK,EAAE,IAAI,OAAO,CAAC;AAAA,IAC1B;AAAA,EACF;AAAA,EACA,OAAO;AAAA;AAgBT,IAAM,cAAc,CAClB,MACA,MACA,WACW;AAAA,EACX,SAAS,IAAI,KAAM,IAAI,KAAK,QAAQ,KAAK,GAAG;AAAA,IAC1C,IAAI,KAAK,IAAI,WAAW,QAAQ;AAAA,MAC9B,OAAO;AAAA,IACT;AAAA,EACF;AAAA,EACA,OAAO;AAAA;AAST,IAAM,eAAe,CACnB,MACA,SACuB;AAAA,EACvB,SAAS,IAAI,EAAG,IAAI,KAAK,QAAQ,KAAK,GAAG;AAAA,IACvC,MAAM,MAAM,KAAK;AAAA,IACjB,IAAI,QAAQ,aAAa,IAAI,KAAK,MAAM;AAAA,MACtC;AAAA,IACF;AAAA,IACA,MAAM,QAAQ,YAAY,MAAM,IAAI,GAAG,IAAI,MAAM;AAAA,IACjD,IAAI,UAAU,IAAI;AAAA,MAChB,OAAO,EAAE,MAAM,KAAK,QAAQ,MAAM,KAAK,IAAI,QAAQ,OAAO,IAAI,GAAG;AAAA,IACnE;AAAA,EACF;AAAA,EACA,OAAO;AAAA;AAGT,IAAM,eAAe;AACrB,IAAM,gBAAgB;AAOtB,IAAM,cAAc,CAAC,MAAc,SAAqC;AAAA,EACtE,MAAM,QAAQ,KAAK,QAAQ,cAAc,IAAI;AAAA,EAC7C,IAAI,UAAU,IAAI;AAAA,IAChB,OAAO;AAAA,EACT;AAAA,EACA,MAAM,QAAQ,KAAK,QAAQ,eAAe,QAAQ,aAAa,MAAM;AAAA,EACrE,OAAO,UAAU,KAAK,OAAO,EAAE,KAAK,QAAQ,cAAc,QAAQ,MAAM;AAAA;AAU1E,IAAM,gBAAgB,CAAC,SAA8B;AAAA,EACnD,MAAM,OAAO,aAAa,IAAI;AAAA,EAC9B,MAAM,SAAsB,CAAC;AAAA,EAC7B,IAAI,SAAS;AAAA,EACb,UAAS;AAAA,IACP,MAAM,OAAO,aAAa,MAAM,MAAM;AAAA,IACtC,MAAM,UAAU,YAAY,MAAM,MAAM;AAAA,IACxC,MAAM,OACJ,SAAS,QAAS,YAAY,QAAQ,QAAQ,QAAQ,KAAK,QACvD,UACA;AAAA,IACN,IAAI,SAAS,MAAM;AAAA,MACjB;AAAA,IACF;AAAA,IACA,OAAO,KACL,EAAE,SAAS,OAAO,MAAM,KAAK,MAAM,QAAQ,KAAK,KAAK,EAAE,GACvD,EAAE,SAAS,MAAM,MAAM,KAAK,MAAM,KAAK,OAAO,KAAK,GAAG,EAAE,CAC1D;AAAA,IACA,SAAS,KAAK;AAAA,EAChB;AAAA,EACA,OAAO,KAAK,EAAE,SAAS,OAAO,MAAM,KAAK,MAAM,MAAM,EAAE,CAAC;AAAA,EACxD,OAAO;AAAA;AAIT,IAAM,iBAAiB,CACrB,OACA,OACA,MACA,eAEA,MAAM,WAAW,kBAAkB,EAAE,EAAE,WAAW,UAAU,IAAI,SAAS;AAAA,EAGvE,MAAM,SAAS,KAAK,GAAG,EAAE;AAAA,EACzB,MAAM,UAAU,OAAO,UAAU,IAAI,KAAK;AAAA,EAC1C,MAAM,UAAU,OAAO,SAAS,KAAK;AAAA,EACrC,MAAM,QAAQ,OAAO,OAAO,KAAK,KAAK,WAAW;AAAA,EAGjD,IAAI,OAAO,OAAO;AAAA,IAEhB,OAAO,KAAK;AAAA,EACd;AAAA,EAGA,IAAI,WAAW,IAAI;AAAA,IACjB,IAAI,YAAY,WAAW;AAAA,MAEzB,OAAO,KAAK;AAAA,IACd;AAAA,IACA,MAAM,UAAU,YAAY,MAAM,MAAM,MAAM,IAAI;AAAA,IAKlD,IAAI,QAAQ,WAAW,GAAG,GAAG;AAAA,MAC3B,OAAO,IAAI,OAAO,OAAO,KAAK,KAAK,QAAQ,MAAM,CAAC,MAAM;AAAA,IAC1D;AAAA,IACA,MAAM,MAAM,KAAK,QAAQ,IAAI,WAAW,OAAO,CAAC;AAAA,IAChD,IAAI,QAAQ,WAAW;AAAA,MAGrB,WAAW,QAAQ,KAAK,IAAI,SAAS;AAAA,MACrC,OAAO,IAAI,UAAU;AAAA,IACvB;AAAA,IACA,OAAO,IAAI,UAAU,WAAW;AAAA,EAClC;AAAA,EAGA,MAAM,MAAM,SAAS,OAAO,QAAQ,eAAe,EAAE,CAAC;AAAA,EACtD,MAAM,OAAO,MAAM,MAAM,IAAI,GAAG;AAAA,EAChC,IAAI,SAAS,WAAW;AAAA,IACtB,WAAW,MAAM,KAAK,MAAM;AAAA,IAC5B,OAAO;AAAA,EACT;AAAA,EACA,MAAM,QAAQ,MAAM,UAAU,IAAI,GAAG;AAAA,EACrC,IAAI,UAAU,WAAW;AAAA,IACvB,WAAW,UAAU,KAAK,KAAK;AAAA,EACjC;AAAA,EACA,MAAM,OAAO,YAAY,MAAM,MAAM,MAAM,IAAI;AAAA,EAC/C,IAAI,YAAY,WAAW;AAAA,IACzB,OAAO,IAAI,UAAU;AAAA,EACvB;AAAA,EAGA,IAAI,QAAQ,WAAW,GAAG,GAAG;AAAA,IAC3B,OAAO,IAAI,OAAO,OAAO,KAAK,KAAK,WAAW;AAAA,EAChD;AAAA,EACA,MAAM,SAAS,KAAK,QAAQ,IAAI,WAAW,OAAO,CAAC;AAAA,EACnD,IAAI,WAAW,WAAW;AAAA,IAGxB,WAAW,QAAQ,KAAK,GAAG,UAAU,SAAS;AAAA,IAC9C,OAAO,IAAI,UAAU;AAAA,EACvB;AAAA,EACA,OAAO,IAAI,UAAU,QAAQ;AAAA,CAC9B;AAMH,IAAM,iBAAiB,CACrB,MACA,OACA,MACA,eAEA,cAAc,IAAI,EACf,IAAI,CAAC,UACJ,MAAM,UACF,MAAM,OACN,eAAe,MAAM,MAAM,OAAO,MAAM,UAAU,CACxD,EACC,KAAK,EAAE;AAGZ,IAAM,iBAAgB;AAEtB,IAAM,YAAY;AAGlB,IAAM,WAAW,CAAC,SAChB,KAAK,SAAS,KAAK,UAAU,EAAE;AAMjC,IAAM,YAAY,CAAC,MAAc,WAA6B;AAAA,EAC5D,IAAI,KAAK,KAAK,MAAM,IAAI;AAAA,IACtB,OAAO;AAAA,EACT;AAAA,EACA,IAAI,UAAU,KAAK,IAAI,GAAG;AAAA,IACxB,OAAO;AAAA,EACT;AAAA,EACA,OAAO,KAAK,WAAW,GAAG,KAAK,KAAK,WAAW,IAAI,IAAI,SAAS;AAAA;AAWlE,IAAM,aAAa,CACjB,MACA,OACA,aACA,WACe;AAAA,EACf,MAAM,aACJ,UAAU,OACN,UAAU,CAAC,eAAc,KAAK,IAAI,IAClC,SAAS,IAAI,KAAK,cAAc;AAAA,EACtC,OAAO,aAAa,eAAe,MAAM,KAAK,IAAI;AAAA;AASpD,IAAM,gBAAgB,CACpB,MACA,OACA,MACA,eACW;AAAA,EACX,IAAI,QAAoB;AAAA,EAGxB,IAAI,cAAc;AAAA,EAKlB,IAAI,aAAa;AAAA,EACjB,IAAI,eAAe;AAAA,EACnB,IAAI,SAAS;AAAA,EACb,MAAM,MAAgB,CAAC;AAAA,EACvB,IAAI,QAAkB,CAAC;AAAA,EACvB,MAAM,QAAQ,MAAY;AAAA,IACxB,IAAI,MAAM,SAAS,GAAG;AAAA,MACpB,IAAI,KAAK,eAAe,MAAM,KAAK;AAAA,CAAI,GAAG,OAAO,MAAM,UAAU,CAAC;AAAA,MAClE,QAAQ,CAAC;AAAA,IACX;AAAA;AAAA,EAEF,WAAW,QAAQ,KAAK,MAAM;AAAA,CAAI,GAAG;AAAA,IACnC,MAAM,QAAQ,KAAK,KAAK,MAAM;AAAA,IAC9B,IAAI,UAAU,QAAQ,cAAc;AAAA,MAClC,IAAI,SAAS,eAAc,KAAK,IAAI,GAAG;AAAA,QACrC,IAAI,KAAK,IAAI;AAAA,QACb,aAAa;AAAA,QACb;AAAA,MACF;AAAA,MACA,eAAe;AAAA,IACjB;AAAA,IACA,SAAS,UAAU,MAAM,MAAM;AAAA,IAC/B,IACE,UAAU,QACV,cACA,EAAE,SAAS,WACX,eAAc,KAAK,IAAI,GACvB;AAAA,MACA,MAAM;AAAA,MACN,eAAe;AAAA,MACf,IAAI,KAAK,IAAI;AAAA,MACb,aAAa;AAAA,MACb;AAAA,IACF;AAAA,IACA,MAAM,OAAmB,WAAW,MAAM,OAAO,aAAa,MAAM;AAAA,IACpE,IAAI,UAAU,QAAQ,SAAS,MAAM;AAAA,MACnC,IAAI,UAAU,MAAM;AAAA,QAClB,cAAc,SAAS,IAAI;AAAA,MAC7B;AAAA,MACA,MAAM;AAAA,MACN,QAAQ;AAAA,MACR,IAAI,KAAK,IAAI;AAAA,MACb,aAAa;AAAA,MACb;AAAA,IACF;AAAA,IACA,IAAI,OAAO;AAAA,MAKT,MAAM;AAAA,MACN,IAAI,KAAK,IAAI;AAAA,MACb,aAAa;AAAA,MACb;AAAA,IACF;AAAA,IACA,MAAM,KAAK,IAAI;AAAA,IACf,aAAa;AAAA,EACf;AAAA,EACA,MAAM;AAAA,EACN,OAAO,IAAI,KAAK;AAAA,CAAI;AAAA;AAetB,IAAM,cAAc,CAAC,MAAmB,UAA2B;AAAA,EACjE,MAAM,MAAM,gBAAgB,KAAK,KAAK,OAAO;AAAA,EAC7C,MAAM,YAAY,IAAI,KACpB,CAAC,YACC,eAAe,QAAQ,MAAM,OAAO,KAAK,MAAM,UAAU,CAAC,MAC1D,QAAQ,IACZ;AAAA,EACA,MAAM,WAAW,YACb,gBACE,cAAc,KAAK,KAAK,SAAS,OAAO,KAAK,MAAM,UAAU,CAAC,CAChE,IACA;AAAA,EACJ,WAAW,WAAW,UAAU;AAAA,IAC9B,MAAM,MAAM,WAAW,QAAQ,IAAI;AAAA,IAGnC,IAAI,CAAC,KAAK,KAAK,QAAQ,IAAI,GAAG,GAAG;AAAA,MAC/B,KAAK,KAAK,QAAQ,IAAI,KAAK,QAAQ,IAAI;AAAA,IACzC;AAAA,EACF;AAAA;AAaF,IAAM,iBAAiB,CACrB,OACA,YACc;AAAA,EACd,MAAM,UAAU,IAAI;AAAA,EACpB,MAAM,UAAU,CAAC,SAAkC;AAAA,IACjD,MAAM,SAAS,QAAQ,IAAI,KAAK,GAAG;AAAA,IACnC,IAAI,QAAQ;AAAA,MACV,OAAO;AAAA,IACT;AAAA,IAIA,MAAM,QAAQ,kBACZ,EAAE,KAAK,KAAK,KAAK,MAAM,aAAa,KAAK,UAAU,OAAO,EAAE,GAC5D,OACA,cAAc,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,OAAO,WACjD,OACF;AAAA,IACA,MAAM,QAAqB;AAAA,MACzB,SAAS,IAAI;AAAA,MACb,SAAS,MAAM;AAAA,MACf,cAAc,MAAM;AAAA,IACtB;AAAA,IACA,QAAQ,IAAI,KAAK,KAAK,KAAK;AAAA,IAC3B,OAAO;AAAA;AAAA,EAET,MAAM,SAAS,IAAI;AAAA,EACnB,WAAW,QAAQ,OAAO;AAAA,IACxB,WAAW,OAAO,aAAa,KAAK,GAAG,GAAG;AAAA,MACxC,MAAM,UAAU,OAAO,IAAI,GAAG;AAAA,MAC9B,IAAI,SAAS;AAAA,QACX,QAAQ,KAAK,IAAI;AAAA,MACnB,EAAO;AAAA,QACL,OAAO,IAAI,KAAK,CAAC,IAAI,CAAC;AAAA;AAAA,IAE1B;AAAA,EACF;AAAA,EACA,MAAM,YAAY,IAAI,IACpB,MAAM,IAAI,CAAC,SAAS,SAAS,KAAK,IAAI,QAAQ,eAAe,EAAE,CAAC,CAAC,CACnE;AAAA,EACA,MAAM,QAAmB;AAAA,IACvB,WAAW,IAAI;AAAA,IACf,MAAM,QAAQ;AAAA,IACd,OAAO,IAAI;AAAA,EACb;AAAA,EACA,YAAY,KAAK,cAAc,QAAQ;AAAA,IACrC,OAAO,UAAU,QAAQ;AAAA,IACzB,IAAI,KAAK,SAAS,KAAK,CAAC,IAAI,SAAS,GAAG,KAAK,CAAC,UAAU,IAAI,GAAG,GAAG;AAAA,MAChE,MAAM,UAAU,IACd,KACA,GAAG,QAAQ,UAAU,IAAI,CAAC,SAAS,KAAK,GAAG,EAAE,KAAK,IAAI,IACxD;AAAA,IACF;AAAA,IACA,MAAM,MAAM,IAAI,KAAK,QAAQ,KAAK,CAAC;AAAA,EACrC;AAAA,EACA,MAAM,QAAQ,MAAM,IAAI,CAAC,SAAS;AAAA,IAChC,MAAM,OAAO,QAAQ,IAAI;AAAA,IAGzB,MAAM,MAAM,IAAI,SAAS,KAAK,IAAI,QAAQ,eAAe,EAAE,CAAC,GAAG,IAAI;AAAA,IACnE,OAAO,EAAE,MAAM,KAAK;AAAA,GACrB;AAAA,EAGD,WAAW,QAAQ,OAAO;AAAA,IACxB,YAAY,MAAM,KAAK;AAAA,EACzB;AAAA,EACA,OAAO,EAAE,OAAO,MAAM;AAAA;AAQxB,IAAM,WAAW,CACf,KACA,qBACuB;AAAA,EACvB,IAAI,cAAc,gBAAgB,GAAG;AAAA,IACnC,OAAO;AAAA,EACT;AAAA,EACA,MAAM,WAAW,UAAS,GAAG,EAAE,QAAQ,eAAe,EAAE;AAAA,EACxD,OAAO,SAAS,YAAY,MAAM,UAAU,YAAY;AAAA;AAS1D,IAAM,cAAc,CAClB,MACA,YAIgB;AAAA,EAChB,MAAM,YAAY,cAAc,KAAK,IAAI,IAAI,KAAK,OAAO,QAAQ;AAAA,EACjE,MAAM,WACJ,cAAc,YACV,CAAC,IACA,QAAQ,sBAAsB,cAAc,CAAC;AAAA,EACpD,OAAO,IAAI,IAAI,CAAC,GAAI,QAAQ,mBAAmB,CAAC,GAAI,GAAG,QAAQ,CAAC;AAAA;AAUlE,IAAM,cAAc,CAClB,MACA,OACA,SAIA,eACgB;AAAA,EAChB,QAAQ,SAAS;AAAA,EACjB,MAAM,QAAQ,SAAS,KAAK,KAAK,KAAK,KAAK,KAAK;AAAA,EAChD,MAAM,OAAO,YAAY,KAAK,MAAM,OAAO;AAAA,EAC3C,MAAM,OAAO,OAAO,YAClB,OAAO,QAAQ,KAAK,IAAI,EAAE,OACxB,EAAE,SACA,CAAC,qBAAqB,IAAI,GAAG,MAC5B,eAAe,IAAI,GAAG,KAAK,KAAK,IAAI,GAAG,EAC5C,CACF;AAAA,EACA,MAAM,SAAS,UAAU,YAAY,OAAO,KAAK,MAAM,MAAM;AAAA,EAC7D,MAAM,OAAO,cAAc,KAAK,QAAQ,KAAK,GAAG,OAAO,KAAK,MAAM,UAAU;AAAA,EAC5E,MAAM,MAAM,oBAAO,UAAU,GAAG;AAAA,GAAU,MAAM;AAAA,EAChD,OAAO;AAAA,IACL,MAAM,EAAE,QAAQ,MAAM,KAAK;AAAA,IAC3B,MAAM;AAAA,IACN,MAAM,SAAS,GAAG;AAAA,IAClB;AAAA,IACA,KAAK,KAAK;AAAA,IACV,MAAM,aAAa,KAAK,UAAU,OAAO;AAAA,IACzC,YAAY,KAAK;AAAA,EACnB;AAAA;AASF,IAAM,WAAW,OACf,UACA,KACA,YAC+B;AAAA,EAC/B,MAAM,UAAU,MAAK,UAAU,GAAG;AAAA,EAClC,IAAI;AAAA,IACF,QAAQ,SAAS,SAAS,oBAAO,MAAM,UAAS,SAAS,OAAO,CAAC;AAAA,IACjE,OAAO;AAAA,MACL;AAAA,MACA;AAAA,MACA;AAAA,MAIA,WAAW,cAAc,KAAK,OAAO,OAAO;AAAA,MAC5C;AAAA,IACF;AAAA,IACA,OAAO,OAAO;AAAA,IAGd,IAAK,MAAgC,SAAS,UAAU;AAAA,MACtD,OAAO;AAAA,IACT;AAAA,IACA,MAAM;AAAA;AAAA;AAgBV,IAAM,WAAW,CAAC,SAA0B,KAAK,WAAW,GAAG;AAU/D,IAAM,WAAW,OAAO,KAAa,UAAuC;AAAA,EAC1E,IAAI,CAAC,MAAM,eAAe,GAAG;AAAA,IAC3B,OAAO;AAAA,MACL,WAAW,MAAM,YAAY;AAAA,MAC7B,MAAM,MAAM,OAAO;AAAA,MACnB,MAAM,MAAM;AAAA,IACd;AAAA,EACF;AAAA,EACA,IAAI;AAAA,IACF,MAAM,SAAS,MAAM,KAAK,MAAK,KAAK,MAAM,IAAI,CAAC;AAAA,IAC/C,OAAO;AAAA,MACL,WAAW,OAAO,YAAY;AAAA,MAC9B,MAAM,OAAO,OAAO;AAAA,MACpB,MAAM,MAAM;AAAA,IACd;AAAA,IACA,MAAM;AAAA,IACN,OAAO;AAAA,MACL,WAAW;AAAA,MACX,MAAM,cAAc,KAAK,MAAM,IAAI;AAAA,MACnC,MAAM,MAAM;AAAA,IACd;AAAA;AAAA;AASJ,IAAM,eAAe,CAAC,GAAe,MACnC,OAAO,EAAE,SAAS,IAAI,OAAO,EAAE,SAAS,KACxC,EAAE,KAAK,cAAc,EAAE,MAAM,MAAM,EAAE,SAAS,MAAM,aAAa,OAAO,CAAC,MACxE,EAAE,OAAO,EAAE,OAAO,KAAK;AAG1B,IAAM,YAAY,OAChB,KACA,MACA,YACsB;AAAA,EACtB,MAAM,QAAkB,CAAC;AAAA,EACzB,MAAM,SAAS,MAAM,SAAQ,KAAK,EAAE,eAAe,KAAK,CAAC;AAAA,EACzD,MAAM,UAAU,MAAM,QAAQ,IAC5B,OACG,OAAO,CAAC,UAAU,EAAE,SAAS,MAAM,IAAI,KAAK,QAAQ,IAAI,MAAM,IAAI,EAAE,EACpE,IAAI,CAAC,UAAU,SAAS,KAAK,KAAK,CAAC,CACxC;AAAA,EACA,WAAW,SAAS,QAAQ,SAAS,YAAY,GAAG;AAAA,IAClD,MAAM,OAAO,MAAK,KAAK,MAAM,IAAI;AAAA,IACjC,IAAI,MAAM,WAAW;AAAA,MAEnB,MAAM,KAAK,GAAI,MAAM,UAAU,MAAM,MAAM,OAAO,CAAE;AAAA,IACtD,EAAO,SAAI,MAAM,QAAQ,cAAc,KAAK,MAAM,IAAI,GAAG;AAAA,MACvD,MAAM,KAAK,UAAS,MAAM,IAAI,CAAC;AAAA,IACjC;AAAA,EACF;AAAA,EACA,OAAO;AAAA;AAIT,IAAM,SAAS,CAAC,YACd,CAAC,GAAG,IAAI,IAAI,OAAO,CAAC,EAAE,MAAM,GAAG,CAAC,EAAE,KAAK,IAAI;AAG7C,IAAM,wBAAwB,CAC5B,MACA,eACiB;AAAA,EACjB,MAAM,cAA4B,CAAC;AAAA,EACnC,IAAI,WAAW,MAAM,SAAS,GAAG;AAAA,IAC/B,YAAY,KAAK;AAAA,MACf,MAAM;AAAA,MACN,SAAS,WAAW,eAAe,WAAW,MAAM,yCAAyC,OAAO,WAAW,KAAK;AAAA,MACpH,UAAU;AAAA,MACV,YACE;AAAA,IACJ,CAAC;AAAA,EACH;AAAA,EACA,IAAI,WAAW,UAAU,SAAS,GAAG;AAAA,IACnC,MAAM,QAAQ,IAAI,IAAI,WAAW,SAAS,EAAE;AAAA,IAC5C,YAAY,KAAK;AAAA,MACf,MAAM;AAAA,MACN,SAAS,WAAW,eAAe,WAAW,UAAU,yBAAyB,qDAAqD,OAAO,WAAW,SAAS;AAAA,MACjK,UAAU;AAAA,MACV,YACE;AAAA,IACJ,CAAC;AAAA,EACH;AAAA,EACA,IAAI,WAAW,QAAQ,SAAS,GAAG;AAAA,IACjC,YAAY,KAAK;AAAA,MACf,MAAM;AAAA,MACN,SAAS,WAAW,eAAe,WAAW,QAAQ,4CAA4C,OAAO,WAAW,OAAO;AAAA,MAC3H,UAAU;AAAA,MACV,YACE;AAAA,IACJ,CAAC;AAAA,EACH;AAAA,EACA,OAAO;AAAA;AAYF,IAAM,iBAAiB,CAC5B,SACA,QACkB;AAAA,EAClB,MAAM,WAAW,SAAQ,IAAI,aAAa,QAAQ,KAAK;AAAA,EAKvD,MAAM,UAAU,IAAI,IAAI,CAAC,GAAG,mBAAmB,GAAI,QAAQ,WAAW,CAAC,CAAE,CAAC;AAAA,EAE1E,MAAM,OAAO,YAAuC;AAAA,IAClD,MAAM,QAAQ,MAAM,UAAU,UAAU,UAAU,OAAO;AAAA,IACzD,MAAM,OAAO,MAAM,QAAQ,IACzB,MAAM,IAAI,CAAC,QAAQ,SAAS,UAAU,KAAK,OAAO,CAAC,CACrD;AAAA,IACA,MAAM,QAAQ,KAAK,OAAO,CAAC,SAA6B,SAAS,IAAI;AAAA,IAGrE,QAAQ,OAAO,UAAU,eAAe,OAAO,OAAO;AAAA,IACtD,MAAM,aAAa,UAAU;AAAA,IAC7B,MAAM,UAAU,MAAM,IAAI,CAAC,SACzB,YAAY,MAAM,OAAO,SAAS,UAAU,CAC9C;AAAA,IACA,OAAO;AAAA,MACL,aAAa,sBAAsB,QAAQ,MAAM,UAAU;AAAA,MAC3D;AAAA,IACF;AAAA;AAAA,EAOF,MAAM,OAAO,OAAO,QAAiC;AAAA,IACnD,MAAM,UAAU,SAAQ,UAAU,GAAG;AAAA,IAKrC,MAAM,MAAM,UAAS,UAAU,OAAO;AAAA,IACtC,IAAI,IAAI,WAAW,IAAI,KAAK,YAAW,GAAG,GAAG;AAAA,MAC3C,MAAM,IAAI,WAAW;AAAA,QACnB,MAAM;AAAA,QACN,MAAM;AAAA,QACN,SAAS,WAAW,QAAQ,sBAAsB;AAAA,QAClD,UAAU;AAAA,QACV,YAAY;AAAA,MACd,CAAC;AAAA,IACH;AAAA,IACA,OAAO,MAAM,UAAS,SAAS,OAAO;AAAA;AAAA,EAGxC,MAAM,WAAW,MAAY;AAAA,IAG3B,MAAM,QAAQ,SAAS,UAAU,EAAE,gBAAgB,MAAM,CAAC;AAAA,IAC1D,IAAI,OAAO,YAAY,GAAG;AAAA,MACxB;AAAA,IACF;AAAA,IACA,MAAM,UAAU,QAAQ,uBAAuB;AAAA,IAC/C,MAAM,IAAI,WAAW;AAAA,MACnB,MAAM;AAAA,MACN,MAAM;AAAA,MACN,SAAS,WAAW,QAAQ,oBAAoB,QAAQ,iBAAiB;AAAA,MACzE,UAAU;AAAA,MACV,YACE;AAAA,IACJ,CAAC;AAAA;AAAA,EAOH,MAAM,QAAQ,CAAC,aAAuC;AAAA,IACpD,IAAI,CAAC,YAAW,QAAQ,GAAG;AAAA,MACzB,OAAO,MAAM;AAAA,IAGf;AAAA,IACA,MAAM,UAAU,SACd,UACA,EAAE,WAAW,KAAK,GAClB,sBAAsB,UAAU,SAAS,QAAQ,CACnD;AAAA,IACA,OAAO,MAAM,QAAQ,MAAM;AAAA;AAAA,EAG7B,OAAO;AAAA,IAIL,aAAa;AAAA,IACb;AAAA,IACA,MAAM,QAAQ;AAAA,IACd,QAAQ,QAAQ;AAAA,IAChB;AAAA,IACA,QAAQ;AAAA,IACR;AAAA,IACA;AAAA,EACF;AAAA;;;AC7gCF,iBAAS;;;ACuDT,IAAM,iBAAiB,IAAI,IAAI;AAAA,EAC7B,CAAC,MAAM,IAAI;AAAA,EACX,CAAC,MAAM,KAAK;AAAA,EACZ,CAAC,MAAM,MAAM;AAAA,EACb,CAAC,MAAM,OAAO;AAAA,EACd,CAAC,MAAM,QAAQ;AAAA,EACf,CAAC,MAAM,SAAS;AAClB,CAAC;AASD,IAAM,oBAAoB;AAE1B,IAAM,aAAa,CAAC,SAClB,KAAK,WAAW,mBAAmB,OAAO,QAAQ;AAGpD,IAAM,aAAa,CACjB,MACA,SACW;AAAA,EAGX,MAAM,SAAS,KAAK,OAAO,SAAS,MAAM,KAAK;AAAA,EAC/C,IAAI,OAAO,SAAU,KAAK,QAAQ,KAAM,WAAW,KAAK,QAAQ,EAAE;AAAA,EAClE,IAAI,CAAC,KAAK,SAAS,KAAK,MAAM,WAAW,GAAG;AAAA,IAC1C,OAAO;AAAA,EACT;AAAA,EAEA,IAAI;AAAA,EACJ,WAAW,QAAQ,KAAK,OAAO;AAAA,IAC7B,QAAQ;AAAA,WACD,UAAU;AAAA,QACb,OAAO,KAAK;AAAA,QACZ;AAAA,MACF;AAAA,WACK,MAAM;AAAA,QACT,OAAO,IAAI;AAAA,QACX;AAAA,MACF;AAAA,WACK,QAAQ;AAAA,QACX,OAAO,KAAK;AAAA,QACZ;AAAA,MACF;AAAA,WACK,kBAAkB;AAAA,QACrB,OAAO,KAAK;AAAA,QACZ;AAAA,MACF;AAAA,eACS;AAAA,QACP,MAAM,MAAM,KAAK,IAAI,IAAI;AAAA,QACzB,IAAI,KAAK,UAAU,QAAQ;AAAA,UACzB,OAAO;AAAA,QACT;AAAA,MACF;AAAA;AAAA,EAEJ;AAAA,EACA,OAAO,MAAM,OAAO,IAAI,SAAS,KAAK,UAAU;AAAA;AAIlD,IAAM,iBAAiB,CAAC,UAAqC;AAAA,EAC3D,MAAM,OAAO,IAAI,KACd,MAAM,YAAY,CAAC,GAAG,IAAI,CAAC,QAAQ,CAAC,IAAI,MAAM,GAAG,CAAU,CAC9D;AAAA,EACA,QAAQ,MAAM,YAAY,CAAC,GAAG,IAAI,CAAC,SAAS,WAAW,MAAM,IAAI,CAAC,EAAE,KAAK,EAAE;AAAA;AAI7E,IAAM,YAAY,CAAC,UACjB,OAAO,UAAU;AAEnB,IAAM,cAAc,CAClB,OACA,YACW;AAAA,EACX,MAAM,SAAS,QAAQ,cAAc,MAAM;AAAA,EAC3C,IAAI,QAAQ;AAAA,IACV,OAAO,OAAO,KAAK;AAAA,EACrB;AAAA,EACA,IAAI,MAAM,UAAU,SAAS;AAAA,IAC3B,MAAM,MAAM,QAAQ,WAAW,KAAK;AAAA,IACpC,MAAM,MAAM,UAAU,MAAM,GAAG,IAAI,MAAM,MAAM;AAAA,IAC/C,OAAO,MAAM,KAAK,QAAQ,SAAS;AAAA,EACrC;AAAA,EACA,IAAI,MAAM,UAAU,SAAS;AAAA,IAC3B,OAAO,yCAAyC,MAAM;AAAA,EACxD;AAAA,EAEA,MAAM,SAAS,eAAe,KAAK;AAAA,EACnC,IAAI,MAAM,UAAU;AAAA,IAClB,MAAM,SAAS,KAAK,OAAO,KAAK,IAAI,IAAI,MAAM,SAAS,KAAK,CAAC,CAAC;AAAA,IAC9D,MAAM,SAAS,MAAM,aAAa,WAAW,OAAO;AAAA,IACpD,OAAO,GAAG,SAAS,UAAU;AAAA,EAC/B;AAAA,EACA,IAAI,MAAM,UAAU,cAAc;AAAA,IAChC,OAAO,KAAK;AAAA,EACd;AAAA,EACA,OAAO,GAAG,eAAe,IAAI,MAAM,SAAS,QAAQ,KAAK,KAAK;AAAA;AAIzD,IAAM,yBAAyB,CACpC,QACA,UAA+B,CAAC,MACrB;AAAA,EACX,MAAM,QAAQ,OAAO,IAAI,CAAC,UAAU,YAAY,OAAO,OAAO,CAAC;AAAA,EAE/D,MAAM,MAAgB,CAAC;AAAA,EACvB,SAAS,IAAI,EAAG,IAAI,MAAM,QAAQ,KAAK,GAAG;AAAA,IACxC,MAAM,OAAO,MAAM,MAAM;AAAA,IACzB,MAAM,WAAW,QAAQ,OAAO,IAAI,IAAI,QAAQ;AAAA,IAChD,MAAM,WAAW,QAAQ,OAAO,IAAI,QAAQ;AAAA,IAC5C,IAAI,IAAI,GAAG;AAAA,MACT,IAAI,KAAK,YAAY,WAAW;AAAA,IAAO;AAAA;AAAA,CAAM;AAAA,IAC/C;AAAA,IACA,IAAI,KAAK,IAAI;AAAA,EACf;AAAA,EACA,OAAO,GAAG,IAAI,KAAK,EAAE;AAAA;AAAA;;;ADtHvB,IAAM,YAAY;AA2BlB,IAAM,gBAAgB,CACpB,UAC4B,OAAO,UAAU,YAAY,UAAU;AAGrE,IAAM,UAAU,CACd,KACA,SAC4B;AAAA,EAC5B,IAAI,UAAmC;AAAA,EACvC,WAAW,OAAO,KAAK,MAAM,GAAG,GAAG;AAAA,IACjC,IAAI,cAAc,OAAO,GAAG;AAAA,MAC1B,UAAU,QAAQ;AAAA,IACpB,EAAO;AAAA,MACL;AAAA;AAAA,EAEJ;AAAA,EACA,OAAO;AAAA;AAGT,IAAM,iBAAgB,CAAC,UACrB,OAAO,UAAU;AAEnB,IAAM,WAAW,CAAC,UAChB,eAAc,KAAK,IAAI,QAAQ;AAGjC,IAAM,kBAAkB,CACtB,KACA,WACA,YACkB;AAAA,EAClB,MAAM,QAAQ,IAAI,MAAM,SAAS;AAAA,EACjC,IAAI,CAAC,OAAO,QAAQ;AAAA,IAClB,OAAO;AAAA,EACT;AAAA,EACA,QAAQ,IAAI,MAAM,QAAQ,MAAM;AAAA,EAChC,OAAO,gCAAgC,aAAa,WAAW,MAAM,QAAQ;AAAA;AAa/E,IAAM,gBAAgB,OACpB,SACA,YAC8B;AAAA,EAC9B,IAAI,QAAQ,QAAQ;AAAA,IAClB,OAAO,QAAQ;AAAA,EACjB;AAAA,EACA,IAAI;AAAA,EACJ,IAAI;AAAA,KAID,EAAE,aAAa,IAAK,MAAa;AAAA,IAGlC,MAAM;AAAA,IACN,MAAM,IAAI,WAAW;AAAA,MACnB,MAAM;AAAA,MACN,SAAS,WAAW,QAAQ;AAAA,MAC5B,UAAU;AAAA,IACZ,CAAC;AAAA;AAAA,EAEH,OAAO,aAAa;AAAA,IAClB,YAAY,QAAQ,cAAc;AAAA,IAClC,SAAS,QAAQ;AAAA,IAEjB,aAAa,UAAU,kBAAkB;AAAA,IACzC,WAAW,QAAQ;AAAA,IACnB,OAAO,QAAQ,SAAS,QAAQ,IAAI;AAAA,IACpC,QAAQ,CAAC;AAAA,EACX,CAAC;AAAA;AAOI,IAAM,eAAe,CAC1B,SACA,QACkB;AAAA,EAClB,MAAM,SAAS,QAAQ,UAAU,CAAC;AAAA,EAGlC,MAAM,QAAQ,cACZ,KAAK,YAAY,MAAK,UAAU,SAAS,QAAQ,IAAI,CACvD;AAAA,EACA,IAAI,WAAW,IAAI;AAAA,EAEnB,MAAM,UAAU,CAAC,QAAqC;AAAA,IACpD,MAAM,YACJ,SAAS,QAAQ,KAAK,OAAO,QAAQ,cAAc,CAAC,KACpD,SAAS,IAAI,GAAG,KAChB;AAAA,IAMF,MAAM,OACJ,YAAY,SAAS,KAAK,SAAQ,SAAS,IAAI,GAAG,KAAK,EAAE,KAAK;AAAA,IAEhE,MAAM,OAA0B,CAAC;AAAA,IACjC,MAAM,QAAQ,SAAS,QAAQ,KAAK,OAAO,SAAS,OAAO,CAAC;AAAA,IAC5D,MAAM,cAAc,SAClB,QAAQ,KAAK,OAAO,eAAe,aAAa,CAClD;AAAA,IACA,IAAI,OAAO;AAAA,MACT,KAAK,QAAQ;AAAA,IACf;AAAA,IACA,IAAI,aAAa;AAAA,MACf,KAAK,cAAc;AAAA,IACrB;AAAA,IAKA,MAAM,SAAU,QAAQ,KAAK,OAAO,QAAQ,MAAM,KAChD,CAAC;AAAA,IACH,MAAM,WAAW,MAAM,QAAQ,MAAM,IACjC,uBAAuB,QAAQ;AAAA,MAC7B,UAAU,CAAC,UAAU;AAAA,QAInB,MAAM,MAAO,MAAM,OAAyC;AAAA,QAC5D,OAAO,MACH,gBAAgB,KAAK,QAAQ,WAAW,QAAQ,OAAO,IACvD;AAAA;AAAA,MAEN,aAAa,QAAQ;AAAA,IACvB,CAAC,IACD;AAAA,IAEJ,MAAM,MAAM,oBAAO,UAAU,UAAU,IAAI;AAAA,IAC3C,OAAO;AAAA,MACL,MAAM,EAAE,QAAQ,MAAM,MAAM,SAAS;AAAA,MAGrC,MAAM,KAAK,KAAK;AAAA,MAChB,MAAM,SAAS,GAAG;AAAA,MAClB,cAAc,SAAS,QAAQ,KAAK,OAAO,gBAAgB,YAAY,CAAC;AAAA,MACxE;AAAA,MACA,KAAK,GAAG;AAAA,IACV;AAAA;AAAA,EAGF,MAAM,OAAO,OACX,UAAU,KAAK,WAAW,SACI;AAAA,IAC9B,MAAM,SAAS,MAAM,cACnB,QAAQ,MACR,OACA,YAAY;AAAA,MACV,MAAM,SAAS,MAAM,cAAc,SAAS,KAAK,WAAW,KAAK;AAAA,MACjE,MAAM,OAAO,MAAM,OAAO,MAAwB,QAAQ,KAAK;AAAA,MAC/D,OAAO,KAAK,IAAI,OAAO;AAAA,OAEzB,OACF;AAAA,IACA,WAAW,IAAI,IAAI,OAAO,QAAQ,IAAI,CAAC,UAAU,CAAC,MAAM,KAAK,KAAK,CAAC,CAAC;AAAA,IACpE,OAAO;AAAA;AAAA,EAGT,MAAM,OAAO,OAAO,QAAiC;AAAA,IACnD,MAAM,SAAS,SAAS,IAAI,GAAG;AAAA,IAC/B,IAAI,QAAQ;AAAA,MACV,OAAO,OAAO,OAAO,OAAO,KAAK;AAAA,IACnC;AAAA,IACA,MAAM,MAAM,MAAM,MAAM,KAAK;AAAA,IAC7B,OAAO,IAAI,KAAK,CAAC,MAAM,EAAE,QAAQ,GAAG,GAAG,OAAO;AAAA;AAAA,EAGhD,OAAO;AAAA,IACL;AAAA,IACA,MAAM,QAAQ;AAAA,IACd,QAAQ,QAAQ;AAAA,IAChB;AAAA,IACA,QAAQ;AAAA,IACR,OAAO,QAAQ,eACX,aACE,MAAM,KAAK,IAAI,GACf,QAAQ,cACR,MAAM,KAAK,CACb,IACA;AAAA,EACN;AAAA;;;ApB3QF,IAAM,cAAc,MAAkC;AAAA,EACpD,MAAM,OAAO,IAAI;AAAA,EACjB,OAAO,CAAC,SAAS;AAAA,IACf,IAAI,OAAO;AAAA,IACX,IAAI,IAAI;AAAA,IACR,OAAO,KAAK,IAAI,IAAI,GAAG;AAAA,MACrB,OAAO,GAAG,QAAQ;AAAA,MAClB,KAAK;AAAA,IACP;AAAA,IACA,KAAK,IAAI,IAAI;AAAA,IACb,OAAO;AAAA;AAAA;AAWX,IAAM,gBAAgB,CACpB,SACA,MACA,aACmB;AAAA,EACnB,eAAe,iBAAiB;AAAA,EAChC,WAAW,MAAK,QAAQ,QAAQ,UAAU,gBAAgB,IAAI;AAAA,EAC9D,UAAU,MAAK,QAAQ,QAAQ,SAAS,IAAI;AAAA,EAC5C,MAAM,QAAQ;AAAA,EACd,SAAS,QAAQ;AAAA,EACjB,aAAa,QAAQ;AAAA,EACrB,SAAS,QAAQ,WAAW,QAAQ,SAAS;AAC/C;AAOA,IAAM,sBAAsB,CAC1B,WAEA,OAAO,YACL,OAAO,QAAQ,OAAO,QAAQ,KAAK,EAAE,IAAI,EAAE,MAAM,SAAS;AAAA,EACxD;AAAA,EACA,OAAO,KAAK,IAAI,WAAW;AAC7B,CAAC,CACH;AAEF,IAAM,cAAc,CAClB,KACA,MACA,QACA,SACA,YACkB;AAAA,EAClB,IAAI,IAAI,SAAS,cAAc;AAAA,IAC7B,OAAO,iBAAiB;AAAA,MACtB,SAAS,IAAI;AAAA,MACb,SAAS,IAAI;AAAA,MACb;AAAA,MACA,QAAQ,IAAI;AAAA,MACZ,aAAa,QAAQ;AAAA,MACrB,MAAM,IAAI;AAAA,IACZ,CAAC;AAAA,EACH;AAAA,EACA,IAAI,IAAI,SAAS,UAAU;AAAA,IAGzB,OAAO,IAAI,OAAO,SAAS,OAAO,IAAI,SAAS,KAAK,IAAI,QAAQ,KAAK;AAAA,EACvE;AAAA,EACA,IAAI,IAAI,SAAS,UAAU;AAAA,IACzB,OAAO,aACL;AAAA,MACE,YAAY,IAAI;AAAA,MAChB,SAAS,IAAI;AAAA,MACb,QAAQ,IAAI;AAAA,MACZ;AAAA,MACA,cAAc,IAAI;AAAA,MAClB,QAAQ,IAAI;AAAA,MACZ,WAAW,IAAI;AAAA,MACf,OAAO,IAAI;AAAA,IACb,GACA,cAAc,SAAS,MAAM,OAAO,CACtC;AAAA,EACF;AAAA,EACA,IAAI,IAAI,SAAS,UAAU;AAAA,IACzB,OAAO,aACL;AAAA,MACE,aAAa,IAAI;AAAA,MACjB,UAAU,IAAI;AAAA,MACd;AAAA,MACA,cAAc,IAAI;AAAA,MAClB,QAAQ,IAAI;AAAA,MACZ,YAAY,IAAI;AAAA,MAChB,gBAAgB,IAAI;AAAA,IACtB,GACA,cAAc,SAAS,MAAM,OAAO,CACtC;AAAA,EACF;AAAA,EACA,IAAI,IAAI,SAAS,YAAY;AAAA,IAC3B,OAAO,eACL;AAAA,MACE,aAAa,OAAO,QAAQ;AAAA,MAC5B,SAAS,IAAI;AAAA,MACb,iBAAiB,OAAO,KAAK,OAAO,YAAY,MAAM;AAAA,MACtD,MAAM,OAAO;AAAA,MACb;AAAA,MACA,QAAQ,IAAI;AAAA,MACZ,qBAAqB,oBAAoB,MAAM;AAAA,MAC/C,OAAO,IAAI;AAAA,MACX,UAAU,OAAO;AAAA,IACnB,GACA,cAAc,SAAS,MAAM,OAAO,CACtC;AAAA,EACF;AAAA,EACA,IAAI,IAAI,SAAS,mBAAmB;AAAA,IAClC,OAAO,qBACL;AAAA,MACE,QAAQ,IAAI;AAAA,MACZ,OAAO,IAAI;AAAA,MACX;AAAA,MACA,OAAO,IAAI;AAAA,MACX,cAAc,IAAI;AAAA,MAClB,QAAQ,IAAI;AAAA,MACZ,aAAa,IAAI;AAAA,MACjB,MAAM,IAAI;AAAA,IACZ,GACA,cAAc,SAAS,MAAM,OAAO,CACtC;AAAA,EACF;AAAA,EACA,OAAO,gBACL;AAAA,IACE,OAAO,IAAI;AAAA,IACX,QAAQ,IAAI;AAAA,IACZ,SAAS,IAAI;AAAA,IACb;AAAA,IACA,cAAc,IAAI;AAAA,IAClB,QAAQ,IAAI;AAAA,IACZ,KAAK,IAAI;AAAA,EACX,GACA,cAAc,SAAS,MAAM,OAAO,CACtC;AAAA;AAIF,IAAM,cAAc,CAAC,aAAqB,SACxC,YAAW,IAAI,IAAI,OAAO,MAAK,SAAQ,WAAW,GAAG,IAAI;AAmBpD,IAAM,wBAAwB,CACnC,QACA,YACmB;AAAA,EACnB,MAAM,cAAc,OAAO,QAAQ,WAAW,CAAC,GAAG,OAChD,CAAC,QAAQ,IAAI,SAAS,YACxB;AAAA,EACA,MAAM,OAAO,WAAW,WAAW,IAAI,WAAW,KAAK;AAAA,EACvD,IAAI,MAAM;AAAA,IACR,OAAO;AAAA,MACL,MAAM,YAAY,QAAQ,MAAM,KAAK,IAAI;AAAA,MACzC,SAAS,KAAK;AAAA,MACd,SAAS,KAAK;AAAA,IAChB;AAAA,EACF;AAAA,EACA,OAAO;AAAA,IACL,MAAM,QAAQ;AAAA,IACd,SAAS,OAAO,QAAQ;AAAA,IACxB,SAAS,OAAO,QAAQ;AAAA,EAC1B;AAAA;AAIF,IAAM,WAAW,CAAC,QAAqC;AAAA,EACrD,IAAI,IAAI,SAAS,UAAU;AAAA,IACzB,OAAO,IAAI,OAAO;AAAA,EACpB;AAAA,EACA,OAAO,IAAI,UAAU,IAAI;AAAA;AAI3B,IAAM,iBAAiB,CACrB,QACA,SACA,YACoB;AAAA,EACpB,MAAM,OAAO,OAAO,QAAQ;AAAA,EAC5B,IAAI,CAAC,QAAQ,KAAK,WAAW,GAAG;AAAA,IAC9B,OAAO;AAAA,MACL,iBAAiB;AAAA,QACf,SAAS,OAAO,QAAQ;AAAA,QACxB,SAAS,OAAO,QAAQ;AAAA,QACxB,MAAM;AAAA,QACN,aAAa,QAAQ;AAAA,QACrB,MAAM,OAAO,QAAQ;AAAA,MACvB,CAAC;AAAA,IACH;AAAA,EACF;AAAA,EAEA,MAAM,UAAU,YAAY;AAAA,EAC5B,OAAO,KAAK,IAAI,CAAC,QACf,YAAY,KAAK,QAAQ,SAAS,GAAG,CAAC,GAAG,QAAQ,SAAS,OAAO,CACnE;AAAA;AAUK,IAAM,iBAAiB,CAC5B,QACA,SACA,YACoB;AAAA,EACpB,MAAM,UAAU,eAAe,QAAQ,SAAS,OAAO;AAAA,EAEvD,MAAM,aAAa,gBAAgB,MAAM;AAAA,EACzC,IAAI,WAAW,SAAS,GAAG;AAAA,IACzB,QAAQ,KACN,cAAc,YAAY,cAAc,SAAS,WAAW,OAAO,CAAC,CACtE;AAAA,EACF;AAAA,EAEA,OAAO;AAAA;;;AfhNF,IAAM,WAAW,CAAC,KAAa,SAA0B;AAAA,EAC9D,MAAM,MAAM,UAAS,KAAK,IAAI;AAAA,EAC9B,OAAO,EAAE,IAAI,WAAW,IAAI,KAAK,YAAW,GAAG;AAAA;AAIjD,IAAM,uBAAuB,CAC3B,QACA,cACmB;AAAA,EACnB,IAAI,CAAC,WAAW;AAAA,IACd,OAAO;AAAA,EACT;AAAA,EACA,OAAO;AAAA,OACF;AAAA,IACH,SAAS;AAAA,SACJ,OAAO;AAAA,MACV,MAAM,UAAU,eAAe,OAAO,QAAQ;AAAA,IAChD;AAAA,IACA,YAAY;AAAA,SACP,OAAO;AAAA,MACV,SAAS,UAAU,WAAW,OAAO,WAAW;AAAA,MAChD,MAAM,UAAU,QAAQ,OAAO,WAAW;AAAA,MAC1C,QAAQ,UAAU,UAAU,OAAO,WAAW;AAAA,IAChD;AAAA,EACF;AAAA;AAyCF,IAAM,qBAAqB,CACzB,OACA,mBACiB;AAAA,EACjB,MAAM,cAA4B,CAAC;AAAA,EACnC,MAAM,OAAO,IAAI;AAAA,EACjB,WAAW,QAAQ,OAAO;AAAA,IAGxB,IAAI,KAAK,cAAc,CAAC,KAAK,cAAc,KAAK,IAAI,KAAK,UAAU,GAAG;AAAA,MACpE;AAAA,IACF;AAAA,IACA,KAAK,IAAI,KAAK,UAAU;AAAA,IACxB,MAAM,WAAW,UAAS,gBAAgB,KAAK,UAAU,EACtD,MAAM,IAAI,EACV,KAAK,GAAG;AAAA,IACX,MAAM,UAAU,KAAK,WAAW,KAAK,OAAO;AAAA,IAC5C,IAAI,aAAa,SAAS;AAAA,MACxB,YAAY,KAAK;AAAA,QACf,MAAM;AAAA,QACN,MAAM,KAAK;AAAA,QACX,SAAS,mBAAmB,KAAK,OAAO,wDAAwD,KAAK,4BAA4B,+CAA+C;AAAA,QAChL,UAAU;AAAA,QACV,YACE;AAAA,MACJ,CAAC;AAAA,IACH;AAAA,EACF;AAAA,EACA,OAAO;AAAA;AAUT,IAAM,yBAAyB,CAC7B,QACA,WACG;AAAA,EAIH,MAAM,oBACJ,OAAO,KAAK,OAAO,YAAY,MAAM,EAAE,SAAS,IAC5C,OAAO,YAAY,SACnB;AAAA,EACN,MAAM,gBAAgB,OAAO,QAAQ,OAAO,QAAQ,KAAK,EAAE,OACzD,IAAI,UAAU,OAAO,KAAK,KAAK,WAAW,EAAE,SAAS,CACvD;AAAA,EACA,MAAM,kBACJ,cAAc,SAAS,IACnB,OAAO,YACL,cAAc,IAAI,EAAE,MAAM,UAAU,CAAC,MAAM,KAAK,WAAW,CAAC,CAC9D,IACA;AAAA,EAEN,MAAM,QAAsB,CAAC;AAAA,EAC7B,MAAM,iBAA+B,CAAC;AAAA,EACtC,IAAI,eAAe;AAAA,EACnB,aAAa,QAAQ,SAAS,iBAAiB,QAAQ;AAAA,IACrD,eAAe,KAAK,GAAG,WAAW;AAAA,IAClC,WAAW,SAAS,SAAS;AAAA,MAC3B,MAAM,aAAa,eAAe,OAAO;AAAA,QACvC,UAAU,OAAO;AAAA,QACjB,aAAa,OAAO,QAAQ;AAAA,QAC5B;AAAA,QACA,MAAM,OAAO;AAAA,QACb,QAAQ;AAAA,UACN,MAAM,OAAO;AAAA,UACb,QAAQ,OAAO;AAAA,UACf,QAAQ,OAAO;AAAA,QACjB;AAAA,QACA;AAAA,QACA,UAAU,OAAO;AAAA,MACnB,CAAC;AAAA,MACD,IAAI,WAAW,MAAM,WAAW,KAAK,WAAW,YAAY,SAAS,GAAG;AAAA,QACtE,gBAAgB;AAAA,MAClB;AAAA,MACA,MAAM,KAAK,GAAG,WAAW,KAAK;AAAA,MAC9B,eAAe,KAAK,GAAG,WAAW,WAAW;AAAA,IAC/C;AAAA,EACF;AAAA,EACA,OAAO,EAAE,aAAa,gBAAgB,cAAc,MAAM;AAAA;AASrD,IAAM,cAAc,OACzB,MACA,UASI,CAAC,MACqB;AAAA,EAC1B,MAAM,OAAO,QAAQ,QAAQ;AAAA,EAC7B,MAAM,UAAU,QAAQ,WAAW;AAAA,EACnC,MAAM,eAAe,MAAM,WAAW,MAAM;AAAA,IAC1C,cAAc,QAAQ;AAAA,EACxB,CAAC;AAAA,EAKD,MAAM,SAAS,mBACb,qBAAqB,aAAa,QAAQ,QAAQ,SAAS,CAC7D;AAAA,EACA,MAAM,UAAU,sBAAsB,MAAM,QAAQ;AAAA,IAClD,YAAY,QAAQ;AAAA,EACtB,CAAC;AAAA,EAID,MAAM,UAAU,eAAe,QAAQ,SAAS;AAAA,IAC9C;AAAA,IACA;AAAA,IACA,SAAS,QAAQ;AAAA,EACnB,CAAC;AAAA,EACD,WAAW,UAAU,SAAS;AAAA,IAC5B,OAAO,WAAW;AAAA,EACpB;AAAA,EAKA,MAAM,cAAkC,QAAQ,QAAQ,CAAC,WACvD,OAAO,UAAU,CAAC,OAAO,cACrB,CAAC,IACD,CAAC,EAAE,QAAQ,OAAO,QAAQ,MAAM,OAAO,YAAY,CAAC,CAC1D;AAAA,EAOA,MAAM,aACJ,OAAO,QAAQ,OAAO,KAAK,WAAW,QAClC,OAAO,KAAK,QAAQ,QAAQ,CAAC,WAC3B,OAAO,SAAS,OAAO,MAAM,gBAAgB,CAAC,IAAI,CAAC,OAAO,IAAI,CAChE,IACA;AAAA,EAEN,OAAO,QAAQ,cAAc,MAAM,QAAQ,IAAI;AAAA,IAC7C,QAAQ,IACN,QAAQ,IAAI,OAAO,YAAY,EAAE,WAAY,MAAM,OAAO,KAAK,EAAG,EAAE,CACtE;AAAA,IACA,mBAAmB,aAAa;AAAA,MAC9B;AAAA,MACA,aAAa,OAAO,UAAU,SAAS,IAAI,CAAC,YAAY,QAAQ,EAAE;AAAA,IACpE,CAAC;AAAA,EACH,CAAC;AAAA,EAOD,MAAM,qBAAmC,CAAC;AAAA,EAC1C,MAAM,QAAQ,IACZ,OAAO,QAAQ,GAAG,QAAQ,cAAc;AAAA,IACtC,IAAI,OAAO,UAAU,CAAC,OAAO,aAAa;AAAA,MACxC,OAAO,CAAC;AAAA,IACV;AAAA,IACA,OAAO,QAAQ,IAAI,OAAO,UAAU;AAAA,MAClC,IAAI,CAAC,MAAM,cAAc,CAAC,qBAAqB,MAAM,KAAK,IAAI,GAAG;AAAA,QAC/D;AAAA,MACF;AAAA,MACA,MAAM,YAAY,MAAM,eAAe,MAAM,KAAK,MAAM;AAAA,QACtD,aAAa,OAAO;AAAA,QACpB,YAAY,mBAAmB,MAAM,KAAK,MAAM,KAAK,IAAI;AAAA,QACzD,YAAY,MAAM;AAAA,MACpB,CAAC;AAAA,MACD,MAAM,WAAW;AAAA,MACjB,mBAAmB,KAAK,GAAG,UAAU,MAAM;AAAA,KAC5C;AAAA,GACF,CACH;AAAA,EAMA,MAAM,mBAAmB,IAAI,IAAI;AAAA,IAC/B,GAAG,OAAO,QAAQ,GAAG,YAAY,iBAC/B,OAAO,QAAQ,cAAc,CAAC,CAAC,CACjC;AAAA,IACA,GAAG,WAAW;AAAA,EAChB,CAAC;AAAA,EAED;AAAA,IACE,aAAa;AAAA,IACb;AAAA,IACA,OAAO;AAAA,MACL,uBAAuB,QAAQ,MAAM;AAAA,EAGzC,MAAM,QACJ,SAAS,WAAW,CAAC,UACjB,SAAS,OAAO,CAAC,SAAS,CAAC,KAAK,KAAK,KAAK,IAC1C;AAAA,EAKN,MAAM,eAAe,0BAA0B,OAAO,YAAY;AAAA,EAClE,MAAM,uBAAqC,CAAC;AAAA,EAC5C,IAAI,aAAa,WAAW,aAAa,WAAW,OAAO;AAAA,IACzD,MAAM,UAAU,MACb,IAAI,CAAC,SAAS,KAAK,UAAU,EAC7B,OAAO,CAAC,SAAyB,SAAS,SAAS;AAAA,IAMtD,MAAM,UAAU,kBAAkB,QAAQ,IAAI;AAAA,IAC9C,MAAM,eAAe,QAClB,QAAQ,CAAC,WAAY,OAAO,cAAc,CAAC,OAAO,WAAW,IAAI,CAAC,CAAE,EACpE,OAAO,CAAC,QAAQ,YAAY,QAAQ,SAAS,SAAS,GAAG,CAAC;AAAA,IAC7D,MAAM,WAAW,qBACf,QAAQ,MACR,cACA,SACA,OACF;AAAA,IACA,WAAW,QAAQ,OAAO;AAAA,MACxB,IAAI,CAAC,KAAK,gBAAgB,KAAK,YAAY;AAAA,QACzC,KAAK,eAAe,SAAS,IAAI,KAAK,UAAU;AAAA,MAClD;AAAA,IACF;AAAA,IAMA,MAAM,UAAU,MAAM,OACpB,CAAC,SACC,KAAK,cACL,CAAC,KAAK,gBACN,aAAa,KAAK,CAAC,QAAQ,SAAS,KAAK,KAAK,cAAc,EAAE,CAAC,CACnE,EAAE;AAAA,IACF,qBAAqB,KACnB,GAAG,2BAA2B,QAAQ,MAAM,OAAO,CACrD;AAAA,EACF;AAAA,EAEA,MAAM,QAAQ,kBAAkB,OAAO;AAAA,IACrC,UAAU,OAAO;AAAA,IACjB,YAAY,WAAW;AAAA,IACvB,MAAM,OAAO;AAAA,IACb,YAAY,OAAO;AAAA,IACnB;AAAA,IACA,UAAU,OAAO;AAAA,EACnB,CAAC;AAAA,EACD,MAAM,WAAW,cAAc,EAAE,QAAQ,SAAS,MAAM,CAAC;AAAA,EAEzD,MAAM,eAAe,OAAO,OACxB,gBAAgB,OAAO,OAAO,MAAM,OAAO,QAAQ,IACnD,CAAC;AAAA,EACL,MAAM,kBAAkB,OAAO,WAC3B,oBAAoB,OAAO,OAAO,QAAQ,IAC1C,CAAC;AAAA,EAEL,OAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA,aAAa;AAAA,MACX,GAAG;AAAA,MACH,GAAG;AAAA,MACH,GAAG,WAAW;AAAA,MACd,GAAG,mBAAmB,OAAO,sBAAsB,QAAQ,OAAO,EAAE,IAAI;AAAA,MACxE,GAAG,MAAM;AAAA,MACT,GAAG;AAAA,MACH,GAAG;AAAA,MACH,GAAG;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,sBAAsB,aAAa;AAAA,EACrC;AAAA;",
|
|
46
|
-
"debugId": "9C98B15F280A03E064756E2164756E21",
|
|
47
|
-
"names": []
|
|
48
|
-
}
|