blume 0.0.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/blume.mjs +18 -0
- package/dist/cli/index.js +12696 -0
- package/dist/cli/index.js.map +145 -0
- package/dist/types/core/bridge.d.ts +24 -0
- package/dist/types/core/config.d.ts +35 -0
- package/dist/types/core/data.d.ts +129 -0
- package/dist/types/core/define-components.d.ts +27 -0
- package/dist/types/core/define-meta.d.ts +16 -0
- package/dist/types/core/deployment-env.d.ts +9 -0
- package/dist/types/core/diagnostics.d.ts +17 -0
- package/dist/types/core/i18n-ui.d.ts +500 -0
- package/dist/types/core/load-module.d.ts +7 -0
- package/dist/types/core/package-root.d.ts +17 -0
- package/dist/types/core/project.d.ts +9 -0
- package/dist/types/core/schema.d.ts +3451 -0
- package/dist/types/core/sources/types.d.ts +107 -0
- package/dist/types/core/types.d.ts +245 -0
- package/dist/types/core/ui-packs/ar.d.ts +3 -0
- package/dist/types/core/ui-packs/bg.d.ts +3 -0
- package/dist/types/core/ui-packs/bn.d.ts +3 -0
- package/dist/types/core/ui-packs/ca.d.ts +3 -0
- package/dist/types/core/ui-packs/cs.d.ts +3 -0
- package/dist/types/core/ui-packs/da.d.ts +3 -0
- package/dist/types/core/ui-packs/de.d.ts +3 -0
- package/dist/types/core/ui-packs/el.d.ts +3 -0
- package/dist/types/core/ui-packs/es.d.ts +3 -0
- package/dist/types/core/ui-packs/fa.d.ts +3 -0
- package/dist/types/core/ui-packs/fi.d.ts +3 -0
- package/dist/types/core/ui-packs/fr.d.ts +3 -0
- package/dist/types/core/ui-packs/he.d.ts +3 -0
- package/dist/types/core/ui-packs/hi.d.ts +3 -0
- package/dist/types/core/ui-packs/hr.d.ts +3 -0
- package/dist/types/core/ui-packs/hu.d.ts +3 -0
- package/dist/types/core/ui-packs/id.d.ts +3 -0
- package/dist/types/core/ui-packs/index.d.ts +13 -0
- package/dist/types/core/ui-packs/it.d.ts +3 -0
- package/dist/types/core/ui-packs/ja.d.ts +3 -0
- package/dist/types/core/ui-packs/ko.d.ts +3 -0
- package/dist/types/core/ui-packs/nl.d.ts +3 -0
- package/dist/types/core/ui-packs/no.d.ts +3 -0
- package/dist/types/core/ui-packs/pl.d.ts +3 -0
- package/dist/types/core/ui-packs/pt-br.d.ts +3 -0
- package/dist/types/core/ui-packs/pt.d.ts +3 -0
- package/dist/types/core/ui-packs/ro.d.ts +3 -0
- package/dist/types/core/ui-packs/ru.d.ts +3 -0
- package/dist/types/core/ui-packs/sk.d.ts +3 -0
- package/dist/types/core/ui-packs/sr.d.ts +3 -0
- package/dist/types/core/ui-packs/sv.d.ts +3 -0
- package/dist/types/core/ui-packs/th.d.ts +3 -0
- package/dist/types/core/ui-packs/tr.d.ts +3 -0
- package/dist/types/core/ui-packs/uk.d.ts +3 -0
- package/dist/types/core/ui-packs/vi.d.ts +3 -0
- package/dist/types/core/ui-packs/zh-tw.d.ts +3 -0
- package/dist/types/core/ui-packs/zh.d.ts +3 -0
- package/dist/types/core/version.d.ts +8 -0
- package/dist/types/index.d.ts +10 -0
- package/dist/types/migrate/mintlify/config.d.ts +2 -0
- package/dist/types/migrate/mintlify/i18n.d.ts +7 -0
- package/dist/types/theme/fonts.d.ts +163 -0
- package/docs/01-quickstart.mdx +99 -0
- package/docs/02-deployment.mdx +129 -0
- package/docs/advanced/api-reference.mdx +114 -0
- package/docs/advanced/blog.mdx +121 -0
- package/docs/advanced/changelog.mdx +113 -0
- package/docs/advanced/custom-pages.mdx +268 -0
- package/docs/advanced/meta.ts +7 -0
- package/docs/changelog/v0-1-0.mdx +12 -0
- package/docs/changelog/v0-2-0.mdx +16 -0
- package/docs/configuration/ai.mdx +228 -0
- package/docs/configuration/analytics.mdx +98 -0
- package/docs/configuration/customization.mdx +91 -0
- package/docs/configuration/export.mdx +70 -0
- package/docs/configuration/index.mdx +290 -0
- package/docs/configuration/meta.ts +15 -0
- package/docs/configuration/search.mdx +172 -0
- package/docs/configuration/seo.mdx +196 -0
- package/docs/configuration/theming.mdx +178 -0
- package/docs/content/components.mdx +651 -0
- package/docs/content/i18n.mdx +205 -0
- package/docs/content/index.mdx +161 -0
- package/docs/content/islands.mdx +94 -0
- package/docs/content/meta.mdx +119 -0
- package/docs/content/meta.ts +15 -0
- package/docs/content/navigation.mdx +168 -0
- package/docs/content/sources.mdx +216 -0
- package/docs/content/syntax.mdx +445 -0
- package/docs/index.mdx +112 -0
- package/docs/reference/cli.mdx +43 -0
- package/docs/reference/frontmatter.mdx +74 -0
- package/docs/reference/meta.ts +7 -0
- package/package.json +150 -6
- package/src/ai/ask.ts +93 -0
- package/src/ai/llms.ts +64 -0
- package/src/ai/markdown.ts +31 -0
- package/src/ai/mcp/data.ts +74 -0
- package/src/ai/mcp/discovery.ts +49 -0
- package/src/ai/mcp/server.ts +225 -0
- package/src/ai/mcp/tools.ts +47 -0
- package/src/assets/icon.png +0 -0
- package/src/astro/examples.ts +104 -0
- package/src/astro/generate.ts +1006 -0
- package/src/astro/index.ts +4 -0
- package/src/astro/integration.ts +74 -0
- package/src/astro/islands.ts +131 -0
- package/src/astro/markdown-negotiation.ts +68 -0
- package/src/astro/pages.ts +87 -0
- package/src/astro/templates.ts +1326 -0
- package/src/cli/commands/add.ts +81 -0
- package/src/cli/commands/build.ts +103 -0
- package/src/cli/commands/dev.ts +108 -0
- package/src/cli/commands/doctor.ts +74 -0
- package/src/cli/commands/eject.ts +57 -0
- package/src/cli/commands/init.ts +98 -0
- package/src/cli/commands/migrate.ts +39 -0
- package/src/cli/commands/preview.ts +39 -0
- package/src/cli/commands/sync.ts +52 -0
- package/src/cli/commands/validate.ts +61 -0
- package/src/cli/index.ts +35 -0
- package/src/cli/log.ts +37 -0
- package/src/cli/prepare.ts +80 -0
- package/src/components/Icon.astro +99 -0
- package/src/components/content/Accordion.astro +8 -0
- package/src/components/content/AccordionItem.astro +121 -0
- package/src/components/content/AutoTypeTable.astro +51 -0
- package/src/components/content/Badge.astro +124 -0
- package/src/components/content/Callout.astro +73 -0
- package/src/components/content/Card.astro +104 -0
- package/src/components/content/CardGroup.astro +14 -0
- package/src/components/content/CodeBlock.astro +28 -0
- package/src/components/content/CodeGroup.astro +13 -0
- package/src/components/content/Color.astro +15 -0
- package/src/components/content/ColorItem.astro +87 -0
- package/src/components/content/ColorRow.astro +10 -0
- package/src/components/content/Column.astro +6 -0
- package/src/components/content/Columns.astro +9 -0
- package/src/components/content/Component.astro +65 -0
- package/src/components/content/Diff.astro +44 -0
- package/src/components/content/Expandable.astro +11 -0
- package/src/components/content/FileTree.astro +8 -0
- package/src/components/content/Frame.astro +70 -0
- package/src/components/content/GithubInfo.astro +110 -0
- package/src/components/content/Math.astro +24 -0
- package/src/components/content/Panel.astro +20 -0
- package/src/components/content/Prompt.astro +129 -0
- package/src/components/content/Step.astro +34 -0
- package/src/components/content/Steps.astro +20 -0
- package/src/components/content/Tab.astro +46 -0
- package/src/components/content/Tabs.astro +273 -0
- package/src/components/content/Tile.astro +42 -0
- package/src/components/content/Tooltip.astro +68 -0
- package/src/components/content/Tree.astro +300 -0
- package/src/components/content/TreeFile.astro +15 -0
- package/src/components/content/TreeFolder.astro +62 -0
- package/src/components/content/TypeTable.astro +106 -0
- package/src/components/content/Update.astro +66 -0
- package/src/components/content/Visibility.astro +12 -0
- package/src/components/content/Warning.astro +9 -0
- package/src/components/content/auto-type-table.ts +141 -0
- package/src/components/content/diff.ts +95 -0
- package/src/components/content/github-info.ts +79 -0
- package/src/components/content/mermaid-element.ts +68 -0
- package/src/components/github-mark.ts +9 -0
- package/src/components/index.ts +14 -0
- package/src/components/islands/AskAI.astro +12 -0
- package/src/components/islands/ask-ai.tsx +156 -0
- package/src/components/layout/Analytics.astro +63 -0
- package/src/components/layout/Banner.astro +50 -0
- package/src/components/layout/Breadcrumbs.astro +31 -0
- package/src/components/layout/Favicon.astro +22 -0
- package/src/components/layout/Fonts.astro +14 -0
- package/src/components/layout/Header.astro +188 -0
- package/src/components/layout/LanguageSwitcher.astro +56 -0
- package/src/components/layout/NavTree.astro +462 -0
- package/src/components/layout/PageActions.astro +438 -0
- package/src/components/layout/PageFeedback.astro +58 -0
- package/src/components/layout/PageLayout.astro +173 -0
- package/src/components/layout/Pagination.astro +56 -0
- package/src/components/layout/ReferenceLayout.astro +107 -0
- package/src/components/layout/RootLayout.astro +537 -0
- package/src/components/layout/Search.astro +608 -0
- package/src/components/layout/TableOfContents.astro +68 -0
- package/src/components/layout/analytics-client.ts +38 -0
- package/src/components/layout/head-scripts.ts +19 -0
- package/src/components/layout/nav-utils.ts +87 -0
- package/src/components/layout/overrides.ts +32 -0
- package/src/components/layout/search/algolia.ts +43 -0
- package/src/components/layout/search/endpoint.ts +22 -0
- package/src/components/layout/search/flexsearch.ts +52 -0
- package/src/components/layout/search/orama-cloud.ts +41 -0
- package/src/components/layout/search/orama.ts +26 -0
- package/src/components/layout/search/pagefind.ts +43 -0
- package/src/components/layout/search/types.ts +163 -0
- package/src/components/layout/search/typesense.ts +60 -0
- package/src/components/layout/toc-element.ts +108 -0
- package/src/core/bridge.ts +92 -0
- package/src/core/config.ts +112 -0
- package/src/core/content.ts +50 -0
- package/src/core/data.ts +128 -0
- package/src/core/define-components.ts +34 -0
- package/src/core/define-meta.ts +20 -0
- package/src/core/deployment-env.ts +73 -0
- package/src/core/diagnostics.ts +104 -0
- package/src/core/frontmatter.ts +43 -0
- package/src/core/graph.ts +128 -0
- package/src/core/i18n-ui.ts +171 -0
- package/src/core/i18n.ts +169 -0
- package/src/core/last-modified.ts +88 -0
- package/src/core/links.ts +352 -0
- package/src/core/load-module.ts +15 -0
- package/src/core/manifest.ts +126 -0
- package/src/core/meta.ts +97 -0
- package/src/core/navigation.ts +392 -0
- package/src/core/package-root.ts +37 -0
- package/src/core/project-graph.ts +153 -0
- package/src/core/project.ts +56 -0
- package/src/core/schema.ts +1057 -0
- package/src/core/server-features.ts +23 -0
- package/src/core/sources/assets.ts +77 -0
- package/src/core/sources/cache.ts +122 -0
- package/src/core/sources/filesystem.ts +99 -0
- package/src/core/sources/mdx-remote.ts +215 -0
- package/src/core/sources/mintlify.ts +161 -0
- package/src/core/sources/normalize.ts +243 -0
- package/src/core/sources/notion.ts +440 -0
- package/src/core/sources/portable-text.ts +143 -0
- package/src/core/sources/read.ts +36 -0
- package/src/core/sources/resolve.ts +158 -0
- package/src/core/sources/sanity.ts +218 -0
- package/src/core/sources/types.ts +105 -0
- package/src/core/tsconfig-aliases.ts +201 -0
- package/src/core/types.ts +261 -0
- package/src/core/ui-packs/ar.ts +47 -0
- package/src/core/ui-packs/bg.ts +47 -0
- package/src/core/ui-packs/bn.ts +47 -0
- package/src/core/ui-packs/ca.ts +47 -0
- package/src/core/ui-packs/cs.ts +47 -0
- package/src/core/ui-packs/da.ts +47 -0
- package/src/core/ui-packs/de.ts +47 -0
- package/src/core/ui-packs/el.ts +47 -0
- package/src/core/ui-packs/es.ts +47 -0
- package/src/core/ui-packs/fa.ts +47 -0
- package/src/core/ui-packs/fi.ts +47 -0
- package/src/core/ui-packs/fr.ts +47 -0
- package/src/core/ui-packs/he.ts +47 -0
- package/src/core/ui-packs/hi.ts +47 -0
- package/src/core/ui-packs/hr.ts +47 -0
- package/src/core/ui-packs/hu.ts +47 -0
- package/src/core/ui-packs/id.ts +47 -0
- package/src/core/ui-packs/index.ts +87 -0
- package/src/core/ui-packs/it.ts +47 -0
- package/src/core/ui-packs/ja.ts +47 -0
- package/src/core/ui-packs/ko.ts +47 -0
- package/src/core/ui-packs/nl.ts +47 -0
- package/src/core/ui-packs/no.ts +47 -0
- package/src/core/ui-packs/pl.ts +47 -0
- package/src/core/ui-packs/pt-br.ts +47 -0
- package/src/core/ui-packs/pt.ts +47 -0
- package/src/core/ui-packs/ro.ts +47 -0
- package/src/core/ui-packs/ru.ts +47 -0
- package/src/core/ui-packs/sk.ts +47 -0
- package/src/core/ui-packs/sr.ts +47 -0
- package/src/core/ui-packs/sv.ts +47 -0
- package/src/core/ui-packs/th.ts +47 -0
- package/src/core/ui-packs/tr.ts +47 -0
- package/src/core/ui-packs/uk.ts +47 -0
- package/src/core/ui-packs/vi.ts +47 -0
- package/src/core/ui-packs/zh-tw.ts +47 -0
- package/src/core/ui-packs/zh.ts +47 -0
- package/src/core/version.ts +23 -0
- package/src/deploy/robots.ts +20 -0
- package/src/deploy/rss.ts +128 -0
- package/src/deploy/sitemap.ts +28 -0
- package/src/index.ts +39 -0
- package/src/markdown/code-title.ts +71 -0
- package/src/markdown/directives.ts +83 -0
- package/src/markdown/heading-anchors.ts +137 -0
- package/src/markdown/index.ts +228 -0
- package/src/markdown/inline-code.ts +108 -0
- package/src/markdown/language-icon.ts +172 -0
- package/src/markdown/math.ts +32 -0
- package/src/markdown/mdast.ts +48 -0
- package/src/markdown/mermaid.ts +37 -0
- package/src/markdown/package-commands.ts +159 -0
- package/src/markdown/package-install.ts +40 -0
- package/src/migrate/fumadocs/config.ts +155 -0
- package/src/migrate/fumadocs/content.ts +365 -0
- package/src/migrate/fumadocs/frontmatter.ts +18 -0
- package/src/migrate/fumadocs/groups.ts +230 -0
- package/src/migrate/fumadocs/index.ts +337 -0
- package/src/migrate/fumadocs/meta.ts +244 -0
- package/src/migrate/migrate.ts +53 -0
- package/src/migrate/mintlify/config.ts +1040 -0
- package/src/migrate/mintlify/content.ts +98 -0
- package/src/migrate/mintlify/frontmatter.ts +126 -0
- package/src/migrate/mintlify/i18n.ts +51 -0
- package/src/migrate/mintlify/icons.ts +128 -0
- package/src/migrate/mintlify/index.ts +266 -0
- package/src/migrate/mintlify/snippets.ts +306 -0
- package/src/migrate/mintlify/transform.ts +80 -0
- package/src/migrate/nextra/content.ts +46 -0
- package/src/migrate/nextra/frontmatter.ts +40 -0
- package/src/migrate/nextra/index.ts +374 -0
- package/src/migrate/nextra/meta.ts +266 -0
- package/src/migrate/shared.ts +720 -0
- package/src/migrate/starlight/config.ts +459 -0
- package/src/migrate/starlight/content.ts +78 -0
- package/src/migrate/starlight/frontmatter.ts +111 -0
- package/src/migrate/starlight/i18n.ts +54 -0
- package/src/migrate/starlight/index.ts +131 -0
- package/src/og/card.ts +92 -0
- package/src/og/index.ts +2 -0
- package/src/openapi/scalar.ts +246 -0
- package/src/registry/eject.ts +310 -0
- package/src/registry/registry.ts +100 -0
- package/src/registry/rewrite-imports.ts +39 -0
- package/src/runtime/index.ts +14 -0
- package/src/search/build.ts +23 -0
- package/src/search/documents.ts +164 -0
- package/src/search/orama-index.ts +66 -0
- package/src/search/providers.ts +91 -0
- package/src/search/sync/algolia.ts +30 -0
- package/src/search/sync/index.ts +50 -0
- package/src/search/sync/orama-cloud.ts +40 -0
- package/src/search/sync/typesense.ts +65 -0
- package/src/seo/jsonld.ts +113 -0
- package/src/theme/entry.ts +637 -0
- package/src/theme/fonts.ts +198 -0
- package/src/theme/icons.ts +184 -0
- package/src/theme/palette.ts +143 -0
- package/src/theme/twoslash.ts +81 -0
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import type { IncomingMessage, ServerResponse } from "node:http";
|
|
2
|
+
|
|
3
|
+
import type { AstroIntegration } from "astro";
|
|
4
|
+
|
|
5
|
+
import { markdownVariantUrl, prefersMarkdown } from "./markdown-negotiation.ts";
|
|
6
|
+
|
|
7
|
+
/** A user page mounted into the generated runtime. */
|
|
8
|
+
export interface BlumePageRoute {
|
|
9
|
+
/** Route pattern, e.g. `/changelog` or `/examples/[slug]`. */
|
|
10
|
+
pattern: string;
|
|
11
|
+
/** Absolute path to the user's `.astro` page file. */
|
|
12
|
+
entrypoint: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface BlumeIntegrationOptions {
|
|
16
|
+
pages: BlumePageRoute[];
|
|
17
|
+
/** Page routes that have a raw-Markdown variant (the content manifest). */
|
|
18
|
+
contentRoutes: string[];
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Dev-server content negotiation: when a client asks for `text/markdown`,
|
|
23
|
+
* transparently rewrite a content-page request to its `.md` variant so the
|
|
24
|
+
* existing raw-Markdown endpoint serves it. Runs only under `blume dev`; static
|
|
25
|
+
* and server builds expose the same content at the `.md` URL. Only routes with
|
|
26
|
+
* a Markdown variant are rewritten, so landing pages and user `.astro` pages
|
|
27
|
+
* keep serving HTML.
|
|
28
|
+
*/
|
|
29
|
+
const negotiateMarkdown =
|
|
30
|
+
(routes: ReadonlySet<string>) =>
|
|
31
|
+
(req: IncomingMessage, res: ServerResponse, next: () => void): void => {
|
|
32
|
+
if (
|
|
33
|
+
(req.method === "GET" || req.method === "HEAD") &&
|
|
34
|
+
prefersMarkdown(req.headers.accept)
|
|
35
|
+
) {
|
|
36
|
+
const variant = markdownVariantUrl(req.url, routes);
|
|
37
|
+
if (variant) {
|
|
38
|
+
res.setHeader("Vary", "Accept");
|
|
39
|
+
req.url = variant;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
next();
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Blume's Astro integration. Mounts user-authored pages from `pages/` into the
|
|
47
|
+
* generated runtime via `injectRoute`, keeping each file in its original
|
|
48
|
+
* location so relative imports and `getStaticPaths` keep working, and teaches
|
|
49
|
+
* the dev server to honour `Accept: text/markdown`.
|
|
50
|
+
*/
|
|
51
|
+
export const blumeIntegration = (
|
|
52
|
+
options: BlumeIntegrationOptions
|
|
53
|
+
): AstroIntegration => ({
|
|
54
|
+
hooks: {
|
|
55
|
+
"astro:config:setup": ({ injectRoute }) => {
|
|
56
|
+
for (const page of options.pages) {
|
|
57
|
+
injectRoute({
|
|
58
|
+
entrypoint: page.entrypoint,
|
|
59
|
+
pattern: page.pattern,
|
|
60
|
+
prerender: true,
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
"astro:server:setup": ({ server }) => {
|
|
65
|
+
// Prepend so the rewrite happens before Astro's own request handler,
|
|
66
|
+
// letting the rewritten URL resolve to the `.md` endpoint.
|
|
67
|
+
server.middlewares.stack.unshift({
|
|
68
|
+
handle: negotiateMarkdown(new Set(options.contentRoutes)),
|
|
69
|
+
route: "",
|
|
70
|
+
});
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
name: "blume",
|
|
74
|
+
});
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
|
|
3
|
+
import { basename, join } from "pathe";
|
|
4
|
+
import { glob } from "tinyglobby";
|
|
5
|
+
|
|
6
|
+
/** Astro hydration directives Blume applies to a convention island. */
|
|
7
|
+
export type IslandClientMode = "idle" | "load" | "only" | "visible";
|
|
8
|
+
|
|
9
|
+
/** Client framework an island is authored in, inferred from its extension. */
|
|
10
|
+
export type IslandFramework = "react" | "svelte" | "vue";
|
|
11
|
+
|
|
12
|
+
/** A discovered `islands/` component, ready to wrap and expose to MDX. */
|
|
13
|
+
export interface IslandSpec {
|
|
14
|
+
/** Hydration directive, from `export const client` or the default. */
|
|
15
|
+
client: IslandClientMode;
|
|
16
|
+
/** Absolute path to the island source file. */
|
|
17
|
+
file: string;
|
|
18
|
+
/** Astro client framework, used to enable the renderer and for `client:only`. */
|
|
19
|
+
framework: IslandFramework;
|
|
20
|
+
/** Component name used in MDX — the file's basename without extension. */
|
|
21
|
+
name: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface IslandDiscovery {
|
|
25
|
+
islands: IslandSpec[];
|
|
26
|
+
warnings: string[];
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** Hydration mode used when an island doesn't declare one. */
|
|
30
|
+
const DEFAULT_CLIENT: IslandClientMode = "visible";
|
|
31
|
+
|
|
32
|
+
const VALID_MODES = new Set<IslandClientMode>([
|
|
33
|
+
"idle",
|
|
34
|
+
"load",
|
|
35
|
+
"only",
|
|
36
|
+
"visible",
|
|
37
|
+
]);
|
|
38
|
+
|
|
39
|
+
/** Island extensions mapped to the Astro renderer that handles them. */
|
|
40
|
+
const FRAMEWORK_BY_EXT: Record<string, IslandFramework> = {
|
|
41
|
+
jsx: "react",
|
|
42
|
+
svelte: "svelte",
|
|
43
|
+
tsx: "react",
|
|
44
|
+
vue: "vue",
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
// Captures the extension so we can both strip it from the name and pick the
|
|
48
|
+
// framework. Kept in sync with the glob below.
|
|
49
|
+
const ISLAND_FILE = /\.(?<ext>jsx|svelte|tsx|vue)$/u;
|
|
50
|
+
|
|
51
|
+
// Matches `export const client = "load"` (with optional type annotation and
|
|
52
|
+
// either quote style). Read statically so we never execute island code in Node.
|
|
53
|
+
const CLIENT_EXPORT =
|
|
54
|
+
/export\s+const\s+client\s*(?::[^=]+)?=\s*["'](?<mode>\w+)["']/u;
|
|
55
|
+
|
|
56
|
+
export const readClientMode = (
|
|
57
|
+
source: string,
|
|
58
|
+
file: string,
|
|
59
|
+
warnings: string[]
|
|
60
|
+
): IslandClientMode => {
|
|
61
|
+
const mode = source.match(CLIENT_EXPORT)?.groups?.mode;
|
|
62
|
+
if (!mode) {
|
|
63
|
+
return DEFAULT_CLIENT;
|
|
64
|
+
}
|
|
65
|
+
if (!VALID_MODES.has(mode as IslandClientMode)) {
|
|
66
|
+
warnings.push(
|
|
67
|
+
`Island "${file}" declares an unknown client mode "${mode}"; defaulting to "${DEFAULT_CLIENT}". Use "load", "idle", "visible", or "only".`
|
|
68
|
+
);
|
|
69
|
+
return DEFAULT_CLIENT;
|
|
70
|
+
}
|
|
71
|
+
return mode as IslandClientMode;
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Discover convention islands under `<root>/islands`. Every `.tsx`/`.jsx`/
|
|
76
|
+
* `.vue`/`.svelte` file becomes a globally-available, hydrated MDX component
|
|
77
|
+
* named after the file (which must be PascalCase to be usable as a JSX tag).
|
|
78
|
+
* Hydration defaults to `client:visible`; a file opts out with `export const
|
|
79
|
+
* client = "load" | "idle" | "only"`. Discovery is path-based (a glob), so no
|
|
80
|
+
* user code is executed.
|
|
81
|
+
*/
|
|
82
|
+
export const discoverIslands = async (
|
|
83
|
+
root: string
|
|
84
|
+
): Promise<IslandDiscovery> => {
|
|
85
|
+
const dir = join(root, "islands");
|
|
86
|
+
const matches = await glob(["**/*.{jsx,svelte,tsx,vue}"], {
|
|
87
|
+
absolute: true,
|
|
88
|
+
cwd: dir,
|
|
89
|
+
onlyFiles: true,
|
|
90
|
+
});
|
|
91
|
+
const files = matches.toSorted();
|
|
92
|
+
const sources = await Promise.all(
|
|
93
|
+
files.map((file) => readFile(file, "utf-8"))
|
|
94
|
+
);
|
|
95
|
+
|
|
96
|
+
const islands: IslandSpec[] = [];
|
|
97
|
+
const warnings: string[] = [];
|
|
98
|
+
const seen = new Map<string, string>();
|
|
99
|
+
|
|
100
|
+
for (const [index, file] of files.entries()) {
|
|
101
|
+
const base = basename(file);
|
|
102
|
+
const ext = base.match(ISLAND_FILE)?.groups?.ext;
|
|
103
|
+
const framework = ext ? FRAMEWORK_BY_EXT[ext] : undefined;
|
|
104
|
+
if (!framework) {
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
const name = base.replace(ISLAND_FILE, "");
|
|
108
|
+
if (!/^[A-Z]/u.test(name)) {
|
|
109
|
+
warnings.push(
|
|
110
|
+
`Island "${file}" must have a PascalCase filename to be used in MDX (e.g. Counter.tsx → <Counter />); skipping it.`
|
|
111
|
+
);
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
const existing = seen.get(name);
|
|
115
|
+
if (existing) {
|
|
116
|
+
warnings.push(
|
|
117
|
+
`Two islands both resolve to <${name}> ("${existing}" and "${file}"); ignoring the second. Give them distinct filenames.`
|
|
118
|
+
);
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
seen.set(name, file);
|
|
122
|
+
islands.push({
|
|
123
|
+
client: readClientMode(sources[index] ?? "", file, warnings),
|
|
124
|
+
file,
|
|
125
|
+
framework,
|
|
126
|
+
name,
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return { islands, warnings };
|
|
131
|
+
};
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HTTP content negotiation for the raw-Markdown variants. The `<route>.md`
|
|
3
|
+
* endpoints already serve a page's source verbatim; these helpers let the dev
|
|
4
|
+
* server honour `Accept: text/markdown` by transparently rewriting a page
|
|
5
|
+
* request to its `.md` variant.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
interface AcceptEntry {
|
|
9
|
+
q: number;
|
|
10
|
+
type: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const parseAccept = (accept: string): AcceptEntry[] =>
|
|
14
|
+
accept.split(",").map((part) => {
|
|
15
|
+
const segments = part.trim().split(";");
|
|
16
|
+
const type = (segments[0] ?? "").trim().toLowerCase();
|
|
17
|
+
const qSegment = segments
|
|
18
|
+
.slice(1)
|
|
19
|
+
.map((segment) => segment.trim())
|
|
20
|
+
.find((segment) => segment.startsWith("q="));
|
|
21
|
+
const q = qSegment ? Number.parseFloat(qSegment.slice(2)) : 1;
|
|
22
|
+
return { q: Number.isNaN(q) ? 1 : q, type };
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Whether the client explicitly prefers Markdown over HTML. Browsers never send
|
|
27
|
+
* `text/markdown`, so an ordinary page request (`text/html`, `*/*`) is false.
|
|
28
|
+
*/
|
|
29
|
+
export const prefersMarkdown = (accept: string | null | undefined): boolean => {
|
|
30
|
+
if (!accept) {
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
let markdownQ = -1;
|
|
34
|
+
let htmlQ = 0;
|
|
35
|
+
for (const { q, type } of parseAccept(accept)) {
|
|
36
|
+
if (type === "text/markdown" || type === "text/x-markdown") {
|
|
37
|
+
markdownQ = Math.max(markdownQ, q);
|
|
38
|
+
} else if (type === "text/html") {
|
|
39
|
+
htmlQ = Math.max(htmlQ, q);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return markdownQ > 0 && markdownQ >= htmlQ;
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Map a page request URL to its `.md` variant, or `null` when the requested
|
|
47
|
+
* path is not a known content route (Vite/Astro internals, assets, API routes,
|
|
48
|
+
* landing pages, and user `.astro` pages all fall through). `routes` is the set
|
|
49
|
+
* of page paths that have a raw-Markdown variant.
|
|
50
|
+
*/
|
|
51
|
+
export const markdownVariantUrl = (
|
|
52
|
+
rawUrl: string | null | undefined,
|
|
53
|
+
routes: ReadonlySet<string>
|
|
54
|
+
): string | null => {
|
|
55
|
+
if (!rawUrl) {
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
const queryIndex = rawUrl.indexOf("?");
|
|
59
|
+
const query = queryIndex === -1 ? "" : rawUrl.slice(queryIndex);
|
|
60
|
+
const rawPath = queryIndex === -1 ? rawUrl : rawUrl.slice(0, queryIndex);
|
|
61
|
+
const pathname =
|
|
62
|
+
rawPath !== "/" && rawPath.endsWith("/") ? rawPath.slice(0, -1) : rawPath;
|
|
63
|
+
if (!routes.has(pathname)) {
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
const target = pathname === "/" ? "/index" : pathname;
|
|
67
|
+
return `${target}.md${query}`;
|
|
68
|
+
};
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { extname, relative } from "pathe";
|
|
2
|
+
import { glob } from "tinyglobby";
|
|
3
|
+
|
|
4
|
+
import type { BlumePageRoute } from "./integration.ts";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Discover user `.astro` pages and map them to route patterns. Files keep their
|
|
8
|
+
* original location; only the route pattern is derived (index -> parent,
|
|
9
|
+
* dynamic `[param]` segments preserved).
|
|
10
|
+
*/
|
|
11
|
+
export const discoverPages = async (
|
|
12
|
+
pagesRoot: string
|
|
13
|
+
): Promise<BlumePageRoute[]> => {
|
|
14
|
+
const files = await glob(["**/*.astro"], {
|
|
15
|
+
absolute: true,
|
|
16
|
+
cwd: pagesRoot,
|
|
17
|
+
onlyFiles: true,
|
|
18
|
+
});
|
|
19
|
+
files.sort();
|
|
20
|
+
|
|
21
|
+
return files.map((file) => {
|
|
22
|
+
const rel = relative(pagesRoot, file);
|
|
23
|
+
const withoutExt = rel.slice(0, rel.length - extname(rel).length);
|
|
24
|
+
const parts = withoutExt.split("/").filter((part) => part !== "index");
|
|
25
|
+
const pattern = parts.length === 0 ? "/" : `/${parts.join("/")}`;
|
|
26
|
+
return { entrypoint: file, pattern };
|
|
27
|
+
});
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
/** A custom-page route that should get a generated OG card. */
|
|
31
|
+
export interface OgCustomRoute {
|
|
32
|
+
/** `og/<slug>.png` path segment; `index` for the site root. */
|
|
33
|
+
slug: string;
|
|
34
|
+
/** Card title. */
|
|
35
|
+
title: string;
|
|
36
|
+
/** Small eyebrow line above the title. */
|
|
37
|
+
eyebrow?: string;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** Skip private (`_partial`, `.well-known`) and Astro dynamic (`[param]`) parts. */
|
|
41
|
+
const PRIVATE_SEGMENT = /^[._]/u;
|
|
42
|
+
|
|
43
|
+
const humanizeSegment = (segment: string): string =>
|
|
44
|
+
segment
|
|
45
|
+
.split(/[-_]/u)
|
|
46
|
+
.filter(Boolean)
|
|
47
|
+
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
|
|
48
|
+
.join(" ");
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Pick the custom-page routes that should get a generated Open Graph card, with
|
|
52
|
+
* the card's slug and text. OG is otherwise content-route only, so a custom page
|
|
53
|
+
* — most importantly the landing `/`, the most-shared URL — would have no card.
|
|
54
|
+
*
|
|
55
|
+
* Dynamic (`[param]`) routes and private segments (`_partials`, `.well-known`)
|
|
56
|
+
* are skipped: they aren't shareable pages. The home is titled with the site
|
|
57
|
+
* title (eyebrow: the site description); a deeper page is titled from its last
|
|
58
|
+
* path segment.
|
|
59
|
+
*/
|
|
60
|
+
export const customOgRoutes = (
|
|
61
|
+
pages: BlumePageRoute[],
|
|
62
|
+
siteTitle: string,
|
|
63
|
+
siteDescription?: string
|
|
64
|
+
): OgCustomRoute[] => {
|
|
65
|
+
const seen = new Set<string>();
|
|
66
|
+
const routes: OgCustomRoute[] = [];
|
|
67
|
+
for (const { pattern } of pages) {
|
|
68
|
+
const segments = pattern.split("/").filter(Boolean);
|
|
69
|
+
if (
|
|
70
|
+
segments.some((part) => PRIVATE_SEGMENT.test(part) || part.includes("["))
|
|
71
|
+
) {
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
const slug = segments.length === 0 ? "index" : segments.join("/");
|
|
75
|
+
if (seen.has(slug)) {
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
seen.add(slug);
|
|
79
|
+
const last = segments.at(-1);
|
|
80
|
+
routes.push(
|
|
81
|
+
last
|
|
82
|
+
? { eyebrow: siteTitle, slug, title: humanizeSegment(last) }
|
|
83
|
+
: { eyebrow: siteDescription, slug, title: siteTitle }
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
return routes;
|
|
87
|
+
};
|