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
package/package.json
CHANGED
|
@@ -92,7 +92,7 @@ Trailing heading markers — `[#custom-id]` (pinned anchor), `[!toc]` (hide from
|
|
|
92
92
|
|
|
93
93
|
## i18n
|
|
94
94
|
|
|
95
|
-
A `loader({ i18n })` setup (locale-suffixed files or locale dirs) → Blume `i18n: { defaultLocale, locales: [{ code, label }] }`. Locale **directories** match Blume's `dir` parser as-is; locale **file suffixes** (`page.cn.mdx`)
|
|
95
|
+
A `loader({ i18n })` setup (locale-suffixed files or locale dirs) → Blume `i18n: { defaultLocale, locales: [{ code, label }] }`. Locale **directories** match Blume's default `dir` parser as-is; locale **file suffixes** (`page.cn.mdx` beside an unsuffixed default-locale `page.mdx`) match the `dot` parser as-is — add `parser: "dot"`, no file moves. Under `dot` a folder's `meta.ts` serves every locale, so build it from the default locale's `meta.json` and report any translated folder titles in `meta.<lang>.json`. Report whichever transform you apply.
|
|
96
96
|
|
|
97
97
|
## Package.json & teardown
|
|
98
98
|
|
|
@@ -64,7 +64,7 @@ Nextra enables math via `nextra({ latex: true })` (KaTeX or MathJax). In Blume,
|
|
|
64
64
|
|
|
65
65
|
## i18n
|
|
66
66
|
|
|
67
|
-
- **v2/v3:** locale **file suffixes** (`index.en.mdx`, `index.zh.mdx`) + `i18n` in `next.config` →
|
|
67
|
+
- **v2/v3:** locale **file suffixes** (`index.en.mdx`, `index.zh.mdx`) + `i18n` in `next.config` → match Blume's `dot` parser as-is, default-locale suffix included: `i18n: { defaultLocale, locales: [{ code, label }], parser: "dot" }`, no file moves. Page titles land in each file's own frontmatter, so they stay per-locale, but under `dot` a folder's `meta.ts` serves every locale — build it from the default locale's `_meta` and report translated folder titles from the other locales' `_meta` files.
|
|
68
68
|
- **v4:** `content/<lang>/` dirs already match Blume's `dir` parser — map the locale list, no file moves.
|
|
69
69
|
|
|
70
70
|
## Package.json & teardown
|
|
@@ -219,10 +219,15 @@ const topKey = (line) => {
|
|
|
219
219
|
return { key: m.groups.key, value: (m.groups.rest ?? "").trim() };
|
|
220
220
|
};
|
|
221
221
|
|
|
222
|
-
// The line index range [start, endExclusive) of a top-level key's block
|
|
222
|
+
// The line index range [start, endExclusive) of a top-level key's block: its
|
|
223
|
+
// indented lines plus any `- ` sequence items written flush at column 0,
|
|
224
|
+
// which YAML also reads as the key's value (`keywords:` then `- one`).
|
|
223
225
|
const blockRange = (fm, start) => {
|
|
224
226
|
let end = start + 1;
|
|
225
|
-
while (
|
|
227
|
+
while (
|
|
228
|
+
end < fm.length &&
|
|
229
|
+
(fm[end] === "" || /^\s/u.test(fm[end]) || /^-(?:\s|$)/u.test(fm[end]))
|
|
230
|
+
) {
|
|
226
231
|
end += 1;
|
|
227
232
|
}
|
|
228
233
|
// Trim trailing blank lines back out so the gap before the next key survives.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { mountBasePath, normalizeBasePath } from "../core/base-path.ts";
|
|
2
2
|
import { repoUrl } from "../core/github.ts";
|
|
3
3
|
import type { BlumeProject } from "../core/project-graph.ts";
|
|
4
4
|
import type { ContentSignalPolicy, ContentSignals } from "../core/schema.ts";
|
|
@@ -173,7 +173,7 @@ export const buildAgentReadability = (
|
|
|
173
173
|
// `site`; concatenate rather than `new URL()` so the subpath is preserved.
|
|
174
174
|
const deployBase = normalizeBasePath(config.deployment.options.base);
|
|
175
175
|
const abs = (path: string): string => {
|
|
176
|
-
const based =
|
|
176
|
+
const based = mountBasePath(deployBase, path);
|
|
177
177
|
return site ? absoluteUrl(site, based) : based;
|
|
178
178
|
};
|
|
179
179
|
|
package/src/ai/ai-catalog.ts
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import { apiNamePhrase } from "../core/api-name.ts";
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
mountBasePath,
|
|
4
|
+
normalizeBasePath,
|
|
5
|
+
withBasePath,
|
|
6
|
+
} from "../core/base-path.ts";
|
|
3
7
|
import type { ResolvedConfig } from "../core/schema.ts";
|
|
4
8
|
import { absoluteUrl } from "../core/site-url.ts";
|
|
5
9
|
import { resolveReferences } from "../openapi/references.ts";
|
|
@@ -227,7 +231,7 @@ export const buildAiCatalog = (
|
|
|
227
231
|
}
|
|
228
232
|
const deployBase = normalizeBasePath(config.deployment.options.base);
|
|
229
233
|
const abs = (path: string): string =>
|
|
230
|
-
absoluteUrl(site,
|
|
234
|
+
absoluteUrl(site, mountBasePath(deployBase, path));
|
|
231
235
|
const host = publisherHost(site);
|
|
232
236
|
const entries = entrySeeds(config, skills, abs).map((seed): CatalogEntry => {
|
|
233
237
|
const entry: CatalogEntry = {
|
package/src/ai/api/handlers.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { mountBasePath } from "../../core/base-path.ts";
|
|
2
2
|
import { absoluteUrl } from "../../core/site-url.ts";
|
|
3
3
|
import type { Navigation } from "../../core/types.ts";
|
|
4
4
|
import type { McpData, McpRoute } from "../mcp/data.ts";
|
|
@@ -88,7 +88,7 @@ export const jsonResponse = (payload: ApiPayload, status = 200): Response =>
|
|
|
88
88
|
|
|
89
89
|
/** The absolute (or root-relative) URL for a base-less path. */
|
|
90
90
|
const siteUrl = (path: string, context: ApiSiteContext): string => {
|
|
91
|
-
const based =
|
|
91
|
+
const based = mountBasePath(context.base, path);
|
|
92
92
|
return context.site ? absoluteUrl(context.site, based) : based;
|
|
93
93
|
};
|
|
94
94
|
|
package/src/ai/api/spec.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { apiNamePhrase } from "../../core/api-name.ts";
|
|
2
|
-
import {
|
|
2
|
+
import { mountBasePath } from "../../core/base-path.ts";
|
|
3
3
|
import { absoluteUrl, siteRoot } from "../../core/site-url.ts";
|
|
4
4
|
import {
|
|
5
5
|
API_NAVIGATION_PATH,
|
|
@@ -688,7 +688,7 @@ export const buildApiSpec = (input: ApiSpecInput): ApiSpecDocument => {
|
|
|
688
688
|
if (input.site) {
|
|
689
689
|
document.externalDocs = {
|
|
690
690
|
description: `${input.name} documentation`,
|
|
691
|
-
url: absoluteUrl(input.site,
|
|
691
|
+
url: absoluteUrl(input.site, mountBasePath(input.base, "/")),
|
|
692
692
|
};
|
|
693
693
|
}
|
|
694
694
|
return document;
|
package/src/ai/api-catalog.ts
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
mountBasePath,
|
|
3
|
+
normalizeBasePath,
|
|
4
|
+
withBasePath,
|
|
5
|
+
} from "../core/base-path.ts";
|
|
2
6
|
import type { ResolvedConfig } from "../core/schema.ts";
|
|
3
7
|
import { absoluteUrl } from "../core/site-url.ts";
|
|
4
8
|
import { resolveReferences } from "../openapi/references.ts";
|
|
@@ -39,7 +43,7 @@ const linksetEntries = (config: ResolvedConfig): LinksetEntry[] => {
|
|
|
39
43
|
const site = config.deployment.options.site ?? null;
|
|
40
44
|
const deployBase = normalizeBasePath(config.deployment.options.base);
|
|
41
45
|
const abs = (path: string): string => {
|
|
42
|
-
const based =
|
|
46
|
+
const based = mountBasePath(deployBase, path);
|
|
43
47
|
return site ? absoluteUrl(site, based) : based;
|
|
44
48
|
};
|
|
45
49
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { mountBasePath, normalizeBasePath } from "../core/base-path.ts";
|
|
2
2
|
import { EN_UI, resolveUIStrings } from "../core/i18n-ui.ts";
|
|
3
3
|
import type { BlumeProject } from "../core/project-graph.ts";
|
|
4
4
|
import { absoluteUrl } from "../core/site-url.ts";
|
|
@@ -29,7 +29,7 @@ const parseDate = (value: string | undefined): Date | null => {
|
|
|
29
29
|
/** A row's link: absolute under a configured site, like llms.txt. */
|
|
30
30
|
const entryUrl = (project: BlumeProject, route: string): string => {
|
|
31
31
|
const { base, site } = project.config.deployment.options;
|
|
32
|
-
const path =
|
|
32
|
+
const path = mountBasePath(normalizeBasePath(base), route);
|
|
33
33
|
return encodeURI(site ? absoluteUrl(site, path) : path);
|
|
34
34
|
};
|
|
35
35
|
|
package/src/ai/link-headers.ts
CHANGED
|
@@ -33,24 +33,33 @@ import { OPENAPI_PATH } from "./api/paths.ts";
|
|
|
33
33
|
* headers. Targets are
|
|
34
34
|
* root-relative under `deployment.base` — RFC 8288 resolves them against the
|
|
35
35
|
* request URL. Returns null when nothing is advertisable.
|
|
36
|
+
*
|
|
37
|
+
* `surface` is where the header is sent. A deployed site (`"build"`) serves
|
|
38
|
+
* every target. The dev server (`"dev"`) serves the routes — the JSON API's
|
|
39
|
+
* OpenAPI document and the Markdown mirror — but not the files `blume build`
|
|
40
|
+
* writes after the build (the API and AI catalogs, `agent-readability.json`,
|
|
41
|
+
* `llms.txt`), so its header leaves those out rather than point agents at
|
|
42
|
+
* 404s.
|
|
36
43
|
*/
|
|
37
44
|
export const buildHomeLinkHeader = (
|
|
38
45
|
config: ResolvedConfig,
|
|
39
|
-
routePaths: readonly string[]
|
|
46
|
+
routePaths: readonly string[],
|
|
47
|
+
surface: "build" | "dev" = "build"
|
|
40
48
|
): string | null => {
|
|
41
49
|
const deployBase = normalizeBasePath(config.deployment.options.base);
|
|
50
|
+
const artifacts = surface === "build";
|
|
42
51
|
const links: string[] = [];
|
|
43
52
|
// RFC 9727 §3: the api-catalog relation is how a homepage advertises the
|
|
44
53
|
// well-known catalog. Its type carries the RFC 9727 profile, whose quotes
|
|
45
54
|
// are escaped inside the quoted `type` value (RFC 8288 quoted-string).
|
|
46
|
-
if (hasApiCatalog(config)) {
|
|
55
|
+
if (artifacts && hasApiCatalog(config)) {
|
|
47
56
|
links.push(
|
|
48
57
|
`<${deployBase}${API_CATALOG_PATH}>; rel="api-catalog"; type="${API_CATALOG_TYPE.replaceAll('"', String.raw`\"`)}"`
|
|
49
58
|
);
|
|
50
59
|
}
|
|
51
60
|
// The ai-catalog spec's own relation for its well-known document, the
|
|
52
61
|
// header form of the `<link rel="ai-catalog">` every page carries.
|
|
53
|
-
if (hasAiCatalog(config)) {
|
|
62
|
+
if (artifacts && hasAiCatalog(config)) {
|
|
54
63
|
links.push(
|
|
55
64
|
`<${deployBase}${AI_CATALOG_PATH}>; rel="ai-catalog"; type="${AI_CATALOG_TYPE}"`
|
|
56
65
|
);
|
|
@@ -62,12 +71,12 @@ export const buildHomeLinkHeader = (
|
|
|
62
71
|
`<${deployBase}${OPENAPI_PATH}>; rel="service-desc"; type="application/json"`
|
|
63
72
|
);
|
|
64
73
|
}
|
|
65
|
-
if (config.agents.agentReadability) {
|
|
74
|
+
if (artifacts && config.agents.agentReadability) {
|
|
66
75
|
links.push(
|
|
67
76
|
`<${deployBase}/agent-readability.json>; rel="describedby"; type="application/json"`
|
|
68
77
|
);
|
|
69
78
|
}
|
|
70
|
-
if (config.agents.llmsTxt.enabled) {
|
|
79
|
+
if (artifacts && config.agents.llmsTxt.enabled) {
|
|
71
80
|
links.push(
|
|
72
81
|
`<${deployBase}/llms.txt>; rel="describedby"; type="text/plain"`
|
|
73
82
|
);
|
package/src/ai/llms.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { mountBasePath, normalizeBasePath } from "../core/base-path.ts";
|
|
2
2
|
import { rewriteRelativeImages } from "../core/content-assets.ts";
|
|
3
3
|
import matter from "../core/frontmatter.ts";
|
|
4
4
|
import type { BlumeProject } from "../core/project-graph.ts";
|
|
@@ -17,14 +17,22 @@ import type { SkillArtifact } from "./skills.ts";
|
|
|
17
17
|
import { applyAgentVisibility } from "./visibility.ts";
|
|
18
18
|
|
|
19
19
|
// Routes carry `basePath`; a `deployment.base` subdirectory is layered on top —
|
|
20
|
-
// with or without a `site` (the mcp.json convention)
|
|
21
|
-
//
|
|
22
|
-
//
|
|
20
|
+
// with or without a `site` (the mcp.json convention), and even over a route
|
|
21
|
+
// that starts with the base's own name — so the emitted URL matches where the
|
|
22
|
+
// page is served. Encoded like the sitemap: a route with spaces or non-ASCII
|
|
23
|
+
// must still yield a valid Markdown link.
|
|
23
24
|
const pageUrl = (route: string, site?: string, base = ""): string => {
|
|
24
|
-
const path =
|
|
25
|
+
const path = mountBasePath(base, route);
|
|
25
26
|
return encodeURI(site ? absoluteUrl(site, path) : path);
|
|
26
27
|
};
|
|
27
28
|
|
|
29
|
+
/**
|
|
30
|
+
* A title as Markdown link text. An unescaped bracket in it — `[Beta] Webhooks`
|
|
31
|
+
* — closes or nests the text early, and the line stops being a link.
|
|
32
|
+
*/
|
|
33
|
+
const linkText = (title: string): string =>
|
|
34
|
+
title.replaceAll(/[\\[\]]/gu, String.raw`\$&`);
|
|
35
|
+
|
|
28
36
|
/** Inputs only the build has: the published skills, collected once per build. */
|
|
29
37
|
export interface LlmsIndexOptions {
|
|
30
38
|
/**
|
|
@@ -176,7 +184,7 @@ export const buildLlmsIndex = (
|
|
|
176
184
|
const line = (page: PageRecord): string => {
|
|
177
185
|
seen.add(page.route);
|
|
178
186
|
const summary = page.description ? `: ${page.description}` : "";
|
|
179
|
-
return `- [${page.title}](${pageUrl(page.route, site, base)})${summary}`;
|
|
187
|
+
return `- [${linkText(page.title)}](${pageUrl(page.route, site, base)})${summary}`;
|
|
180
188
|
};
|
|
181
189
|
|
|
182
190
|
// One nav level -> Markdown blocks: the level's loose pages as a link list,
|
|
@@ -259,7 +267,10 @@ export const buildLlmsIndex = (
|
|
|
259
267
|
blocks.push(
|
|
260
268
|
"## RSS Feeds",
|
|
261
269
|
feeds
|
|
262
|
-
.map(
|
|
270
|
+
.map(
|
|
271
|
+
(feed) =>
|
|
272
|
+
`- [${linkText(feed.title)}](${pageUrl(feed.path, site, base)})`
|
|
273
|
+
)
|
|
263
274
|
.join("\n")
|
|
264
275
|
);
|
|
265
276
|
}
|
package/src/ai/mcp/discovery.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { mountBasePath } from "../../core/base-path.ts";
|
|
2
2
|
import { absoluteUrl, siteRoot } from "../../core/site-url.ts";
|
|
3
3
|
import { trimChar } from "../../core/trim.ts";
|
|
4
4
|
import { MCP_TOOLS } from "./tools.ts";
|
|
@@ -18,7 +18,7 @@ export interface McpDiscoveryInput {
|
|
|
18
18
|
const serverUrl = (input: McpDiscoveryInput): string => {
|
|
19
19
|
// The endpoint is a generated Astro page, so it's served under
|
|
20
20
|
// `deployment.base` like every other route (the sitemap/llms.txt convention).
|
|
21
|
-
const path =
|
|
21
|
+
const path = mountBasePath(input.base, input.route);
|
|
22
22
|
// Concatenate rather than `new URL(path, site)` — a root-absolute path
|
|
23
23
|
// would drop the base path of a subpath deployment (`acme.com/docs`).
|
|
24
24
|
return input.site ? absoluteUrl(input.site, path) : path;
|
package/src/ai/mcp/query.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
|
|
3
3
|
import {
|
|
4
|
+
mountBasePath,
|
|
4
5
|
normalizeRoute as normalizePageRoute,
|
|
5
6
|
stripBasePath,
|
|
6
|
-
withBasePath,
|
|
7
7
|
} from "../../core/base-path.ts";
|
|
8
8
|
import { absoluteUrl } from "../../core/site-url.ts";
|
|
9
9
|
import { trimEnd } from "../../core/trim.ts";
|
|
@@ -244,7 +244,7 @@ export const normalizeRoute = (input: string, data: McpData): string => {
|
|
|
244
244
|
export const urlFor = (route: string, data: McpData): string => {
|
|
245
245
|
// Routes are base-less manifest paths; layer `deployment.base` on top so the
|
|
246
246
|
// URL matches where the page is served (the sitemap/llms.txt convention).
|
|
247
|
-
const path =
|
|
247
|
+
const path = mountBasePath(data.base, route);
|
|
248
248
|
// Concatenate rather than `new URL(path, site)` — a root-absolute path
|
|
249
249
|
// would drop the base path of a subpath deployment (`acme.com/docs`).
|
|
250
250
|
return data.site ? absoluteUrl(data.site, path) : path;
|
package/src/ai/mcp/server.ts
CHANGED
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
} from "@modelcontextprotocol/sdk/types.js";
|
|
11
11
|
import { z } from "zod";
|
|
12
12
|
|
|
13
|
-
import {
|
|
13
|
+
import { mountBasePath } from "../../core/base-path.ts";
|
|
14
14
|
import { readCappedText } from "../../core/request-body.ts";
|
|
15
15
|
import type { McpData } from "./data.ts";
|
|
16
16
|
import {
|
|
@@ -104,7 +104,7 @@ interface PageResource {
|
|
|
104
104
|
const resourceUri = (route: string, data: McpData): string =>
|
|
105
105
|
data.site
|
|
106
106
|
? urlFor(route, data)
|
|
107
|
-
: `${LOCAL_RESOURCE_SCHEME}${
|
|
107
|
+
: `${LOCAL_RESOURCE_SCHEME}${mountBasePath(data.base, route)}`;
|
|
108
108
|
|
|
109
109
|
/** The `pages` key a resource URI (either form, or a bare route) names. */
|
|
110
110
|
const resourceRoute = (uri: string, data: McpData): string =>
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { mountBasePath, normalizeBasePath } from "../core/base-path.ts";
|
|
1
2
|
import type { ApiOperationRef, OpenApiData } from "../openapi/model.ts";
|
|
2
3
|
import { operationOf, specAddresses, specOf } from "../openapi/model.ts";
|
|
3
4
|
import { asSentence } from "../openapi/sentence.ts";
|
|
@@ -20,10 +21,14 @@ const inlineText = (text: string): string =>
|
|
|
20
21
|
.replaceAll(/\s+/gu, " ")
|
|
21
22
|
.replaceAll(/[\\`*_[\]<>~]/gu, String.raw`\$&`);
|
|
22
23
|
|
|
23
|
-
/**
|
|
24
|
+
/**
|
|
25
|
+
* One operation as a link, for the tag listings. `href` is the operation's
|
|
26
|
+
* served URL: its route under the deployment base.
|
|
27
|
+
*/
|
|
24
28
|
const listItem = (
|
|
25
29
|
signature: string,
|
|
26
|
-
|
|
30
|
+
href: string,
|
|
31
|
+
operation: Pick<ApiOperationRef, "deprecated" | "summary">
|
|
27
32
|
): string => {
|
|
28
33
|
const tail = [
|
|
29
34
|
// Title-like summaries ("Get a flag") need their own period before the
|
|
@@ -33,7 +38,7 @@ const listItem = (
|
|
|
33
38
|
]
|
|
34
39
|
.filter(Boolean)
|
|
35
40
|
.join(" ");
|
|
36
|
-
return `- [${inlineCode(signature)}](${linkDestination(
|
|
41
|
+
return `- [${inlineCode(signature)}](${linkDestination(href)})${tail ? ` — ${tail}` : ""}`;
|
|
37
42
|
};
|
|
38
43
|
|
|
39
44
|
/**
|
|
@@ -60,9 +65,16 @@ const listItem = (
|
|
|
60
65
|
* endpoint and what it does is the part that was missing altogether.
|
|
61
66
|
*
|
|
62
67
|
* `specs` is the parsed `blume:openapi` data — empty when the project has no
|
|
63
|
-
* API reference, in which case every serializer declines.
|
|
68
|
+
* API reference, in which case every serializer declines. `deployBase` is the
|
|
69
|
+
* configured `deployment.base`: operation routes carry `basePath` but not it,
|
|
70
|
+
* and the links written here are made after the agent surfaces rebase the
|
|
71
|
+
* page's own links, so they take it here, as the rendered page's do.
|
|
64
72
|
*/
|
|
65
|
-
export const openapiComponentSerializers = (
|
|
73
|
+
export const openapiComponentSerializers = (
|
|
74
|
+
specs: OpenApiData,
|
|
75
|
+
deployBase?: string
|
|
76
|
+
) => {
|
|
77
|
+
const base = normalizeBasePath(deployBase);
|
|
66
78
|
const spec = (source: EvaluatedValue) =>
|
|
67
79
|
isString(source) ? specOf(specs, source) : undefined;
|
|
68
80
|
|
|
@@ -97,7 +109,11 @@ export const openapiComponentSerializers = (specs: OpenApiData) => {
|
|
|
97
109
|
const items = Object.values(data.operations)
|
|
98
110
|
.filter((operation) => operation.tagSlug === tag)
|
|
99
111
|
.map((operation) =>
|
|
100
|
-
listItem(
|
|
112
|
+
listItem(
|
|
113
|
+
operationSignature(data, operation),
|
|
114
|
+
mountBasePath(base, operation.route),
|
|
115
|
+
operation
|
|
116
|
+
)
|
|
101
117
|
);
|
|
102
118
|
return items.length > 0 ? items.join("\n") : null;
|
|
103
119
|
},
|
package/src/ai/relative-links.ts
CHANGED
|
@@ -2,9 +2,10 @@ import { basename } from "pathe";
|
|
|
2
2
|
|
|
3
3
|
import {
|
|
4
4
|
isInternalPath,
|
|
5
|
+
mountBasePath,
|
|
5
6
|
normalizeBasePath,
|
|
7
|
+
withAuthoredBasePath,
|
|
6
8
|
withBasePath,
|
|
7
|
-
withComposedBasePath,
|
|
8
9
|
} from "../core/base-path.ts";
|
|
9
10
|
import { nextFenceState } from "../core/code-fences.ts";
|
|
10
11
|
import type { FenceState } from "../core/code-fences.ts";
|
|
@@ -16,7 +17,7 @@ import {
|
|
|
16
17
|
routeOfLinkedFile,
|
|
17
18
|
} from "../core/links.ts";
|
|
18
19
|
import type { RelativeLinkBase } from "../core/links.ts";
|
|
19
|
-
import { localizeHref } from "../core/locale-links.ts";
|
|
20
|
+
import { localizeHref, servesRoute } from "../core/locale-links.ts";
|
|
20
21
|
import type { BlumeProject } from "../core/project-graph.ts";
|
|
21
22
|
import { extractLinks } from "../core/sources/normalize.ts";
|
|
22
23
|
|
|
@@ -27,14 +28,16 @@ import { extractLinks } from "../core/sources/normalize.ts";
|
|
|
27
28
|
* link lands on `/install`; the rendered page rewrites it to the route it
|
|
28
29
|
* means (`markdown/relative-links.ts`), and the Markdown an agent reads gets
|
|
29
30
|
* the same rewrite from the same resolver (`resolveRelativeHref`), so both
|
|
30
|
-
* point at one page. Root-relative
|
|
31
|
-
* `<Card href="/guide"
|
|
32
|
-
*
|
|
33
|
-
* `components/content/base-href.ts`)
|
|
31
|
+
* point at one page. Root-relative links (`[x](/guide)`,
|
|
32
|
+
* `<Card href="/guide">`, ``) get what the rendered page
|
|
33
|
+
* gives them too (`markdown/base-links.ts`,
|
|
34
|
+
* `components/content/base-href.ts`): a page link the `deployment.base` +
|
|
35
|
+
* `basePath` prefix, a public file (`/spec.pdf`) or image the
|
|
36
|
+
* `deployment.base` alone, and, on a page in a prefixed locale, a page link
|
|
34
37
|
* that locale's copy of the route when it is served
|
|
35
|
-
* (`components/layout/LocaleLinks.astro`). Inline links, component
|
|
36
|
-
* and reference definitions are rewritten; fenced and inline code,
|
|
37
|
-
* asset links, and external URLs are left as written.
|
|
38
|
+
* (`components/layout/LocaleLinks.astro`). Inline links, images, component
|
|
39
|
+
* `href`s, and reference definitions are rewritten; fenced and inline code,
|
|
40
|
+
* relative images and asset links, and external URLs are left as written.
|
|
38
41
|
*/
|
|
39
42
|
|
|
40
43
|
/** The page a Markdown text was written for. */
|
|
@@ -67,13 +70,6 @@ const MAYBE_RELATIVE =
|
|
|
67
70
|
const MAYBE_PAGE_LINK =
|
|
68
71
|
/\]\([\t ]*<?(?![a-z][\d+.a-z-]*:|\/\/|#)|^ {0,3}\[[^\]\n]+\]:[\t ]*<?(?![a-z][\d+.a-z-]*:|\/\/|#)|\shref=["'](?![a-z][\d+.a-z-]*:|\/\/|#)/imu;
|
|
69
72
|
|
|
70
|
-
/**
|
|
71
|
-
* A path whose final segment carries a file extension — a `public/` asset or
|
|
72
|
-
* a raw `.md` twin, which the rendered page neither bases nor localizes (see
|
|
73
|
-
* `markdown/base-links.ts`).
|
|
74
|
-
*/
|
|
75
|
-
const ASSET_PATH = /\.[\da-z]+$/iu;
|
|
76
|
-
|
|
77
73
|
interface Splice {
|
|
78
74
|
column: number;
|
|
79
75
|
length: number;
|
|
@@ -109,18 +105,23 @@ const definitionSplice = (
|
|
|
109
105
|
return { column: lead.length + angled, length: target.length, text: route };
|
|
110
106
|
};
|
|
111
107
|
|
|
112
|
-
/**
|
|
108
|
+
/**
|
|
109
|
+
* Every routed splice in `text`, by 0-based line: links and images, then
|
|
110
|
+
* definitions. An image target goes through `imaged`, everything else through
|
|
111
|
+
* `routed`.
|
|
112
|
+
*/
|
|
113
113
|
const collectSplices = (
|
|
114
114
|
text: string,
|
|
115
115
|
lines: readonly string[],
|
|
116
|
-
routed: (target: string) => string | undefined
|
|
116
|
+
routed: (target: string) => string | undefined,
|
|
117
|
+
imaged: (target: string) => string | undefined
|
|
117
118
|
): Map<number, Splice[]> => {
|
|
118
119
|
const splices = new Map<number, Splice[]>();
|
|
119
120
|
const add = (index: number, splice: Splice): void => {
|
|
120
121
|
splices.set(index, [...(splices.get(index) ?? []), splice]);
|
|
121
122
|
};
|
|
122
123
|
for (const link of extractLinks(text)) {
|
|
123
|
-
const route = link.image ?
|
|
124
|
+
const route = (link.image ? imaged : routed)(link.target);
|
|
124
125
|
if (route !== undefined) {
|
|
125
126
|
add(link.line - 1, {
|
|
126
127
|
column: link.column - 1,
|
|
@@ -186,14 +187,19 @@ export const relativeLinkRewriter = (
|
|
|
186
187
|
const navPath = navPathBySource.get(sourcePath) ?? basename(sourcePath);
|
|
187
188
|
const resolveFile = (path: string): string | undefined =>
|
|
188
189
|
routeOfLinkedFile(fileRoutes, { navPath, sourcePath }, path);
|
|
190
|
+
// A resolved route is one Blume serves, so the deployment base goes on
|
|
191
|
+
// unconditionally — even over a route that starts with the base's name.
|
|
189
192
|
return (target) => {
|
|
190
193
|
const route = resolveRelativeHref(target, from, resolveFile, (path) =>
|
|
191
194
|
routes.has(path)
|
|
192
195
|
);
|
|
193
|
-
return route === undefined ? undefined :
|
|
196
|
+
return route === undefined ? undefined : mountBasePath(deployBase, route);
|
|
194
197
|
};
|
|
195
198
|
};
|
|
196
199
|
|
|
200
|
+
/** Whether a page is served at a based, fragment-less path. */
|
|
201
|
+
const servesPage = (route: string): boolean => servesRoute(routes, route);
|
|
202
|
+
|
|
197
203
|
return (text, page) => {
|
|
198
204
|
const locale = localeByRoute.get(page.route);
|
|
199
205
|
// The page's locale moves root links only when it has a URL prefix.
|
|
@@ -212,21 +218,31 @@ export const relativeLinkRewriter = (
|
|
|
212
218
|
return text;
|
|
213
219
|
}
|
|
214
220
|
const relative = relativeRoute(page);
|
|
215
|
-
// What the rendered page makes of `/guide`: based
|
|
221
|
+
// What the rendered page makes of `/guide`: based (a public file like
|
|
222
|
+
// `/spec.pdf` under the deployment base alone), then moved into the
|
|
216
223
|
// page's locale when that route is served.
|
|
217
224
|
const rooted = (target: string): string | undefined => {
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
225
|
+
const based = withAuthoredBasePath(
|
|
226
|
+
deployBase,
|
|
227
|
+
basePath,
|
|
228
|
+
target,
|
|
229
|
+
servesPage
|
|
230
|
+
);
|
|
222
231
|
const href = localize ? localizeHref(based, localize) : based;
|
|
223
232
|
return href === target ? undefined : href;
|
|
224
233
|
};
|
|
225
234
|
const routed = (target: string): string | undefined =>
|
|
226
235
|
isInternalPath(target) ? rooted(target) : relative?.(target);
|
|
236
|
+
// An image is always a file: `public/` (or a generated asset endpoint),
|
|
237
|
+
// served under the deployment base but never `basePath`. A relative one
|
|
238
|
+
// is left to `core/content-assets.ts`.
|
|
239
|
+
const imaged = (target: string): string | undefined => {
|
|
240
|
+
const based = withBasePath(deployBase, target);
|
|
241
|
+
return based === target ? undefined : based;
|
|
242
|
+
};
|
|
227
243
|
|
|
228
244
|
const lines = text.split("\n");
|
|
229
|
-
const splices = collectSplices(text, lines, routed);
|
|
245
|
+
const splices = collectSplices(text, lines, routed, imaged);
|
|
230
246
|
|
|
231
247
|
return lines
|
|
232
248
|
.map((line, index) => {
|
package/src/ai/serializers.ts
CHANGED
|
@@ -18,7 +18,8 @@ export const projectComponentSerializers = (
|
|
|
18
18
|
({
|
|
19
19
|
...exampleComponentSerializers(project.examples ?? {}),
|
|
20
20
|
...openapiComponentSerializers(
|
|
21
|
-
project.sources.find(isOpenApiSource)?.openApiData() ?? {}
|
|
21
|
+
project.sources.find(isOpenApiSource)?.openApiData() ?? {},
|
|
22
|
+
project.config.deployment.options.base
|
|
22
23
|
),
|
|
23
24
|
...project.config.agents.markdownComponents,
|
|
24
25
|
}) satisfies Record<string, ComponentMarkdown>;
|
package/src/ai/skills.ts
CHANGED
|
@@ -151,11 +151,16 @@ const skillMeta = (raw: string, dirName: string): SkillMetaResult => {
|
|
|
151
151
|
* hold a `SKILL.md` (the layout `npx skills add` consumes). Subdirectories
|
|
152
152
|
* without one are ignored silently — the directory may hold other assets —
|
|
153
153
|
* while a present-but-invalid skill earns a warning so it isn't dropped
|
|
154
|
-
* behind the publisher's back.
|
|
154
|
+
* behind the publisher's back. Two directories declaring the same `name`
|
|
155
|
+
* would publish to the same artifact path under two index entries, so the
|
|
156
|
+
* first directory in sorted order keeps the name and the other is skipped
|
|
157
|
+
* with a warning naming both.
|
|
155
158
|
*/
|
|
156
159
|
export const collectSkills = async (dir: string): Promise<CollectedSkills> => {
|
|
157
160
|
const skills: SkillArtifact[] = [];
|
|
158
161
|
const warnings: string[] = [];
|
|
162
|
+
/** Skill name → the directory that claimed it first. */
|
|
163
|
+
const owners = new Map<string, string>();
|
|
159
164
|
const items = await readdir(dir, { withFileTypes: true });
|
|
160
165
|
for (const item of items.toSorted((a, b) => (a.name < b.name ? -1 : 1))) {
|
|
161
166
|
if (!item.isDirectory() || item.name.startsWith(".")) {
|
|
@@ -177,6 +182,14 @@ export const collectSkills = async (dir: string): Promise<CollectedSkills> => {
|
|
|
177
182
|
}
|
|
178
183
|
continue;
|
|
179
184
|
}
|
|
185
|
+
const owner = owners.get(meta.name);
|
|
186
|
+
if (owner) {
|
|
187
|
+
warnings.push(
|
|
188
|
+
`Skills "${owner}" and "${item.name}" both declare the name "${meta.name}"; publishing "${owner}" and skipping "${item.name}".`
|
|
189
|
+
);
|
|
190
|
+
continue;
|
|
191
|
+
}
|
|
192
|
+
owners.set(meta.name, item.name);
|
|
180
193
|
// A lone SKILL.md ships verbatim; supporting resources ship as an
|
|
181
194
|
// archive so the skill's relative references resolve after unpacking.
|
|
182
195
|
const single = entries.length === 1;
|