blume 2.0.3 → 2.1.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 +1 -0
- package/CHANGELOG.md +142 -0
- package/README.md +1 -1
- package/dist/cli/{chunk-5f86nr5m.js → chunk-073rdvgb.js} +21 -48
- package/dist/cli/{chunk-5f86nr5m.js.map → chunk-073rdvgb.js.map} +4 -5
- package/dist/cli/{chunk-ga0pf4aj.js → chunk-0n9xkp3d.js} +12 -3
- package/dist/cli/{chunk-ga0pf4aj.js.map → chunk-0n9xkp3d.js.map} +3 -3
- package/dist/cli/chunk-150jk7km.js +52 -0
- package/dist/cli/chunk-150jk7km.js.map +10 -0
- package/dist/cli/{chunk-9t7a85s3.js → chunk-313vnn40.js} +30 -19
- package/dist/cli/chunk-313vnn40.js.map +10 -0
- package/dist/cli/{chunk-tkacnehg.js → chunk-3v3ffbrj.js} +2 -2
- package/dist/cli/{chunk-q58y5e6a.js → chunk-3x6x3pph.js} +46 -24
- package/dist/cli/chunk-3x6x3pph.js.map +10 -0
- package/dist/cli/{chunk-1d7ve1dm.js → chunk-42t4jxdx.js} +37 -5
- package/dist/cli/chunk-42t4jxdx.js.map +12 -0
- package/dist/cli/{chunk-8ktnccpt.js → chunk-54ztw1nj.js} +21 -2
- package/dist/cli/chunk-54ztw1nj.js.map +10 -0
- package/dist/cli/{chunk-hm3vjy5s.js → chunk-5vjtgs6e.js} +28 -10
- package/dist/cli/{chunk-hm3vjy5s.js.map → chunk-5vjtgs6e.js.map} +4 -4
- package/dist/cli/chunk-66w9j7p6.js +322 -0
- package/dist/cli/chunk-66w9j7p6.js.map +14 -0
- package/dist/cli/{chunk-2hsdwb9n.js → chunk-7ppf1rva.js} +34 -18
- package/dist/cli/chunk-7ppf1rva.js.map +10 -0
- package/dist/cli/{chunk-r9rcc4w7.js → chunk-8y2kmjv8.js} +7 -7
- package/dist/cli/{chunk-r20tn01b.js → chunk-c1y538kd.js} +8 -2
- package/dist/cli/{chunk-r20tn01b.js.map → chunk-c1y538kd.js.map} +3 -3
- package/dist/cli/{chunk-2eytanqx.js → chunk-c887fj0s.js} +2 -2
- package/dist/cli/{chunk-p3v96n38.js → chunk-cfyg47zg.js} +16 -6
- package/dist/cli/chunk-cfyg47zg.js.map +10 -0
- package/dist/cli/chunk-cm89scdv.js +443 -0
- package/dist/cli/chunk-cm89scdv.js.map +11 -0
- package/dist/cli/{chunk-akbpwfxc.js → chunk-dzwehpy7.js} +2 -2
- package/dist/cli/{chunk-wrr3j9w9.js → chunk-e6atmn4h.js} +299 -72
- package/dist/cli/chunk-e6atmn4h.js.map +36 -0
- package/dist/cli/{chunk-tzmab476.js → chunk-e8xw9qkh.js} +8 -6
- package/dist/cli/{chunk-tzmab476.js.map → chunk-e8xw9qkh.js.map} +3 -3
- package/dist/cli/{chunk-6dsbexzp.js → chunk-e9q7a2ak.js} +17 -15
- package/dist/cli/{chunk-6dsbexzp.js.map → chunk-e9q7a2ak.js.map} +2 -2
- package/dist/cli/{chunk-pv29h0wf.js → chunk-ehweg4fr.js} +5155 -1249
- package/dist/cli/chunk-ehweg4fr.js.map +218 -0
- package/dist/cli/{chunk-c8chx29p.js → chunk-ep6nd0rn.js} +390 -318
- package/dist/cli/chunk-ep6nd0rn.js.map +36 -0
- package/dist/cli/{chunk-p73c0m7w.js → chunk-fdrkhpjy.js} +18 -15
- package/dist/cli/{chunk-p73c0m7w.js.map → chunk-fdrkhpjy.js.map} +3 -3
- package/dist/cli/{chunk-2z928egk.js → chunk-ft9vfys6.js} +5 -5
- package/dist/cli/{chunk-5m5nmvyq.js → chunk-gb420vvz.js} +316 -79
- package/dist/cli/chunk-gb420vvz.js.map +17 -0
- package/dist/cli/{chunk-vg9r4eb9.js → chunk-hebn0tpx.js} +86 -13
- package/dist/cli/chunk-hebn0tpx.js.map +14 -0
- package/dist/cli/{chunk-35d4wj9f.js → chunk-htrwgabr.js} +41 -23
- package/dist/cli/chunk-htrwgabr.js.map +15 -0
- package/dist/cli/{chunk-g4hq16wv.js → chunk-hx3xqnk9.js} +16 -14
- package/dist/cli/{chunk-g4hq16wv.js.map → chunk-hx3xqnk9.js.map} +2 -2
- package/dist/cli/chunk-j134vrp2.js +65 -0
- package/dist/cli/chunk-j134vrp2.js.map +10 -0
- package/dist/cli/{chunk-a58773jm.js → chunk-kma9f6sf.js} +2 -2
- package/dist/cli/chunk-kwrg63tm.js +31 -0
- package/dist/cli/chunk-kwrg63tm.js.map +10 -0
- package/dist/cli/{chunk-ey84smr6.js → chunk-mrcccgnh.js} +6 -6
- package/dist/cli/{chunk-ey84smr6.js.map → chunk-mrcccgnh.js.map} +1 -1
- package/dist/cli/{chunk-yfyb25rh.js → chunk-n0v7m8ry.js} +24 -8
- package/dist/cli/chunk-n0v7m8ry.js.map +14 -0
- package/dist/cli/{chunk-j85vccga.js → chunk-ncj7w596.js} +576 -367
- package/dist/cli/chunk-ncj7w596.js.map +35 -0
- package/dist/cli/{chunk-5xvm6tfj.js → chunk-nq1d20w2.js} +17 -15
- package/dist/cli/{chunk-5xvm6tfj.js.map → chunk-nq1d20w2.js.map} +2 -2
- package/dist/cli/{chunk-pehfxfta.js → chunk-nrns10mj.js} +6 -98
- package/dist/cli/chunk-nrns10mj.js.map +10 -0
- package/dist/cli/{chunk-crgn1q09.js → chunk-sds923fa.js} +4 -4
- package/dist/cli/chunk-t2m5j4pr.js +104 -0
- package/dist/cli/chunk-t2m5j4pr.js.map +11 -0
- package/dist/cli/{chunk-acanzt5p.js → chunk-tdqhyxfz.js} +6 -6
- package/dist/cli/chunk-tphac74f.js +126 -0
- package/dist/cli/chunk-tphac74f.js.map +11 -0
- package/dist/cli/chunk-w1r4vase.js +18 -0
- package/dist/cli/chunk-w1r4vase.js.map +10 -0
- package/dist/cli/{chunk-3em5wd2y.js → chunk-zccbjcj0.js} +4 -3
- package/dist/cli/{chunk-3em5wd2y.js.map → chunk-zccbjcj0.js.map} +2 -2
- package/dist/cli/index.js +31 -20
- package/dist/cli/index.js.map +5 -5
- package/dist/types/ai/agent-surface.d.ts +8 -7
- package/dist/types/ai/ai-catalog.d.ts +7 -0
- package/dist/types/ai/ask-context.d.ts +26 -0
- package/dist/types/ai/ask.d.ts +271 -58
- package/dist/types/ai/component-markdown.d.ts +2 -0
- package/dist/types/ai/index.d.ts +2 -2
- package/dist/types/ai/openapi-components.d.ts +4 -1
- package/dist/types/ai/site-skill.d.ts +26 -0
- package/dist/types/ai/skills.d.ts +16 -0
- package/dist/types/astro/integration.d.ts +9 -0
- package/dist/types/astro/markdown-negotiation.d.ts +3 -0
- package/dist/types/astro/pages.d.ts +10 -0
- package/dist/types/captcha/hcaptcha.d.ts +27 -0
- package/dist/types/captcha/index.d.ts +28 -0
- package/dist/types/captcha/schema.d.ts +29 -0
- package/dist/types/captcha/turnstile.d.ts +28 -0
- package/dist/types/cli/init/scaffold.d.ts +1 -1
- package/dist/types/components/content/api-field.d.ts +62 -0
- package/dist/types/components/content/api-page.d.ts +100 -0
- package/dist/types/components/layout/nav-utils.d.ts +26 -0
- package/dist/types/components/openapi/code-samples.d.ts +29 -0
- package/dist/types/components/openapi/helpers.d.ts +158 -0
- package/dist/types/components/openapi/security.d.ts +95 -0
- package/dist/types/components/openapi/snippets.d.ts +60 -0
- package/dist/types/consent/ethyca.d.ts +48 -0
- package/dist/types/consent/index.d.ts +37 -0
- package/dist/types/consent/native.d.ts +28 -0
- package/dist/types/consent/osano.d.ts +42 -0
- package/dist/types/consent/schema.d.ts +110 -0
- package/dist/types/core/changelog-index.d.ts +8 -0
- package/dist/types/core/config-input.d.ts +217 -23
- package/dist/types/core/data.d.ts +53 -4
- package/dist/types/core/footer.d.ts +8 -0
- package/dist/types/core/i18n-ui.d.ts +88 -0
- package/dist/types/core/includes.d.ts +8 -2
- package/dist/types/core/links.d.ts +8 -0
- package/dist/types/core/localizable.d.ts +7 -0
- package/dist/types/core/page-modes.d.ts +29 -0
- package/dist/types/core/probe.d.ts +2 -0
- package/dist/types/core/project-graph.d.ts +6 -0
- package/dist/types/core/redirect-patterns.d.ts +152 -0
- package/dist/types/core/related.d.ts +40 -0
- package/dist/types/core/schema.d.ts +670 -56
- package/dist/types/core/sources/cache.d.ts +3 -0
- package/dist/types/core/sources/contentful-rich-text.d.ts +11 -2
- package/dist/types/core/sources/contentful.d.ts +17 -1
- package/dist/types/core/sources/lexical.d.ts +5 -0
- package/dist/types/core/sources/normalize.d.ts +20 -1
- package/dist/types/core/sources/read.d.ts +5 -0
- package/dist/types/core/sources/remote.d.ts +37 -10
- package/dist/types/core/sources/sanity.d.ts +1 -1
- package/dist/types/core/types.d.ts +18 -1
- package/dist/types/core/variables.d.ts +42 -0
- package/dist/types/deploy/adapter-output.d.ts +2 -1
- package/dist/types/deploy/adapters/cloudflare.d.ts +7 -5
- package/dist/types/deploy/adapters/node.d.ts +58 -23
- package/dist/types/deploy/adapters/registry.d.ts +10 -0
- package/dist/types/deploy/adapters/types.d.ts +3 -2
- package/dist/types/deploy/node-headers.d.ts +13 -2
- package/dist/types/deploy/platforms/cloudflare.d.ts +15 -7
- package/dist/types/deploy/platforms/netlify.d.ts +7 -0
- package/dist/types/deploy/platforms/node.d.ts +3 -1
- package/dist/types/deploy/platforms/types.d.ts +18 -7
- package/dist/types/deploy/platforms/vercel.d.ts +26 -1
- package/dist/types/deploy/redirects.d.ts +55 -11
- package/dist/types/deploy/sitemap.d.ts +2 -1
- package/dist/types/deploy/vercel-negotiation.d.ts +51 -21
- package/dist/types/markdown/code-title.d.ts +17 -0
- package/dist/types/markdown/features.d.ts +3 -2
- package/dist/types/narration/build.d.ts +70 -0
- package/dist/types/narration/provider.d.ts +26 -0
- package/dist/types/narration/script.d.ts +154 -0
- package/dist/types/openapi/checks.d.ts +27 -0
- package/dist/types/openapi/graphql.d.ts +6 -0
- package/dist/types/openapi/model.d.ts +27 -10
- package/dist/types/openapi/overlay.d.ts +54 -0
- package/dist/types/openapi/parse.d.ts +16 -0
- package/dist/types/openapi/references.d.ts +30 -4
- package/dist/types/ratelimit/cloudflare.d.ts +39 -0
- package/dist/types/ratelimit/index.d.ts +27 -0
- package/dist/types/ratelimit/memory.d.ts +36 -0
- package/dist/types/ratelimit/schema.d.ts +119 -0
- package/dist/types/ratelimit/upstash.d.ts +46 -0
- package/dist/types/ratelimit/wrangler.d.ts +23 -0
- package/dist/types/reference/asyncapi.d.ts +10 -10
- package/dist/types/reference/graphql.d.ts +89 -11
- package/dist/types/reference/index.d.ts +1 -1
- package/dist/types/reference/openapi.d.ts +33 -12
- package/dist/types/reference/options.d.ts +82 -6
- package/dist/types/reference/scalar.d.ts +13 -0
- package/dist/types/reference/schema.d.ts +144 -36
- package/dist/types/search/adapters/registry.d.ts +3 -0
- package/dist/types/search/adapters/typesense.d.ts +14 -4
- package/dist/types/search/build.d.ts +3 -1
- package/dist/types/search/documents.d.ts +12 -1
- package/dist/types/search/orama-index.d.ts +13 -9
- package/dist/types/search/sync/algolia.d.ts +5 -2
- package/dist/types/search/sync/index.d.ts +3 -1
- package/dist/types/search/sync/typesense.d.ts +11 -7
- package/dist/types/seo/metatags.d.ts +20 -0
- package/dist/types/sources/contentful.d.ts +16 -0
- package/dist/types/sources/github-releases.d.ts +8 -1
- package/dist/types/sources/payload.d.ts +2 -0
- package/dist/types/sources/registry.d.ts +54 -0
- package/dist/types/sources/shared.d.ts +3 -0
- package/dist/types/sources/strapi.d.ts +2 -0
- package/docs/01-quickstart.mdx +4 -2
- package/docs/02-deployment.mdx +71 -17
- package/docs/03-upgrading.mdx +4 -4
- package/docs/04-migrating.mdx +1 -1
- package/docs/advanced/changelog.mdx +13 -2
- package/docs/advanced/skills.mdx +15 -1
- package/docs/cli/audit.mdx +51 -8
- package/docs/cli/doctor.mdx +5 -1
- package/docs/cli/evals.mdx +4 -4
- package/docs/cli/index.mdx +1 -0
- package/docs/cli/translate.mdx +1 -0
- package/docs/cli/validate.mdx +4 -3
- package/docs/configuration/analytics.mdx +3 -1
- package/docs/configuration/assistant.mdx +194 -38
- package/docs/configuration/consent.mdx +100 -0
- package/docs/configuration/customization.mdx +8 -6
- package/docs/configuration/export.mdx +5 -3
- package/docs/configuration/index.mdx +64 -5
- package/docs/configuration/meta.ts +3 -0
- package/docs/configuration/narration.mdx +119 -0
- package/docs/configuration/rate-limiting.mdx +90 -0
- package/docs/configuration/search.mdx +63 -12
- package/docs/configuration/theming.mdx +3 -1
- package/docs/content/components.mdx +108 -2
- package/docs/content/frontmatter.mdx +80 -2
- package/docs/content/i18n.mdx +39 -4
- package/docs/content/includes.mdx +16 -0
- package/docs/content/meta.mdx +1 -0
- package/docs/content/meta.ts +1 -0
- package/docs/content/navigation.mdx +32 -10
- package/docs/content/sources/contentful.mdx +35 -0
- package/docs/content/sources/custom.mdx +43 -0
- package/docs/content/sources/github-releases.mdx +39 -0
- package/docs/content/sources/index.mdx +91 -0
- package/docs/content/sources/meta.ts +16 -0
- package/docs/content/sources/notion.mdx +32 -0
- package/docs/content/sources/obsidian.mdx +37 -0
- package/docs/content/sources/payload.mdx +31 -0
- package/docs/content/sources/remote-mdx.mdx +44 -0
- package/docs/content/sources/sanity.mdx +29 -0
- package/docs/content/sources/strapi.mdx +32 -0
- package/docs/content/syntax.mdx +69 -4
- package/docs/content/variables.mdx +41 -0
- package/docs/discoverability/agent-discovery.mdx +29 -2
- package/docs/discoverability/index.mdx +2 -2
- package/docs/discoverability/json-api.mdx +2 -0
- package/docs/discoverability/llms-txt.mdx +2 -2
- package/docs/discoverability/markdown.mdx +3 -1
- package/docs/discoverability/mcp.mdx +4 -0
- package/docs/discoverability/metadata.mdx +17 -0
- package/docs/discoverability/sitemap-and-robots.mdx +1 -1
- package/docs/references/api-pages.mdx +74 -0
- package/docs/references/asyncapi.mdx +4 -2
- package/docs/references/graphql.mdx +14 -2
- package/docs/references/meta.ts +1 -1
- package/docs/references/openapi.mdx +148 -7
- package/docs/references/scalar.mdx +3 -1
- package/package.json +43 -8
- package/skills/blume/SKILL.md +3 -0
- package/skills/blume-migrate/SKILL.md +6 -6
- package/skills/blume-migrate/references/docusaurus.md +7 -7
- package/skills/blume-migrate/references/fumadocs.md +17 -13
- package/skills/blume-migrate/references/mintlify.md +37 -19
- package/skills/blume-migrate/references/nextra.md +10 -7
- package/skills/blume-migrate/references/starlight.md +11 -8
- package/skills/blume-migrate/scripts/mintlify-codemod.mjs +60 -14
- package/skills/blume-write-skill/SKILL.md +82 -0
- package/src/ai/agent-readability.ts +2 -1
- package/src/ai/agent-surface.ts +9 -7
- package/src/ai/ai-catalog.ts +6 -3
- package/src/ai/ask-context.ts +103 -20
- package/src/ai/ask-data.ts +6 -0
- package/src/ai/ask-limits.ts +13 -0
- package/src/ai/ask-tools.ts +142 -0
- package/src/ai/ask.ts +354 -76
- package/src/ai/changelog-markdown.ts +11 -6
- package/src/ai/component-markdown.ts +76 -1
- package/src/ai/cors.ts +14 -3
- package/src/ai/index.ts +12 -0
- package/src/ai/llms.ts +6 -2
- package/src/ai/markdown.ts +2 -1
- package/src/ai/mcp/data.ts +6 -0
- package/src/ai/mcp/query.ts +14 -6
- package/src/ai/openapi-components.ts +36 -6
- package/src/ai/relative-links.ts +4 -38
- package/src/ai/site-skill.ts +274 -0
- package/src/ai/skills.ts +35 -2
- package/src/ai/write-skill.ts +97 -0
- package/src/astro/generate.ts +210 -66
- package/src/astro/index.ts +1 -0
- package/src/astro/integration.ts +42 -4
- package/src/astro/markdown-negotiation.ts +3 -0
- package/src/astro/module-types.ts +9 -1
- package/src/astro/pages.ts +23 -0
- package/src/astro/templates.ts +406 -108
- package/src/astro/variables.ts +31 -0
- package/src/audit/catalog.ts +38 -3
- package/src/audit/checks/contrast.ts +183 -0
- package/src/audit/checks/i18n.ts +2 -3
- package/src/audit/checks/indexability.ts +2 -3
- package/src/audit/checks/links.ts +19 -13
- package/src/audit/checks/network.ts +110 -6
- package/src/audit/checks/redirects.ts +6 -4
- package/src/audit/checks/robots.ts +58 -2
- package/src/audit/checks/sitemap.ts +2 -3
- package/src/audit/redirects.ts +46 -15
- package/src/audit/run.ts +5 -0
- package/src/audit/types.ts +4 -1
- package/src/audit/url.ts +14 -5
- package/src/blume-modules.d.ts +12 -1
- package/src/captcha/client.ts +152 -0
- package/src/captcha/hcaptcha.ts +36 -0
- package/src/captcha/index.ts +28 -0
- package/src/captcha/schema.ts +32 -0
- package/src/captcha/turnstile.ts +38 -0
- package/src/captcha/verify.ts +71 -0
- package/src/cli/args.ts +51 -0
- package/src/cli/build-failure.ts +23 -0
- package/src/cli/command-meta.ts +5 -0
- package/src/cli/commands/audit.ts +10 -1
- package/src/cli/commands/dev.ts +18 -2
- package/src/cli/commands/doctor.ts +67 -28
- package/src/cli/commands/eval.ts +36 -4
- package/src/cli/commands/init.ts +6 -4
- package/src/cli/commands/skill.ts +96 -0
- package/src/cli/commands/validate.ts +10 -1
- package/src/cli/index.ts +5 -0
- package/src/cli/init/scaffold.ts +3 -3
- package/src/cli/prepare.ts +6 -3
- package/src/cli/required-secrets.ts +72 -30
- package/src/components/BlumePage.astro +2 -0
- package/src/components/content/ApiEndpoint.astro +18 -0
- package/src/components/content/ApiField.astro +79 -0
- package/src/components/content/ApiPlayground.astro +66 -0
- package/src/components/content/ApiRail.astro +19 -0
- package/src/components/content/Color.astro +1 -0
- package/src/components/content/FileTree.astro +1 -0
- package/src/components/content/GithubInfo.astro +1 -0
- package/src/components/content/Panel.astro +1 -0
- package/src/components/content/ParamField.astro +25 -0
- package/src/components/content/RequestExample.astro +17 -0
- package/src/components/content/ResponseExample.astro +17 -0
- package/src/components/content/ResponseField.astro +32 -0
- package/src/components/content/Step.astro +1 -0
- package/src/components/content/Tile.astro +1 -0
- package/src/components/content/Tooltip.astro +1 -0
- package/src/components/content/Tree.astro +1 -0
- package/src/components/content/TypeTable.astro +4 -1
- package/src/components/content/View.astro +21 -0
- package/src/components/content/ViewSwitcher.astro +168 -0
- package/src/components/content/api-field.ts +103 -0
- package/src/components/content/api-page.ts +248 -0
- package/src/components/content/mermaid-element.ts +15 -1
- package/src/components/content/view-switcher.ts +71 -0
- package/src/components/index.ts +2 -0
- package/src/components/islands/Assistant.astro +10 -1
- package/src/components/islands/assistant.tsx +104 -14
- package/src/components/islands/code-question.ts +62 -0
- package/src/components/islands/hooks.ts +136 -21
- package/src/components/islands/support-link.ts +68 -0
- package/src/components/layout/Analytics.astro +11 -2
- package/src/components/layout/Banner.astro +14 -10
- package/src/components/layout/Breadcrumbs.astro +6 -2
- package/src/components/layout/ConsentBanner.astro +62 -0
- package/src/components/layout/ConsentHead.astro +38 -0
- package/src/components/layout/Directory.astro +94 -0
- package/src/components/layout/DrawerSwitchers.astro +51 -0
- package/src/components/layout/Empty.astro +2 -2
- package/src/components/layout/Header.astro +122 -80
- package/src/components/layout/LanguageSwitcher.astro +48 -15
- package/src/components/layout/NarrationPlayer.astro +1085 -0
- package/src/components/layout/NavSelector.astro +33 -7
- package/src/components/layout/PageActions.astro +139 -24
- package/src/components/layout/PageFeedback.astro +54 -49
- package/src/components/layout/PageLayout.astro +71 -3
- package/src/components/layout/ReferenceLayout.astro +17 -6
- package/src/components/layout/RelatedPages.astro +42 -0
- package/src/components/layout/RootLayout.astro +318 -47
- package/src/components/layout/Search.astro +47 -19
- package/src/components/layout/SiteFooter.astro +124 -0
- package/src/components/layout/SiteMetatags.astro +16 -0
- package/src/components/layout/TableOfContents.astro +24 -17
- package/src/components/layout/consent/native.ts +79 -0
- package/src/components/layout/consent/types.ts +37 -0
- package/src/components/layout/footer-links.ts +53 -0
- package/src/components/layout/footer-socials.ts +87 -0
- package/src/components/layout/head-scripts.ts +6 -1
- package/src/components/layout/locale-redirect.ts +107 -0
- package/src/components/layout/nav-utils.ts +83 -0
- package/src/components/layout/page-feedback.ts +96 -0
- package/src/components/layout/search/algolia.ts +10 -1
- package/src/components/layout/search/analytics.ts +126 -0
- package/src/components/layout/search/endpoint.ts +48 -5
- package/src/components/layout/search/flexsearch.ts +17 -5
- package/src/components/layout/search/orama-cloud.ts +26 -14
- package/src/components/layout/search/pagefind.ts +114 -5
- package/src/components/layout/search/types.ts +32 -0
- package/src/components/layout/search/typesense.ts +19 -3
- package/src/components/openapi/AsyncApiOperation.astro +14 -7
- package/src/components/openapi/Authorization.astro +10 -0
- package/src/components/openapi/GraphqlOperation.astro +5 -0
- package/src/components/openapi/Operation.astro +184 -13
- package/src/components/openapi/ParametersTable.astro +8 -4
- package/src/components/openapi/Playground.astro +7 -1
- package/src/components/openapi/RequestBody.astro +24 -5
- package/src/components/openapi/RequestPanel.astro +59 -7
- package/src/components/openapi/Responses.astro +7 -4
- package/src/components/openapi/SchemaTable.astro +15 -3
- package/src/components/openapi/async-snippets.ts +65 -9
- package/src/components/openapi/async.ts +123 -29
- package/src/components/openapi/code-samples.ts +97 -0
- package/src/components/openapi/graphql-helpers.ts +35 -12
- package/src/components/openapi/helpers.ts +36 -2
- package/src/components/openapi/message-model.ts +54 -11
- package/src/components/openapi/message.ts +14 -4
- package/src/components/openapi/operation-model.ts +16 -7
- package/src/components/openapi/playground-client.ts +24 -5
- package/src/components/openapi/playground-schema.ts +1 -1
- package/src/components/openapi/schema-tree.ts +44 -14
- package/src/components/openapi/security.ts +16 -3
- package/src/components/openapi/snippets.ts +873 -10
- package/src/components/openapi/validate-json.ts +4 -3
- package/src/components/props.ts +6 -0
- package/src/consent/client.ts +95 -0
- package/src/consent/clients.ts +19 -0
- package/src/consent/ethyca.ts +71 -0
- package/src/consent/head.ts +24 -0
- package/src/consent/index.ts +37 -0
- package/src/consent/init.ts +29 -0
- package/src/consent/native.ts +36 -0
- package/src/consent/osano.ts +59 -0
- package/src/consent/schema.ts +30 -0
- package/src/core/builtin-tags.ts +5 -0
- package/src/core/changelog-index.ts +31 -1
- package/src/core/client-address.ts +19 -0
- package/src/core/component-diagnostics.ts +53 -0
- package/src/core/component-overrides.ts +32 -0
- package/src/core/config-input.ts +225 -23
- package/src/core/data.ts +46 -4
- package/src/core/diagnostics.ts +8 -2
- package/src/core/footer.ts +26 -0
- package/src/core/graph.ts +39 -36
- package/src/core/i18n-ui.ts +84 -2
- package/src/core/includes.ts +21 -4
- package/src/core/links.ts +63 -19
- package/src/core/localizable.ts +26 -0
- package/src/core/navigation.ts +43 -12
- package/src/core/page-modes.ts +56 -0
- package/src/core/probe.ts +26 -6
- package/src/core/project-graph.ts +163 -16
- package/src/core/redirect-patterns.ts +520 -0
- package/src/core/related.ts +131 -0
- package/src/core/schema.ts +339 -66
- package/src/core/sources/cache.ts +6 -0
- package/src/core/sources/contentful-rich-text.ts +29 -6
- package/src/core/sources/contentful.ts +135 -25
- package/src/core/sources/filesystem.ts +26 -4
- package/src/core/sources/github-releases.ts +12 -1
- package/src/core/sources/lexical.ts +15 -10
- package/src/core/sources/mdx-remote.ts +62 -10
- package/src/core/sources/normalize.ts +228 -41
- package/src/core/sources/notion.ts +7 -1
- package/src/core/sources/obsidian.ts +16 -3
- package/src/core/sources/payload.ts +38 -15
- package/src/core/sources/read.ts +8 -3
- package/src/core/sources/remote.ts +72 -15
- package/src/core/sources/resolve.ts +11 -1
- package/src/core/sources/sanity.ts +43 -25
- package/src/core/sources/strapi.ts +2 -2
- package/src/core/types.ts +19 -1
- package/src/core/ui-packs/ar.ts +44 -0
- package/src/core/ui-packs/bg.ts +46 -0
- package/src/core/ui-packs/bn.ts +44 -0
- package/src/core/ui-packs/ca.ts +47 -0
- package/src/core/ui-packs/cs.ts +45 -0
- package/src/core/ui-packs/da.ts +46 -0
- package/src/core/ui-packs/de.ts +47 -0
- package/src/core/ui-packs/el.ts +46 -0
- package/src/core/ui-packs/es.ts +47 -0
- package/src/core/ui-packs/fa.ts +46 -0
- package/src/core/ui-packs/fi.ts +46 -0
- package/src/core/ui-packs/fr.ts +47 -0
- package/src/core/ui-packs/he.ts +44 -0
- package/src/core/ui-packs/hi.ts +45 -0
- package/src/core/ui-packs/hr.ts +46 -0
- package/src/core/ui-packs/hu.ts +45 -0
- package/src/core/ui-packs/id.ts +46 -0
- package/src/core/ui-packs/it.ts +46 -0
- package/src/core/ui-packs/ja.ts +46 -0
- package/src/core/ui-packs/ko.ts +45 -0
- package/src/core/ui-packs/nl.ts +47 -0
- package/src/core/ui-packs/no.ts +45 -0
- package/src/core/ui-packs/pl.ts +46 -0
- package/src/core/ui-packs/pt-br.ts +47 -0
- package/src/core/ui-packs/pt.ts +46 -0
- package/src/core/ui-packs/ro.ts +46 -0
- package/src/core/ui-packs/ru.ts +46 -0
- package/src/core/ui-packs/sk.ts +45 -0
- package/src/core/ui-packs/sr.ts +46 -0
- package/src/core/ui-packs/sv.ts +45 -0
- package/src/core/ui-packs/th.ts +44 -0
- package/src/core/ui-packs/tr.ts +46 -0
- package/src/core/ui-packs/uk.ts +46 -0
- package/src/core/ui-packs/vi.ts +46 -0
- package/src/core/ui-packs/zh-tw.ts +44 -0
- package/src/core/ui-packs/zh.ts +44 -0
- package/src/core/variables.ts +103 -0
- package/src/deploy/adapter-output.ts +7 -2
- package/src/deploy/adapters/cloudflare.ts +7 -5
- package/src/deploy/adapters/node.ts +47 -9
- package/src/deploy/adapters/types.ts +7 -3
- package/src/deploy/artifacts.ts +79 -9
- package/src/deploy/cloudflare-negotiation.ts +35 -5
- package/src/deploy/headers.ts +2 -1
- package/src/deploy/node-headers.ts +63 -9
- package/src/deploy/platforms/cloudflare.ts +45 -9
- package/src/deploy/platforms/netlify.ts +35 -2
- package/src/deploy/platforms/node.ts +12 -2
- package/src/deploy/platforms/types.ts +21 -7
- package/src/deploy/platforms/vercel.ts +149 -17
- package/src/deploy/redirects.ts +151 -24
- package/src/deploy/rss.ts +3 -1
- package/src/deploy/sitemap.ts +39 -5
- package/src/deploy/vercel-negotiation.ts +326 -102
- package/src/eval/agents.ts +18 -1
- package/src/eval/prompts.ts +6 -3
- package/src/eval/report.ts +34 -13
- package/src/eval/run.ts +5 -0
- package/src/markdown/api-rail.ts +259 -0
- package/src/markdown/code-title.ts +35 -9
- package/src/markdown/features.ts +3 -2
- package/src/markdown/fence-meta.ts +2 -0
- package/src/markdown/include.ts +11 -1
- package/src/markdown/index.ts +28 -5
- package/src/markdown/math.ts +4 -4
- package/src/markdown/relative-links.ts +3 -2
- package/src/markdown/variables.ts +56 -0
- package/src/markdown/views.ts +82 -0
- package/src/narration/build.ts +412 -0
- package/src/narration/player.ts +249 -0
- package/src/narration/provider.ts +29 -0
- package/src/narration/script.ts +592 -0
- package/src/og/card.ts +7 -6
- package/src/openapi/checks.ts +273 -0
- package/src/openapi/graphql-build.ts +21 -0
- package/src/openapi/graphql.ts +11 -0
- package/src/openapi/model.ts +72 -27
- package/src/openapi/overlay.ts +256 -0
- package/src/openapi/parse.ts +182 -4
- package/src/openapi/references.ts +64 -8
- package/src/openapi/render-mdx.ts +5 -1
- package/src/openapi/scalar.ts +40 -6
- package/src/openapi/source.ts +40 -15
- package/src/ratelimit/cloudflare.ts +54 -0
- package/src/ratelimit/index.ts +30 -0
- package/src/ratelimit/memory.ts +45 -0
- package/src/ratelimit/runtime.ts +247 -0
- package/src/ratelimit/schema.ts +28 -0
- package/src/ratelimit/upstash.ts +60 -0
- package/src/ratelimit/wrangler.ts +70 -0
- package/src/reference/asyncapi.ts +2 -2
- package/src/reference/graphql.ts +20 -3
- package/src/reference/index.ts +1 -0
- package/src/reference/openapi.ts +21 -7
- package/src/reference/options.ts +80 -8
- package/src/reference/scalar.ts +33 -8
- package/src/registry/eject.ts +23 -5
- package/src/registry/registry.ts +7 -0
- package/src/search/adapters/locale-scope.ts +30 -0
- package/src/search/adapters/typesense.ts +14 -4
- package/src/search/build.ts +21 -3
- package/src/search/documents.ts +26 -2
- package/src/search/orama-index.ts +191 -34
- package/src/search/pagefind-ranking.ts +39 -0
- package/src/search/source-pages.ts +29 -0
- package/src/search/sync/algolia.ts +111 -10
- package/src/search/sync/index.ts +52 -13
- package/src/search/sync/orama-cloud.ts +3 -0
- package/src/search/sync/typesense.ts +76 -26
- package/src/seo/metatags.ts +67 -0
- package/src/sources/contentful.ts +12 -0
- package/src/sources/github-releases.ts +7 -1
- package/src/sources/shared.ts +3 -0
- package/src/theme/chrome-icons.ts +1 -0
- package/src/theme/color.ts +500 -0
- package/src/theme/entry.ts +85 -6
- package/src/theme/palette.ts +121 -45
- package/src/translate/prompts.ts +1 -0
- package/src/translate/validate.ts +31 -3
- package/src/upgrade/upgrade.ts +4 -171
- package/dist/cli/chunk-1d7ve1dm.js.map +0 -12
- package/dist/cli/chunk-2hsdwb9n.js.map +0 -10
- package/dist/cli/chunk-35d4wj9f.js.map +0 -15
- package/dist/cli/chunk-364znk6q.js +0 -75
- package/dist/cli/chunk-364znk6q.js.map +0 -11
- package/dist/cli/chunk-5m5nmvyq.js.map +0 -13
- package/dist/cli/chunk-8ktnccpt.js.map +0 -10
- package/dist/cli/chunk-9t7a85s3.js.map +0 -10
- package/dist/cli/chunk-b07cmahc.js +0 -145
- package/dist/cli/chunk-b07cmahc.js.map +0 -11
- package/dist/cli/chunk-c8chx29p.js.map +0 -36
- package/dist/cli/chunk-e04dxsz1.js +0 -39
- package/dist/cli/chunk-e04dxsz1.js.map +0 -10
- package/dist/cli/chunk-j85vccga.js.map +0 -36
- package/dist/cli/chunk-p3v96n38.js.map +0 -10
- package/dist/cli/chunk-pehfxfta.js.map +0 -10
- package/dist/cli/chunk-pv29h0wf.js.map +0 -190
- package/dist/cli/chunk-q58y5e6a.js.map +0 -10
- package/dist/cli/chunk-vg9r4eb9.js.map +0 -14
- package/dist/cli/chunk-wrr3j9w9.js.map +0 -35
- package/dist/cli/chunk-yfyb25rh.js.map +0 -14
- package/docs/content/sources.mdx +0 -334
- /package/dist/cli/{chunk-tkacnehg.js.map → chunk-3v3ffbrj.js.map} +0 -0
- /package/dist/cli/{chunk-r9rcc4w7.js.map → chunk-8y2kmjv8.js.map} +0 -0
- /package/dist/cli/{chunk-2eytanqx.js.map → chunk-c887fj0s.js.map} +0 -0
- /package/dist/cli/{chunk-akbpwfxc.js.map → chunk-dzwehpy7.js.map} +0 -0
- /package/dist/cli/{chunk-2z928egk.js.map → chunk-ft9vfys6.js.map} +0 -0
- /package/dist/cli/{chunk-a58773jm.js.map → chunk-kma9f6sf.js.map} +0 -0
- /package/dist/cli/{chunk-crgn1q09.js.map → chunk-sds923fa.js.map} +0 -0
- /package/dist/cli/{chunk-acanzt5p.js.map → chunk-tdqhyxfz.js.map} +0 -0
|
@@ -2,6 +2,7 @@ import { normalizeBasePath, stripBasePath } from "../../core/base-path.ts";
|
|
|
2
2
|
import type { Diagnostic } from "../../core/types.ts";
|
|
3
3
|
import { finding } from "../catalog.ts";
|
|
4
4
|
import { pageSite } from "../locate.ts";
|
|
5
|
+
import { redirectAt } from "../redirects.ts";
|
|
5
6
|
import type { AuditContext, CheckModule } from "../types.ts";
|
|
6
7
|
import { decodePath, normalizePath, siteOrigin } from "../url.ts";
|
|
7
8
|
|
|
@@ -100,9 +101,7 @@ const checkListedUrl = (
|
|
|
100
101
|
);
|
|
101
102
|
const page = context.byUrl.get(path);
|
|
102
103
|
if (!page) {
|
|
103
|
-
const redirect = context.redirects
|
|
104
|
-
(entry) => normalizePath(entry.from) === path
|
|
105
|
-
);
|
|
104
|
+
const redirect = redirectAt(context.redirects, path);
|
|
106
105
|
return [
|
|
107
106
|
finding(
|
|
108
107
|
"BLUME_AUDIT_SITEMAP_BAD_URL",
|
package/src/audit/redirects.ts
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import {
|
|
2
|
+
isPatternPath,
|
|
3
|
+
patternDestination,
|
|
4
|
+
} from "../core/redirect-patterns.ts";
|
|
1
5
|
import type { RedirectResolution } from "./types.ts";
|
|
2
6
|
import { normalizePath } from "./url.ts";
|
|
3
7
|
|
|
@@ -7,18 +11,6 @@ interface ConfiguredRedirect {
|
|
|
7
11
|
status: number;
|
|
8
12
|
}
|
|
9
13
|
|
|
10
|
-
/**
|
|
11
|
-
* Follow every configured redirect through to its destination, classifying what
|
|
12
|
-
* it lands on.
|
|
13
|
-
*
|
|
14
|
-
* - `loop` — the chain revisits a hop it has already been to. Never resolves.
|
|
15
|
-
* - `broken` — the chain ends somewhere the build does not serve.
|
|
16
|
-
* - `chain` — it resolves, but through at least one intermediate redirect.
|
|
17
|
-
* - `ok` — one hop, straight to a real page.
|
|
18
|
-
*
|
|
19
|
-
* An external destination (`https://…`) is always `ok`: it's outside the site,
|
|
20
|
-
* so there's no local page to check it against.
|
|
21
|
-
*/
|
|
22
14
|
/**
|
|
23
15
|
* A destination's page path: the part before any query string or fragment. A
|
|
24
16
|
* redirect to `/guide#setup` or `/search?q=x` lands on the `/guide` / `/search`
|
|
@@ -30,6 +22,22 @@ const pathOnly = (value: string): string => {
|
|
|
30
22
|
return cut === -1 ? value : value.slice(0, cut);
|
|
31
23
|
};
|
|
32
24
|
|
|
25
|
+
/**
|
|
26
|
+
* Follow every configured redirect through to its destination, classifying what
|
|
27
|
+
* it lands on.
|
|
28
|
+
*
|
|
29
|
+
* - `loop` — the chain revisits a hop it has already been to. Never resolves.
|
|
30
|
+
* - `broken` — the chain ends somewhere the build does not serve.
|
|
31
|
+
* - `chain` — it resolves, but through at least one intermediate redirect.
|
|
32
|
+
* - `ok` — one hop, straight to a real page.
|
|
33
|
+
* - `pattern` — a pattern (`/beta/:slug*`), which covers paths rather than
|
|
34
|
+
* naming one, so there is no single chain to walk; {@link redirectAt}
|
|
35
|
+
* resolves a path it covers.
|
|
36
|
+
*
|
|
37
|
+
* An external destination (`https://…`) is always `ok`: it's outside the site,
|
|
38
|
+
* so there's no local page to check it against. A hop onto a path a pattern
|
|
39
|
+
* covers continues through that pattern.
|
|
40
|
+
*/
|
|
33
41
|
export const resolveRedirects = (
|
|
34
42
|
redirects: readonly ConfiguredRedirect[],
|
|
35
43
|
/** Whether the build serves a normalized path — see `isServed`. */
|
|
@@ -39,9 +47,14 @@ export const resolveRedirects = (
|
|
|
39
47
|
for (const redirect of redirects) {
|
|
40
48
|
byFrom.set(normalizePath(redirect.from), redirect);
|
|
41
49
|
}
|
|
50
|
+
const hopFrom = (path: string): string | undefined =>
|
|
51
|
+
byFrom.get(path)?.to ?? patternDestination(redirects, path);
|
|
42
52
|
|
|
43
53
|
return redirects.map((redirect) => {
|
|
44
54
|
const from = normalizePath(redirect.from);
|
|
55
|
+
if (isPatternPath(redirect.from)) {
|
|
56
|
+
return { ...redirect, chain: [from], outcome: "pattern" as const };
|
|
57
|
+
}
|
|
45
58
|
const chain: string[] = [from];
|
|
46
59
|
const seen = new Set<string>([from]);
|
|
47
60
|
let current = redirect.to;
|
|
@@ -63,11 +76,11 @@ export const resolveRedirects = (
|
|
|
63
76
|
}
|
|
64
77
|
chain.push(next);
|
|
65
78
|
seen.add(next);
|
|
66
|
-
const hop =
|
|
67
|
-
if (
|
|
79
|
+
const hop = hopFrom(next);
|
|
80
|
+
if (hop === undefined) {
|
|
68
81
|
break;
|
|
69
82
|
}
|
|
70
|
-
current = hop
|
|
83
|
+
current = hop;
|
|
71
84
|
}
|
|
72
85
|
|
|
73
86
|
const destination = chain.at(-1) ?? from;
|
|
@@ -84,3 +97,21 @@ export const resolveRedirects = (
|
|
|
84
97
|
};
|
|
85
98
|
});
|
|
86
99
|
};
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* The configured redirect a path takes, for the checks that report a link,
|
|
103
|
+
* canonical, sitemap entry, or `hreflang` pointing through one: the exact
|
|
104
|
+
* redirect from that path, else the pattern covering it, its `to` filled in
|
|
105
|
+
* for this path.
|
|
106
|
+
*/
|
|
107
|
+
export const redirectAt = (
|
|
108
|
+
redirects: readonly RedirectResolution[],
|
|
109
|
+
path: string
|
|
110
|
+
): { to: string } | undefined => {
|
|
111
|
+
const exact = redirects.find((entry) => normalizePath(entry.from) === path);
|
|
112
|
+
if (exact) {
|
|
113
|
+
return exact;
|
|
114
|
+
}
|
|
115
|
+
const to = patternDestination(redirects, path);
|
|
116
|
+
return to === undefined ? undefined : { to };
|
|
117
|
+
};
|
package/src/audit/run.ts
CHANGED
|
@@ -12,6 +12,7 @@ import { CHECKS } from "./catalog.ts";
|
|
|
12
12
|
import type { CheckId } from "./catalog.ts";
|
|
13
13
|
import { assetChecks } from "./checks/assets.ts";
|
|
14
14
|
import { contentChecks } from "./checks/content.ts";
|
|
15
|
+
import { contrastChecks } from "./checks/contrast.ts";
|
|
15
16
|
import { dnsAidChecks } from "./checks/dns-aid.ts";
|
|
16
17
|
import { duplicateChecks } from "./checks/duplicates.ts";
|
|
17
18
|
import { i18nChecks } from "./checks/i18n.ts";
|
|
@@ -51,6 +52,7 @@ const MODULES: CheckModule[] = [
|
|
|
51
52
|
ogImageChecks,
|
|
52
53
|
i18nChecks,
|
|
53
54
|
assetChecks,
|
|
55
|
+
contrastChecks,
|
|
54
56
|
sitemapChecks,
|
|
55
57
|
robotsChecks,
|
|
56
58
|
llmsChecks,
|
|
@@ -67,6 +69,8 @@ export interface AuditOptions {
|
|
|
67
69
|
origin?: string;
|
|
68
70
|
/** Probe outbound links (`--external`). */
|
|
69
71
|
external?: boolean;
|
|
72
|
+
/** Outbound URLs not to probe (`--ignore`). */
|
|
73
|
+
ignore?: (url: string) => boolean;
|
|
70
74
|
/** Only report these check ids or categories. */
|
|
71
75
|
only?: string[];
|
|
72
76
|
/** Suppress these check ids or categories. */
|
|
@@ -157,6 +161,7 @@ export const runAudit = async (options: AuditOptions): Promise<AuditResult> => {
|
|
|
157
161
|
siteOrigin(project.config.deployment.options.site),
|
|
158
162
|
normalizeBasePath(project.config.deployment.options.base)
|
|
159
163
|
),
|
|
164
|
+
ignore: options.ignore ?? (() => false),
|
|
160
165
|
llms: crawl.llms,
|
|
161
166
|
origin,
|
|
162
167
|
pages: crawl.pages,
|
package/src/audit/types.ts
CHANGED
|
@@ -13,6 +13,7 @@ import type {
|
|
|
13
13
|
export type AuditTier = "static" | "network" | "external";
|
|
14
14
|
|
|
15
15
|
export type AuditCategory =
|
|
16
|
+
| "accessibility"
|
|
16
17
|
| "content"
|
|
17
18
|
| "duplicates"
|
|
18
19
|
| "indexability"
|
|
@@ -110,7 +111,7 @@ export interface RedirectResolution {
|
|
|
110
111
|
status: number;
|
|
111
112
|
/** Every hop from `from` to the final target, inclusive. */
|
|
112
113
|
chain: string[];
|
|
113
|
-
outcome: "ok" | "loop" | "broken" | "chain";
|
|
114
|
+
outcome: "ok" | "loop" | "broken" | "chain" | "pattern";
|
|
114
115
|
}
|
|
115
116
|
|
|
116
117
|
/** One urlset file behind a sitemap, for the per-file size limits. */
|
|
@@ -248,6 +249,8 @@ export interface AuditContext {
|
|
|
248
249
|
robots: RobotsDoc | null;
|
|
249
250
|
llms: LlmsDoc | null;
|
|
250
251
|
thresholds: AuditThresholds;
|
|
252
|
+
/** Outbound URLs the external tier doesn't probe (`--ignore`). */
|
|
253
|
+
ignore: (url: string) => boolean;
|
|
251
254
|
}
|
|
252
255
|
|
|
253
256
|
/** One category's checks. Modules, not per-check closures — see catalog.ts. */
|
package/src/audit/url.ts
CHANGED
|
@@ -45,10 +45,11 @@ export type ResolvedHref =
|
|
|
45
45
|
| { kind: "self-origin"; path: string; hash: string }
|
|
46
46
|
/**
|
|
47
47
|
* A path on this origin that lacks `deployment.base`. The host serves the
|
|
48
|
-
* whole build under the base, so nothing answers here — `path`
|
|
49
|
-
* as requested, base missing.
|
|
48
|
+
* whole build under the base, so nothing in the build answers here — `path`
|
|
49
|
+
* is the path as requested, base missing. `absolute` when the href spelled
|
|
50
|
+
* out the origin: a full URL can name another app on the same host.
|
|
50
51
|
*/
|
|
51
|
-
| { kind: "outside-base"; path: string }
|
|
52
|
+
| { kind: "outside-base"; path: string; absolute: boolean }
|
|
52
53
|
/** An absolute URL on another origin. */
|
|
53
54
|
| { kind: "external"; url: string }
|
|
54
55
|
/** In-page anchor, `mailto:`, `tel:`, `javascript:`, data URI — not a page link. */
|
|
@@ -139,7 +140,11 @@ export const resolveHref = (
|
|
|
139
140
|
if (origin && parsed.origin === origin) {
|
|
140
141
|
const served = decodePath(parsed.pathname);
|
|
141
142
|
if (!underBase(deployBase, served)) {
|
|
142
|
-
return {
|
|
143
|
+
return {
|
|
144
|
+
absolute: true,
|
|
145
|
+
kind: "outside-base",
|
|
146
|
+
path: normalizePath(served),
|
|
147
|
+
};
|
|
143
148
|
}
|
|
144
149
|
return {
|
|
145
150
|
hash: parsed.hash.slice(1),
|
|
@@ -171,7 +176,11 @@ export const resolveHref = (
|
|
|
171
176
|
// (`./auth`) was resolved against the base-less page URL above, so it
|
|
172
177
|
// inherits the page's base by construction.
|
|
173
178
|
if (target.startsWith("/") && !underBase(deployBase, served)) {
|
|
174
|
-
return {
|
|
179
|
+
return {
|
|
180
|
+
absolute: false,
|
|
181
|
+
kind: "outside-base",
|
|
182
|
+
path: normalizePath(served),
|
|
183
|
+
};
|
|
175
184
|
}
|
|
176
185
|
return {
|
|
177
186
|
hash: resolved.hash.slice(1),
|
package/src/blume-modules.d.ts
CHANGED
|
@@ -12,7 +12,18 @@ declare module "blume:search-client" {
|
|
|
12
12
|
// biome-ignore lint/style/useImportType: ambient module must stay a global script
|
|
13
13
|
// oxlint-disable-next-line typescript/consistent-type-imports
|
|
14
14
|
type Fn = import("./components/layout/search/types.ts").SearchFn;
|
|
15
|
-
|
|
15
|
+
type Options =
|
|
16
|
+
// biome-ignore lint/style/useImportType: ambient module must stay a global script
|
|
17
|
+
// oxlint-disable-next-line typescript/consistent-type-imports
|
|
18
|
+
import("./components/layout/search/types.ts").SearchClientOptions;
|
|
19
|
+
export const createSearch: (options?: Options) => Fn | Promise<Fn>;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
declare module "blume:consent-client" {
|
|
23
|
+
/** Start the configured consent adapter's browser module (see `consentClientTemplate`); a no-op for one without. */
|
|
24
|
+
// oxlint-disable-next-line typescript/consistent-type-imports
|
|
25
|
+
type Consent = import("./components/layout/consent/types.ts").BlumeConsent;
|
|
26
|
+
export const startConsentClient: (consent: Consent) => void;
|
|
16
27
|
}
|
|
17
28
|
|
|
18
29
|
declare module "blume:ask" {
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The browser half of the bot check: a fresh token for each question the
|
|
3
|
+
* assistant sends (both providers' tokens are single-use). The provider's
|
|
4
|
+
* script loads with the first question rather than with the page, and one
|
|
5
|
+
* widget serves every question after it: an invisible hCaptcha, or a
|
|
6
|
+
* Turnstile that only shows itself when it needs the reader to interact,
|
|
7
|
+
* in a small container at the foot of the viewport.
|
|
8
|
+
*
|
|
9
|
+
* The client router swaps `<body>` on every navigation, taking the container
|
|
10
|
+
* and its widget with it, so a question after a swap mounts a new one.
|
|
11
|
+
*/
|
|
12
|
+
import type { CaptchaSettings } from "./schema.ts";
|
|
13
|
+
|
|
14
|
+
/** Each provider's script, rendering only when asked to. */
|
|
15
|
+
export const CAPTCHA_SCRIPTS = {
|
|
16
|
+
hcaptcha: "https://js.hcaptcha.com/1/api.js?render=explicit",
|
|
17
|
+
turnstile:
|
|
18
|
+
"https://challenges.cloudflare.com/turnstile/v0/api.js?render=explicit",
|
|
19
|
+
} as const;
|
|
20
|
+
|
|
21
|
+
/** What Blume asks a Turnstile widget for. */
|
|
22
|
+
interface TurnstileRenderOptions {
|
|
23
|
+
appearance: "interaction-only";
|
|
24
|
+
callback: (token: string) => void;
|
|
25
|
+
"error-callback": () => void;
|
|
26
|
+
execution: "execute";
|
|
27
|
+
sitekey: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** The slice of `window.turnstile` the check uses. */
|
|
31
|
+
export interface TurnstileApi {
|
|
32
|
+
execute: (widget: string) => void;
|
|
33
|
+
render: (container: HTMLElement, options: TurnstileRenderOptions) => string;
|
|
34
|
+
reset: (widget: string) => void;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** What Blume asks an hCaptcha widget for. */
|
|
38
|
+
interface HcaptchaRenderOptions {
|
|
39
|
+
sitekey: string;
|
|
40
|
+
size: "invisible";
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** The slice of `window.hcaptcha` the check uses. */
|
|
44
|
+
export interface HcaptchaApi {
|
|
45
|
+
execute: (
|
|
46
|
+
widget: string,
|
|
47
|
+
options: { async: true }
|
|
48
|
+
) => Promise<{ response: string }>;
|
|
49
|
+
render: (container: HTMLElement, options: HcaptchaRenderOptions) => string;
|
|
50
|
+
reset: (widget: string) => void;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** The window, with the providers' globals once their scripts ran. */
|
|
54
|
+
type CaptchaWindow = Window & {
|
|
55
|
+
hcaptcha?: HcaptchaApi;
|
|
56
|
+
turnstile?: TurnstileApi;
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
/** A Turnstile token on its way. */
|
|
60
|
+
interface PendingToken {
|
|
61
|
+
reject: (error: Error) => void;
|
|
62
|
+
resolve: (token: string) => void;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** Get a token for the next question. */
|
|
66
|
+
export type CaptchaTokenGetter = (settings: CaptchaSettings) => Promise<string>;
|
|
67
|
+
|
|
68
|
+
/** A token getter with its own script, container, and widget. */
|
|
69
|
+
export const createCaptchaClient = (): CaptchaTokenGetter => {
|
|
70
|
+
let loaded: Promise<void> | undefined;
|
|
71
|
+
let host: HTMLElement | undefined;
|
|
72
|
+
let widget: string | undefined;
|
|
73
|
+
let pending: PendingToken | undefined;
|
|
74
|
+
|
|
75
|
+
const load = (kind: CaptchaSettings["kind"]): Promise<void> => {
|
|
76
|
+
// oxlint-disable-next-line promise/avoid-new -- adapt the script tag's load events
|
|
77
|
+
loaded ??= new Promise<void>((resolve, reject) => {
|
|
78
|
+
const tag = document.createElement("script");
|
|
79
|
+
tag.async = true;
|
|
80
|
+
tag.src = CAPTCHA_SCRIPTS[kind];
|
|
81
|
+
tag.addEventListener("load", () => resolve());
|
|
82
|
+
tag.addEventListener("error", () => {
|
|
83
|
+
// Let the next question try again.
|
|
84
|
+
loaded = undefined;
|
|
85
|
+
reject(new Error(`The ${kind} script didn't load.`));
|
|
86
|
+
});
|
|
87
|
+
document.head.append(tag);
|
|
88
|
+
});
|
|
89
|
+
return loaded;
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
// The widget's container, mounted again when a swap took the last one.
|
|
93
|
+
const mount = (): HTMLElement => {
|
|
94
|
+
if (host?.isConnected) {
|
|
95
|
+
return host;
|
|
96
|
+
}
|
|
97
|
+
host = document.createElement("div");
|
|
98
|
+
host.className = "fixed bottom-4 left-1/2 z-[60] -translate-x-1/2";
|
|
99
|
+
host.dataset.blumeCaptcha = "";
|
|
100
|
+
document.body.append(host);
|
|
101
|
+
widget = undefined;
|
|
102
|
+
return host;
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
const turnstileToken = (api: TurnstileApi, siteKey: string) =>
|
|
106
|
+
// oxlint-disable-next-line promise/avoid-new -- adapt Turnstile's token callbacks
|
|
107
|
+
new Promise<string>((resolve, reject) => {
|
|
108
|
+
pending = { reject, resolve };
|
|
109
|
+
const target = mount();
|
|
110
|
+
if (widget === undefined) {
|
|
111
|
+
widget = api.render(target, {
|
|
112
|
+
appearance: "interaction-only",
|
|
113
|
+
callback: (token) => pending?.resolve(token),
|
|
114
|
+
"error-callback": () =>
|
|
115
|
+
pending?.reject(new Error("The Turnstile check failed.")),
|
|
116
|
+
execution: "execute",
|
|
117
|
+
sitekey: siteKey,
|
|
118
|
+
});
|
|
119
|
+
} else {
|
|
120
|
+
api.reset(widget);
|
|
121
|
+
}
|
|
122
|
+
api.execute(widget);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
const hcaptchaToken = async (api: HcaptchaApi, siteKey: string) => {
|
|
126
|
+
const target = mount();
|
|
127
|
+
if (widget === undefined) {
|
|
128
|
+
widget = api.render(target, { sitekey: siteKey, size: "invisible" });
|
|
129
|
+
} else {
|
|
130
|
+
api.reset(widget);
|
|
131
|
+
}
|
|
132
|
+
const { response } = await api.execute(widget, { async: true });
|
|
133
|
+
return response;
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
return async (settings) => {
|
|
137
|
+
await load(settings.kind);
|
|
138
|
+
// SAFETY: CaptchaWindow only adds the providers' optional globals, each
|
|
139
|
+
// checked before use.
|
|
140
|
+
const { hcaptcha, turnstile } = window as CaptchaWindow;
|
|
141
|
+
if (settings.kind === "hcaptcha" && hcaptcha) {
|
|
142
|
+
return hcaptchaToken(hcaptcha, settings.siteKey);
|
|
143
|
+
}
|
|
144
|
+
if (settings.kind === "turnstile" && turnstile) {
|
|
145
|
+
return turnstileToken(turnstile, settings.siteKey);
|
|
146
|
+
}
|
|
147
|
+
throw new Error(`The ${settings.kind} script loaded without its API.`);
|
|
148
|
+
};
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
/** The page's token getter: one script and one widget per page load. */
|
|
152
|
+
export const captchaToken = createCaptchaClient();
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
import type { AdapterDescriptor } from "../core/adapter.ts";
|
|
4
|
+
import { adapterDescriptorSchema } from "../core/adapter.ts";
|
|
5
|
+
|
|
6
|
+
/** Options for {@link hcaptcha}. */
|
|
7
|
+
export interface HcaptchaOptions {
|
|
8
|
+
/** The site key, from the hCaptcha dashboard. */
|
|
9
|
+
siteKey: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export const hcaptchaOptionsSchema = z.strictObject({
|
|
13
|
+
siteKey: z.string().min(1),
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
export type HcaptchaAdapter = AdapterDescriptor<"hcaptcha", HcaptchaOptions>;
|
|
17
|
+
|
|
18
|
+
export const hcaptchaAdapterSchema = adapterDescriptorSchema(
|
|
19
|
+
"hcaptcha",
|
|
20
|
+
hcaptchaOptionsSchema
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
/** Where the assistant's route checks an hCaptcha token. */
|
|
24
|
+
export const HCAPTCHA_VERIFY_URL = "https://api.hcaptcha.com/siteverify";
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* hCaptcha, as an invisible check: most readers never see a challenge. The
|
|
28
|
+
* site key is public and goes to the browser; the secret the route checks
|
|
29
|
+
* tokens with comes from `HCAPTCHA_SECRET_KEY`.
|
|
30
|
+
*/
|
|
31
|
+
export const hcaptcha = (options: HcaptchaOptions): HcaptchaAdapter => ({
|
|
32
|
+
kind: "hcaptcha",
|
|
33
|
+
options,
|
|
34
|
+
requiredSecrets: ["HCAPTCHA_SECRET_KEY"],
|
|
35
|
+
runtimeDeps: [],
|
|
36
|
+
});
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bot checks for the assistant (`ai.assistant.captcha` in `blume.config.ts`).
|
|
3
|
+
* The assistant gets a token from the check before each question and the
|
|
4
|
+
* generated route verifies it before calling the model:
|
|
5
|
+
*
|
|
6
|
+
* ```ts
|
|
7
|
+
* import { defineConfig } from "blume";
|
|
8
|
+
* import { turnstile } from "blume/captcha";
|
|
9
|
+
*
|
|
10
|
+
* export default defineConfig({
|
|
11
|
+
* ai: {
|
|
12
|
+
* assistant: {
|
|
13
|
+
* enabled: true,
|
|
14
|
+
* captcha: turnstile({ siteKey: "0x4AAAAAAA…" }),
|
|
15
|
+
* },
|
|
16
|
+
* },
|
|
17
|
+
* });
|
|
18
|
+
* ```
|
|
19
|
+
*
|
|
20
|
+
* Each factory returns a plain descriptor (see `core/adapter.ts`); the browser
|
|
21
|
+
* half is `client.ts`, the server half `verify.ts`.
|
|
22
|
+
*/
|
|
23
|
+
export type { AdapterDescriptor, JsonValue } from "../core/adapter.ts";
|
|
24
|
+
export { hcaptcha } from "./hcaptcha.ts";
|
|
25
|
+
export type { HcaptchaAdapter, HcaptchaOptions } from "./hcaptcha.ts";
|
|
26
|
+
export type { CaptchaAdapter, CaptchaSettings } from "./schema.ts";
|
|
27
|
+
export { turnstile } from "./turnstile.ts";
|
|
28
|
+
export type { TurnstileAdapter, TurnstileOptions } from "./turnstile.ts";
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
import { hcaptchaAdapterSchema } from "./hcaptcha.ts";
|
|
4
|
+
import { turnstileAdapterSchema } from "./turnstile.ts";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* The configured bot check, as its factory returned it. Anything that isn't
|
|
8
|
+
* one fails with the adapter hint; an adapter's own option errors keep their
|
|
9
|
+
* messages.
|
|
10
|
+
*/
|
|
11
|
+
export const captchaAdapterSchema = z.discriminatedUnion(
|
|
12
|
+
"kind",
|
|
13
|
+
[hcaptchaAdapterSchema, turnstileAdapterSchema],
|
|
14
|
+
{
|
|
15
|
+
error:
|
|
16
|
+
'captcha takes one adapter — e.g. `captcha: turnstile({ siteKey })` or `captcha: hcaptcha({ siteKey })`, imported from "blume/captcha".',
|
|
17
|
+
}
|
|
18
|
+
);
|
|
19
|
+
|
|
20
|
+
export type CaptchaAdapter = z.output<typeof captchaAdapterSchema>;
|
|
21
|
+
|
|
22
|
+
/** What the browser needs to run the check: never the secret. */
|
|
23
|
+
export interface CaptchaSettings {
|
|
24
|
+
kind: CaptchaAdapter["kind"];
|
|
25
|
+
siteKey: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** The public half of an adapter, for the data snapshot. */
|
|
29
|
+
export const captchaSettings = (adapter: CaptchaAdapter): CaptchaSettings => ({
|
|
30
|
+
kind: adapter.kind,
|
|
31
|
+
siteKey: adapter.options.siteKey,
|
|
32
|
+
});
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
import type { AdapterDescriptor } from "../core/adapter.ts";
|
|
4
|
+
import { adapterDescriptorSchema } from "../core/adapter.ts";
|
|
5
|
+
|
|
6
|
+
/** Options for {@link turnstile}. */
|
|
7
|
+
export interface TurnstileOptions {
|
|
8
|
+
/** The widget's site key, from the Cloudflare dashboard. */
|
|
9
|
+
siteKey: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export const turnstileOptionsSchema = z.strictObject({
|
|
13
|
+
siteKey: z.string().min(1),
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
export type TurnstileAdapter = AdapterDescriptor<"turnstile", TurnstileOptions>;
|
|
17
|
+
|
|
18
|
+
export const turnstileAdapterSchema = adapterDescriptorSchema(
|
|
19
|
+
"turnstile",
|
|
20
|
+
turnstileOptionsSchema
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
/** Where the assistant's route checks a Turnstile token. */
|
|
24
|
+
export const TURNSTILE_VERIFY_URL =
|
|
25
|
+
"https://challenges.cloudflare.com/turnstile/v0/siteverify";
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Cloudflare Turnstile. The site key is public and goes to the browser; the
|
|
29
|
+
* secret key the route checks tokens with comes from `TURNSTILE_SECRET_KEY`.
|
|
30
|
+
* Set the widget to Invisible or Managed in the Cloudflare dashboard: most
|
|
31
|
+
* readers never see a challenge.
|
|
32
|
+
*/
|
|
33
|
+
export const turnstile = (options: TurnstileOptions): TurnstileAdapter => ({
|
|
34
|
+
kind: "turnstile",
|
|
35
|
+
options,
|
|
36
|
+
requiredSecrets: ["TURNSTILE_SECRET_KEY"],
|
|
37
|
+
runtimeDeps: [],
|
|
38
|
+
});
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The server half of the bot check: the generated assistant route verifies
|
|
3
|
+
* the token the browser sent with the question against the provider before
|
|
4
|
+
* it calls the model. A failed or unreachable check refuses the question
|
|
5
|
+
* (and logs why), so a server that can't reach the provider shows up as a
|
|
6
|
+
* refused question rather than a silently unprotected route.
|
|
7
|
+
*/
|
|
8
|
+
import { clientAddressOf } from "../core/client-address.ts";
|
|
9
|
+
import type { ClientContext } from "../core/client-address.ts";
|
|
10
|
+
import { HCAPTCHA_VERIFY_URL } from "./hcaptcha.ts";
|
|
11
|
+
import type { CaptchaAdapter } from "./schema.ts";
|
|
12
|
+
import { TURNSTILE_VERIFY_URL } from "./turnstile.ts";
|
|
13
|
+
|
|
14
|
+
/** The longest token either provider issues, with room to spare. */
|
|
15
|
+
const MAX_TOKEN_LENGTH = 4096;
|
|
16
|
+
|
|
17
|
+
/** What the provider answers about a token. */
|
|
18
|
+
interface VerifyReply {
|
|
19
|
+
"error-codes"?: string[];
|
|
20
|
+
success?: boolean;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** What the route hands the check from its runtime. */
|
|
24
|
+
export interface VerifyOptions {
|
|
25
|
+
/** For tests; defaults to the global `fetch`. */
|
|
26
|
+
fetch?: typeof fetch;
|
|
27
|
+
/** The provider's secret key. */
|
|
28
|
+
secret: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Whether the provider confirms `token` came from a person on this site. The
|
|
33
|
+
* route passes the request body's `captcha` when it's a string.
|
|
34
|
+
*/
|
|
35
|
+
export const verifyCaptcha = async (
|
|
36
|
+
adapter: CaptchaAdapter,
|
|
37
|
+
token: string | undefined,
|
|
38
|
+
context: ClientContext,
|
|
39
|
+
options: VerifyOptions
|
|
40
|
+
): Promise<boolean> => {
|
|
41
|
+
if (!token || token.length > MAX_TOKEN_LENGTH) {
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
const form = new URLSearchParams({ response: token, secret: options.secret });
|
|
45
|
+
const address = clientAddressOf(context);
|
|
46
|
+
if (address) {
|
|
47
|
+
form.set("remoteip", address);
|
|
48
|
+
}
|
|
49
|
+
if (adapter.kind === "hcaptcha") {
|
|
50
|
+
form.set("sitekey", adapter.options.siteKey);
|
|
51
|
+
}
|
|
52
|
+
try {
|
|
53
|
+
const response = await (options.fetch ?? fetch)(
|
|
54
|
+
adapter.kind === "hcaptcha" ? HCAPTCHA_VERIFY_URL : TURNSTILE_VERIFY_URL,
|
|
55
|
+
{ body: form, method: "POST" }
|
|
56
|
+
);
|
|
57
|
+
// SAFETY: both providers answer JSON with a `success` flag; anything
|
|
58
|
+
// else reads as a failure below.
|
|
59
|
+
const reply = (await response.json()) as VerifyReply;
|
|
60
|
+
if (reply.success === true) {
|
|
61
|
+
return true;
|
|
62
|
+
}
|
|
63
|
+
console.warn(
|
|
64
|
+
`The ${adapter.kind} check refused a question:`,
|
|
65
|
+
reply["error-codes"] ?? response.status
|
|
66
|
+
);
|
|
67
|
+
} catch (error) {
|
|
68
|
+
console.error(`The ${adapter.kind} check could not be reached:`, error);
|
|
69
|
+
}
|
|
70
|
+
return false;
|
|
71
|
+
};
|
package/src/cli/args.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import picomatch from "picomatch";
|
|
2
|
+
|
|
1
3
|
import { logger } from "./log.ts";
|
|
2
4
|
|
|
3
5
|
const MAX_PORT = 65_535;
|
|
@@ -22,6 +24,55 @@ export const parsePort = (value?: string): number | undefined => {
|
|
|
22
24
|
return port;
|
|
23
25
|
};
|
|
24
26
|
|
|
27
|
+
/**
|
|
28
|
+
* Every value a repeatable string flag was given (`--ignore a --ignore b`), in
|
|
29
|
+
* order. citty 0.2 parses with `node:util.parseArgs` without its `multiple`
|
|
30
|
+
* option, so the parsed `args` keep only the last value; this reads the raw
|
|
31
|
+
* arguments the way that parser does instead: `--name=value`, or `--name`
|
|
32
|
+
* followed by the next token whatever it looks like (`""` when there is
|
|
33
|
+
* none), and nothing after a bare `--`.
|
|
34
|
+
*/
|
|
35
|
+
export const repeatedFlag = (
|
|
36
|
+
rawArgs: readonly string[],
|
|
37
|
+
name: string
|
|
38
|
+
): string[] => {
|
|
39
|
+
const flag = `--${name}`;
|
|
40
|
+
const values: string[] = [];
|
|
41
|
+
for (let index = 0; index < rawArgs.length; index += 1) {
|
|
42
|
+
const arg = rawArgs[index];
|
|
43
|
+
if (arg === "--") {
|
|
44
|
+
break;
|
|
45
|
+
}
|
|
46
|
+
if (arg === flag) {
|
|
47
|
+
values.push(rawArgs[index + 1] ?? "");
|
|
48
|
+
index += 1;
|
|
49
|
+
} else if (arg?.startsWith(`${flag}=`)) {
|
|
50
|
+
values.push(arg.slice(flag.length + 1));
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return values;
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* The `--ignore` globs of `blume validate` and `blume audit`, as a test for
|
|
58
|
+
* the external URLs they skip: any glob matching the full URL skips it, with
|
|
59
|
+
* `dot` set so `**` also crosses a segment like `/.well-known/`. No globs skip
|
|
60
|
+
* nothing. An empty one (`--ignore` with nothing after it) exits with an
|
|
61
|
+
* error: it names no URL, and picomatch throws on it.
|
|
62
|
+
*/
|
|
63
|
+
export const parseIgnoreFlag = (
|
|
64
|
+
rawArgs: readonly string[]
|
|
65
|
+
): ((url: string) => boolean) => {
|
|
66
|
+
const globs = repeatedFlag(rawArgs, "ignore");
|
|
67
|
+
if (globs.includes("")) {
|
|
68
|
+
logger.error(
|
|
69
|
+
'Invalid --ignore "": pass a glob for the URLs to skip (e.g. --ignore "https://example.com/**").'
|
|
70
|
+
);
|
|
71
|
+
process.exit(1);
|
|
72
|
+
}
|
|
73
|
+
return picomatch(globs, { dot: true });
|
|
74
|
+
};
|
|
75
|
+
|
|
25
76
|
/**
|
|
26
77
|
* The longest `--timeout` a timer can hold, in seconds. Node clamps a
|
|
27
78
|
* `setTimeout` delay above 2^31−1 ms (about 24.8 days) to 1 ms, so a larger
|