blume 2.0.2 → 2.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +65 -0
- package/dist/cli/{chunk-6k4ftwze.js → chunk-1d7ve1dm.js} +1 -1
- package/dist/cli/{chunk-zp79m0ts.js → chunk-2eytanqx.js} +2 -2
- package/dist/cli/{chunk-8g8ytmgx.js → chunk-2hsdwb9n.js} +19 -19
- package/dist/cli/{chunk-8g8ytmgx.js.map → chunk-2hsdwb9n.js.map} +1 -1
- package/dist/cli/{chunk-g698a744.js → chunk-2z928egk.js} +5 -5
- package/dist/cli/{chunk-ppzjqwx2.js → chunk-35d4wj9f.js} +17 -18
- package/dist/cli/{chunk-ppzjqwx2.js.map → chunk-35d4wj9f.js.map} +3 -3
- package/dist/cli/{chunk-mqc662a6.js → chunk-364znk6q.js} +2 -2
- package/dist/cli/{chunk-gs7r695n.js → chunk-3em5wd2y.js} +21 -8
- package/dist/cli/{chunk-gs7r695n.js.map → chunk-3em5wd2y.js.map} +3 -3
- package/dist/cli/{chunk-k7pj68a8.js → chunk-5f86nr5m.js} +15 -15
- package/dist/cli/{chunk-hr8ne106.js → chunk-5m5nmvyq.js} +68 -43
- package/dist/cli/chunk-5m5nmvyq.js.map +13 -0
- package/dist/cli/{chunk-91ws1n6j.js → chunk-5xvm6tfj.js} +14 -14
- package/dist/cli/{chunk-w4bxdvsa.js → chunk-6dsbexzp.js} +14 -14
- package/dist/cli/{chunk-00gs3wqs.js → chunk-8ktnccpt.js} +1 -1
- package/dist/cli/{chunk-j85scx15.js → chunk-9t7a85s3.js} +130 -44
- package/dist/cli/chunk-9t7a85s3.js.map +10 -0
- package/dist/cli/{chunk-bbnwccaz.js → chunk-a58773jm.js} +2 -2
- package/dist/cli/{chunk-nfcyttvj.js → chunk-acanzt5p.js} +9 -9
- package/dist/cli/{chunk-nfcyttvj.js.map → chunk-acanzt5p.js.map} +1 -1
- package/dist/cli/{chunk-wdrt2k2v.js → chunk-akbpwfxc.js} +90 -26
- package/dist/cli/chunk-akbpwfxc.js.map +10 -0
- package/dist/cli/{chunk-sqw4ekg1.js → chunk-b07cmahc.js} +2 -2
- package/dist/cli/{chunk-xh43dwgw.js → chunk-c8chx29p.js} +42 -29
- package/dist/cli/{chunk-xh43dwgw.js.map → chunk-c8chx29p.js.map} +9 -9
- package/dist/cli/{chunk-7mbqtmgb.js → chunk-crgn1q09.js} +21 -12
- package/dist/cli/chunk-crgn1q09.js.map +10 -0
- package/dist/cli/chunk-e04dxsz1.js +39 -0
- package/dist/cli/chunk-e04dxsz1.js.map +10 -0
- package/dist/cli/{chunk-4e9b9ra6.js → chunk-ey84smr6.js} +3 -3
- package/dist/cli/{chunk-d1v5rhy0.js → chunk-g4hq16wv.js} +13 -13
- package/dist/cli/{chunk-273ygyr4.js → chunk-ga0pf4aj.js} +7 -7
- package/dist/cli/{chunk-273ygyr4.js.map → chunk-ga0pf4aj.js.map} +3 -3
- package/dist/cli/{chunk-pbg5a4s3.js → chunk-hm3vjy5s.js} +64 -45
- package/dist/cli/chunk-hm3vjy5s.js.map +19 -0
- package/dist/cli/{chunk-5r8g91qn.js → chunk-j85vccga.js} +306 -107
- package/dist/cli/chunk-j85vccga.js.map +36 -0
- package/dist/cli/{chunk-n1yg3tj3.js → chunk-p3v96n38.js} +6 -6
- package/dist/cli/{chunk-n1yg3tj3.js.map → chunk-p3v96n38.js.map} +3 -3
- package/dist/cli/{chunk-7vtckvaw.js → chunk-p73c0m7w.js} +14 -14
- package/dist/cli/{chunk-7vtckvaw.js.map → chunk-p73c0m7w.js.map} +1 -1
- package/dist/cli/{chunk-qkb5a8sa.js → chunk-pehfxfta.js} +3 -3
- package/dist/cli/{chunk-v6ya5kcb.js → chunk-pv29h0wf.js} +1223 -978
- package/dist/cli/{chunk-v6ya5kcb.js.map → chunk-pv29h0wf.js.map} +47 -46
- package/dist/cli/{chunk-6dtt0zfn.js → chunk-q58y5e6a.js} +10 -10
- package/dist/cli/{chunk-6dtt0zfn.js.map → chunk-q58y5e6a.js.map} +3 -3
- package/dist/cli/{chunk-fsmrqk8a.js → chunk-r20tn01b.js} +1 -1
- package/dist/cli/{chunk-bfwp9vp6.js → chunk-r9rcc4w7.js} +7 -7
- package/dist/cli/{chunk-h7k3nq3v.js → chunk-tkacnehg.js} +2 -2
- package/dist/cli/{chunk-h2ez8dzb.js → chunk-tzmab476.js} +4 -4
- package/dist/cli/{chunk-3yce002v.js → chunk-vg9r4eb9.js} +6 -6
- package/dist/cli/{chunk-3yce002v.js.map → chunk-vg9r4eb9.js.map} +4 -4
- package/dist/cli/{chunk-hqp2ajnh.js → chunk-wrr3j9w9.js} +16 -17
- package/dist/cli/{chunk-hqp2ajnh.js.map → chunk-wrr3j9w9.js.map} +7 -7
- package/dist/cli/{chunk-ddndchfr.js → chunk-yfyb25rh.js} +38 -26
- package/dist/cli/chunk-yfyb25rh.js.map +14 -0
- package/dist/cli/index.js +20 -18
- package/dist/cli/index.js.map +3 -3
- package/dist/types/ai/link-headers.d.ts +8 -1
- package/dist/types/ai/openapi-components.d.ts +5 -2
- package/dist/types/ai/relative-links.d.ts +9 -7
- package/dist/types/ai/skills.d.ts +4 -1
- package/dist/types/ai/tar.d.ts +1 -3
- package/dist/types/analytics/index.d.ts +2 -0
- package/dist/types/analytics/one-dollar-stats.d.ts +48 -0
- package/dist/types/analytics/schema.d.ts +14 -0
- package/dist/types/astro/integration.d.ts +3 -2
- package/dist/types/core/base-path.d.ts +21 -7
- package/dist/types/core/config-input.d.ts +9 -7
- package/dist/types/core/directive-diagnostics.d.ts +12 -0
- package/dist/types/core/heading-markers.d.ts +5 -7
- package/dist/types/core/i18n.d.ts +2 -0
- package/dist/types/core/last-modified.d.ts +10 -0
- package/dist/types/core/meta.d.ts +8 -0
- package/dist/types/core/schema.d.ts +7 -0
- package/dist/types/core/sources/lower.d.ts +29 -16
- package/dist/types/core/sources/normalize.d.ts +13 -1
- package/dist/types/core/sources/watch.d.ts +12 -5
- package/dist/types/core/standard-schema.d.ts +5 -0
- package/dist/types/deploy/artifacts.d.ts +6 -4
- package/dist/types/deploy/headers.d.ts +5 -0
- package/dist/types/deploy/platforms/types.d.ts +8 -0
- package/dist/types/deploy/redirects.d.ts +24 -13
- package/dist/types/markdown/directives.d.ts +62 -0
- package/dist/types/markdown/features.d.ts +21 -0
- package/dist/types/markdown/mdast.d.ts +63 -0
- package/dist/types/openapi/asyncapi.d.ts +4 -2
- package/docs/02-deployment.mdx +4 -2
- package/docs/08-faq.mdx +1 -1
- package/docs/advanced/custom-pages.mdx +3 -3
- package/docs/cli/audit.mdx +17 -1
- package/docs/cli/evals.mdx +3 -3
- package/docs/cli/translate.mdx +3 -3
- package/docs/cli/version.mdx +1 -1
- package/docs/configuration/analytics.mdx +20 -1
- package/docs/configuration/customization.mdx +2 -0
- package/docs/configuration/search.mdx +1 -1
- package/docs/content/components.mdx +1 -1
- package/docs/content/frontmatter.mdx +1 -1
- package/docs/content/i18n.mdx +2 -0
- package/docs/content/index.mdx +4 -2
- package/docs/content/islands.mdx +1 -1
- package/docs/content/meta.mdx +3 -1
- package/docs/content/sources.mdx +1 -5
- package/docs/content/syntax.mdx +14 -0
- package/docs/content/versioning.mdx +1 -1
- package/docs/discoverability/agent-discovery.mdx +1 -1
- package/docs/discoverability/index.mdx +2 -1
- package/docs/discoverability/markdown.mdx +2 -2
- package/docs/discoverability/open-graph.mdx +5 -3
- package/docs/discoverability/rss.mdx +1 -1
- package/docs/references/asyncapi.mdx +1 -1
- package/docs/references/graphql.mdx +1 -1
- package/docs/references/openapi.mdx +5 -3
- package/package.json +1 -1
- package/skills/blume-migrate/references/fumadocs.md +1 -1
- package/skills/blume-migrate/references/nextra.md +1 -1
- package/skills/blume-migrate/scripts/mintlify-codemod.mjs +7 -2
- package/src/ai/agent-readability.ts +2 -2
- package/src/ai/ai-catalog.ts +6 -2
- package/src/ai/api/handlers.ts +2 -2
- package/src/ai/api/spec.ts +2 -2
- package/src/ai/api-catalog.ts +6 -2
- package/src/ai/changelog-markdown.ts +2 -2
- package/src/ai/link-headers.ts +14 -5
- package/src/ai/llms.ts +18 -7
- package/src/ai/mcp/discovery.ts +2 -2
- package/src/ai/mcp/query.ts +2 -2
- package/src/ai/mcp/server.ts +2 -2
- package/src/ai/openapi-components.ts +22 -6
- package/src/ai/relative-links.ts +42 -26
- package/src/ai/serializers.ts +2 -1
- package/src/ai/skills.ts +14 -1
- package/src/ai/tar.ts +139 -12
- package/src/analytics/head.ts +4 -0
- package/src/analytics/index.ts +5 -0
- package/src/analytics/one-dollar-stats.ts +86 -0
- package/src/analytics/schema.ts +2 -0
- package/src/astro/generate.ts +1 -1
- package/src/astro/include-hmr.ts +31 -12
- package/src/astro/include-refresh.ts +19 -8
- package/src/astro/integration.ts +108 -14
- package/src/astro/runtime-modules.ts +28 -13
- package/src/astro/templates.ts +81 -37
- package/src/audit/checks/links.ts +8 -1
- package/src/audit/graph.ts +3 -1
- package/src/audit/run.ts +12 -9
- package/src/audit/snapshot.ts +5 -0
- package/src/cli/args.ts +32 -0
- package/src/cli/commands/audit.ts +4 -1
- package/src/cli/commands/doctor.ts +3 -1
- package/src/cli/commands/eval.ts +19 -11
- package/src/cli/commands/translate.ts +9 -12
- package/src/cli/commands/validate.ts +3 -1
- package/src/cli/dev-lock.ts +157 -37
- package/src/cli/report-format.ts +11 -6
- package/src/components/colors.ts +19 -0
- package/src/components/content/Badge.astro +5 -3
- package/src/components/content/Component.astro +2 -2
- package/src/components/content/Tab.astro +0 -1
- package/src/components/content/Tabs.astro +4 -0
- package/src/components/content/badge-color.ts +5 -2
- package/src/components/content/base-href.ts +13 -36
- package/src/components/islands/assistant.tsx +21 -4
- package/src/components/islands/base-path.ts +47 -10
- package/src/components/islands/hooks.ts +35 -19
- package/src/components/islands/webmcp.ts +4 -2
- package/src/components/layout/Breadcrumbs.astro +5 -2
- package/src/components/layout/DiscoveryLinks.astro +9 -5
- package/src/components/layout/Header.astro +2 -2
- package/src/components/layout/LanguageSwitcher.astro +2 -2
- package/src/components/layout/NavSelector.astro +2 -2
- package/src/components/layout/NavTabMenu.astro +3 -3
- package/src/components/layout/NavTree.astro +16 -6
- package/src/components/layout/PageActions.astro +4 -3
- package/src/components/layout/PageLayout.astro +7 -6
- package/src/components/layout/Pagination.astro +3 -3
- package/src/components/layout/RootLayout.astro +5 -5
- package/src/components/layout/Search.astro +33 -13
- package/src/components/layout/VersionBanner.astro +2 -2
- package/src/components/layout/analytics-client.ts +12 -0
- package/src/components/layout/toc-active.ts +41 -0
- package/src/components/layout/toc-element.ts +8 -14
- package/src/components/openapi/ApiTagOperations.astro +2 -2
- package/src/components/openapi/AsyncApiOperation.astro +7 -4
- package/src/components/openapi/GraphqlChip.astro +2 -2
- package/src/components/openapi/Operation.astro +2 -0
- package/src/components/openapi/Playground.astro +4 -4
- package/src/components/openapi/RequestPanel.astro +5 -2
- package/src/components/openapi/SchemaTable.astro +7 -0
- package/src/components/openapi/async.ts +38 -6
- package/src/components/openapi/helpers.ts +19 -9
- package/src/components/openapi/message-composer.ts +6 -1
- package/src/components/openapi/message.ts +17 -2
- package/src/components/openapi/operation-model.ts +81 -11
- package/src/components/openapi/panel.ts +4 -2
- package/src/components/openapi/param-style.ts +181 -0
- package/src/components/openapi/playground-client.ts +73 -16
- package/src/components/openapi/request.ts +190 -26
- package/src/components/openapi/schema-tree.ts +30 -24
- package/src/components/openapi/snippets.ts +85 -6
- package/src/components/openapi/ws-client.ts +18 -2
- package/src/core/base-path.ts +62 -16
- package/src/core/config-input.ts +9 -7
- package/src/core/diagnostics.ts +2 -0
- package/src/core/directive-diagnostics.ts +99 -0
- package/src/core/frontmatter.ts +21 -18
- package/src/core/heading-markers.ts +5 -18
- package/src/core/i18n.ts +31 -3
- package/src/core/last-modified.ts +25 -3
- package/src/core/locale-links.ts +5 -1
- package/src/core/meta.ts +33 -19
- package/src/core/navigation.ts +28 -8
- package/src/core/project-graph.ts +27 -5
- package/src/core/schema.ts +24 -4
- package/src/core/sources/contentful-rich-text.ts +27 -20
- package/src/core/sources/filesystem.ts +20 -2
- package/src/core/sources/github-releases.ts +25 -6
- package/src/core/sources/lexical.ts +23 -18
- package/src/core/sources/lower.ts +201 -34
- package/src/core/sources/mdx-remote.ts +51 -16
- package/src/core/sources/normalize.ts +325 -90
- package/src/core/sources/notion.ts +52 -25
- package/src/core/sources/obsidian.ts +23 -6
- package/src/core/sources/portable-text.ts +38 -21
- package/src/core/sources/strapi-blocks.ts +20 -16
- package/src/core/sources/watch.ts +20 -7
- package/src/core/standard-schema.ts +10 -6
- package/src/core/version-cut.ts +52 -15
- package/src/deploy/artifacts.ts +27 -6
- package/src/deploy/cloudflare-negotiation.ts +4 -12
- package/src/deploy/headers.ts +8 -4
- package/src/deploy/node-headers.ts +1 -1
- package/src/deploy/platforms/cloudflare.ts +12 -3
- package/src/deploy/platforms/netlify.ts +1 -0
- package/src/deploy/platforms/node.ts +1 -0
- package/src/deploy/platforms/static.ts +1 -0
- package/src/deploy/platforms/types.ts +8 -0
- package/src/deploy/platforms/vercel.ts +1 -0
- package/src/deploy/redirects.ts +54 -18
- package/src/deploy/robots.ts +2 -2
- package/src/deploy/rss.ts +4 -3
- package/src/deploy/sitemap.ts +7 -5
- package/src/markdown/base-links.ts +34 -36
- package/src/markdown/directives.ts +242 -36
- package/src/markdown/features.ts +17 -0
- package/src/markdown/index.ts +7 -6
- package/src/markdown/mdast.ts +5 -2
- package/src/markdown/relative-links.ts +12 -4
- package/src/og/card.ts +129 -5
- package/src/og/derive.ts +41 -31
- package/src/og/index.ts +1 -0
- package/src/openapi/asyncapi.ts +4 -2
- package/src/openapi/model.ts +22 -14
- package/src/openapi/proxy.ts +63 -10
- package/src/openapi/render-mdx.ts +41 -2
- package/src/registry/eject.ts +181 -24
- package/src/search/adapters/version-scope.ts +30 -0
- package/src/search/documents.ts +67 -26
- package/src/search/popular.ts +2 -1
- package/src/seo/jsonld.ts +7 -3
- package/src/translate/meta.ts +68 -24
- package/src/translate/run.ts +3 -3
- package/src/translate/validate.ts +4 -1
- package/src/translate/work-list.ts +56 -17
- package/dist/cli/chunk-5r8g91qn.js.map +0 -34
- package/dist/cli/chunk-7mbqtmgb.js.map +0 -10
- package/dist/cli/chunk-ddndchfr.js.map +0 -14
- package/dist/cli/chunk-esh98wmb.js +0 -23
- package/dist/cli/chunk-esh98wmb.js.map +0 -10
- package/dist/cli/chunk-hr8ne106.js.map +0 -13
- package/dist/cli/chunk-j85scx15.js.map +0 -10
- package/dist/cli/chunk-pbg5a4s3.js.map +0 -19
- package/dist/cli/chunk-wdrt2k2v.js.map +0 -10
- /package/dist/cli/{chunk-6k4ftwze.js.map → chunk-1d7ve1dm.js.map} +0 -0
- /package/dist/cli/{chunk-zp79m0ts.js.map → chunk-2eytanqx.js.map} +0 -0
- /package/dist/cli/{chunk-g698a744.js.map → chunk-2z928egk.js.map} +0 -0
- /package/dist/cli/{chunk-mqc662a6.js.map → chunk-364znk6q.js.map} +0 -0
- /package/dist/cli/{chunk-k7pj68a8.js.map → chunk-5f86nr5m.js.map} +0 -0
- /package/dist/cli/{chunk-91ws1n6j.js.map → chunk-5xvm6tfj.js.map} +0 -0
- /package/dist/cli/{chunk-w4bxdvsa.js.map → chunk-6dsbexzp.js.map} +0 -0
- /package/dist/cli/{chunk-00gs3wqs.js.map → chunk-8ktnccpt.js.map} +0 -0
- /package/dist/cli/{chunk-bbnwccaz.js.map → chunk-a58773jm.js.map} +0 -0
- /package/dist/cli/{chunk-sqw4ekg1.js.map → chunk-b07cmahc.js.map} +0 -0
- /package/dist/cli/{chunk-4e9b9ra6.js.map → chunk-ey84smr6.js.map} +0 -0
- /package/dist/cli/{chunk-d1v5rhy0.js.map → chunk-g4hq16wv.js.map} +0 -0
- /package/dist/cli/{chunk-qkb5a8sa.js.map → chunk-pehfxfta.js.map} +0 -0
- /package/dist/cli/{chunk-fsmrqk8a.js.map → chunk-r20tn01b.js.map} +0 -0
- /package/dist/cli/{chunk-bfwp9vp6.js.map → chunk-r9rcc4w7.js.map} +0 -0
- /package/dist/cli/{chunk-h7k3nq3v.js.map → chunk-tkacnehg.js.map} +0 -0
- /package/dist/cli/{chunk-h2ez8dzb.js.map → chunk-tzmab476.js.map} +0 -0
|
@@ -8,11 +8,11 @@
|
|
|
8
8
|
* generated pages through aliases or relative paths. `generateRuntime` now
|
|
9
9
|
* publishes them here and `runtimeModulesPlugin` serves them to Vite as
|
|
10
10
|
* virtual modules, so a regeneration never round-trips the disk or waits on a
|
|
11
|
-
* file watcher: publishing invalidates the changed modules in every
|
|
12
|
-
* server (Vite walks the importers, so the
|
|
13
|
-
* on the next request) and asks the
|
|
14
|
-
*
|
|
15
|
-
* watch debounce.
|
|
11
|
+
* file watcher: publishing invalidates the changed modules in every Vite
|
|
12
|
+
* environment of every live dev server (Vite walks the importers, so the
|
|
13
|
+
* pages that render them re-evaluate on the next request) and asks the
|
|
14
|
+
* browser to reload — the same effect a JSON file change used to reach
|
|
15
|
+
* through the watcher, minus the write and the watch debounce.
|
|
16
16
|
*
|
|
17
17
|
* The registry hangs off `globalThis`, not module state. On a published
|
|
18
18
|
* install the CLI bundle (`dist/cli`) carries its own copy of this module,
|
|
@@ -61,18 +61,29 @@ interface RuntimeModuleNode {
|
|
|
61
61
|
id: string | null;
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
+
/** One Vite environment's module graph, as far as the registry touches it. */
|
|
65
|
+
interface RuntimeModuleGraph {
|
|
66
|
+
getModuleById: (id: string) => RuntimeModuleNode | undefined;
|
|
67
|
+
invalidateModule: (mod: RuntimeModuleNode) => void;
|
|
68
|
+
}
|
|
69
|
+
|
|
64
70
|
/**
|
|
65
71
|
* The Vite dev-server slice the registry touches (structurally typed, like
|
|
66
72
|
* every Blume-authored Vite plugin — see `includeHmrPlugin`).
|
|
67
73
|
*/
|
|
68
74
|
export interface RuntimeModuleServer {
|
|
75
|
+
/**
|
|
76
|
+
* Every Vite environment, each with its own module graph. `moduleGraph` on
|
|
77
|
+
* the server itself is Vite's backward-compatible view of only `client`
|
|
78
|
+
* and `ssr`, and those are not the only environments that render: with
|
|
79
|
+
* `@astrojs/cloudflare` the `ssr` environment runs in workerd, so Astro
|
|
80
|
+
* renders the prerendered pages (every content page) in its `prerender`
|
|
81
|
+
* environment instead.
|
|
82
|
+
*/
|
|
83
|
+
environments: Record<string, { moduleGraph: RuntimeModuleGraph }>;
|
|
69
84
|
httpServer?: {
|
|
70
85
|
once: (event: "close", listener: () => void) => void;
|
|
71
86
|
} | null;
|
|
72
|
-
moduleGraph: {
|
|
73
|
-
getModuleById: (id: string) => RuntimeModuleNode | undefined;
|
|
74
|
-
invalidateModule: (mod: RuntimeModuleNode) => void;
|
|
75
|
-
};
|
|
76
87
|
ws: { send: (payload: { type: "full-reload" }) => void };
|
|
77
88
|
}
|
|
78
89
|
|
|
@@ -131,10 +142,14 @@ export const publishRuntimeModules = (
|
|
|
131
142
|
return changed;
|
|
132
143
|
}
|
|
133
144
|
for (const server of servers) {
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
145
|
+
// Every environment that loaded a module holds its own copy, and any of
|
|
146
|
+
// them may be the one rendering pages on this adapter.
|
|
147
|
+
for (const { moduleGraph } of Object.values(server.environments)) {
|
|
148
|
+
for (const id of changed) {
|
|
149
|
+
const mod = moduleGraph.getModuleById(`${RESOLVED_PREFIX}${id}`);
|
|
150
|
+
if (mod) {
|
|
151
|
+
moduleGraph.invalidateModule(mod);
|
|
152
|
+
}
|
|
138
153
|
}
|
|
139
154
|
}
|
|
140
155
|
server.ws.send({ type: "full-reload" });
|
package/src/astro/templates.ts
CHANGED
|
@@ -19,9 +19,12 @@ import { deployPlatform } from "../deploy/platforms/index.ts";
|
|
|
19
19
|
import { adapterRoot, distDir } from "../deploy/platforms/paths.ts";
|
|
20
20
|
import { applyBaseToAstroRedirects } from "../deploy/redirects.ts";
|
|
21
21
|
import type { OgCache } from "../og/cache.ts";
|
|
22
|
-
import type { OgFont, OgFontFamilies } from "../og/card.ts";
|
|
22
|
+
import type { OgFont, OgFontFamilies, OgGoogleFont } from "../og/card.ts";
|
|
23
23
|
import type { MixedbreadOptions } from "../search/adapters/mixedbread.ts";
|
|
24
|
-
import type {
|
|
24
|
+
import type {
|
|
25
|
+
ResolvedSearchAdapter,
|
|
26
|
+
SearchAdapterKind,
|
|
27
|
+
} from "../search/adapters/registry.ts";
|
|
25
28
|
import { buildFontEntries, fontLocaleCodes } from "../theme/fonts.ts";
|
|
26
29
|
import { importSpecifier, wrapperPropsType } from "./component-slots.ts";
|
|
27
30
|
import type { ExampleSpec } from "./examples.ts";
|
|
@@ -398,14 +401,32 @@ export interface ClientFeatures {
|
|
|
398
401
|
/** Every feature on: what a checkout that predates the detection shipped. */
|
|
399
402
|
const ALL_CLIENT_FEATURES: ClientFeatures = { epub: true, mermaid: true };
|
|
400
403
|
|
|
404
|
+
/**
|
|
405
|
+
* The npm module each browser-queried search adapter's client imports (see
|
|
406
|
+
* `components/layout/search/`). The `<Search>` dialog lazy-imports that client
|
|
407
|
+
* on first open, after the dev dep optimizer's startup run, so the first
|
|
408
|
+
* search of a fresh `blume dev` session discovered the library, re-ran the
|
|
409
|
+
* optimizer, and reloaded the page. Pagefind loads its script from the build
|
|
410
|
+
* output and Mixedbread queries through the server, so neither has one.
|
|
411
|
+
*/
|
|
412
|
+
const SEARCH_CLIENT_DEPS: Partial<Record<SearchAdapterKind, string>> = {
|
|
413
|
+
algolia: "algoliasearch/lite",
|
|
414
|
+
flexsearch: "flexsearch",
|
|
415
|
+
orama: "@orama/orama",
|
|
416
|
+
"orama-cloud": "@oramacloud/client",
|
|
417
|
+
typesense: "typesense",
|
|
418
|
+
};
|
|
419
|
+
|
|
401
420
|
const resolveOptimizeDeps = (options: {
|
|
402
421
|
aliases: Record<string, string> | undefined;
|
|
403
422
|
context: ProjectContext;
|
|
404
423
|
features: ClientFeatures;
|
|
405
424
|
needsReact: boolean;
|
|
406
425
|
reactCompilerPath: string | null | undefined;
|
|
426
|
+
searchKind: SearchAdapterKind;
|
|
407
427
|
}): OptimizeDepsConfig => {
|
|
408
428
|
const { context, features } = options;
|
|
429
|
+
const searchDep = SEARCH_CLIENT_DEPS[options.searchKind];
|
|
409
430
|
const optimizeDepsEntries = [
|
|
410
431
|
...(context.pagesRoot ? [`${context.pagesRoot}/**/*.astro`] : []),
|
|
411
432
|
`${context.root}/islands/**/*.{jsx,svelte,tsx,vue}`,
|
|
@@ -418,6 +439,8 @@ const resolveOptimizeDeps = (options: {
|
|
|
418
439
|
// Mermaid costs the dev server seconds at startup for nothing otherwise.
|
|
419
440
|
...(features.mermaid ? ["blume > mermaid"] : []),
|
|
420
441
|
...(features.epub ? ["blume > epub-gen-memory/bundle"] : []),
|
|
442
|
+
// Only the configured search adapter's client library.
|
|
443
|
+
...(searchDep ? [`blume > ${searchDep}`] : []),
|
|
421
444
|
// Astro's own client-router/prefetch virtual modules are deliberately NOT
|
|
422
445
|
// forced in here: they read Vite `define`-injected constants
|
|
423
446
|
// (__PREFETCH_PREFETCH_ALL__ and friends) that a pre-bundled copy loses,
|
|
@@ -538,7 +561,7 @@ const blumeIntegrationOptions = (options: {
|
|
|
538
561
|
buildArtifactsRoot: ".",
|
|
539
562
|
contentRoutes: options.contentRoutes,
|
|
540
563
|
homeLinkHeader:
|
|
541
|
-
buildHomeLinkHeader(options.config, options.contentRoutes) ??
|
|
564
|
+
buildHomeLinkHeader(options.config, options.contentRoutes, "dev") ??
|
|
542
565
|
undefined,
|
|
543
566
|
pages: options.pages,
|
|
544
567
|
}
|
|
@@ -623,6 +646,7 @@ export const astroConfigTemplate = (options: {
|
|
|
623
646
|
features,
|
|
624
647
|
needsReact,
|
|
625
648
|
reactCompilerPath: options.reactCompilerPath,
|
|
649
|
+
searchKind: config.search.provider.kind,
|
|
626
650
|
});
|
|
627
651
|
|
|
628
652
|
const {
|
|
@@ -660,7 +684,8 @@ export const astroConfigTemplate = (options: {
|
|
|
660
684
|
const basedRedirects = applyBaseToAstroRedirects(
|
|
661
685
|
config.redirects,
|
|
662
686
|
config.basePath,
|
|
663
|
-
deployment.options.base ?? ""
|
|
687
|
+
deployment.options.base ?? "",
|
|
688
|
+
new Set(contentRoutes)
|
|
664
689
|
);
|
|
665
690
|
const redirectsOption =
|
|
666
691
|
basedRedirects.length > 0
|
|
@@ -892,6 +917,10 @@ ${userConfigSetup}export default defineConfig({
|
|
|
892
917
|
// \`/bundle\` subpath that is actually imported: optimizing the package
|
|
893
918
|
// root leaves that entry out. Production (Rollup) already handles the
|
|
894
919
|
// interop, so all of this only affects dev.
|
|
920
|
+
//
|
|
921
|
+
// The search adapter's client library rides the list for the same reason
|
|
922
|
+
// as the compiler runtime: the search dialog lazy-imports it on first
|
|
923
|
+
// open, and discovering it then re-optimizes and reloads the page.
|
|
895
924
|
optimizeDeps: {
|
|
896
925
|
entries: ${JSON.stringify(optimizeDepsEntries)},
|
|
897
926
|
include: ${JSON.stringify(optimizeDepsInclude)},
|
|
@@ -1246,9 +1275,15 @@ export const askEndpointTemplate = (
|
|
|
1246
1275
|
"messages",
|
|
1247
1276
|
...backend.template.fields,
|
|
1248
1277
|
];
|
|
1278
|
+
// The request's signal aborts when the reader closes the panel mid-answer
|
|
1279
|
+
// (the client drops the connection): passing it on stops the model call,
|
|
1280
|
+
// which otherwise kept generating, and billing, to completion into a closed
|
|
1281
|
+
// connection. `streamText` ends an aborted call through its abort path, not
|
|
1282
|
+
// `onError`, so the reader leaving is never logged as a provider error.
|
|
1249
1283
|
const call = ` const result = streamText({
|
|
1250
1284
|
${streamFields.join(",\n ")},
|
|
1251
1285
|
${onError}
|
|
1286
|
+
abortSignal: request.signal,
|
|
1252
1287
|
});`;
|
|
1253
1288
|
const stream = grounded
|
|
1254
1289
|
? ` const instructions =
|
|
@@ -1792,7 +1827,7 @@ export const navFragmentTemplate = (): string =>
|
|
|
1792
1827
|
`---
|
|
1793
1828
|
// Generated by Blume. Do not edit.
|
|
1794
1829
|
import NavTree from "blume/components/layout/NavTree.astro";
|
|
1795
|
-
import {
|
|
1830
|
+
import { withMountedBase } from "blume/components/islands/base-path.ts";
|
|
1796
1831
|
import {
|
|
1797
1832
|
hiddenDefaultLocale,
|
|
1798
1833
|
navGroupIds,
|
|
@@ -1831,7 +1866,7 @@ if (!(variant && node && node.kind === "group")) {
|
|
|
1831
1866
|
}
|
|
1832
1867
|
const strings =
|
|
1833
1868
|
locale === "default" ? data.ui.nav : (data.uiByLocale[locale] ?? data.ui).nav;
|
|
1834
|
-
const fragmentBase =
|
|
1869
|
+
const fragmentBase = withMountedBase(\`/blume-nav/\${version}/\${locale}\`);
|
|
1835
1870
|
---
|
|
1836
1871
|
|
|
1837
1872
|
<NavTree
|
|
@@ -1855,6 +1890,7 @@ export const ogEndpointTemplate = (
|
|
|
1855
1890
|
* Absent, every card renders on every build.
|
|
1856
1891
|
*/
|
|
1857
1892
|
cache?: OgCache;
|
|
1893
|
+
fallbacks?: OgGoogleFont[];
|
|
1858
1894
|
families?: OgFontFamilies;
|
|
1859
1895
|
fonts?: OgFont[];
|
|
1860
1896
|
/**
|
|
@@ -1868,7 +1904,7 @@ export const ogEndpointTemplate = (
|
|
|
1868
1904
|
): string =>
|
|
1869
1905
|
`// Generated by Blume. Do not edit.
|
|
1870
1906
|
import { cachedOgImage } from "blume/og";
|
|
1871
|
-
import type { OgCache, OgFont, OgFontFamilies } from "blume/og";
|
|
1907
|
+
import type { OgCache, OgFont, OgFontFamilies, OgGoogleFont } from "blume/og";
|
|
1872
1908
|
import data from "blume:data";
|
|
1873
1909
|
|
|
1874
1910
|
export const prerender = true;
|
|
@@ -1886,6 +1922,9 @@ const fonts: OgFont[] = ${JSON.stringify(og.fonts ?? [])};
|
|
|
1886
1922
|
const families: OgFontFamilies | undefined = ${
|
|
1887
1923
|
og.families ? JSON.stringify(og.families) : "undefined"
|
|
1888
1924
|
};
|
|
1925
|
+
// Script fallbacks (Noto families, the configured locales' first). A card
|
|
1926
|
+
// fetches them only when its text has a glyph Takumi's built-in font lacks.
|
|
1927
|
+
const fallbacks: OgGoogleFont[] = ${JSON.stringify(og.fallbacks ?? [])};
|
|
1889
1928
|
|
|
1890
1929
|
// Rendered cards are kept on disk between builds, keyed by everything that
|
|
1891
1930
|
// decides their pixels, so a rebuild renders only the cards whose title,
|
|
@@ -1958,6 +1997,7 @@ export async function GET({ props }: { props: CardProps }) {
|
|
|
1958
1997
|
accent: data.config.og.palette?.accent ?? data.config.theme.accent.light,
|
|
1959
1998
|
brand: data.config.title,
|
|
1960
1999
|
description: props.description ?? data.config.og.description,
|
|
2000
|
+
fallbacks,
|
|
1961
2001
|
families,
|
|
1962
2002
|
fonts,
|
|
1963
2003
|
logo: data.config.og.logo,
|
|
@@ -2153,8 +2193,8 @@ export const catchAllPageTemplate = (options: {
|
|
|
2153
2193
|
import { getEntry, render } from "astro:content";
|
|
2154
2194
|
import type { CollectionKey } from "astro:content";
|
|
2155
2195
|
import RootLayout from "blume/components/layout/RootLayout.astro";
|
|
2156
|
-
import { withBase } from "blume/components/islands/base-path.ts";
|
|
2157
|
-
import {
|
|
2196
|
+
import { withBase, withMountedBase } from "blume/components/islands/base-path.ts";
|
|
2197
|
+
import { mountBasePath, stripBasePath } from "blume/core/base-path.ts";
|
|
2158
2198
|
import { resolveSlot } from "blume/components/layout/overrides.ts";
|
|
2159
2199
|
${componentImports}
|
|
2160
2200
|
import data from "blume:data";
|
|
@@ -2219,12 +2259,16 @@ const base = data.config.site ? data.config.site.replace(/\\/$/, "") : null;
|
|
|
2219
2259
|
const ogPath = data.config.og.enabled
|
|
2220
2260
|
? encodeURI(\`/og/\${route === "/" ? "index" : route.slice(1)}.png\`)
|
|
2221
2261
|
: null;
|
|
2222
|
-
const ogRel = seo.image ?? ogPath;
|
|
2223
2262
|
// Absolute URLs also carry the deployment base (the page is served under it):
|
|
2224
2263
|
// \`site + base + path\`. Only absolutize root-relative paths: \`seo.image\` may be
|
|
2225
|
-
// an external URL, which passes through verbatim (mirrors PageLayout).
|
|
2226
|
-
|
|
2227
|
-
|
|
2264
|
+
// an external URL, which passes through verbatim (mirrors PageLayout). An
|
|
2265
|
+
// authored \`seo.image\` keeps a base written by hand; the generated card is
|
|
2266
|
+
// always mounted under it.
|
|
2267
|
+
const absoluteOg = (path: string, based: (path: string) => string) =>
|
|
2268
|
+
base && path.startsWith("/") ? \`\${base}\${based(path)}\` : path;
|
|
2269
|
+
const ogImage = seo.image
|
|
2270
|
+
? absoluteOg(seo.image, withBase)
|
|
2271
|
+
: ogPath && absoluteOg(ogPath, withMountedBase);
|
|
2228
2272
|
// Blume's generated card has known dimensions the layout can declare; a user's
|
|
2229
2273
|
// \`seo.image\` could be any size or format, so it gets none.
|
|
2230
2274
|
const ogGenerated = !seo.image && Boolean(ogPath);
|
|
@@ -2233,7 +2277,7 @@ const ogGenerated = !seo.image && Boolean(ogPath);
|
|
|
2233
2277
|
// itself (a guest post crediting its own author) over the configured default.
|
|
2234
2278
|
const x = { ...data.config.x, ...(seo.x?.creator ? { creator: seo.x.creator } : {}) };
|
|
2235
2279
|
|
|
2236
|
-
const basedRoute =
|
|
2280
|
+
const basedRoute = withMountedBase(route);
|
|
2237
2281
|
|
|
2238
2282
|
// Locale resolution. With i18n on, pick the active locale's nav + dictionary,
|
|
2239
2283
|
// build hreflang alternates, and derive the language-switcher targets.
|
|
@@ -2281,7 +2325,7 @@ const contentDir = i18n
|
|
|
2281
2325
|
: "ltr";
|
|
2282
2326
|
// The root route keeps its trailing slash (\`https://site/\`) so canonical and
|
|
2283
2327
|
// hreflang URLs byte-match the sitemap's <loc> for the home page.
|
|
2284
|
-
const absolute = (path: string) => base +
|
|
2328
|
+
const absolute = (path: string) => base + withMountedBase(path);
|
|
2285
2329
|
|
|
2286
2330
|
// A fallback page renders the fallback locale's page at this locale's URL, so
|
|
2287
2331
|
// its canonical names the page it copies, and search engines never rank the
|
|
@@ -2326,7 +2370,7 @@ const xDefault = defaultAlt && base ? absolute(defaultAlt.path) : null;
|
|
|
2326
2370
|
// carries sits *after* the base (\`/docs/ja/guide\`), while \`localePrefix\` is
|
|
2327
2371
|
// base-less (\`/ja\`). Stripping and re-adding a locale therefore happen in
|
|
2328
2372
|
// base-less space, with the base re-applied at the end — the same
|
|
2329
|
-
// \`
|
|
2373
|
+
// \`mountBasePath(basePath, localizeRoute(...))\` composition the manifest uses to
|
|
2330
2374
|
// build every real route. Done in based space, \`stripLocale\` matches nothing
|
|
2331
2375
|
// and \`localizeRoute\` prepends a second prefix (\`/ja/docs/ja/guide\`). Only a
|
|
2332
2376
|
// switcher entry for a locale with no real translation reaches this fallback:
|
|
@@ -2334,7 +2378,7 @@ const xDefault = defaultAlt && base ? absolute(defaultAlt.path) : null;
|
|
|
2334
2378
|
// generated reference (which has no \`alternates\` at all) for every locale
|
|
2335
2379
|
// but its own.
|
|
2336
2380
|
const mountLocalized = (logical: string, codeArg: string) =>
|
|
2337
|
-
|
|
2381
|
+
mountBasePath(data.config.basePath, localizeRoute(logical, codeArg));
|
|
2338
2382
|
const logicalRoute = i18n
|
|
2339
2383
|
? stripLocale(stripBasePath(data.config.basePath, route), locale)
|
|
2340
2384
|
: route;
|
|
@@ -2363,7 +2407,7 @@ const versionRootFor = (id: string) => {
|
|
|
2363
2407
|
const logical = id ? \`/\${id}\` : "/";
|
|
2364
2408
|
return i18n
|
|
2365
2409
|
? mountLocalized(logical, locale)
|
|
2366
|
-
:
|
|
2410
|
+
: mountBasePath(data.config.basePath, logical);
|
|
2367
2411
|
};
|
|
2368
2412
|
const samePageSwitch = versionsConfig
|
|
2369
2413
|
? versionsConfig.switcher.redirect === "same-page"
|
|
@@ -2463,7 +2507,7 @@ const LayoutComponent = resolveSlot(layoutOverrides.Layout, RootLayout);
|
|
|
2463
2507
|
exportEpub={${options.exportEpub}}${
|
|
2464
2508
|
options.navFragments
|
|
2465
2509
|
? `
|
|
2466
|
-
navFragmentBase={
|
|
2510
|
+
navFragmentBase={withMountedBase(\`/blume-nav/\${version || "current"}/\${i18n && localePrefix(locale) ? locale : "default"}\`)}`
|
|
2467
2511
|
: ""
|
|
2468
2512
|
}
|
|
2469
2513
|
openInChat={data.config.openInChat}
|
|
@@ -2516,7 +2560,7 @@ export const changelogIndexTemplate = (options: {
|
|
|
2516
2560
|
// Generated by Blume. Do not edit.
|
|
2517
2561
|
import { getCollection } from "astro:content";
|
|
2518
2562
|
import RootLayout from "blume/components/layout/RootLayout.astro";
|
|
2519
|
-
import {
|
|
2563
|
+
import { withMountedBase } from "blume/components/islands/base-path.ts";
|
|
2520
2564
|
import { resolveSlot } from "blume/components/layout/overrides.ts";
|
|
2521
2565
|
import { resolveDateFormatOptions } from "blume/core/date-format.ts";
|
|
2522
2566
|
import { layoutOverrides } from "../generated/components.ts";
|
|
@@ -2615,7 +2659,7 @@ const items = changelogEntries.map((entry) => {
|
|
|
2615
2659
|
return {
|
|
2616
2660
|
date: date ? formatRowDate(date) : null,
|
|
2617
2661
|
dateTime: date ? date.toISOString().slice(0, 10) : null,
|
|
2618
|
-
href: route ?
|
|
2662
|
+
href: route ? withMountedBase(route) : null,
|
|
2619
2663
|
id: slugify(label),
|
|
2620
2664
|
label,
|
|
2621
2665
|
tag: entry.data.changelog?.category ?? null,
|
|
@@ -2651,14 +2695,14 @@ for (const item of items) {
|
|
|
2651
2695
|
|
|
2652
2696
|
const base = data.config.site ? data.config.site.replace(/\\/$/, "") : null;
|
|
2653
2697
|
// The canonical URL carries the deployment base (the page is served under it),
|
|
2654
|
-
// matching how the catch-all canonicalizes via \`
|
|
2655
|
-
const basedRoute =
|
|
2698
|
+
// matching how the catch-all canonicalizes via \`withMountedBase(route)\`.
|
|
2699
|
+
const basedRoute = withMountedBase("/changelog");
|
|
2656
2700
|
const canonical = base ? base + basedRoute : null;
|
|
2657
2701
|
|
|
2658
2702
|
// The generated OG card for this route (the /og endpoint emits it alongside
|
|
2659
2703
|
// the content-route cards), absolutized like the catch-all's so crawlers get
|
|
2660
2704
|
// a full URL when the site is known.
|
|
2661
|
-
const ogPath = data.config.og.enabled ?
|
|
2705
|
+
const ogPath = data.config.og.enabled ? withMountedBase("/og/changelog.png") : null;
|
|
2662
2706
|
const ogImage = ogPath && base ? base + ogPath : ogPath;
|
|
2663
2707
|
|
|
2664
2708
|
// The page chrome (h1, title, description) comes from the translatable
|
|
@@ -2780,8 +2824,8 @@ export const notFoundPageTemplate = (): string => `---
|
|
|
2780
2824
|
// Generated by Blume. Do not edit. Override by adding \`pages/404.astro\`.
|
|
2781
2825
|
import Icon from "blume/components/Icon.astro";
|
|
2782
2826
|
import PageLayout from "blume/components/layout/PageLayout.astro";
|
|
2783
|
-
import {
|
|
2784
|
-
import {
|
|
2827
|
+
import { withMountedBase } from "blume/components/islands/base-path.ts";
|
|
2828
|
+
import { mountBasePath } from "blume/core/base-path.ts";
|
|
2785
2829
|
import data from "blume:data";
|
|
2786
2830
|
|
|
2787
2831
|
export const prerender = true;
|
|
@@ -2805,14 +2849,14 @@ const htmlLang = i18n ? i18n.defaultLocale : "en";
|
|
|
2805
2849
|
// their resolved target when the section has no index page of its own.
|
|
2806
2850
|
const suggestions = [
|
|
2807
2851
|
...data.navigation.tabs.map((tab) => ({
|
|
2808
|
-
href:
|
|
2852
|
+
href: withMountedBase(tab.href ?? tab.path),
|
|
2809
2853
|
label: tab.label,
|
|
2810
2854
|
})),
|
|
2811
2855
|
...(data.config.discovery.sitemap
|
|
2812
|
-
? [{ href:
|
|
2856
|
+
? [{ href: withMountedBase("/sitemap.xml"), label: nf.sitemap }]
|
|
2813
2857
|
: []),
|
|
2814
2858
|
...(data.config.discovery.llmsTxt
|
|
2815
|
-
? [{ href:
|
|
2859
|
+
? [{ href: withMountedBase("/llms.txt"), label: nf.llms }]
|
|
2816
2860
|
: []),
|
|
2817
2861
|
];
|
|
2818
2862
|
|
|
@@ -2824,7 +2868,7 @@ const suggestions = [
|
|
|
2824
2868
|
// Localized routes sit under both bases (\`{deployment.base}/{basePath}/ar/…\`),
|
|
2825
2869
|
// so the script strips the two together, trailing slash included.
|
|
2826
2870
|
const { basePath } = data.config;
|
|
2827
|
-
const siteRoot =
|
|
2871
|
+
const siteRoot = withMountedBase(mountBasePath(basePath, "/"));
|
|
2828
2872
|
const localeBase = siteRoot.endsWith("/") ? siteRoot : siteRoot + "/";
|
|
2829
2873
|
const localized = Object.fromEntries(
|
|
2830
2874
|
(i18n?.locales ?? [])
|
|
@@ -2837,7 +2881,7 @@ const localized = Object.fromEntries(
|
|
|
2837
2881
|
description: strings.description,
|
|
2838
2882
|
dir: l.dir ?? "ltr",
|
|
2839
2883
|
home: strings.home,
|
|
2840
|
-
homeHref:
|
|
2884
|
+
homeHref: withMountedBase(mountBasePath(basePath, "/" + l.code)),
|
|
2841
2885
|
suggestions: strings.suggestions,
|
|
2842
2886
|
title: strings.title,
|
|
2843
2887
|
},
|
|
@@ -2887,7 +2931,7 @@ const localizedJson = JSON.stringify(localized).replaceAll("<", "\\\\u003c");
|
|
|
2887
2931
|
<a
|
|
2888
2932
|
class="mt-8 inline-flex items-center gap-1.5 rounded-full bg-accent py-2 pe-4 ps-3.5 text-sm font-medium text-accent-foreground transition-opacity hover:opacity-90"
|
|
2889
2933
|
data-nf-home
|
|
2890
|
-
href={
|
|
2934
|
+
href={withMountedBase("/")}
|
|
2891
2935
|
>
|
|
2892
2936
|
<Icon class="rtl:-scale-x-100" name="arrow-left" size={14} />
|
|
2893
2937
|
<span data-nf="home">{nf.home}</span>
|
|
@@ -2969,7 +3013,7 @@ const localizedJson = JSON.stringify(localized).replaceAll("<", "\\\\u003c");
|
|
|
2969
3013
|
*/
|
|
2970
3014
|
export const notFoundMarkdownTemplate =
|
|
2971
3015
|
(): string => `// Generated by Blume. Do not edit. Override by adding \`pages/404.astro\`.
|
|
2972
|
-
import {
|
|
3016
|
+
import { withMountedBase } from "blume/components/islands/base-path.ts";
|
|
2973
3017
|
import { absoluteUrl } from "blume/core/site-url.ts";
|
|
2974
3018
|
import data from "blume:data";
|
|
2975
3019
|
|
|
@@ -2980,7 +3024,7 @@ const nf = data.ui.notFound;
|
|
|
2980
3024
|
// Absolute for internal routes when the site is known; an external tab href
|
|
2981
3025
|
// passes through untouched.
|
|
2982
3026
|
const href = (path: string): string => {
|
|
2983
|
-
const based =
|
|
3027
|
+
const based = withMountedBase(path);
|
|
2984
3028
|
return data.config.site && based.startsWith("/") && !based.startsWith("//")
|
|
2985
3029
|
? absoluteUrl(data.config.site, based)
|
|
2986
3030
|
: based;
|
|
@@ -3039,7 +3083,7 @@ export function GET() {
|
|
|
3039
3083
|
export const notFoundJsonTemplate =
|
|
3040
3084
|
(): string => `// Generated by Blume. Do not edit. Override by adding \`pages/404.astro\`.
|
|
3041
3085
|
import { problem } from "blume/ai/api/problem.ts";
|
|
3042
|
-
import {
|
|
3086
|
+
import { withMountedBase } from "blume/components/islands/base-path.ts";
|
|
3043
3087
|
import { absoluteUrl } from "blume/core/site-url.ts";
|
|
3044
3088
|
import data from "blume:data";
|
|
3045
3089
|
|
|
@@ -3050,7 +3094,7 @@ const nf = data.ui.notFound;
|
|
|
3050
3094
|
// Absolute for internal routes when the site is known; an external tab href
|
|
3051
3095
|
// passes through untouched.
|
|
3052
3096
|
const href = (path: string): string => {
|
|
3053
|
-
const based =
|
|
3097
|
+
const based = withMountedBase(path);
|
|
3054
3098
|
return data.config.site && based.startsWith("/") && !based.startsWith("//")
|
|
3055
3099
|
? absoluteUrl(data.config.site, based)
|
|
3056
3100
|
: based;
|
|
@@ -3225,7 +3269,7 @@ ${wrapperPropsType("Example")}
|
|
|
3225
3269
|
/**
|
|
3226
3270
|
* The route prefix `<Component />` preview frames are served under:
|
|
3227
3271
|
* `{basePath}/blume-examples/<example path>`. `deployment.base` is layered on
|
|
3228
|
-
* top by Astro (components apply it with `
|
|
3272
|
+
* top by Astro (components apply it with `withMountedBase`).
|
|
3229
3273
|
*/
|
|
3230
3274
|
export const examplesRouteBase = (basePath: string): string =>
|
|
3231
3275
|
`${basePath}/blume-examples`;
|
|
@@ -14,6 +14,9 @@ import { isServed, normalizePath, resolveHref, siteOrigin } from "../url.ts";
|
|
|
14
14
|
/** Browser-magic fragments that scroll without needing a matching id. */
|
|
15
15
|
const MAGIC_FRAGMENTS = new Set(["", "top"]);
|
|
16
16
|
|
|
17
|
+
/** Where a fragment's directives (text fragments) start. */
|
|
18
|
+
const FRAGMENT_DIRECTIVE = ":~:";
|
|
19
|
+
|
|
17
20
|
/** Whether a fragment lands on an id of the target page. */
|
|
18
21
|
const anchorResolves = (target: PageSnapshot, fragment: string): boolean => {
|
|
19
22
|
if (MAGIC_FRAGMENTS.has(fragment)) {
|
|
@@ -65,8 +68,12 @@ export const linkChecks: CheckModule = {
|
|
|
65
68
|
page: PageSnapshot,
|
|
66
69
|
link: SnapshotLink,
|
|
67
70
|
target: PageSnapshot,
|
|
68
|
-
|
|
71
|
+
hash: string
|
|
69
72
|
): void => {
|
|
73
|
+
// A text fragment (`#:~:text=…`) is a directive the browser strips
|
|
74
|
+
// before it looks for an id; only the part in front of `:~:` names an
|
|
75
|
+
// element, and an empty one scrolls to the text instead.
|
|
76
|
+
const [fragment = ""] = hash.split(FRAGMENT_DIRECTIVE);
|
|
70
77
|
if (anchorResolves(target, fragment)) {
|
|
71
78
|
return;
|
|
72
79
|
}
|
package/src/audit/graph.ts
CHANGED
|
@@ -58,7 +58,8 @@ export const buildGraph = (
|
|
|
58
58
|
* Non-indexable pages are excluded — a `noindex` page is *meant* to be
|
|
59
59
|
* unreachable — as is the home page, which is nobody's job to link to. Under a
|
|
60
60
|
* `basePath` the home page's built URL is the base itself, so the caller names
|
|
61
|
-
* it via `homeUrl`.
|
|
61
|
+
* it via `homeUrl`. An i18n fallback copy is excluded too: it stands in for a
|
|
62
|
+
* translation nobody has written yet, so the page to link to is the original.
|
|
62
63
|
*/
|
|
63
64
|
export const orphanPages = (
|
|
64
65
|
pages: PageSnapshot[],
|
|
@@ -68,6 +69,7 @@ export const orphanPages = (
|
|
|
68
69
|
pages.filter(
|
|
69
70
|
(page) =>
|
|
70
71
|
page.indexable &&
|
|
72
|
+
page.route?.fallback !== true &&
|
|
71
73
|
page.url !== "/" &&
|
|
72
74
|
page.url !== homeUrl &&
|
|
73
75
|
(graph.contentIn.get(page.url)?.size ?? 0) === 0
|
package/src/audit/run.ts
CHANGED
|
@@ -2,10 +2,12 @@ import { readFile } from "node:fs/promises";
|
|
|
2
2
|
|
|
3
3
|
import pMap from "p-map";
|
|
4
4
|
|
|
5
|
-
import { normalizeBasePath
|
|
5
|
+
import { normalizeBasePath } from "../core/base-path.ts";
|
|
6
|
+
import { routeSetFor } from "../core/locale-links.ts";
|
|
6
7
|
import type { BlumeProject } from "../core/project-graph.ts";
|
|
7
8
|
import type { Diagnostic } from "../core/types.ts";
|
|
8
9
|
import { deployStaticDir } from "../deploy/adapter-output.ts";
|
|
10
|
+
import { applyBaseToAstroRedirects } from "../deploy/redirects.ts";
|
|
9
11
|
import { CHECKS } from "./catalog.ts";
|
|
10
12
|
import type { CheckId } from "./catalog.ts";
|
|
11
13
|
import { assetChecks } from "./checks/assets.ts";
|
|
@@ -162,14 +164,15 @@ export const runAudit = async (options: AuditOptions): Promise<AuditResult> => {
|
|
|
162
164
|
redirects: resolveRedirects(
|
|
163
165
|
// Redirects are authored as if mounted at root; the built page URLs they
|
|
164
166
|
// are checked against carry `basePath` (it's a real directory in the
|
|
165
|
-
// build), so both sides gain it here
|
|
166
|
-
// `
|
|
167
|
-
//
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
167
|
+
// build), so both sides gain it here, based exactly as the build bases
|
|
168
|
+
// them — a `to` naming a public file stays at the root, and external
|
|
169
|
+
// `to` URLs pass through.
|
|
170
|
+
applyBaseToAstroRedirects(
|
|
171
|
+
project.config.redirects,
|
|
172
|
+
basePath,
|
|
173
|
+
"",
|
|
174
|
+
routeSetFor(project.manifest.routes)
|
|
175
|
+
),
|
|
173
176
|
// Pages, static files, and server routes alike: a redirect may
|
|
174
177
|
// legitimately land on a served asset (`/old-whitepaper` ->
|
|
175
178
|
// `/files/whitepaper.pdf`), and the same predicate the link and llms.txt
|
package/src/audit/snapshot.ts
CHANGED
|
@@ -196,8 +196,13 @@ export const buildSnapshot = (options: {
|
|
|
196
196
|
scripts: collectAssets(document, "script[src]", "src"),
|
|
197
197
|
source: route?.sourcePath,
|
|
198
198
|
styles: collectAssets(document, 'link[rel="stylesheet"][href]', "href"),
|
|
199
|
+
// Only the document's own titles: an accessible inline SVG carries a
|
|
200
|
+
// `<title>` of its own (an SVG-namespace element, not the page title), and
|
|
201
|
+
// counting it would flag BLUME_AUDIT_TITLE_MULTIPLE — or stand in for a
|
|
202
|
+
// head `<title>` that is genuinely missing.
|
|
199
203
|
titles: document
|
|
200
204
|
.querySelectorAll("title")
|
|
205
|
+
.filter((title) => !title.closest("svg"))
|
|
201
206
|
.map((title) => title.text.trim())
|
|
202
207
|
.filter((text) => text.length > 0),
|
|
203
208
|
twitter: prefixedMeta(document, "name", "twitter:"),
|
package/src/cli/args.ts
CHANGED
|
@@ -21,3 +21,35 @@ export const parsePort = (value?: string): number | undefined => {
|
|
|
21
21
|
}
|
|
22
22
|
return port;
|
|
23
23
|
};
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* The longest `--timeout` a timer can hold, in seconds. Node clamps a
|
|
27
|
+
* `setTimeout` delay above 2^31−1 ms (about 24.8 days) to 1 ms, so a larger
|
|
28
|
+
* value would time every agent out the moment it starts.
|
|
29
|
+
*/
|
|
30
|
+
export const MAX_TIMEOUT_S = Math.floor(2_147_483_647 / 1000);
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Parse a `--timeout` value in whole seconds, or return `fallback` when unset.
|
|
34
|
+
* A non-integer, non-positive, or too-long value exits with an error.
|
|
35
|
+
*/
|
|
36
|
+
export const parseTimeoutSeconds = (
|
|
37
|
+
value: string | undefined,
|
|
38
|
+
fallback: number
|
|
39
|
+
): number => {
|
|
40
|
+
if (value === undefined) {
|
|
41
|
+
return fallback;
|
|
42
|
+
}
|
|
43
|
+
const timeout = Number(value);
|
|
44
|
+
if (!Number.isInteger(timeout) || timeout <= 0) {
|
|
45
|
+
logger.error(`Invalid --timeout "${value}" (whole seconds).`);
|
|
46
|
+
process.exit(1);
|
|
47
|
+
}
|
|
48
|
+
if (timeout > MAX_TIMEOUT_S) {
|
|
49
|
+
logger.error(
|
|
50
|
+
`Invalid --timeout "${value}" (at most ${MAX_TIMEOUT_S} seconds, about 24 days).`
|
|
51
|
+
);
|
|
52
|
+
process.exit(1);
|
|
53
|
+
}
|
|
54
|
+
return timeout;
|
|
55
|
+
};
|
|
@@ -238,7 +238,10 @@ export const auditCommand = defineCommand({
|
|
|
238
238
|
process.stderr.write(formatReport(result, root, { verbose: args.verbose }));
|
|
239
239
|
|
|
240
240
|
if (shouldFail(result, gate)) {
|
|
241
|
-
process.exit
|
|
241
|
+
// Set the code and return rather than `process.exit`, which doesn't wait
|
|
242
|
+
// for a piped stderr: a long report was cut off after the first 64 KiB,
|
|
243
|
+
// in exactly the CI logs that need to show every finding.
|
|
244
|
+
process.exitCode = 1;
|
|
242
245
|
}
|
|
243
246
|
},
|
|
244
247
|
});
|
|
@@ -187,7 +187,9 @@ export const doctorCommand = defineCommand({
|
|
|
187
187
|
logger.success("No problems found.");
|
|
188
188
|
}
|
|
189
189
|
if (hadErrors) {
|
|
190
|
-
process.exit
|
|
190
|
+
// Set the code and return rather than `process.exit`, which doesn't wait
|
|
191
|
+
// for a piped stderr: a long diagnostic list would be cut off.
|
|
192
|
+
process.exitCode = 1;
|
|
191
193
|
}
|
|
192
194
|
},
|
|
193
195
|
});
|