blume 0.6.7 → 0.8.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/CHANGELOG.md +618 -0
- package/LICENSE +21 -0
- package/README.md +107 -0
- package/dist/cli/index.js +2609 -1041
- package/dist/cli/index.js.map +110 -103
- package/dist/types/ai/component-markdown.d.ts +34 -0
- package/dist/types/components/content/youtube.d.ts +18 -0
- package/dist/types/core/base-path.d.ts +47 -0
- package/dist/types/core/config-input.d.ts +110 -12
- package/dist/types/core/config.d.ts +6 -4
- package/dist/types/core/data.d.ts +4 -0
- package/dist/types/core/i18n-ui.d.ts +477 -135
- package/dist/types/core/schema.d.ts +309 -195
- package/dist/types/core/sources/types.d.ts +2 -0
- package/dist/types/core/types.d.ts +6 -1
- package/dist/types/index.d.ts +1 -0
- package/dist/types/openapi/references.d.ts +60 -0
- package/docs/01-quickstart.mdx +5 -2
- package/docs/02-deployment.mdx +24 -9
- package/docs/03-faq.mdx +46 -16
- package/docs/advanced/custom-pages.mdx +1 -1
- package/docs/advanced/skills.mdx +1 -1
- package/docs/configuration/ai.mdx +49 -10
- package/docs/configuration/customization.mdx +11 -0
- package/docs/configuration/index.mdx +33 -3
- package/docs/configuration/seo.mdx +2 -2
- package/docs/content/components.mdx +30 -3
- package/docs/content/i18n.mdx +1 -1
- package/docs/content/islands.mdx +8 -0
- package/docs/content/navigation.mdx +3 -3
- package/docs/content/sources.mdx +1 -1
- package/docs/content/syntax.mdx +17 -2
- package/docs/index.mdx +2 -2
- package/docs/reference/cli.mdx +8 -6
- package/package.json +15 -4
- package/skills/blume/SKILL.md +5 -3
- package/skills/blume-update-docs/SKILL.md +3 -2
- package/src/ai/agent-readability.ts +11 -5
- package/src/ai/ask-context.ts +7 -2
- package/src/ai/ask-data.ts +3 -0
- package/src/ai/ask.ts +12 -7
- package/src/ai/component-markdown.ts +461 -0
- package/src/ai/llms.ts +143 -23
- package/src/ai/markdown.ts +35 -6
- package/src/ai/mcp/data.ts +33 -8
- package/src/ai/mcp/discovery.ts +10 -3
- package/src/ai/mcp/server.ts +24 -7
- package/src/ai/visibility.ts +74 -0
- package/src/astro/component-slots.ts +16 -4
- package/src/astro/examples.ts +12 -7
- package/src/astro/generate.ts +393 -189
- package/src/astro/index.ts +5 -1
- package/src/astro/integration.ts +9 -5
- package/src/astro/islands.ts +11 -5
- package/src/astro/markdown-negotiation.ts +2 -2
- package/src/astro/pages.ts +89 -22
- package/src/astro/templates.ts +259 -25
- package/src/blume-modules.d.ts +8 -0
- package/src/cli/commands/build.ts +131 -38
- package/src/cli/commands/check.ts +1 -1
- package/src/cli/commands/dev.ts +71 -17
- package/src/cli/commands/doctor.ts +2 -2
- package/src/cli/commands/eject.ts +47 -19
- package/src/cli/commands/init.ts +120 -180
- package/src/cli/commands/preview.ts +4 -1
- package/src/cli/commands/validate.ts +44 -2
- package/src/cli/dev-lock.ts +34 -19
- package/src/cli/eject-scripts.ts +72 -0
- package/src/cli/env.ts +15 -5
- package/src/cli/init/questions.ts +158 -0
- package/src/cli/init/scaffold.ts +380 -0
- package/src/cli/required-secrets.ts +2 -1
- package/src/components/content/AccordionItem.astro +23 -4
- package/src/components/content/Badge.astro +3 -1
- package/src/components/content/Card.astro +4 -2
- package/src/components/content/CodeBlock.astro +3 -0
- package/src/components/content/Component.astro +30 -16
- package/src/components/content/Diff.astro +3 -1
- package/src/components/content/Step.astro +10 -1
- package/src/components/content/Tabs.astro +15 -3
- package/src/components/content/Tile.astro +2 -1
- package/src/components/content/Tooltip.astro +3 -1
- package/src/components/content/Update.astro +9 -2
- package/src/components/content/auto-type-table.ts +25 -9
- package/src/components/content/base-href.ts +33 -0
- package/src/components/content/changelog-element.ts +9 -2
- package/src/components/content/diff.ts +12 -6
- package/src/components/content/mermaid-element.ts +10 -2
- package/src/components/index.ts +23 -1
- package/src/components/islands/AskAI.astro +5 -2
- package/src/components/islands/ask-ai.tsx +68 -12
- package/src/components/islands/base-path.ts +28 -0
- package/src/components/islands/hooks.ts +44 -9
- package/src/components/layout/Banner.astro +12 -3
- package/src/components/layout/Breadcrumbs.astro +2 -1
- package/src/components/layout/Favicon.astro +3 -2
- package/src/components/layout/Header.astro +15 -5
- package/src/components/layout/LanguageSwitcher.astro +2 -1
- package/src/components/layout/Logo.astro +13 -4
- package/src/components/layout/NavSelector.astro +2 -1
- package/src/components/layout/NavTree.astro +22 -7
- package/src/components/layout/PageActions.astro +25 -10
- package/src/components/layout/PageFeedback.astro +4 -1
- package/src/components/layout/PageLayout.astro +51 -9
- package/src/components/layout/Pagination.astro +3 -2
- package/src/components/layout/ReferenceLayout.astro +8 -1
- package/src/components/layout/RootLayout.astro +74 -13
- package/src/components/layout/Search.astro +107 -27
- package/src/components/layout/nav-utils.ts +18 -10
- package/src/components/layout/search/algolia.ts +11 -2
- package/src/components/layout/search/endpoint.ts +11 -5
- package/src/components/layout/search/orama-cloud.ts +8 -2
- package/src/components/layout/search/pagefind.ts +3 -0
- package/src/components/layout/search/types.ts +5 -1
- package/src/components/layout/search/typesense.ts +4 -1
- package/src/components/layout/toc-element.ts +8 -2
- package/src/components/openapi/ApiTagOperations.astro +2 -1
- package/src/components/openapi/Operation.astro +47 -40
- package/src/components/openapi/RequestPanel.astro +8 -2
- package/src/components/openapi/helpers.ts +71 -3
- package/src/components/openapi/panel.ts +1 -1
- package/src/components/openapi/snippets.ts +25 -11
- package/src/core/base-path.ts +94 -0
- package/src/core/builtin-tags.ts +2 -0
- package/src/core/component-overrides.ts +103 -74
- package/src/core/config-input.ts +118 -17
- package/src/core/config.ts +8 -5
- package/src/core/content.ts +2 -0
- package/src/core/data.ts +4 -0
- package/src/core/diagnostics.ts +54 -34
- package/src/core/gitignore.ts +4 -1
- package/src/core/graph.ts +166 -88
- package/src/core/i18n-ui.ts +63 -3
- package/src/core/last-modified.ts +15 -6
- package/src/core/links.ts +69 -25
- package/src/core/manifest.ts +62 -45
- package/src/core/nav-diagnostics.ts +1 -1
- package/src/core/navigation.ts +144 -58
- package/src/core/package-json.ts +17 -2
- package/src/core/project-graph.ts +25 -15
- package/src/core/schema.ts +605 -620
- package/src/core/sources/assets.ts +6 -1
- package/src/core/sources/filesystem.ts +4 -0
- package/src/core/sources/github-releases.ts +2 -1
- package/src/core/sources/mdx-remote.ts +76 -63
- package/src/core/sources/normalize.ts +236 -91
- package/src/core/sources/notion.ts +27 -18
- package/src/core/sources/types.ts +2 -0
- package/src/core/tsconfig-aliases.ts +59 -30
- package/src/core/types.ts +6 -1
- package/src/core/ui-packs/ar.ts +1 -0
- package/src/core/ui-packs/bg.ts +1 -0
- package/src/core/ui-packs/bn.ts +1 -0
- package/src/core/ui-packs/ca.ts +1 -0
- package/src/core/ui-packs/cs.ts +1 -0
- package/src/core/ui-packs/da.ts +1 -0
- package/src/core/ui-packs/de.ts +1 -0
- package/src/core/ui-packs/el.ts +1 -0
- package/src/core/ui-packs/es.ts +1 -0
- package/src/core/ui-packs/fa.ts +1 -0
- package/src/core/ui-packs/fi.ts +1 -0
- package/src/core/ui-packs/fr.ts +2 -1
- package/src/core/ui-packs/he.ts +1 -0
- package/src/core/ui-packs/hi.ts +1 -0
- package/src/core/ui-packs/hr.ts +1 -0
- package/src/core/ui-packs/hu.ts +1 -0
- package/src/core/ui-packs/id.ts +1 -0
- package/src/core/ui-packs/it.ts +1 -0
- package/src/core/ui-packs/ja.ts +1 -0
- package/src/core/ui-packs/ko.ts +1 -0
- package/src/core/ui-packs/nl.ts +1 -0
- package/src/core/ui-packs/no.ts +1 -0
- package/src/core/ui-packs/pl.ts +1 -0
- package/src/core/ui-packs/pt-br.ts +1 -0
- package/src/core/ui-packs/pt.ts +1 -0
- package/src/core/ui-packs/ro.ts +1 -0
- package/src/core/ui-packs/ru.ts +1 -0
- package/src/core/ui-packs/sk.ts +1 -0
- package/src/core/ui-packs/sr.ts +1 -0
- package/src/core/ui-packs/sv.ts +1 -0
- package/src/core/ui-packs/th.ts +1 -0
- package/src/core/ui-packs/tr.ts +1 -0
- package/src/core/ui-packs/uk.ts +1 -0
- package/src/core/ui-packs/vi.ts +1 -0
- package/src/core/ui-packs/zh-tw.ts +1 -0
- package/src/core/ui-packs/zh.ts +1 -0
- package/src/deploy/adapter-output.ts +18 -8
- package/src/deploy/redirects.ts +25 -2
- package/src/deploy/robots.ts +6 -1
- package/src/deploy/rss.ts +10 -3
- package/src/deploy/sitemap.ts +59 -13
- package/src/index.ts +5 -0
- package/src/markdown/base-links.ts +60 -0
- package/src/markdown/code-title.ts +11 -14
- package/src/markdown/index.ts +46 -9
- package/src/markdown/inline-code.ts +14 -4
- package/src/markdown/package-commands.ts +10 -4
- package/src/markdown/themes.ts +24 -0
- package/src/openapi/model.ts +15 -5
- package/src/openapi/parse.ts +21 -0
- package/src/openapi/references.ts +75 -21
- package/src/openapi/render-mdx.ts +11 -6
- package/src/openapi/scalar.ts +32 -16
- package/src/openapi/source.ts +59 -10
- package/src/registry/eject.ts +247 -19
- package/src/registry/registry.ts +0 -3
- package/src/search/build.ts +3 -0
- package/src/search/documents.ts +36 -4
- package/src/search/sync/typesense.ts +6 -4
- package/src/seo/jsonld.ts +28 -17
- package/src/theme/entry.ts +85 -20
package/src/astro/generate.ts
CHANGED
|
@@ -43,7 +43,10 @@ import { isOpenApiSource } from "../openapi/source.ts";
|
|
|
43
43
|
import { registry } from "../registry/registry.ts";
|
|
44
44
|
import { buildSearchDocuments } from "../search/documents.ts";
|
|
45
45
|
import { searchProviderMeta, servesStaticIndex } from "../search/providers.ts";
|
|
46
|
-
import {
|
|
46
|
+
import {
|
|
47
|
+
examplesEntryTemplate,
|
|
48
|
+
tailwindEntryTemplate,
|
|
49
|
+
} from "../theme/entry.ts";
|
|
47
50
|
import { buildFontsCss, configuredCssVars } from "../theme/fonts.ts";
|
|
48
51
|
import { buildThemeCss } from "../theme/palette.ts";
|
|
49
52
|
import { twoslashCss } from "../theme/twoslash.ts";
|
|
@@ -51,7 +54,12 @@ import { planComponentSlots } from "./component-slots.ts";
|
|
|
51
54
|
import type { ComponentSlotPlan } from "./component-slots.ts";
|
|
52
55
|
import { discoverExamples } from "./examples.ts";
|
|
53
56
|
import { discoverIslands } from "./islands.ts";
|
|
54
|
-
import {
|
|
57
|
+
import {
|
|
58
|
+
customOgRoutes,
|
|
59
|
+
discoverPages,
|
|
60
|
+
hasGeneratedChangelog,
|
|
61
|
+
routeIsTaken,
|
|
62
|
+
} from "./pages.ts";
|
|
55
63
|
import {
|
|
56
64
|
askEndpointTemplate,
|
|
57
65
|
astroConfigTemplate,
|
|
@@ -61,6 +69,7 @@ import {
|
|
|
61
69
|
envTemplate,
|
|
62
70
|
exampleMapTemplate,
|
|
63
71
|
exampleWrapperTemplate,
|
|
72
|
+
examplesPageTemplate,
|
|
64
73
|
exampleSlug,
|
|
65
74
|
islandMapTemplate,
|
|
66
75
|
islandWrapperTemplate,
|
|
@@ -93,6 +102,47 @@ const canResolveFrom = (fromDir: string, spec: string): boolean => {
|
|
|
93
102
|
}
|
|
94
103
|
};
|
|
95
104
|
|
|
105
|
+
/**
|
|
106
|
+
* Absolute path to `babel-plugin-react-compiler`, resolved from Blume's own
|
|
107
|
+
* package root (Blume ships it). Returns null when React or the compiler is off.
|
|
108
|
+
*
|
|
109
|
+
* The path must be absolute: @vitejs/plugin-react resolves babel plugins from
|
|
110
|
+
* the *project* root, not `.blume/`, so a bare specifier fails in a user project
|
|
111
|
+
* that never installed the plugin directly. Resolving from `packageRoot()` binds
|
|
112
|
+
* to Blume's shipped copy regardless of the user's package manager or hoisting.
|
|
113
|
+
*/
|
|
114
|
+
const resolveReactCompiler = (
|
|
115
|
+
config: ResolvedConfig,
|
|
116
|
+
needsReact: boolean
|
|
117
|
+
): string | null => {
|
|
118
|
+
if (!(needsReact && config.react.compiler)) {
|
|
119
|
+
return null;
|
|
120
|
+
}
|
|
121
|
+
try {
|
|
122
|
+
return createRequire(
|
|
123
|
+
pathToFileURL(join(packageRoot(), "_.js")).href
|
|
124
|
+
).resolve("babel-plugin-react-compiler");
|
|
125
|
+
} catch {
|
|
126
|
+
return null;
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Warning (as a spreadable list) for the case where the React Compiler was
|
|
132
|
+
* requested but its plugin couldn't be resolved — so the build silently drops
|
|
133
|
+
* to uncompiled output rather than failing.
|
|
134
|
+
*/
|
|
135
|
+
const reactCompilerWarnings = (
|
|
136
|
+
config: ResolvedConfig,
|
|
137
|
+
needsReact: boolean,
|
|
138
|
+
compilerPath: string | null
|
|
139
|
+
): string[] =>
|
|
140
|
+
needsReact && config.react.compiler && !compilerPath
|
|
141
|
+
? [
|
|
142
|
+
"React Compiler is enabled but `babel-plugin-react-compiler` could not be resolved; falling back to an uncompiled build. Reinstall Blume, or set `react: { compiler: false }` to silence this.",
|
|
143
|
+
]
|
|
144
|
+
: [];
|
|
145
|
+
|
|
96
146
|
/**
|
|
97
147
|
* Realpath of the `astro` package node resolves from a directory, or null when
|
|
98
148
|
* none resolves. Comparing this for `.blume/` against Blume's own deps tells
|
|
@@ -145,7 +195,12 @@ const linkDepsJunction = async (
|
|
|
145
195
|
link: string,
|
|
146
196
|
depsDir: string
|
|
147
197
|
): Promise<void> => {
|
|
148
|
-
|
|
198
|
+
let existing: Awaited<ReturnType<typeof lstat>> | null;
|
|
199
|
+
try {
|
|
200
|
+
existing = await lstat(link);
|
|
201
|
+
} catch {
|
|
202
|
+
existing = null;
|
|
203
|
+
}
|
|
149
204
|
if (existing) {
|
|
150
205
|
if (!existing.isSymbolicLink()) {
|
|
151
206
|
return;
|
|
@@ -282,12 +337,61 @@ export const prerenderDepsPlugin = (
|
|
|
282
337
|
},
|
|
283
338
|
});
|
|
284
339
|
|
|
340
|
+
/** The subset of Rollup's plugin context `blume:server-app-resolve` needs. */
|
|
341
|
+
interface ServerAppResolveContext {
|
|
342
|
+
resolve: (source: string) => Promise<{ id: string } | null>;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* Work around an Astro + Vite dev bug that breaks content renames.
|
|
347
|
+
*
|
|
348
|
+
* Astro's dev SSR entry is the virtual module `astro:server-app`, but its
|
|
349
|
+
* resolver only matches the exact id (`/^astro:server-app$/`). Whenever the
|
|
350
|
+
* route set changes — a content add, remove, or rename — Astro triggers a full
|
|
351
|
+
* page reload, during which Vite re-requests the entry as `astro:server-app.js`.
|
|
352
|
+
* The trailing `.js` misses Astro's filter, so the load fails ("Failed to load
|
|
353
|
+
* url astro:server-app.js") and Vite's SSR module runner is left corrupted: the
|
|
354
|
+
* in-memory content store never reconnects, so `getEntry` returns undefined and
|
|
355
|
+
* the renamed page 404s until the dev server is manually restarted.
|
|
356
|
+
*
|
|
357
|
+
* Stripping the spurious `.js` and delegating back to Astro's resolver lets the
|
|
358
|
+
* reload complete cleanly, so the renamed route resolves without a restart.
|
|
359
|
+
*/
|
|
360
|
+
export const serverAppResolvePlugin = (): {
|
|
361
|
+
enforce: "pre";
|
|
362
|
+
name: string;
|
|
363
|
+
resolveId: (
|
|
364
|
+
this: ServerAppResolveContext,
|
|
365
|
+
id: string
|
|
366
|
+
) => Promise<string | null>;
|
|
367
|
+
} => ({
|
|
368
|
+
enforce: "pre",
|
|
369
|
+
name: "blume:server-app-resolve",
|
|
370
|
+
async resolveId(id) {
|
|
371
|
+
if (id === "astro:server-app.js") {
|
|
372
|
+
const resolved = await this.resolve("astro:server-app");
|
|
373
|
+
return resolved?.id ?? null;
|
|
374
|
+
}
|
|
375
|
+
return null;
|
|
376
|
+
},
|
|
377
|
+
});
|
|
378
|
+
|
|
285
379
|
/** Astro integration package each non-React island framework needs installed. */
|
|
286
380
|
const ISLAND_FRAMEWORK_DEPS: Record<string, string> = {
|
|
287
381
|
svelte: "@astrojs/svelte",
|
|
288
382
|
vue: "@astrojs/vue",
|
|
289
383
|
};
|
|
290
384
|
|
|
385
|
+
/**
|
|
386
|
+
* Adapter package the project must install itself for each deployment
|
|
387
|
+
* platform whose adapter Blume doesn't ship. Node and Vercel ship with Blume,
|
|
388
|
+
* so they never need this.
|
|
389
|
+
*/
|
|
390
|
+
const DEPLOYMENT_ADAPTER_DEPS: Record<string, string> = {
|
|
391
|
+
cloudflare: "@astrojs/cloudflare",
|
|
392
|
+
netlify: "@astrojs/netlify",
|
|
393
|
+
};
|
|
394
|
+
|
|
291
395
|
/**
|
|
292
396
|
* Warn when a Vue/Svelte island is present but its Astro integration isn't
|
|
293
397
|
* installed — Vite would otherwise fail opaquely on the generated config import.
|
|
@@ -309,6 +413,75 @@ const islandFrameworkWarnings = (
|
|
|
309
413
|
return warnings;
|
|
310
414
|
};
|
|
311
415
|
|
|
416
|
+
/**
|
|
417
|
+
* Warn when the resolved server-output adapter is one the project must install
|
|
418
|
+
* itself (Netlify/Cloudflare; Node and Vercel ship with Blume). The generated
|
|
419
|
+
* astro.config.mjs imports the adapter package directly — and on those
|
|
420
|
+
* platforms the adapter is even auto-selected from env vars — so warn early
|
|
421
|
+
* rather than let the build die with an opaque ERR_MODULE_NOT_FOUND from the
|
|
422
|
+
* hidden generated config. Availability mirrors the search-provider check: a
|
|
423
|
+
* dep resolves from the project root or from the Blume package itself.
|
|
424
|
+
*/
|
|
425
|
+
const deploymentAdapterWarnings = (
|
|
426
|
+
deployment: ResolvedConfig["deployment"],
|
|
427
|
+
root: string
|
|
428
|
+
): string[] => {
|
|
429
|
+
const dep =
|
|
430
|
+
deployment.output === "server" && deployment.adapter
|
|
431
|
+
? DEPLOYMENT_ADAPTER_DEPS[deployment.adapter]
|
|
432
|
+
: undefined;
|
|
433
|
+
if (
|
|
434
|
+
dep &&
|
|
435
|
+
!(canResolveFrom(root, dep) || canResolveFrom(packageRoot(), dep))
|
|
436
|
+
) {
|
|
437
|
+
return [
|
|
438
|
+
`Deployment adapter "${deployment.adapter}" needs "${dep}", which isn't installed. Run \`npm install ${dep}\` (or your package manager's equivalent).`,
|
|
439
|
+
];
|
|
440
|
+
}
|
|
441
|
+
return [];
|
|
442
|
+
};
|
|
443
|
+
|
|
444
|
+
/** Absolute path to the configured `examples.css`, or null when unset. */
|
|
445
|
+
const examplesCssFile = (root: string, config: ResolvedConfig): string | null =>
|
|
446
|
+
config.examples.css ? join(root, config.examples.css) : null;
|
|
447
|
+
|
|
448
|
+
/**
|
|
449
|
+
* Write the per-example preview route (`{basePath}/blume-examples/<path>`)
|
|
450
|
+
* that `<Component />` iframes embed — the iframe boundary is what isolates
|
|
451
|
+
* previews from the docs CSS. Nested under `basePath` in the filesystem so
|
|
452
|
+
* the routes stay reachable behind a proxy that only forwards the base;
|
|
453
|
+
* pruneOrphans clears a stale copy when `basePath` changes or the last
|
|
454
|
+
* example is removed. Returns (as a spreadable list) a warning when the
|
|
455
|
+
* configured `examples.css` doesn't exist.
|
|
456
|
+
*/
|
|
457
|
+
const writeExamplesPreview = async (options: {
|
|
458
|
+
config: ResolvedConfig;
|
|
459
|
+
hasExamples: boolean;
|
|
460
|
+
root: string;
|
|
461
|
+
srcDir: string;
|
|
462
|
+
write: (path: string, content: string) => Promise<boolean>;
|
|
463
|
+
}): Promise<string[]> => {
|
|
464
|
+
const { config, hasExamples, root, srcDir, write } = options;
|
|
465
|
+
if (hasExamples) {
|
|
466
|
+
await write(
|
|
467
|
+
join(
|
|
468
|
+
srcDir,
|
|
469
|
+
"pages",
|
|
470
|
+
...config.basePath.split("/").filter(Boolean),
|
|
471
|
+
"blume-examples",
|
|
472
|
+
"[...path].astro"
|
|
473
|
+
),
|
|
474
|
+
examplesPageTemplate()
|
|
475
|
+
);
|
|
476
|
+
}
|
|
477
|
+
const cssFile = examplesCssFile(root, config);
|
|
478
|
+
return cssFile && !existsSync(cssFile)
|
|
479
|
+
? [
|
|
480
|
+
`examples.css points at "${config.examples.css}", which doesn't exist; previews render without it.`,
|
|
481
|
+
]
|
|
482
|
+
: [];
|
|
483
|
+
};
|
|
484
|
+
|
|
312
485
|
/** Read a file's contents, or return an empty string if it is absent. */
|
|
313
486
|
const readOptional = async (path: string | null): Promise<string> => {
|
|
314
487
|
if (!path) {
|
|
@@ -331,16 +504,24 @@ export const detectNeedsReact = async (root: string): Promise<boolean> => {
|
|
|
331
504
|
return matches.length > 0;
|
|
332
505
|
};
|
|
333
506
|
|
|
507
|
+
/** Block math (`$$…$$`) or an explicitly authored `<Math …>` component. */
|
|
508
|
+
const containsMath = (content: string): boolean =>
|
|
509
|
+
content.includes("$$") || content.includes("<Math");
|
|
510
|
+
|
|
334
511
|
/**
|
|
335
|
-
* Detect whether the project
|
|
336
|
-
*
|
|
337
|
-
*
|
|
338
|
-
*
|
|
339
|
-
*
|
|
340
|
-
*
|
|
512
|
+
* Detect whether the project can render math: block math (`$$…$$`) or an
|
|
513
|
+
* explicit `<Math>` tag in any local `.md`/`.mdx`, or in staged (non-filesystem)
|
|
514
|
+
* source bodies. Drives whether the generated runtime imports the `<Math>`
|
|
515
|
+
* component and KaTeX's stylesheet, so a math-free site ships no KaTeX CSS.
|
|
516
|
+
* Math parsing itself is always on but block-only, so one of those literals is
|
|
517
|
+
* a necessary condition — no false negatives. A stray `$$` (e.g. inside a code
|
|
518
|
+
* fence) merely over-includes the idempotent import, which is harmless.
|
|
341
519
|
*/
|
|
342
|
-
export const detectUsesMath = async (
|
|
343
|
-
|
|
520
|
+
export const detectUsesMath = async (
|
|
521
|
+
root: string,
|
|
522
|
+
staged: Iterable<string> = []
|
|
523
|
+
): Promise<boolean> => {
|
|
524
|
+
const files = await glob(["**/*.{md,mdx}"], {
|
|
344
525
|
cwd: root,
|
|
345
526
|
ignore: ["**/node_modules/**", "**/.blume/**", "**/dist/**"],
|
|
346
527
|
onlyFiles: true,
|
|
@@ -348,7 +529,7 @@ export const detectUsesMath = async (root: string): Promise<boolean> => {
|
|
|
348
529
|
const contents = await Promise.all(
|
|
349
530
|
files.map((file) => readOptional(join(root, file)))
|
|
350
531
|
);
|
|
351
|
-
return contents.some(
|
|
532
|
+
return [...contents, ...staged].some(containsMath);
|
|
352
533
|
};
|
|
353
534
|
|
|
354
535
|
const writeIfChanged = async (
|
|
@@ -397,12 +578,14 @@ export const pruneOrphans = async (
|
|
|
397
578
|
cwd: srcDir,
|
|
398
579
|
onlyFiles: true,
|
|
399
580
|
});
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
.
|
|
405
|
-
|
|
581
|
+
const removals: Promise<void>[] = [];
|
|
582
|
+
for (const path of existing) {
|
|
583
|
+
const normalized = normalize(path);
|
|
584
|
+
if (!written.has(normalized)) {
|
|
585
|
+
removals.push(rm(normalized, { force: true }));
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
await Promise.all(removals);
|
|
406
589
|
};
|
|
407
590
|
|
|
408
591
|
/**
|
|
@@ -601,7 +784,8 @@ export const buildRuntimeData = (project: BlumeProject): string => {
|
|
|
601
784
|
return null;
|
|
602
785
|
}
|
|
603
786
|
const rel = relative(context.root, sourcePath).split("\\").join("/");
|
|
604
|
-
|
|
787
|
+
const editPath = github?.dir ? `${github.dir}/${rel}` : rel;
|
|
788
|
+
return `${editBase}/${editPath}`;
|
|
605
789
|
};
|
|
606
790
|
|
|
607
791
|
const { i18n } = config;
|
|
@@ -659,6 +843,8 @@ export const buildRuntimeData = (project: BlumeProject): string => {
|
|
|
659
843
|
? { suggestions: config.ai.ask.suggestions }
|
|
660
844
|
: null,
|
|
661
845
|
banner: resolveBanner(config),
|
|
846
|
+
basePath: config.basePath,
|
|
847
|
+
codeThemes: config.markdown.codeBlocks.theme,
|
|
662
848
|
codeWrap: config.markdown.code.wrap,
|
|
663
849
|
description: config.description,
|
|
664
850
|
favicon: resolveFavicon(project),
|
|
@@ -800,6 +986,7 @@ const writeMcpFiles = async (
|
|
|
800
986
|
}
|
|
801
987
|
const data = await buildMcpData(project);
|
|
802
988
|
const discoveryInput = {
|
|
989
|
+
base: data.base,
|
|
803
990
|
name: data.name,
|
|
804
991
|
route: plan.route,
|
|
805
992
|
site: data.site,
|
|
@@ -878,32 +1065,6 @@ export interface GenerateResult {
|
|
|
878
1065
|
warnings: string[];
|
|
879
1066
|
}
|
|
880
1067
|
|
|
881
|
-
/**
|
|
882
|
-
* Whether to generate the default `/changelog` index. Written when there are
|
|
883
|
-
* `type: changelog` entries — or when a release-backed changelog source is
|
|
884
|
-
* configured, so its route (and any nav tab pointing at it) still resolves to an
|
|
885
|
-
* empty timeline on a build where the source could not be fetched (e.g. CI
|
|
886
|
-
* without a token). Skipped when a user content page or a custom `.astro` page
|
|
887
|
-
* already owns `/changelog`.
|
|
888
|
-
*/
|
|
889
|
-
const shouldGenerateChangelog = (
|
|
890
|
-
project: BlumeProject,
|
|
891
|
-
userPages: { pattern: string }[]
|
|
892
|
-
): boolean => {
|
|
893
|
-
const hasChangelog = project.graph.pages.some(
|
|
894
|
-
(page) =>
|
|
895
|
-
page.contentType === "changelog" &&
|
|
896
|
-
!(page.meta.draft || page.meta.sidebar.hidden)
|
|
897
|
-
);
|
|
898
|
-
const hasChangelogSource = (project.config.content.sources ?? []).some(
|
|
899
|
-
(source) => source.type === "github-releases"
|
|
900
|
-
);
|
|
901
|
-
return (
|
|
902
|
-
(hasChangelog || hasChangelogSource) &&
|
|
903
|
-
!routeIsTaken(userPages, project.graph.pages, "/changelog")
|
|
904
|
-
);
|
|
905
|
-
};
|
|
906
|
-
|
|
907
1068
|
/**
|
|
908
1069
|
* Statically analyze the user's `components.ts` (never executing it) and plan the
|
|
909
1070
|
* generated `components.ts` module plus any hydration wrappers. Returns the plan
|
|
@@ -947,6 +1108,7 @@ export const generateRuntime = async (
|
|
|
947
1108
|
const themePath = join(srcDir, "generated", "app.css");
|
|
948
1109
|
const searchClientPath = join(srcDir, "generated", "search-client.ts");
|
|
949
1110
|
const examplesPath = join(srcDir, "generated", "examples.ts");
|
|
1111
|
+
const examplesThemePath = join(srcDir, "generated", "examples.css");
|
|
950
1112
|
const openapiPath = join(srcDir, "generated", "openapi.json");
|
|
951
1113
|
|
|
952
1114
|
// Record every file this pass writes so orphans (from a now-disabled feature)
|
|
@@ -962,29 +1124,38 @@ export const generateRuntime = async (
|
|
|
962
1124
|
const askEnabled = config.ai.ask?.enabled ?? false;
|
|
963
1125
|
const exportPdf = config.export.pdf;
|
|
964
1126
|
const exportEpub = config.export.epub;
|
|
1127
|
+
// Staged (non-filesystem) sources materialize into `.blume/content`; keyed by
|
|
1128
|
+
// entryId so i18n duplicates of one entry write a single file. Collected here
|
|
1129
|
+
// so math detection also sees staged bodies (they never live under root).
|
|
1130
|
+
const staged = collectStaged(project);
|
|
1131
|
+
// Statically analyze `components.ts` overrides (never executed): drives the
|
|
1132
|
+
// `islands` group, hydration on layout/mdx overrides, string-path resolution,
|
|
1133
|
+
// and the "framework component with no client mode" diagnostic. Independent of
|
|
1134
|
+
// the discovery reads, so it joins the same parallel batch.
|
|
965
1135
|
const [
|
|
966
1136
|
pages,
|
|
967
1137
|
detectedReact,
|
|
968
1138
|
usesMath,
|
|
969
1139
|
userTheme,
|
|
1140
|
+
userExamplesCss,
|
|
970
1141
|
islandDiscovery,
|
|
971
1142
|
exampleDiscovery,
|
|
1143
|
+
componentSlots,
|
|
972
1144
|
] = await Promise.all([
|
|
973
1145
|
context.pagesRoot ? discoverPages(context.pagesRoot) : Promise.resolve([]),
|
|
974
1146
|
detectNeedsReact(context.root),
|
|
975
|
-
detectUsesMath(context.root),
|
|
1147
|
+
detectUsesMath(context.root, staged.values()),
|
|
976
1148
|
readOptional(context.themeFile),
|
|
1149
|
+
readOptional(examplesCssFile(context.root, config)),
|
|
977
1150
|
discoverIslands(context.root),
|
|
978
|
-
discoverExamples(context.root, config.examples),
|
|
1151
|
+
discoverExamples(context.root, config.examples.source),
|
|
1152
|
+
buildComponentSlots(context.componentsFile),
|
|
979
1153
|
]);
|
|
980
|
-
// Statically analyze `components.ts` overrides (never executed): drives the
|
|
981
|
-
// `islands` group, hydration on layout/mdx overrides, string-path resolution,
|
|
982
|
-
// and the "framework component with no client mode" diagnostic.
|
|
983
1154
|
const {
|
|
984
1155
|
plan: slotPlan,
|
|
985
1156
|
tags: overrideTags,
|
|
986
1157
|
warnings: overrideWarnings,
|
|
987
|
-
} =
|
|
1158
|
+
} = componentSlots;
|
|
988
1159
|
|
|
989
1160
|
// Each island/example framework enables its Astro renderer. React also
|
|
990
1161
|
// switches on for any project `.tsx`/`.jsx` and for Ask AI; Vue/Svelte are
|
|
@@ -999,6 +1170,12 @@ export const generateRuntime = async (
|
|
|
999
1170
|
const needsVue = frameworks.has("vue");
|
|
1000
1171
|
const needsSvelte = frameworks.has("svelte");
|
|
1001
1172
|
|
|
1173
|
+
// Absolute path to the React Compiler babel plugin (null when off). Resolved
|
|
1174
|
+
// here, Node-side, so the generated config points babel straight at Blume's
|
|
1175
|
+
// shipped copy — see resolveReactCompiler. Any unresolved-but-requested
|
|
1176
|
+
// warning is folded into `warnings` below (declared later).
|
|
1177
|
+
const reactCompilerPath = resolveReactCompiler(config, needsReact);
|
|
1178
|
+
|
|
1002
1179
|
// Custom pages that should get a generated OG card (the home most of all).
|
|
1003
1180
|
// Computed before the MCP `.well-known` routes are appended below — those are
|
|
1004
1181
|
// private and filtered out anyway, but the intent is the user's pages.
|
|
@@ -1010,9 +1187,6 @@ export const generateRuntime = async (
|
|
|
1010
1187
|
const mcp = planMcp(project, srcDir, pages);
|
|
1011
1188
|
pages.push(...mcp.discoveryPages);
|
|
1012
1189
|
|
|
1013
|
-
// Staged (non-filesystem) sources materialize into `.blume/content`; keyed by
|
|
1014
|
-
// entryId so i18n duplicates of one entry write a single file.
|
|
1015
|
-
const staged = collectStaged(project);
|
|
1016
1190
|
const hasStaged = staged.size > 0;
|
|
1017
1191
|
// Only emit a project-scanning `docs` collection when a filesystem source
|
|
1018
1192
|
// actually feeds it. An all-staged project (openapi/notion/…) has only staged
|
|
@@ -1020,120 +1194,135 @@ export const generateRuntime = async (
|
|
|
1020
1194
|
// project root for nothing — see contentConfigTemplate.
|
|
1021
1195
|
const hasFilesystemSource = project.sources.some((source) => !source.staged);
|
|
1022
1196
|
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1197
|
+
// All of these write to distinct generated paths and never read one another's
|
|
1198
|
+
// output, so the structural files, the per-convention hydration wrappers, and
|
|
1199
|
+
// the Ask/MCP writers all run in one parallel batch. Only the structural
|
|
1200
|
+
// writes' change flags feed `structuralChange`, so they stay a nested group.
|
|
1201
|
+
const [structural] = await Promise.all([
|
|
1202
|
+
Promise.all([
|
|
1203
|
+
write(
|
|
1204
|
+
join(out, "astro.config.mjs"),
|
|
1205
|
+
astroConfigTemplate({
|
|
1206
|
+
aliases: resolveTsconfigAliases(context.root),
|
|
1207
|
+
config,
|
|
1208
|
+
contentRoutes: project.manifest.routes.map((route) => route.path),
|
|
1209
|
+
context,
|
|
1210
|
+
dataPath,
|
|
1211
|
+
examplesPath,
|
|
1212
|
+
examplesThemePath,
|
|
1213
|
+
needsReact,
|
|
1214
|
+
needsSvelte,
|
|
1215
|
+
needsVue,
|
|
1216
|
+
openapiPath,
|
|
1217
|
+
pages,
|
|
1218
|
+
reactCompilerPath,
|
|
1219
|
+
searchClientPath,
|
|
1220
|
+
themePath,
|
|
1221
|
+
})
|
|
1222
|
+
),
|
|
1223
|
+
write(
|
|
1224
|
+
join(out, "package.json"),
|
|
1225
|
+
runtimePackageTemplate(
|
|
1226
|
+
runtimeDependencies({ config, needsReact, needsSvelte, needsVue })
|
|
1227
|
+
)
|
|
1228
|
+
),
|
|
1229
|
+
write(join(out, "tsconfig.json"), runtimeTsconfigTemplate()),
|
|
1230
|
+
write(join(srcDir, "env.d.ts"), envTemplate()),
|
|
1231
|
+
write(
|
|
1232
|
+
join(srcDir, "content.config.ts"),
|
|
1233
|
+
contentConfigTemplate({
|
|
1234
|
+
collection: resolveDocsCollection(config, context),
|
|
1235
|
+
config,
|
|
1236
|
+
context,
|
|
1237
|
+
filesystem: hasFilesystemSource,
|
|
1238
|
+
staged: hasStaged,
|
|
1239
|
+
})
|
|
1240
|
+
),
|
|
1241
|
+
write(
|
|
1242
|
+
join(srcDir, "pages", "[...slug].astro"),
|
|
1243
|
+
catchAllPageTemplate({
|
|
1244
|
+
askEnabled,
|
|
1245
|
+
exportEpub,
|
|
1246
|
+
exportPdf,
|
|
1247
|
+
mathEnabled: usesMath,
|
|
1248
|
+
needsReact,
|
|
1249
|
+
})
|
|
1250
|
+
),
|
|
1251
|
+
write(join(srcDir, "generated", "components.ts"), slotPlan.module),
|
|
1252
|
+
write(
|
|
1253
|
+
join(srcDir, "generated", "islands.ts"),
|
|
1254
|
+
islandMapTemplate(islandDiscovery.islands)
|
|
1255
|
+
),
|
|
1256
|
+
write(
|
|
1257
|
+
join(srcDir, "generated", "examples.ts"),
|
|
1258
|
+
exampleMapTemplate(exampleDiscovery.examples, config.basePath)
|
|
1259
|
+
),
|
|
1260
|
+
// The isolated Tailwind entry for `<Component />` preview frames: only
|
|
1261
|
+
// example files (and the project sources they import) are scanned, so
|
|
1262
|
+
// the docs theme never reaches a preview.
|
|
1263
|
+
write(
|
|
1264
|
+
examplesThemePath,
|
|
1265
|
+
examplesEntryTemplate({
|
|
1266
|
+
configTokens: buildThemeCss(config.theme),
|
|
1267
|
+
sources: [`${context.root}/**/*.{astro,jsx,svelte,ts,tsx,vue}`],
|
|
1268
|
+
userCss: userExamplesCss,
|
|
1269
|
+
})
|
|
1270
|
+
),
|
|
1271
|
+
write(
|
|
1039
1272
|
themePath,
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1273
|
+
tailwindEntryTemplate({
|
|
1274
|
+
configTokens: `${buildThemeCss(config.theme)}${buildFontsCss(config.theme.fonts)}`,
|
|
1275
|
+
sources: [
|
|
1276
|
+
`${BLUME_SRC}/**/*.{astro,ts,tsx}`,
|
|
1277
|
+
`${context.root}/**/*.{astro,mdx,ts,tsx}`,
|
|
1278
|
+
],
|
|
1279
|
+
twoslashCss: twoslashCss(),
|
|
1280
|
+
userTheme,
|
|
1281
|
+
})
|
|
1282
|
+
),
|
|
1283
|
+
]),
|
|
1284
|
+
// Per-island hydration wrappers for the `islands/` convention. The map
|
|
1285
|
+
// module (written above, always) imports these; orphans from removed
|
|
1286
|
+
// islands are pruned at the end of the pass.
|
|
1287
|
+
Promise.all(
|
|
1288
|
+
islandDiscovery.islands.map((island) =>
|
|
1289
|
+
write(
|
|
1290
|
+
join(srcDir, "generated", "islands", `${island.name}.astro`),
|
|
1291
|
+
islandWrapperTemplate(island)
|
|
1292
|
+
)
|
|
1046
1293
|
)
|
|
1047
1294
|
),
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
),
|
|
1060
|
-
write(
|
|
1061
|
-
join(srcDir, "pages", "[...slug].astro"),
|
|
1062
|
-
catchAllPageTemplate({
|
|
1063
|
-
askEnabled,
|
|
1064
|
-
exportEpub,
|
|
1065
|
-
exportPdf,
|
|
1066
|
-
mathEnabled: usesMath,
|
|
1067
|
-
needsReact,
|
|
1068
|
-
})
|
|
1069
|
-
),
|
|
1070
|
-
write(join(srcDir, "generated", "components.ts"), slotPlan.module),
|
|
1071
|
-
write(
|
|
1072
|
-
join(srcDir, "generated", "islands.ts"),
|
|
1073
|
-
islandMapTemplate(islandDiscovery.islands)
|
|
1074
|
-
),
|
|
1075
|
-
write(
|
|
1076
|
-
join(srcDir, "generated", "examples.ts"),
|
|
1077
|
-
exampleMapTemplate(exampleDiscovery.examples)
|
|
1295
|
+
// Per-override hydration wrappers for `defineComponents` islands and
|
|
1296
|
+
// `client:*` layout/mdx overrides. The generated `components.ts` (written
|
|
1297
|
+
// above) imports these; orphans from removed overrides are pruned at the
|
|
1298
|
+
// end of the pass.
|
|
1299
|
+
Promise.all(
|
|
1300
|
+
slotPlan.wrappers.map((wrapper) =>
|
|
1301
|
+
write(
|
|
1302
|
+
join(srcDir, "generated", "component-slots", `${wrapper.name}.astro`),
|
|
1303
|
+
wrapper.content
|
|
1304
|
+
)
|
|
1305
|
+
)
|
|
1078
1306
|
),
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1307
|
+
// Per-example live wrappers for the `examples/` convention, resolved by
|
|
1308
|
+
// `<Component path>` through the `examples.ts` map (written above, always).
|
|
1309
|
+
Promise.all(
|
|
1310
|
+
exampleDiscovery.examples.map((example) =>
|
|
1311
|
+
write(
|
|
1312
|
+
join(
|
|
1313
|
+
srcDir,
|
|
1314
|
+
"generated",
|
|
1315
|
+
"examples",
|
|
1316
|
+
`${exampleSlug(example.path)}.astro`
|
|
1317
|
+
),
|
|
1318
|
+
exampleWrapperTemplate(example)
|
|
1319
|
+
)
|
|
1320
|
+
)
|
|
1090
1321
|
),
|
|
1322
|
+
writeAskFiles(project, srcDir, write),
|
|
1323
|
+
writeMcpFiles(project, mcp, write),
|
|
1091
1324
|
]);
|
|
1092
1325
|
|
|
1093
|
-
// Per-island hydration wrappers for the `islands/` convention. The map module
|
|
1094
|
-
// (written above, always) imports these; orphans from removed islands are
|
|
1095
|
-
// pruned at the end of the pass.
|
|
1096
|
-
await Promise.all(
|
|
1097
|
-
islandDiscovery.islands.map((island) =>
|
|
1098
|
-
write(
|
|
1099
|
-
join(srcDir, "generated", "islands", `${island.name}.astro`),
|
|
1100
|
-
islandWrapperTemplate(island)
|
|
1101
|
-
)
|
|
1102
|
-
)
|
|
1103
|
-
);
|
|
1104
|
-
|
|
1105
|
-
// Per-override hydration wrappers for `defineComponents` islands and `client:*`
|
|
1106
|
-
// layout/mdx overrides. The generated `components.ts` (written above) imports
|
|
1107
|
-
// these; orphans from removed overrides are pruned at the end of the pass.
|
|
1108
|
-
await Promise.all(
|
|
1109
|
-
slotPlan.wrappers.map((wrapper) =>
|
|
1110
|
-
write(
|
|
1111
|
-
join(srcDir, "generated", "component-slots", `${wrapper.name}.astro`),
|
|
1112
|
-
wrapper.content
|
|
1113
|
-
)
|
|
1114
|
-
)
|
|
1115
|
-
);
|
|
1116
|
-
|
|
1117
|
-
// Per-example live wrappers for the `examples/` convention, resolved by
|
|
1118
|
-
// `<Component path>` through the `examples.ts` map (written above, always).
|
|
1119
|
-
await Promise.all(
|
|
1120
|
-
exampleDiscovery.examples.map((example) =>
|
|
1121
|
-
write(
|
|
1122
|
-
join(
|
|
1123
|
-
srcDir,
|
|
1124
|
-
"generated",
|
|
1125
|
-
"examples",
|
|
1126
|
-
`${exampleSlug(example.path)}.astro`
|
|
1127
|
-
),
|
|
1128
|
-
exampleWrapperTemplate(example)
|
|
1129
|
-
)
|
|
1130
|
-
)
|
|
1131
|
-
);
|
|
1132
|
-
|
|
1133
|
-
await writeAskFiles(project, srcDir, write);
|
|
1134
|
-
|
|
1135
|
-
await writeMcpFiles(project, mcp, write);
|
|
1136
|
-
|
|
1137
1326
|
if (config.seo.og.enabled) {
|
|
1138
1327
|
await write(
|
|
1139
1328
|
join(srcDir, "pages", "og", "[...slug].png.ts"),
|
|
@@ -1142,7 +1331,7 @@ export const generateRuntime = async (
|
|
|
1142
1331
|
}
|
|
1143
1332
|
|
|
1144
1333
|
// Changelog index (`/changelog`), rendered through the Update timeline layout.
|
|
1145
|
-
if (
|
|
1334
|
+
if (hasGeneratedChangelog(project, pages)) {
|
|
1146
1335
|
await write(
|
|
1147
1336
|
join(srcDir, "pages", "changelog.astro"),
|
|
1148
1337
|
changelogIndexTemplate({
|
|
@@ -1155,12 +1344,23 @@ export const generateRuntime = async (
|
|
|
1155
1344
|
);
|
|
1156
1345
|
}
|
|
1157
1346
|
|
|
1158
|
-
//
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
//
|
|
1162
|
-
//
|
|
1163
|
-
|
|
1347
|
+
// Three independent writes: the per-example preview routes that
|
|
1348
|
+
// `<Component />` iframes embed (returning a warning when the configured
|
|
1349
|
+
// examples.css is missing), the default 404 page (`/404`, unless the project
|
|
1350
|
+
// already owns the route), and the provider-specific client loader behind
|
|
1351
|
+
// the `blume:search-client` alias — always (re)generated so the alias
|
|
1352
|
+
// resolves even when search is disabled.
|
|
1353
|
+
const [examplesWarnings] = await Promise.all([
|
|
1354
|
+
writeExamplesPreview({
|
|
1355
|
+
config,
|
|
1356
|
+
hasExamples: exampleDiscovery.examples.length > 0,
|
|
1357
|
+
root: context.root,
|
|
1358
|
+
srcDir,
|
|
1359
|
+
write,
|
|
1360
|
+
}),
|
|
1361
|
+
writeNotFoundPage(write, srcDir, pages, project.graph.pages),
|
|
1362
|
+
write(searchClientPath, searchClientTemplate(config)),
|
|
1363
|
+
]);
|
|
1164
1364
|
|
|
1165
1365
|
// Client-loaded providers (orama, flexsearch) ship a static index + endpoint.
|
|
1166
1366
|
if (servesStaticIndex(config.search.provider)) {
|
|
@@ -1191,11 +1391,11 @@ export const generateRuntime = async (
|
|
|
1191
1391
|
),
|
|
1192
1392
|
write(
|
|
1193
1393
|
join(srcDir, "pages", "[...slug].md.ts"),
|
|
1194
|
-
rawMarkdownEndpointTemplate()
|
|
1394
|
+
rawMarkdownEndpointTemplate("md")
|
|
1195
1395
|
),
|
|
1196
1396
|
write(
|
|
1197
1397
|
join(srcDir, "pages", "[...slug].mdx.ts"),
|
|
1198
|
-
rawMarkdownEndpointTemplate()
|
|
1398
|
+
rawMarkdownEndpointTemplate("mdx")
|
|
1199
1399
|
),
|
|
1200
1400
|
]);
|
|
1201
1401
|
|
|
@@ -1222,9 +1422,11 @@ export const generateRuntime = async (
|
|
|
1222
1422
|
// mounted on its configured route and regenerated each run.
|
|
1223
1423
|
const warnings: string[] = [
|
|
1224
1424
|
...(depsLinkWarning ? [depsLinkWarning] : []),
|
|
1425
|
+
...reactCompilerWarnings(config, needsReact, reactCompilerPath),
|
|
1225
1426
|
...mcp.warnings,
|
|
1226
1427
|
...islandDiscovery.warnings,
|
|
1227
1428
|
...exampleDiscovery.warnings,
|
|
1429
|
+
...examplesWarnings,
|
|
1228
1430
|
...overrideWarnings,
|
|
1229
1431
|
];
|
|
1230
1432
|
|
|
@@ -1236,7 +1438,7 @@ export const generateRuntime = async (
|
|
|
1236
1438
|
...pages.map((page) => page.pattern),
|
|
1237
1439
|
...referenceTabs(config).map((tab) => tab.path),
|
|
1238
1440
|
]);
|
|
1239
|
-
if (
|
|
1441
|
+
if (hasGeneratedChangelog(project, pages)) {
|
|
1240
1442
|
navTargetRoutes.add("/changelog");
|
|
1241
1443
|
}
|
|
1242
1444
|
warnings.push(
|
|
@@ -1287,7 +1489,10 @@ export const generateRuntime = async (
|
|
|
1287
1489
|
|
|
1288
1490
|
// React ships with Blume; Vue/Svelte islands need their Astro integration
|
|
1289
1491
|
// installed by the project. Warn early rather than let Vite fail to resolve it.
|
|
1290
|
-
warnings.push(
|
|
1492
|
+
warnings.push(
|
|
1493
|
+
...deploymentAdapterWarnings(config.deployment, context.root),
|
|
1494
|
+
...islandFrameworkWarnings(frameworks, context.root)
|
|
1495
|
+
);
|
|
1291
1496
|
if (hasScalarReferences(config)) {
|
|
1292
1497
|
const references = await buildReferenceFiles({
|
|
1293
1498
|
config,
|
|
@@ -1302,27 +1507,26 @@ export const generateRuntime = async (
|
|
|
1302
1507
|
);
|
|
1303
1508
|
}
|
|
1304
1509
|
|
|
1305
|
-
// Data and manifest are not "structural" for Astro; they hot-reload.
|
|
1306
|
-
await write(
|
|
1307
|
-
join(srcDir, "generated", "data.json"),
|
|
1308
|
-
buildRuntimeData(project)
|
|
1309
|
-
);
|
|
1310
1510
|
// The parsed OpenAPI specs behind the `blume:openapi` alias. Always written
|
|
1311
1511
|
// (even as `{}`) so the alias resolves whether or not a reference is enabled;
|
|
1312
1512
|
// the source parsed the specs during the scan, so this is just serialization.
|
|
1313
1513
|
const openApiSource = project.sources.find(isOpenApiSource);
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
)
|
|
1318
|
-
await
|
|
1319
|
-
join(
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1514
|
+
// These write to distinct trees and never read one another, so they batch.
|
|
1515
|
+
// `data.json`/`openapi.json` and the manifest are not "structural" for Astro;
|
|
1516
|
+
// they hot-reload. `writeStagedContent` owns the `.blume/content` tree (its
|
|
1517
|
+
// own pruning), outside `.blume/src`, so a removed remote entry doesn't linger.
|
|
1518
|
+
await Promise.all([
|
|
1519
|
+
write(join(srcDir, "generated", "data.json"), buildRuntimeData(project)),
|
|
1520
|
+
write(
|
|
1521
|
+
openapiPath,
|
|
1522
|
+
`${JSON.stringify(openApiSource ? openApiSource.openApiData() : {})}\n`
|
|
1523
|
+
),
|
|
1524
|
+
write(
|
|
1525
|
+
join(out, "blume.manifest.json"),
|
|
1526
|
+
`${JSON.stringify(project.manifest, null, 2)}\n`
|
|
1527
|
+
),
|
|
1528
|
+
writeStagedContent(out, staged),
|
|
1529
|
+
]);
|
|
1326
1530
|
|
|
1327
1531
|
// Remove anything under `.blume/src` this pass didn't write — e.g. an Ask AI
|
|
1328
1532
|
// endpoint left behind after the feature was switched off.
|