blume 0.2.0 → 0.4.0
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/dist/cli/index.js +2429 -792
- package/dist/cli/index.js.map +63 -44
- package/dist/types/core/data.d.ts +16 -0
- package/dist/types/core/define-components.d.ts +9 -2
- package/dist/types/core/diagnostics.d.ts +5 -0
- package/dist/types/core/schema.d.ts +313 -778
- package/dist/types/core/types.d.ts +2 -2
- package/dist/types/migrate/mintlify/assets.d.ts +8 -0
- package/docs/01-quickstart.mdx +5 -16
- package/docs/02-deployment.mdx +26 -40
- package/docs/advanced/api-reference.mdx +10 -37
- package/docs/advanced/blog.mdx +9 -25
- package/docs/advanced/changelog.mdx +10 -33
- package/docs/advanced/custom-pages.mdx +66 -61
- package/docs/configuration/ai.mdx +47 -91
- package/docs/configuration/analytics.mdx +20 -38
- package/docs/configuration/customization.mdx +92 -27
- package/docs/configuration/export.mdx +9 -34
- package/docs/configuration/index.mdx +78 -85
- package/docs/configuration/search.mdx +17 -54
- package/docs/configuration/seo.mdx +18 -44
- package/docs/configuration/theming.mdx +20 -42
- package/docs/content/components.mdx +42 -101
- package/docs/content/i18n.mdx +21 -72
- package/docs/content/index.mdx +18 -48
- package/docs/content/islands.mdx +79 -33
- package/docs/content/meta.mdx +23 -50
- package/docs/content/navigation.mdx +42 -56
- package/docs/content/sources.mdx +20 -83
- package/docs/content/syntax.mdx +37 -105
- package/docs/index.mdx +13 -51
- package/docs/reference/cli.mdx +49 -18
- package/docs/reference/frontmatter.mdx +2 -5
- package/package.json +3 -1
- package/src/ai/ask-context.ts +131 -0
- package/src/ai/ask-data.ts +25 -0
- package/src/astro/component-slots.ts +165 -0
- package/src/astro/generate.ts +132 -13
- package/src/astro/integration.ts +85 -3
- package/src/astro/islands.ts +6 -2
- package/src/astro/markdown-negotiation.ts +17 -3
- package/src/astro/pages.ts +11 -13
- package/src/astro/static-assets.ts +117 -0
- package/src/astro/templates.ts +120 -50
- package/src/blume-modules.d.ts +25 -0
- package/src/cli/args.ts +23 -0
- package/src/cli/commands/build.ts +209 -1
- package/src/cli/commands/check.ts +62 -0
- package/src/cli/commands/dev.ts +32 -3
- package/src/cli/commands/doctor.ts +32 -6
- package/src/cli/commands/eject.ts +3 -1
- package/src/cli/commands/init.ts +184 -16
- package/src/cli/commands/preview.ts +2 -1
- package/src/cli/commands/validate.ts +27 -2
- package/src/cli/dev-lock.ts +84 -0
- package/src/cli/index.ts +15 -0
- package/src/cli/internal-error.ts +63 -0
- package/src/cli/log.ts +41 -1
- package/src/cli/prepare.ts +17 -3
- package/src/cli/required-secrets.ts +44 -0
- package/src/components/BlumePage.astro +109 -0
- package/src/components/content/YouTube.astro +35 -0
- package/src/components/content/youtube.ts +46 -0
- package/src/components/index.ts +3 -3
- package/src/components/islands/ask-ai.tsx +29 -15
- package/src/components/islands/hooks.ts +188 -0
- package/src/components/layout/Empty.astro +6 -0
- package/src/components/layout/Header.astro +24 -39
- package/src/components/layout/Logo.astro +50 -0
- package/src/components/layout/NavSelector.astro +75 -0
- package/src/components/layout/PageLayout.astro +38 -2
- package/src/components/layout/RootLayout.astro +70 -4
- package/src/components/layout/hydration-hint.ts +30 -0
- package/src/components/layout/overrides.ts +6 -4
- package/src/components/props.ts +71 -0
- package/src/core/assets.ts +31 -0
- package/src/core/bridge.ts +10 -0
- package/src/core/builtin-tags.ts +40 -0
- package/src/core/component-diagnostics.ts +44 -0
- package/src/core/component-overrides.ts +478 -0
- package/src/core/config.ts +8 -0
- package/src/core/data.ts +14 -0
- package/src/core/define-components.ts +9 -2
- package/src/core/diagnostics.ts +95 -1
- package/src/core/gitignore.ts +30 -0
- package/src/core/graph.ts +7 -0
- package/src/core/links.ts +60 -19
- package/src/core/nav-diagnostics.ts +205 -0
- package/src/core/project-graph.ts +40 -1
- package/src/core/schema.ts +35 -96
- package/src/core/sources/mdx-remote.ts +54 -8
- package/src/core/sources/normalize.ts +57 -1
- package/src/core/sources/notion.ts +49 -5
- package/src/core/sources/sanity.ts +5 -1
- package/src/core/types.ts +2 -2
- package/src/deploy/redirects.ts +43 -0
- package/src/deploy/rss.ts +1 -8
- package/src/deploy/sitemap.ts +20 -1
- package/src/deploy/xml.ts +8 -0
- package/src/markdown/directives.ts +15 -7
- package/src/markdown/package-commands.ts +26 -4
- package/src/migrate/fumadocs/content.ts +14 -1
- package/src/migrate/fumadocs/groups.ts +7 -0
- package/src/migrate/fumadocs/index.ts +5 -2
- package/src/migrate/mintlify/assets.ts +46 -0
- package/src/migrate/mintlify/config.ts +1 -176
- package/src/migrate/mintlify/index.ts +53 -45
- package/src/migrate/shared.ts +12 -27
- package/src/migrate/starlight/config.ts +0 -4
- package/src/og/card.ts +175 -38
- package/src/registry/eject.ts +52 -12
- package/src/registry/registry.ts +172 -0
- package/src/registry/rewrite-imports.ts +31 -19
- package/src/runtime/index.ts +61 -0
- package/src/search/documents.ts +23 -5
- package/src/search/sync/algolia.ts +5 -1
- package/src/search/sync/typesense.ts +24 -16
- package/src/theme/palette.ts +26 -7
- package/src/vite-env.d.ts +14 -0
package/src/search/documents.ts
CHANGED
|
@@ -47,17 +47,35 @@ const MARKDOWN_PUNCT = /[*_~>]+/gu;
|
|
|
47
47
|
const WHITESPACE = /\s+/gu;
|
|
48
48
|
|
|
49
49
|
/** Reduce Markdown/MDX to plain, searchable text. */
|
|
50
|
-
const toPlainText = (markdown: string): string =>
|
|
51
|
-
markdown
|
|
50
|
+
const toPlainText = (markdown: string): string => {
|
|
51
|
+
const withoutBlocks = markdown
|
|
52
52
|
.replaceAll(CODE_FENCE, " ")
|
|
53
53
|
.replaceAll(IMAGE, " ")
|
|
54
|
-
.replaceAll(LINK, "$<text>")
|
|
55
|
-
|
|
56
|
-
|
|
54
|
+
.replaceAll(LINK, "$<text>");
|
|
55
|
+
|
|
56
|
+
// Strip HTML/JSX from the prose, but keep the contents of inline code — an
|
|
57
|
+
// angle-bracket span like `<T>` inside `Array<T>` is a type parameter, not a
|
|
58
|
+
// tag, and stripping it would drop those tokens from the search index. Split
|
|
59
|
+
// on inline-code spans and only run the HTML strip on the text between them.
|
|
60
|
+
const pieces: string[] = [];
|
|
61
|
+
let cursor = 0;
|
|
62
|
+
for (const match of withoutBlocks.matchAll(INLINE_CODE)) {
|
|
63
|
+
const start = match.index ?? 0;
|
|
64
|
+
pieces.push(
|
|
65
|
+
withoutBlocks.slice(cursor, start).replaceAll(HTML_OR_JSX, " ")
|
|
66
|
+
);
|
|
67
|
+
pieces.push(match.groups?.code ?? "");
|
|
68
|
+
cursor = start + match[0].length;
|
|
69
|
+
}
|
|
70
|
+
pieces.push(withoutBlocks.slice(cursor).replaceAll(HTML_OR_JSX, " "));
|
|
71
|
+
|
|
72
|
+
return pieces
|
|
73
|
+
.join("")
|
|
57
74
|
.replaceAll(HEADING_MARK, "")
|
|
58
75
|
.replaceAll(MARKDOWN_PUNCT, " ")
|
|
59
76
|
.replaceAll(WHITESPACE, " ")
|
|
60
77
|
.trim();
|
|
78
|
+
};
|
|
61
79
|
|
|
62
80
|
interface Crumbs {
|
|
63
81
|
breadcrumb: string[];
|
|
@@ -9,6 +9,10 @@ export interface AlgoliaSyncConfig {
|
|
|
9
9
|
* Upload the search records to Algolia. Uses the admin key from
|
|
10
10
|
* `ALGOLIA_ADMIN_API_KEY` (never the config, which holds only the public,
|
|
11
11
|
* search-only key). Throws on a missing key/config so the caller can warn.
|
|
12
|
+
*
|
|
13
|
+
* Uses `replaceAllObjects`, which atomically replaces the index contents, so
|
|
14
|
+
* pages deleted or renamed since the last sync don't linger as stale search
|
|
15
|
+
* hits that 404 when clicked.
|
|
12
16
|
*/
|
|
13
17
|
export const syncAlgolia = async (
|
|
14
18
|
records: SearchRecord[],
|
|
@@ -23,7 +27,7 @@ export const syncAlgolia = async (
|
|
|
23
27
|
}
|
|
24
28
|
const { algoliasearch } = await import("algoliasearch");
|
|
25
29
|
const client = algoliasearch(config.appId, adminKey);
|
|
26
|
-
await client.
|
|
30
|
+
await client.replaceAllObjects({
|
|
27
31
|
indexName: config.indexName,
|
|
28
32
|
objects: records.map((record) => ({ ...record, objectID: record._id })),
|
|
29
33
|
});
|
|
@@ -8,9 +8,13 @@ export interface TypesenseSyncConfig {
|
|
|
8
8
|
}
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
|
-
* Import the search records into a Typesense collection
|
|
12
|
-
*
|
|
13
|
-
*
|
|
11
|
+
* Import the search records into a Typesense collection. Uses the admin key
|
|
12
|
+
* from `TYPESENSE_ADMIN_API_KEY`. Throws on a missing key/config so the caller
|
|
13
|
+
* can warn.
|
|
14
|
+
*
|
|
15
|
+
* The collection is dropped and recreated on each sync so that pages deleted or
|
|
16
|
+
* renamed since the last sync don't linger as stale search hits that 404 when
|
|
17
|
+
* clicked (an upsert alone never removes them).
|
|
14
18
|
*/
|
|
15
19
|
export const syncTypesense = async (
|
|
16
20
|
records: SearchRecord[],
|
|
@@ -35,20 +39,24 @@ export const syncTypesense = async (
|
|
|
35
39
|
],
|
|
36
40
|
});
|
|
37
41
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
{ name: "content", type: "string" },
|
|
46
|
-
{ name: "url", type: "string" },
|
|
47
|
-
{ facet: true, name: "tag", optional: true, type: "string" },
|
|
48
|
-
],
|
|
49
|
-
name: config.collection,
|
|
50
|
-
});
|
|
42
|
+
const collection = client.collections(config.collection);
|
|
43
|
+
const exists = await collection
|
|
44
|
+
.retrieve()
|
|
45
|
+
.then(() => true)
|
|
46
|
+
.catch(() => false);
|
|
47
|
+
if (exists) {
|
|
48
|
+
await collection.delete();
|
|
51
49
|
}
|
|
50
|
+
await client.collections().create({
|
|
51
|
+
fields: [
|
|
52
|
+
{ name: "title", type: "string" },
|
|
53
|
+
{ name: "description", optional: true, type: "string" },
|
|
54
|
+
{ name: "content", type: "string" },
|
|
55
|
+
{ name: "url", type: "string" },
|
|
56
|
+
{ facet: true, name: "tag", optional: true, type: "string" },
|
|
57
|
+
],
|
|
58
|
+
name: config.collection,
|
|
59
|
+
});
|
|
52
60
|
|
|
53
61
|
const documents = records.map((record) => ({
|
|
54
62
|
content: record.content,
|
package/src/theme/palette.ts
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import type { ResolvedConfig } from "../core/schema.ts";
|
|
2
2
|
|
|
3
|
+
const FALLBACK_ACCENT = "oklch(0.62 0.16 250)";
|
|
4
|
+
|
|
3
5
|
/** Named accent presets mapped to OKLCH values. */
|
|
4
6
|
const ACCENTS: Record<string, string> = {
|
|
5
|
-
blue:
|
|
7
|
+
blue: FALLBACK_ACCENT,
|
|
6
8
|
green: "oklch(0.6 0.16 150)",
|
|
7
9
|
orange: "oklch(0.68 0.17 50)",
|
|
8
10
|
pink: "oklch(0.65 0.2 350)",
|
|
@@ -11,6 +13,19 @@ const ACCENTS: Record<string, string> = {
|
|
|
11
13
|
teal: "oklch(0.6 0.12 195)",
|
|
12
14
|
};
|
|
13
15
|
|
|
16
|
+
// Characters valid in a CSS color value (hex, rgb/hsl/oklch functions, named
|
|
17
|
+
// colors). Anything else — notably `;`, `{`, `}` — could break out of the
|
|
18
|
+
// declaration and inject rules, so such a value is rejected.
|
|
19
|
+
const CSS_COLOR = /^[\w\s#%.,()/+-]+$/u;
|
|
20
|
+
|
|
21
|
+
/** Pass a raw color through only if it can't break out of a CSS declaration. */
|
|
22
|
+
const safeColor = (value: string, fallback: string): string =>
|
|
23
|
+
CSS_COLOR.test(value.trim()) ? value.trim() : fallback;
|
|
24
|
+
|
|
25
|
+
/** Like {@link safeColor} but drops an unsafe/absent value to `null`. */
|
|
26
|
+
const safeColorOrNull = (value: string | undefined): string | null =>
|
|
27
|
+
value && CSS_COLOR.test(value.trim()) ? value.trim() : null;
|
|
28
|
+
|
|
14
29
|
const RADII: Record<ResolvedConfig["theme"]["radius"], string> = {
|
|
15
30
|
lg: "0.75rem",
|
|
16
31
|
md: "0.5rem",
|
|
@@ -66,7 +81,7 @@ const themeRootCss = (
|
|
|
66
81
|
"--blume-action-foreground",
|
|
67
82
|
options.action ? "oklch(1 0 0)" : null
|
|
68
83
|
),
|
|
69
|
-
...cssToken("--blume-background", theme.background),
|
|
84
|
+
...cssToken("--blume-background", safeColorOrNull(theme.background)),
|
|
70
85
|
...cssToken(
|
|
71
86
|
"--blume-background-image",
|
|
72
87
|
theme.backgroundImage ? backgroundImageCss(theme.backgroundImage) : null
|
|
@@ -83,7 +98,7 @@ const themeDarkCss = (
|
|
|
83
98
|
): string => {
|
|
84
99
|
const tokens = [
|
|
85
100
|
...cssToken("--blume-accent", accentDark),
|
|
86
|
-
...cssToken("--blume-background", theme.backgroundDark),
|
|
101
|
+
...cssToken("--blume-background", safeColorOrNull(theme.backgroundDark)),
|
|
87
102
|
...cssToken(
|
|
88
103
|
"--blume-background-image",
|
|
89
104
|
theme.backgroundImageDark
|
|
@@ -106,7 +121,7 @@ ${tokens.join("\n")}
|
|
|
106
121
|
* arbitrary colors without a config change.
|
|
107
122
|
*/
|
|
108
123
|
export const resolveAccent = (theme: ResolvedConfig["theme"]): string =>
|
|
109
|
-
ACCENTS[theme.accent] ?? theme.accent;
|
|
124
|
+
ACCENTS[theme.accent] ?? safeColor(theme.accent, FALLBACK_ACCENT);
|
|
110
125
|
|
|
111
126
|
/** Resolve the configured radius preset to a CSS length. */
|
|
112
127
|
export const resolveRadius = (theme: ResolvedConfig["theme"]): string =>
|
|
@@ -118,11 +133,15 @@ export const resolveRadius = (theme: ResolvedConfig["theme"]): string =>
|
|
|
118
133
|
* arbitrary colors without a config change.
|
|
119
134
|
*/
|
|
120
135
|
export const buildThemeCss = (theme: ResolvedConfig["theme"]): string => {
|
|
121
|
-
const accent =
|
|
136
|
+
const accent =
|
|
137
|
+
ACCENTS[theme.accent] ?? safeColor(theme.accent, FALLBACK_ACCENT);
|
|
122
138
|
const accentDark = theme.accentDark
|
|
123
|
-
? (ACCENTS[theme.accentDark] ??
|
|
139
|
+
? (ACCENTS[theme.accentDark] ??
|
|
140
|
+
safeColor(theme.accentDark, FALLBACK_ACCENT))
|
|
141
|
+
: null;
|
|
142
|
+
const action = theme.action
|
|
143
|
+
? (ACCENTS[theme.action] ?? safeColor(theme.action, FALLBACK_ACCENT))
|
|
124
144
|
: null;
|
|
125
|
-
const action = theme.action ? (ACCENTS[theme.action] ?? theme.action) : null;
|
|
126
145
|
const backgroundDecoration = backgroundDecorationCss(
|
|
127
146
|
theme.backgroundDecoration
|
|
128
147
|
);
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
// Ambient types for the Vite `import.meta.env` fields Blume's client islands read
|
|
2
|
+
// (`.astro` files aren't typechecked, so only real `.ts`/`.tsx` sources need this).
|
|
3
|
+
interface ImportMetaEnv {
|
|
4
|
+
/** Deployment base path, always with a trailing slash (e.g. `/` or `/docs/`). */
|
|
5
|
+
readonly BASE_URL: string;
|
|
6
|
+
readonly DEV: boolean;
|
|
7
|
+
readonly MODE: string;
|
|
8
|
+
readonly PROD: boolean;
|
|
9
|
+
readonly SSR: boolean;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
interface ImportMeta {
|
|
13
|
+
readonly env: ImportMetaEnv;
|
|
14
|
+
}
|