blume 0.2.0 → 0.3.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 +1921 -560
- package/dist/cli/index.js.map +36 -24
- 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 +26 -502
- package/dist/types/core/types.d.ts +2 -2
- package/docs/02-deployment.mdx +21 -2
- package/docs/advanced/custom-pages.mdx +63 -1
- package/docs/configuration/ai.mdx +20 -3
- package/docs/configuration/customization.mdx +103 -5
- package/docs/configuration/index.mdx +13 -0
- package/docs/configuration/seo.mdx +5 -0
- package/docs/content/islands.mdx +73 -0
- package/docs/content/navigation.mdx +25 -0
- package/docs/index.mdx +3 -12
- package/docs/reference/cli.mdx +42 -0
- 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 +59 -0
- package/src/astro/pages.ts +5 -12
- package/src/astro/templates.ts +92 -44
- package/src/blume-modules.d.ts +25 -0
- package/src/cli/commands/build.ts +186 -1
- package/src/cli/commands/check.ts +62 -0
- package/src/cli/commands/dev.ts +21 -1
- package/src/cli/commands/doctor.ts +23 -6
- package/src/cli/commands/init.ts +163 -15
- package/src/cli/commands/validate.ts +16 -2
- package/src/cli/index.ts +15 -0
- package/src/cli/internal-error.ts +63 -0
- package/src/cli/log.ts +30 -1
- package/src/cli/prepare.ts +17 -3
- package/src/cli/required-secrets.ts +44 -0
- package/src/components/BlumePage.astro +107 -0
- package/src/components/index.ts +3 -3
- package/src/components/islands/ask-ai.tsx +15 -1
- 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 +68 -0
- package/src/core/builtin-tags.ts +39 -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 +90 -1
- package/src/core/graph.ts +7 -0
- package/src/core/nav-diagnostics.ts +205 -0
- package/src/core/project-graph.ts +40 -1
- package/src/core/schema.ts +28 -96
- package/src/core/sources/normalize.ts +51 -0
- package/src/core/types.ts +2 -2
- package/src/deploy/redirects.ts +43 -0
- package/src/migrate/mintlify/config.ts +1 -176
- package/src/migrate/starlight/config.ts +0 -4
- package/src/og/card.ts +163 -38
- package/src/registry/eject.ts +39 -9
- package/src/registry/registry.ts +166 -0
- package/src/runtime/index.ts +61 -0
- package/src/vite-env.d.ts +14 -0
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
import { EN_UI } from "../../core/i18n-ui.ts";
|
|
3
3
|
import type { UIStrings } from "../../core/i18n-ui.ts";
|
|
4
|
+
import type { BlumeClientData } from "../../core/data.ts";
|
|
4
5
|
import type {
|
|
5
6
|
Heading,
|
|
6
7
|
LocaleSwitchOption,
|
|
@@ -12,6 +13,7 @@ import { buildStructuredData } from "../../seo/jsonld.ts";
|
|
|
12
13
|
import Analytics from "./Analytics.astro";
|
|
13
14
|
import Banner from "./Banner.astro";
|
|
14
15
|
import Breadcrumbs from "./Breadcrumbs.astro";
|
|
16
|
+
import Empty from "./Empty.astro";
|
|
15
17
|
import Favicon from "./Favicon.astro";
|
|
16
18
|
import Fonts from "./Fonts.astro";
|
|
17
19
|
import { bannerInitScript, themeInitScript } from "./head-scripts.ts";
|
|
@@ -108,9 +110,20 @@ interface Props {
|
|
|
108
110
|
/**
|
|
109
111
|
* User layout-slot overrides from `components.ts` (`defineComponents`). Each
|
|
110
112
|
* key replaces the matching built-in; unknown keys are ignored. Wired slots:
|
|
111
|
-
* `Header`, `
|
|
113
|
+
* `Header`, `Logo`, `Search`, `Sidebar`, `MobileNav`, `Breadcrumbs`,
|
|
114
|
+
* `TableOfContents`, `Pagination`, `Feedback`, plus the content-injection slots
|
|
115
|
+
* `PageHeader`, `PageFooter`, and `Footer` (which have no built-in and render
|
|
116
|
+
* only when overridden). `Layout` (replacing this whole component) is wired in
|
|
117
|
+
* the generated page templates, not here.
|
|
112
118
|
*/
|
|
113
119
|
layout?: Record<string, ComponentOverride>;
|
|
120
|
+
/**
|
|
121
|
+
* Snapshot serialized for React island hooks (`blume/hooks`). Rendered into a
|
|
122
|
+
* JSON `<script>` when present; omitted for zero-JS pages that need no islands.
|
|
123
|
+
*/
|
|
124
|
+
clientData?: BlumeClientData | null;
|
|
125
|
+
/** Table-of-contents settings (`toc` config): visibility + heading range. */
|
|
126
|
+
toc?: { enabled: boolean; maxLevel: number; minLevel: number };
|
|
114
127
|
}
|
|
115
128
|
|
|
116
129
|
const {
|
|
@@ -152,21 +165,44 @@ const {
|
|
|
152
165
|
xDefault,
|
|
153
166
|
localeSwitch,
|
|
154
167
|
layout = {},
|
|
168
|
+
clientData,
|
|
169
|
+
toc = { enabled: true, maxLevel: 3, minLevel: 2 },
|
|
155
170
|
} = Astro.props;
|
|
156
171
|
|
|
172
|
+
// Serialized once for island hooks; `<` escaped so content can't break the tag.
|
|
173
|
+
const clientDataJson = clientData
|
|
174
|
+
? JSON.stringify(clientData).replaceAll("<", "\\u003c")
|
|
175
|
+
: null;
|
|
176
|
+
|
|
157
177
|
// Layout-slot overrides: render the user's component when configured, else the
|
|
158
178
|
// built-in. Each override receives the same props as the component it replaces.
|
|
179
|
+
// `Logo` and `Search` are resolved inside the header (forwarded via `layout`).
|
|
159
180
|
const HeaderSlot = resolveSlot(layout.Header, Header);
|
|
160
181
|
const SidebarSlot = resolveSlot(layout.Sidebar, NavTree);
|
|
161
182
|
const BreadcrumbsSlot = resolveSlot(layout.Breadcrumbs, Breadcrumbs);
|
|
162
183
|
const TableOfContentsSlot = resolveSlot(layout.TableOfContents, TableOfContents);
|
|
163
184
|
const PaginationSlot = resolveSlot(layout.Pagination, Pagination);
|
|
185
|
+
// The mobile drawer reuses the sidebar unless a distinct `MobileNav` is given.
|
|
186
|
+
const MobileNavSlot = layout.MobileNav
|
|
187
|
+
? resolveSlot(layout.MobileNav, NavTree)
|
|
188
|
+
: null;
|
|
189
|
+
// Content-injection slots with no built-in: the `Empty` fallback renders nothing
|
|
190
|
+
// when unconfigured, so these are safe to render unconditionally.
|
|
191
|
+
const FooterSlot = resolveSlot(layout.Footer, Empty);
|
|
192
|
+
const PageHeaderSlot = resolveSlot(layout.PageHeader, Empty);
|
|
193
|
+
const PageFooterSlot = resolveSlot(layout.PageFooter, Empty);
|
|
194
|
+
// The "Was this page helpful?" rating (rendered only when `feedback` is on).
|
|
195
|
+
const FeedbackSlot = resolveSlot(layout.Feedback, PageFeedback);
|
|
164
196
|
|
|
165
197
|
const strings = ui ?? EN_UI;
|
|
166
198
|
// Filter search to the active language only when the site is multi-locale.
|
|
167
199
|
const searchLocale =
|
|
168
200
|
localeSwitch && localeSwitch.length > 1 ? locale : undefined;
|
|
169
|
-
|
|
201
|
+
// TOC entries: the configured heading range, or none when the TOC is disabled
|
|
202
|
+
// (an empty list makes TableOfContents render nothing).
|
|
203
|
+
const tocHeadings = toc.enabled
|
|
204
|
+
? headings.filter((h) => h.depth >= toc.minLevel && h.depth <= toc.maxLevel)
|
|
205
|
+
: [];
|
|
170
206
|
const pageTitle = page.title ? `${page.title} - ${site.title}` : site.title;
|
|
171
207
|
const description = page.description ?? site.description;
|
|
172
208
|
|
|
@@ -282,6 +318,7 @@ const bannerScript = banner?.dismissible
|
|
|
282
318
|
<Banner banner={banner} />
|
|
283
319
|
<HeaderSlot
|
|
284
320
|
askEnabled={askEnabled}
|
|
321
|
+
layout={layout}
|
|
285
322
|
localeSwitch={localeSwitch}
|
|
286
323
|
logo={logo}
|
|
287
324
|
navigation={navigation}
|
|
@@ -300,7 +337,20 @@ const bannerScript = banner?.dismissible
|
|
|
300
337
|
class="fixed top-16 start-0 z-[35] h-[calc(100dvh-4rem)] w-64 max-w-[80vw] -translate-x-[105%] overflow-y-auto border-border border-e bg-background px-5 pt-4 pb-6 transition-transform rtl:translate-x-[105%] [:where([data-blume-nav-open])_&]:translate-x-0! lg:sticky lg:z-auto lg:w-auto lg:max-w-none lg:translate-x-0! lg:border-e-0 lg:bg-transparent lg:px-4"
|
|
301
338
|
>
|
|
302
339
|
<nav>
|
|
303
|
-
|
|
340
|
+
{
|
|
341
|
+
MobileNavSlot ? (
|
|
342
|
+
<>
|
|
343
|
+
<div class="lg:hidden">
|
|
344
|
+
<MobileNavSlot currentRoute={page.route} items={sidebar} />
|
|
345
|
+
</div>
|
|
346
|
+
<div class="hidden lg:block">
|
|
347
|
+
<SidebarSlot currentRoute={page.route} items={sidebar} />
|
|
348
|
+
</div>
|
|
349
|
+
</>
|
|
350
|
+
) : (
|
|
351
|
+
<SidebarSlot currentRoute={page.route} items={sidebar} />
|
|
352
|
+
)
|
|
353
|
+
}
|
|
304
354
|
</nav>
|
|
305
355
|
</aside>
|
|
306
356
|
<main class="min-w-0 px-6 pt-6 pb-10 lg:px-8 xl:px-10" id="blume-content">
|
|
@@ -310,9 +360,11 @@ const bannerScript = banner?.dismissible
|
|
|
310
360
|
title={strings.toc.title}
|
|
311
361
|
variant="mobile"
|
|
312
362
|
/>
|
|
363
|
+
<PageHeaderSlot headings={tocHeadings} page={page} route={page.route} />
|
|
313
364
|
<article class="prose mx-auto max-w-[42rem]" dir={contentDir}>
|
|
314
365
|
<slot />
|
|
315
366
|
</article>
|
|
367
|
+
<PageFooterSlot headings={tocHeadings} page={page} route={page.route} />
|
|
316
368
|
{
|
|
317
369
|
formattedLastModified && (
|
|
318
370
|
<p class="mx-auto mt-10 max-w-[42rem] text-muted-foreground text-sm">
|
|
@@ -320,7 +372,7 @@ const bannerScript = banner?.dismissible
|
|
|
320
372
|
</p>
|
|
321
373
|
)
|
|
322
374
|
}
|
|
323
|
-
{feedback && <
|
|
375
|
+
{feedback && <FeedbackSlot strings={strings.feedback} />}
|
|
324
376
|
<PaginationSlot next={next} prev={prev} strings={strings.page} />
|
|
325
377
|
</main>
|
|
326
378
|
<aside
|
|
@@ -344,6 +396,17 @@ const bannerScript = banner?.dismissible
|
|
|
344
396
|
/>
|
|
345
397
|
</aside>
|
|
346
398
|
</div>
|
|
399
|
+
<FooterSlot navigation={navigation} site={site} ui={strings} />
|
|
400
|
+
{
|
|
401
|
+
clientDataJson && (
|
|
402
|
+
<script
|
|
403
|
+
id="blume-client-data"
|
|
404
|
+
is:inline
|
|
405
|
+
set:html={clientDataJson}
|
|
406
|
+
type="application/json"
|
|
407
|
+
/>
|
|
408
|
+
)
|
|
409
|
+
}
|
|
347
410
|
<button
|
|
348
411
|
aria-label="Close navigation"
|
|
349
412
|
class="fixed inset-0 z-[30] hidden cursor-pointer border-0 bg-black/40 [:where([data-blume-nav-open])_&]:block lg:hidden"
|
|
@@ -358,6 +421,9 @@ const bannerScript = banner?.dismissible
|
|
|
358
421
|
// Registers the <blume-toc> custom element: scrollspy for the table of
|
|
359
422
|
// contents, highlighting the section currently in view as you scroll.
|
|
360
423
|
import "./toc-element.ts";
|
|
424
|
+
// Dev-only: adds a friendly hint after a React island hydration mismatch.
|
|
425
|
+
// Tree-shaken out of production builds.
|
|
426
|
+
import "./hydration-hint.ts";
|
|
361
427
|
|
|
362
428
|
const svg = (name: string) =>
|
|
363
429
|
`<svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">${icons[name]}</svg>`;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
// Dev-only: when React reports a hydration mismatch for an island, follow it
|
|
2
|
+
// with a friendly Blume hint pointing at the islands guide. Guarded by
|
|
3
|
+
// `import.meta.env.DEV`, so the whole block is tree-shaken out of production
|
|
4
|
+
// builds (the module ships no runtime code there).
|
|
5
|
+
|
|
6
|
+
const HINT =
|
|
7
|
+
"[blume] A hydration mismatch was detected in an island. Make sure props passed from `.astro`/MDX are serializable and the component renders the same output on the server and client (avoid `Date.now()`, `Math.random()`, `window` at render time). See https://useblume.dev/docs/content/islands";
|
|
8
|
+
|
|
9
|
+
const PATTERNS = [
|
|
10
|
+
/hydrat/iu,
|
|
11
|
+
/did not match/iu,
|
|
12
|
+
/server-rendered html/iu,
|
|
13
|
+
/server rendered html/iu,
|
|
14
|
+
];
|
|
15
|
+
|
|
16
|
+
if (import.meta.env.DEV && typeof window !== "undefined") {
|
|
17
|
+
const original = console.error.bind(console);
|
|
18
|
+
let shown = false;
|
|
19
|
+
console.error = (...args: unknown[]) => {
|
|
20
|
+
original(...args);
|
|
21
|
+
if (shown) {
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
const text = args.map(String).join(" ");
|
|
25
|
+
if (PATTERNS.some((pattern) => pattern.test(text))) {
|
|
26
|
+
shown = true;
|
|
27
|
+
original(HINT);
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
}
|
|
@@ -4,10 +4,12 @@ import type { ComponentOverride } from "../../core/define-components.ts";
|
|
|
4
4
|
* Resolve a layout-slot override to the component Astro should render, falling
|
|
5
5
|
* back to Blume's built-in when no usable override is configured.
|
|
6
6
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
7
|
+
* By the time values reach here, the generated `components.ts` has already turned
|
|
8
|
+
* path strings and hydrated (`client:*`) overrides into imported components /
|
|
9
|
+
* wrappers, so the runtime map holds real components. This handles the remaining
|
|
10
|
+
* cases: a bare component reference, an `IslandDescriptor` (`{ component }`,
|
|
11
|
+
* unwrapped to its component), and — as a safety net for overrides that couldn't
|
|
12
|
+
* be resolved at build time — a leftover string, which falls back to the built-in.
|
|
11
13
|
*/
|
|
12
14
|
export const resolveSlot = <T>(
|
|
13
15
|
override: ComponentOverride | undefined,
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
// Public prop types for Blume's built-in components, so users can type their
|
|
2
|
+
// overrides and wrappers: `import type { CalloutProps } from "blume/components"`.
|
|
3
|
+
//
|
|
4
|
+
// Each type is derived straight from the component with Astro's `ComponentProps`,
|
|
5
|
+
// so it can never drift from the real props. `typeof import(...)` keeps these
|
|
6
|
+
// type-only (no runtime import); the file-level disable is because that syntax is
|
|
7
|
+
// the only way to reference an `.astro` component's type without a value import.
|
|
8
|
+
// oxlint-disable typescript/consistent-type-imports
|
|
9
|
+
import type { ComponentProps } from "astro/types";
|
|
10
|
+
|
|
11
|
+
export type AccordionProps = ComponentProps<
|
|
12
|
+
typeof import("./content/Accordion.astro").default
|
|
13
|
+
>;
|
|
14
|
+
export type AccordionItemProps = ComponentProps<
|
|
15
|
+
typeof import("./content/AccordionItem.astro").default
|
|
16
|
+
>;
|
|
17
|
+
export type BadgeProps = ComponentProps<
|
|
18
|
+
typeof import("./content/Badge.astro").default
|
|
19
|
+
>;
|
|
20
|
+
export type CalloutProps = ComponentProps<
|
|
21
|
+
typeof import("./content/Callout.astro").default
|
|
22
|
+
>;
|
|
23
|
+
export type CardProps = ComponentProps<
|
|
24
|
+
typeof import("./content/Card.astro").default
|
|
25
|
+
>;
|
|
26
|
+
export type CardGroupProps = ComponentProps<
|
|
27
|
+
typeof import("./content/CardGroup.astro").default
|
|
28
|
+
>;
|
|
29
|
+
export type CodeGroupProps = ComponentProps<
|
|
30
|
+
typeof import("./content/CodeGroup.astro").default
|
|
31
|
+
>;
|
|
32
|
+
export type ColumnProps = ComponentProps<
|
|
33
|
+
typeof import("./content/Column.astro").default
|
|
34
|
+
>;
|
|
35
|
+
export type ColumnsProps = ComponentProps<
|
|
36
|
+
typeof import("./content/Columns.astro").default
|
|
37
|
+
>;
|
|
38
|
+
export type ExpandableProps = ComponentProps<
|
|
39
|
+
typeof import("./content/Expandable.astro").default
|
|
40
|
+
>;
|
|
41
|
+
export type FrameProps = ComponentProps<
|
|
42
|
+
typeof import("./content/Frame.astro").default
|
|
43
|
+
>;
|
|
44
|
+
export type PanelProps = ComponentProps<
|
|
45
|
+
typeof import("./content/Panel.astro").default
|
|
46
|
+
>;
|
|
47
|
+
export type PromptProps = ComponentProps<
|
|
48
|
+
typeof import("./content/Prompt.astro").default
|
|
49
|
+
>;
|
|
50
|
+
export type StepProps = ComponentProps<
|
|
51
|
+
typeof import("./content/Step.astro").default
|
|
52
|
+
>;
|
|
53
|
+
export type StepsProps = ComponentProps<
|
|
54
|
+
typeof import("./content/Steps.astro").default
|
|
55
|
+
>;
|
|
56
|
+
export type TabProps = ComponentProps<
|
|
57
|
+
typeof import("./content/Tab.astro").default
|
|
58
|
+
>;
|
|
59
|
+
export type TabsProps = ComponentProps<
|
|
60
|
+
typeof import("./content/Tabs.astro").default
|
|
61
|
+
>;
|
|
62
|
+
export type TileProps = ComponentProps<
|
|
63
|
+
typeof import("./content/Tile.astro").default
|
|
64
|
+
>;
|
|
65
|
+
export type TooltipProps = ComponentProps<
|
|
66
|
+
typeof import("./content/Tooltip.astro").default
|
|
67
|
+
>;
|
|
68
|
+
export type IconProps = ComponentProps<typeof import("./Icon.astro").default>;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The MDX component tags Blume ships (the keys of the catch-all's component map,
|
|
3
|
+
* plus composed sub-parts like `Color.Item`/`Tree.File`). Used by the
|
|
4
|
+
* missing-component diagnostic to tell an unknown `<Tag>` from a built-in. Keep
|
|
5
|
+
* in sync with the component map in `astro/templates.ts`.
|
|
6
|
+
*/
|
|
7
|
+
export const BUILTIN_MDX_TAGS = new Set<string>([
|
|
8
|
+
"Accordion",
|
|
9
|
+
"AccordionItem",
|
|
10
|
+
"AutoTypeTable",
|
|
11
|
+
"Badge",
|
|
12
|
+
"Callout",
|
|
13
|
+
"Card",
|
|
14
|
+
"CardGroup",
|
|
15
|
+
"CodeBlock",
|
|
16
|
+
"CodeGroup",
|
|
17
|
+
"Color",
|
|
18
|
+
"Column",
|
|
19
|
+
"Columns",
|
|
20
|
+
"Component",
|
|
21
|
+
"Diff",
|
|
22
|
+
"Expandable",
|
|
23
|
+
"FileTree",
|
|
24
|
+
"Frame",
|
|
25
|
+
"GithubInfo",
|
|
26
|
+
"Icon",
|
|
27
|
+
"Math",
|
|
28
|
+
"Panel",
|
|
29
|
+
"Prompt",
|
|
30
|
+
"Step",
|
|
31
|
+
"Steps",
|
|
32
|
+
"Tab",
|
|
33
|
+
"Tabs",
|
|
34
|
+
"Tile",
|
|
35
|
+
"Tooltip",
|
|
36
|
+
"Tree",
|
|
37
|
+
"TypeTable",
|
|
38
|
+
"Visibility",
|
|
39
|
+
]);
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { BUILTIN_MDX_TAGS } from "./builtin-tags.ts";
|
|
2
|
+
import type { Diagnostic, PageRecord } from "./types.ts";
|
|
3
|
+
|
|
4
|
+
/** `CardGroup` → `card-group`, matching registry item names. */
|
|
5
|
+
const toKebab = (tag: string): string =>
|
|
6
|
+
tag
|
|
7
|
+
.replaceAll(/(?<lower>[a-z0-9])(?<upper>[A-Z])/gu, "$<lower>-$<upper>")
|
|
8
|
+
.toLowerCase();
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Warn when an `.mdx` page uses a `<Component>` tag that resolves to nothing —
|
|
12
|
+
* a built-in, an island, or a `components.ts` override — so a typo surfaces as a
|
|
13
|
+
* friendly diagnostic (with a `blume add` hint where one exists) instead of a raw
|
|
14
|
+
* MDX "X is not defined" build error. `extraTags` are the project's own known
|
|
15
|
+
* components (islands + overrides); `registryNames` gates the install hint.
|
|
16
|
+
*/
|
|
17
|
+
export const validateUsedComponents = (
|
|
18
|
+
pages: PageRecord[],
|
|
19
|
+
extraTags: Set<string>,
|
|
20
|
+
registryNames: Set<string>
|
|
21
|
+
): Diagnostic[] => {
|
|
22
|
+
const diagnostics: Diagnostic[] = [];
|
|
23
|
+
const seen = new Set<string>();
|
|
24
|
+
for (const page of pages) {
|
|
25
|
+
for (const tag of page.componentsUsed ?? []) {
|
|
26
|
+
if (BUILTIN_MDX_TAGS.has(tag) || extraTags.has(tag) || seen.has(tag)) {
|
|
27
|
+
continue;
|
|
28
|
+
}
|
|
29
|
+
seen.add(tag);
|
|
30
|
+
const name = toKebab(tag);
|
|
31
|
+
const suggestion = registryNames.has(name)
|
|
32
|
+
? `Run \`blume add ${name}\` to install it, or register <${tag}> in components.ts (mdx).`
|
|
33
|
+
: `Register <${tag}> in components.ts (mdx), or add an islands/${tag}.tsx component.`;
|
|
34
|
+
diagnostics.push({
|
|
35
|
+
code: "BLUME_UNKNOWN_COMPONENT",
|
|
36
|
+
file: page.sourcePath ?? page.id,
|
|
37
|
+
message: `<${tag}> is used in ${page.route} but isn't a known component.`,
|
|
38
|
+
severity: "warning",
|
|
39
|
+
suggestion,
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return diagnostics;
|
|
44
|
+
};
|