blume 0.1.5 → 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 +2123 -555
- package/dist/cli/index.js.map +39 -25
- 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 +136 -508
- package/dist/types/core/types.d.ts +2 -2
- package/docs/02-deployment.mdx +21 -2
- package/docs/advanced/changelog.mdx +28 -1
- package/docs/advanced/custom-pages.mdx +63 -2
- package/docs/configuration/ai.mdx +20 -3
- package/docs/configuration/customization.mdx +103 -5
- package/docs/configuration/index.mdx +25 -11
- package/docs/configuration/search.mdx +13 -1
- package/docs/configuration/seo.mdx +5 -0
- package/docs/configuration/theming.mdx +51 -0
- package/docs/content/components.mdx +18 -0
- package/docs/content/islands.mdx +73 -0
- package/docs/content/navigation.mdx +25 -0
- package/docs/content/sources.mdx +43 -0
- package/docs/content/syntax.mdx +1 -1
- package/docs/index.mdx +3 -12
- package/docs/reference/cli.mdx +49 -1
- package/docs/reference/frontmatter.mdx +9 -1
- 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 +162 -26
- package/src/astro/integration.ts +59 -0
- package/src/astro/pages.ts +5 -12
- package/src/astro/templates.ts +102 -45
- 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/env.ts +84 -0
- package/src/cli/index.ts +20 -0
- package/src/cli/internal-error.ts +63 -0
- package/src/cli/log.ts +30 -1
- package/src/cli/prepare.ts +22 -3
- package/src/cli/required-secrets.ts +44 -0
- package/src/components/BlumePage.astro +107 -0
- package/src/components/content/CodeBlock.astro +7 -2
- 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 +54 -96
- package/src/core/sources/github-releases.ts +200 -0
- package/src/core/sources/normalize.ts +51 -0
- package/src/core/sources/resolve.ts +16 -0
- package/src/core/types.ts +2 -2
- package/src/deploy/redirects.ts +43 -0
- package/src/markdown/index.ts +24 -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
- package/docs/changelog/v0-1-0.mdx +0 -12
- package/docs/changelog/v0-2-0.mdx +0 -16
package/src/astro/generate.ts
CHANGED
|
@@ -14,10 +14,13 @@ import { pathToFileURL } from "node:url";
|
|
|
14
14
|
import { basename, dirname, join, normalize, relative } from "pathe";
|
|
15
15
|
import { glob } from "tinyglobby";
|
|
16
16
|
|
|
17
|
+
import { buildAskData } from "../ai/ask-data.ts";
|
|
17
18
|
import { resolveAskBackend } from "../ai/ask.ts";
|
|
18
19
|
import { buildRawMarkdown } from "../ai/markdown.ts";
|
|
19
20
|
import { buildMcpData } from "../ai/mcp/data.ts";
|
|
20
21
|
import { buildMcpDiscovery, buildMcpServerCard } from "../ai/mcp/discovery.ts";
|
|
22
|
+
import { validateUsedComponents } from "../core/component-diagnostics.ts";
|
|
23
|
+
import { analyzeComponentOverrides } from "../core/component-overrides.ts";
|
|
21
24
|
import type {
|
|
22
25
|
BlumeBanner,
|
|
23
26
|
BlumeData,
|
|
@@ -26,6 +29,7 @@ import type {
|
|
|
26
29
|
} from "../core/data.ts";
|
|
27
30
|
import { EN_UI, resolveUIStrings } from "../core/i18n-ui.ts";
|
|
28
31
|
import { resolveFallbackLocale } from "../core/i18n.ts";
|
|
32
|
+
import { validateNavTargets } from "../core/nav-diagnostics.ts";
|
|
29
33
|
import { packageRoot } from "../core/package-root.ts";
|
|
30
34
|
import type { BlumeProject } from "../core/project-graph.ts";
|
|
31
35
|
import type { ResolvedConfig } from "../core/schema.ts";
|
|
@@ -37,12 +41,15 @@ import {
|
|
|
37
41
|
hasReferences,
|
|
38
42
|
referenceTabs,
|
|
39
43
|
} from "../openapi/scalar.ts";
|
|
44
|
+
import { registry } from "../registry/registry.ts";
|
|
40
45
|
import { buildSearchDocuments } from "../search/documents.ts";
|
|
41
46
|
import { searchProviderMeta, servesStaticIndex } from "../search/providers.ts";
|
|
42
47
|
import { tailwindEntryTemplate } from "../theme/entry.ts";
|
|
43
48
|
import { buildFontsCss, configuredCssVars } from "../theme/fonts.ts";
|
|
44
49
|
import { buildThemeCss } from "../theme/palette.ts";
|
|
45
50
|
import { twoslashCss } from "../theme/twoslash.ts";
|
|
51
|
+
import { planComponentSlots } from "./component-slots.ts";
|
|
52
|
+
import type { ComponentSlotPlan } from "./component-slots.ts";
|
|
46
53
|
import { discoverExamples } from "./examples.ts";
|
|
47
54
|
import { discoverIslands } from "./islands.ts";
|
|
48
55
|
import { customOgRoutes, discoverPages, routeIsTaken } from "./pages.ts";
|
|
@@ -72,7 +79,6 @@ import {
|
|
|
72
79
|
searchClientTemplate,
|
|
73
80
|
searchEndpointTemplate,
|
|
74
81
|
stagedContentDir,
|
|
75
|
-
userComponentsTemplate,
|
|
76
82
|
} from "./templates.ts";
|
|
77
83
|
|
|
78
84
|
/** Absolute path to the Blume package `src` directory. */
|
|
@@ -661,6 +667,7 @@ export const buildRuntimeData = (project: BlumeProject): string => {
|
|
|
661
667
|
structuredData: config.seo.structuredData,
|
|
662
668
|
theme: config.theme,
|
|
663
669
|
title: config.title,
|
|
670
|
+
toc: config.toc,
|
|
664
671
|
},
|
|
665
672
|
feeds: buildRssFeeds(project).map((feed) => ({
|
|
666
673
|
href: feed.path,
|
|
@@ -787,6 +794,33 @@ const writeMcpFiles = async (
|
|
|
787
794
|
]);
|
|
788
795
|
};
|
|
789
796
|
|
|
797
|
+
/**
|
|
798
|
+
* Write the Ask AI endpoint and, unless the backend runs its own retrieval
|
|
799
|
+
* (Inkeep), the grounding snapshot the endpoint queries at request time. A no-op
|
|
800
|
+
* when Ask AI is disabled.
|
|
801
|
+
*/
|
|
802
|
+
const writeAskFiles = async (
|
|
803
|
+
project: BlumeProject,
|
|
804
|
+
srcDir: string,
|
|
805
|
+
write: (path: string, content: string) => Promise<boolean>
|
|
806
|
+
): Promise<void> => {
|
|
807
|
+
const { ask } = project.config.ai;
|
|
808
|
+
if (!ask?.enabled) {
|
|
809
|
+
return;
|
|
810
|
+
}
|
|
811
|
+
const grounded = ask.provider !== "inkeep";
|
|
812
|
+
if (grounded) {
|
|
813
|
+
await write(
|
|
814
|
+
join(srcDir, "generated", "ask-data.json"),
|
|
815
|
+
`${JSON.stringify(await buildAskData(project))}\n`
|
|
816
|
+
);
|
|
817
|
+
}
|
|
818
|
+
await write(
|
|
819
|
+
join(srcDir, "pages", "api", "ask.ts"),
|
|
820
|
+
askEndpointTemplate(resolveAskBackend(ask), grounded)
|
|
821
|
+
);
|
|
822
|
+
};
|
|
823
|
+
|
|
790
824
|
/**
|
|
791
825
|
* Write the default 404 page at Astro's reserved `src/pages/404.astro` path so
|
|
792
826
|
* static builds emit `dist/404.html`. Skipped when the project already owns
|
|
@@ -813,6 +847,57 @@ export interface GenerateResult {
|
|
|
813
847
|
warnings: string[];
|
|
814
848
|
}
|
|
815
849
|
|
|
850
|
+
/**
|
|
851
|
+
* Whether to generate the default `/changelog` index. Written when there are
|
|
852
|
+
* `type: changelog` entries — or when a release-backed changelog source is
|
|
853
|
+
* configured, so its route (and any nav tab pointing at it) still resolves to an
|
|
854
|
+
* empty timeline on a build where the source could not be fetched (e.g. CI
|
|
855
|
+
* without a token). Skipped when a user content page already owns `/changelog`.
|
|
856
|
+
*/
|
|
857
|
+
const shouldGenerateChangelog = (project: BlumeProject): boolean => {
|
|
858
|
+
const hasChangelog = project.graph.pages.some(
|
|
859
|
+
(page) =>
|
|
860
|
+
page.contentType === "changelog" &&
|
|
861
|
+
!(page.meta.draft || page.meta.sidebar.hidden)
|
|
862
|
+
);
|
|
863
|
+
const hasChangelogSource = (project.config.content.sources ?? []).some(
|
|
864
|
+
(source) => source.type === "github-releases"
|
|
865
|
+
);
|
|
866
|
+
const changelogRouteTaken = project.graph.pages.some(
|
|
867
|
+
(page) => page.route === "/changelog"
|
|
868
|
+
);
|
|
869
|
+
return (hasChangelog || hasChangelogSource) && !changelogRouteTaken;
|
|
870
|
+
};
|
|
871
|
+
|
|
872
|
+
/**
|
|
873
|
+
* Statically analyze the user's `components.ts` (never executing it) and plan the
|
|
874
|
+
* generated `components.ts` module plus any hydration wrappers. Returns the plan
|
|
875
|
+
* and the analyzer's warnings; a project with no components file gets an empty
|
|
876
|
+
* plan and no warnings.
|
|
877
|
+
*/
|
|
878
|
+
const buildComponentSlots = async (
|
|
879
|
+
componentsFile: string | null
|
|
880
|
+
): Promise<{
|
|
881
|
+
plan: ComponentSlotPlan;
|
|
882
|
+
/** MDX tags the overrides define (for the unknown-component check). */
|
|
883
|
+
tags: string[];
|
|
884
|
+
warnings: string[];
|
|
885
|
+
}> => {
|
|
886
|
+
const analysis = componentsFile
|
|
887
|
+
? analyzeComponentOverrides(
|
|
888
|
+
await readFile(componentsFile, "utf-8"),
|
|
889
|
+
componentsFile
|
|
890
|
+
)
|
|
891
|
+
: null;
|
|
892
|
+
return {
|
|
893
|
+
plan: planComponentSlots(componentsFile, analysis),
|
|
894
|
+
tags: analysis
|
|
895
|
+
? [...analysis.mdx, ...analysis.islands].map((entry) => entry.key)
|
|
896
|
+
: [],
|
|
897
|
+
warnings: analysis ? analysis.warnings : [],
|
|
898
|
+
};
|
|
899
|
+
};
|
|
900
|
+
|
|
816
901
|
/**
|
|
817
902
|
* Write (or update) the generated `.blume/` Astro runtime for a project.
|
|
818
903
|
* Only files whose content changed are rewritten so Vite HMR stays fast.
|
|
@@ -851,12 +936,23 @@ export const generateRuntime = async (
|
|
|
851
936
|
discoverIslands(context.root),
|
|
852
937
|
discoverExamples(context.root, config.examples),
|
|
853
938
|
]);
|
|
939
|
+
// Statically analyze `components.ts` overrides (never executed): drives the
|
|
940
|
+
// `islands` group, hydration on layout/mdx overrides, string-path resolution,
|
|
941
|
+
// and the "framework component with no client mode" diagnostic.
|
|
942
|
+
const {
|
|
943
|
+
plan: slotPlan,
|
|
944
|
+
tags: overrideTags,
|
|
945
|
+
warnings: overrideWarnings,
|
|
946
|
+
} = await buildComponentSlots(context.componentsFile);
|
|
947
|
+
|
|
854
948
|
// Each island/example framework enables its Astro renderer. React also
|
|
855
949
|
// switches on for any project `.tsx`/`.jsx` and for Ask AI; Vue/Svelte are
|
|
856
|
-
// island/example-driven. `.astro` examples need no renderer.
|
|
950
|
+
// island/example-driven. `.astro` examples need no renderer. Component
|
|
951
|
+
// overrides referencing a framework component enable its renderer too.
|
|
857
952
|
const frameworks = new Set<string>([
|
|
858
953
|
...islandDiscovery.islands.map((island) => island.framework),
|
|
859
954
|
...exampleDiscovery.examples.map((example) => example.framework),
|
|
955
|
+
...slotPlan.frameworks,
|
|
860
956
|
]);
|
|
861
957
|
const needsReact = detectedReact || askEnabled || frameworks.has("react");
|
|
862
958
|
const needsVue = frameworks.has("vue");
|
|
@@ -865,7 +961,7 @@ export const generateRuntime = async (
|
|
|
865
961
|
// Custom pages that should get a generated OG card (the home most of all).
|
|
866
962
|
// Computed before the MCP `.well-known` routes are appended below — those are
|
|
867
963
|
// private and filtered out anyway, but the intent is the user's pages.
|
|
868
|
-
const ogRoutes = customOgRoutes(pages, config.title
|
|
964
|
+
const ogRoutes = customOgRoutes(pages, config.title);
|
|
869
965
|
|
|
870
966
|
// The hosted MCP server. The `.well-known` discovery docs are injected as
|
|
871
967
|
// prerendered routes alongside user pages; the server endpoint itself is a
|
|
@@ -915,12 +1011,10 @@ export const generateRuntime = async (
|
|
|
915
1011
|
exportEpub,
|
|
916
1012
|
exportPdf,
|
|
917
1013
|
mathEnabled: config.markdown.math,
|
|
1014
|
+
needsReact,
|
|
918
1015
|
})
|
|
919
1016
|
),
|
|
920
|
-
write(
|
|
921
|
-
join(srcDir, "generated", "components.ts"),
|
|
922
|
-
userComponentsTemplate(context.componentsFile)
|
|
923
|
-
),
|
|
1017
|
+
write(join(srcDir, "generated", "components.ts"), slotPlan.module),
|
|
924
1018
|
write(
|
|
925
1019
|
join(srcDir, "generated", "islands.ts"),
|
|
926
1020
|
islandMapTemplate(islandDiscovery.islands)
|
|
@@ -955,6 +1049,18 @@ export const generateRuntime = async (
|
|
|
955
1049
|
)
|
|
956
1050
|
);
|
|
957
1051
|
|
|
1052
|
+
// Per-override hydration wrappers for `defineComponents` islands and `client:*`
|
|
1053
|
+
// layout/mdx overrides. The generated `components.ts` (written above) imports
|
|
1054
|
+
// these; orphans from removed overrides are pruned at the end of the pass.
|
|
1055
|
+
await Promise.all(
|
|
1056
|
+
slotPlan.wrappers.map((wrapper) =>
|
|
1057
|
+
write(
|
|
1058
|
+
join(srcDir, "generated", "component-slots", `${wrapper.name}.astro`),
|
|
1059
|
+
wrapper.content
|
|
1060
|
+
)
|
|
1061
|
+
)
|
|
1062
|
+
);
|
|
1063
|
+
|
|
958
1064
|
// Per-example live wrappers for the `examples/` convention, resolved by
|
|
959
1065
|
// `<Component path>` through the `examples.ts` map (written above, always).
|
|
960
1066
|
await Promise.all(
|
|
@@ -971,12 +1077,7 @@ export const generateRuntime = async (
|
|
|
971
1077
|
)
|
|
972
1078
|
);
|
|
973
1079
|
|
|
974
|
-
|
|
975
|
-
await write(
|
|
976
|
-
join(srcDir, "pages", "api", "ask.ts"),
|
|
977
|
-
askEndpointTemplate(resolveAskBackend(config.ai.ask))
|
|
978
|
-
);
|
|
979
|
-
}
|
|
1080
|
+
await writeAskFiles(project, srcDir, write);
|
|
980
1081
|
|
|
981
1082
|
await writeMcpFiles(project, mcp, write);
|
|
982
1083
|
|
|
@@ -987,21 +1088,17 @@ export const generateRuntime = async (
|
|
|
987
1088
|
);
|
|
988
1089
|
}
|
|
989
1090
|
|
|
990
|
-
// Changelog index (`/changelog`)
|
|
991
|
-
|
|
992
|
-
// when a user content page already occupies the `/changelog` route.
|
|
993
|
-
const hasChangelog = project.graph.pages.some(
|
|
994
|
-
(page) =>
|
|
995
|
-
page.contentType === "changelog" &&
|
|
996
|
-
!(page.meta.draft || page.meta.sidebar.hidden)
|
|
997
|
-
);
|
|
998
|
-
const changelogRouteTaken = project.graph.pages.some(
|
|
999
|
-
(page) => page.route === "/changelog"
|
|
1000
|
-
);
|
|
1001
|
-
if (hasChangelog && !changelogRouteTaken) {
|
|
1091
|
+
// Changelog index (`/changelog`), rendered through the Update timeline layout.
|
|
1092
|
+
if (shouldGenerateChangelog(project)) {
|
|
1002
1093
|
await write(
|
|
1003
1094
|
join(srcDir, "pages", "changelog.astro"),
|
|
1004
|
-
changelogIndexTemplate({
|
|
1095
|
+
changelogIndexTemplate({
|
|
1096
|
+
askEnabled,
|
|
1097
|
+
exportEpub,
|
|
1098
|
+
exportPdf,
|
|
1099
|
+
needsReact,
|
|
1100
|
+
staged: hasStaged,
|
|
1101
|
+
})
|
|
1005
1102
|
);
|
|
1006
1103
|
}
|
|
1007
1104
|
|
|
@@ -1075,8 +1172,47 @@ export const generateRuntime = async (
|
|
|
1075
1172
|
...mcp.warnings,
|
|
1076
1173
|
...islandDiscovery.warnings,
|
|
1077
1174
|
...exampleDiscovery.warnings,
|
|
1175
|
+
...overrideWarnings,
|
|
1078
1176
|
];
|
|
1079
1177
|
|
|
1178
|
+
// Missing-navigation-target check, now that every servable route is known:
|
|
1179
|
+
// content routes, custom `.astro` pages, the generated changelog, and any
|
|
1180
|
+
// OpenAPI reference tabs.
|
|
1181
|
+
const navTargetRoutes = new Set<string>([
|
|
1182
|
+
...project.graph.routes.keys(),
|
|
1183
|
+
...pages.map((page) => page.pattern),
|
|
1184
|
+
...referenceTabs(config).map((tab) => tab.path),
|
|
1185
|
+
]);
|
|
1186
|
+
if (shouldGenerateChangelog(project)) {
|
|
1187
|
+
navTargetRoutes.add("/changelog");
|
|
1188
|
+
}
|
|
1189
|
+
warnings.push(
|
|
1190
|
+
...validateNavTargets(project.graph.navigation, navTargetRoutes).map(
|
|
1191
|
+
(diagnostic) =>
|
|
1192
|
+
diagnostic.suggestion
|
|
1193
|
+
? `${diagnostic.message} ${diagnostic.suggestion}`
|
|
1194
|
+
: diagnostic.message
|
|
1195
|
+
)
|
|
1196
|
+
);
|
|
1197
|
+
|
|
1198
|
+
// Unknown-component check: a `<Tag>` in MDX that isn't a built-in, an island,
|
|
1199
|
+
// or a `components.ts` override. Needs the project's own components, known here.
|
|
1200
|
+
const knownComponentTags = new Set<string>([
|
|
1201
|
+
...islandDiscovery.islands.map((island) => island.name),
|
|
1202
|
+
...overrideTags,
|
|
1203
|
+
]);
|
|
1204
|
+
warnings.push(
|
|
1205
|
+
...validateUsedComponents(
|
|
1206
|
+
project.graph.pages,
|
|
1207
|
+
knownComponentTags,
|
|
1208
|
+
new Set(registry.map((item) => item.name))
|
|
1209
|
+
).map((diagnostic) =>
|
|
1210
|
+
diagnostic.suggestion
|
|
1211
|
+
? `${diagnostic.message} ${diagnostic.suggestion}`
|
|
1212
|
+
: diagnostic.message
|
|
1213
|
+
)
|
|
1214
|
+
);
|
|
1215
|
+
|
|
1080
1216
|
// Provider SDKs are optional peers; warn (rather than fail opaquely in Vite)
|
|
1081
1217
|
// when the configured provider's package isn't installed. A dep is available
|
|
1082
1218
|
// if the project installed it (resolves from the root) OR Blume ships it
|
package/src/astro/integration.ts
CHANGED
|
@@ -2,8 +2,64 @@ import type { IncomingMessage, ServerResponse } from "node:http";
|
|
|
2
2
|
|
|
3
3
|
import type { AstroIntegration } from "astro";
|
|
4
4
|
|
|
5
|
+
import { enrichDiagnostic } from "../core/diagnostics.ts";
|
|
6
|
+
import type { Diagnostic } from "../core/types.ts";
|
|
5
7
|
import { markdownVariantUrl, prefersMarkdown } from "./markdown-negotiation.ts";
|
|
6
8
|
|
|
9
|
+
/** The dev server's HMR channel — either `.ws` (Vite ≤5) or `.hot` (Vite 6+). */
|
|
10
|
+
interface OverlayChannel {
|
|
11
|
+
send: (payload: unknown) => void;
|
|
12
|
+
}
|
|
13
|
+
interface OverlayServer {
|
|
14
|
+
hot?: OverlayChannel;
|
|
15
|
+
ws?: OverlayChannel;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// Set on `astro:server:setup`; read by `showBlumeErrorOverlay` so the CLI's
|
|
19
|
+
// regeneration can push Blume diagnostics into Vite's browser error overlay.
|
|
20
|
+
// Same-process module singleton (dev and the integration share the instance).
|
|
21
|
+
let overlayServer: OverlayServer | null = null;
|
|
22
|
+
|
|
23
|
+
const overlayChannel = (): OverlayChannel | undefined =>
|
|
24
|
+
overlayServer?.ws ?? overlayServer?.hot;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Surface Blume's own diagnostics (config/frontmatter/content errors) in the
|
|
28
|
+
* Vite/Astro browser error overlay during `blume dev`, so they don't hide in the
|
|
29
|
+
* terminal. A no-op when there are no errors or the dev server isn't up. The
|
|
30
|
+
* overlay clears itself on the next successful HMR update.
|
|
31
|
+
*/
|
|
32
|
+
export const showBlumeErrorOverlay = (diagnostics: Diagnostic[]): void => {
|
|
33
|
+
const errors = diagnostics
|
|
34
|
+
.filter((diagnostic) => diagnostic.severity === "error")
|
|
35
|
+
.map(enrichDiagnostic);
|
|
36
|
+
const channel = overlayChannel();
|
|
37
|
+
if (errors.length === 0 || !channel) {
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
const body = errors
|
|
41
|
+
.map((diagnostic) => {
|
|
42
|
+
const where = diagnostic.file
|
|
43
|
+
? `\n at ${diagnostic.file}${diagnostic.line ? `:${diagnostic.line}` : ""}`
|
|
44
|
+
: "";
|
|
45
|
+
const fix = diagnostic.suggestion
|
|
46
|
+
? `\n fix: ${diagnostic.suggestion}`
|
|
47
|
+
: "";
|
|
48
|
+
const docs = diagnostic.docsUrl ? `\n docs: ${diagnostic.docsUrl}` : "";
|
|
49
|
+
return `[${diagnostic.code}] ${diagnostic.message}${where}${fix}${docs}`;
|
|
50
|
+
})
|
|
51
|
+
.join("\n\n");
|
|
52
|
+
channel.send({
|
|
53
|
+
err: {
|
|
54
|
+
id: errors[0]?.file,
|
|
55
|
+
message: `Blume found ${errors.length} error(s):\n\n${body}`,
|
|
56
|
+
plugin: "blume",
|
|
57
|
+
stack: "",
|
|
58
|
+
},
|
|
59
|
+
type: "error",
|
|
60
|
+
});
|
|
61
|
+
};
|
|
62
|
+
|
|
7
63
|
/** A user page mounted into the generated runtime. */
|
|
8
64
|
export interface BlumePageRoute {
|
|
9
65
|
/** Route pattern, e.g. `/changelog` or `/examples/[slug]`. */
|
|
@@ -62,6 +118,9 @@ export const blumeIntegration = (
|
|
|
62
118
|
}
|
|
63
119
|
},
|
|
64
120
|
"astro:server:setup": ({ server }) => {
|
|
121
|
+
// Keep a handle on the dev server so Blume diagnostics can be pushed to
|
|
122
|
+
// its browser error overlay (see `showBlumeErrorOverlay`).
|
|
123
|
+
overlayServer = server as unknown as OverlayServer;
|
|
65
124
|
// Prepend so the rewrite happens before Astro's own request handler,
|
|
66
125
|
// letting the rewritten URL resolve to the `.md` endpoint.
|
|
67
126
|
server.middlewares.stack.unshift({
|
package/src/astro/pages.ts
CHANGED
|
@@ -45,10 +45,8 @@ export const routeIsTaken = (
|
|
|
45
45
|
export interface OgCustomRoute {
|
|
46
46
|
/** `og/<slug>.png` path segment; `index` for the site root. */
|
|
47
47
|
slug: string;
|
|
48
|
-
/** Card
|
|
48
|
+
/** Card headline. */
|
|
49
49
|
title: string;
|
|
50
|
-
/** Small eyebrow line above the title. */
|
|
51
|
-
eyebrow?: string;
|
|
52
50
|
}
|
|
53
51
|
|
|
54
52
|
/** Skip private (`_partial`, `.well-known`) and Astro dynamic (`[param]`) parts. */
|
|
@@ -68,13 +66,12 @@ const humanizeSegment = (segment: string): string =>
|
|
|
68
66
|
*
|
|
69
67
|
* Dynamic (`[param]`) routes and private segments (`_partials`, `.well-known`)
|
|
70
68
|
* are skipped: they aren't shareable pages. The home is titled with the site
|
|
71
|
-
* title
|
|
72
|
-
*
|
|
69
|
+
* title; a deeper page is titled from its last path segment. The card's brand
|
|
70
|
+
* lockup, description, and footer come from the resolved config at render time.
|
|
73
71
|
*/
|
|
74
72
|
export const customOgRoutes = (
|
|
75
73
|
pages: BlumePageRoute[],
|
|
76
|
-
siteTitle: string
|
|
77
|
-
siteDescription?: string
|
|
74
|
+
siteTitle: string
|
|
78
75
|
): OgCustomRoute[] => {
|
|
79
76
|
const seen = new Set<string>();
|
|
80
77
|
const routes: OgCustomRoute[] = [];
|
|
@@ -91,11 +88,7 @@ export const customOgRoutes = (
|
|
|
91
88
|
}
|
|
92
89
|
seen.add(slug);
|
|
93
90
|
const last = segments.at(-1);
|
|
94
|
-
routes.push(
|
|
95
|
-
last
|
|
96
|
-
? { eyebrow: siteTitle, slug, title: humanizeSegment(last) }
|
|
97
|
-
: { eyebrow: siteDescription, slug, title: siteTitle }
|
|
98
|
-
);
|
|
91
|
+
routes.push({ slug, title: last ? humanizeSegment(last) : siteTitle });
|
|
99
92
|
}
|
|
100
93
|
return routes;
|
|
101
94
|
};
|