blume 1.6.5 → 1.7.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 +39 -0
- package/bin/blume.mjs +3 -2
- package/dist/cli/chunk-0qhq7b8q.js +111 -0
- package/dist/cli/chunk-0qhq7b8q.js.map +11 -0
- package/dist/cli/chunk-18tjv4f7.js +96 -0
- package/dist/cli/chunk-18tjv4f7.js.map +10 -0
- package/dist/cli/chunk-27gtm2ym.js +69 -0
- package/dist/cli/chunk-27gtm2ym.js.map +11 -0
- package/dist/cli/chunk-2aj8ddew.js +72 -0
- package/dist/cli/chunk-2aj8ddew.js.map +10 -0
- package/dist/cli/chunk-3r94j3tc.js +221 -0
- package/dist/cli/chunk-3r94j3tc.js.map +10 -0
- package/dist/cli/chunk-4trphnvy.js +102 -0
- package/dist/cli/chunk-4trphnvy.js.map +11 -0
- package/dist/cli/chunk-4xyggvgf.js +21 -0
- package/dist/cli/chunk-4xyggvgf.js.map +10 -0
- package/dist/cli/chunk-5d4q7121.js +4064 -0
- package/dist/cli/chunk-5d4q7121.js.map +40 -0
- package/dist/cli/chunk-5hs6gb7n.js +32 -0
- package/dist/cli/chunk-5hs6gb7n.js.map +10 -0
- package/dist/cli/chunk-6kzzpsx8.js +26 -0
- package/dist/cli/chunk-6kzzpsx8.js.map +10 -0
- package/dist/cli/chunk-8gnpdsn1.js +952 -0
- package/dist/cli/chunk-8gnpdsn1.js.map +12 -0
- package/dist/cli/chunk-9qs6acpw.js +176 -0
- package/dist/cli/chunk-9qs6acpw.js.map +10 -0
- package/dist/cli/chunk-agy5rzxy.js +2453 -0
- package/dist/cli/chunk-agy5rzxy.js.map +15 -0
- package/dist/cli/chunk-bcy492zc.js +16 -0
- package/dist/cli/chunk-bcy492zc.js.map +10 -0
- package/dist/cli/chunk-btfr9yvw.js +41 -0
- package/dist/cli/chunk-btfr9yvw.js.map +10 -0
- package/dist/cli/chunk-cbjnx4s8.js +73 -0
- package/dist/cli/chunk-cbjnx4s8.js.map +10 -0
- package/dist/cli/chunk-cfw6x4rm.js +1967 -0
- package/dist/cli/chunk-cfw6x4rm.js.map +34 -0
- package/dist/cli/chunk-ckh3a410.js +277 -0
- package/dist/cli/chunk-ckh3a410.js.map +11 -0
- package/dist/cli/chunk-drke6t0h.js +259 -0
- package/dist/cli/chunk-drke6t0h.js.map +11 -0
- package/dist/cli/chunk-ev67ycx0.js +15 -0
- package/dist/cli/chunk-ev67ycx0.js.map +10 -0
- package/dist/cli/chunk-ey89bjj1.js +209 -0
- package/dist/cli/chunk-ey89bjj1.js.map +11 -0
- package/dist/cli/chunk-j6pxe0dt.js +69 -0
- package/dist/cli/chunk-j6pxe0dt.js.map +11 -0
- package/dist/cli/chunk-jk1zwka1.js +387 -0
- package/dist/cli/chunk-jk1zwka1.js.map +12 -0
- package/dist/cli/chunk-jtb45atp.js +467 -0
- package/dist/cli/chunk-jtb45atp.js.map +14 -0
- package/dist/cli/chunk-jxkxjsc1.js +76 -0
- package/dist/cli/chunk-jxkxjsc1.js.map +10 -0
- package/dist/cli/chunk-kwx90v78.js +81 -0
- package/dist/cli/chunk-kwx90v78.js.map +10 -0
- package/dist/cli/chunk-n0nyat6g.js +30 -0
- package/dist/cli/chunk-n0nyat6g.js.map +10 -0
- package/dist/cli/chunk-pxj10x8y.js +35 -0
- package/dist/cli/chunk-pxj10x8y.js.map +10 -0
- package/dist/cli/chunk-qq9nm3qd.js +1141 -0
- package/dist/cli/chunk-qq9nm3qd.js.map +19 -0
- package/dist/cli/chunk-s102bysw.js +5170 -0
- package/dist/cli/chunk-s102bysw.js.map +47 -0
- package/dist/cli/chunk-s5dsk8bj.js +769 -0
- package/dist/cli/chunk-s5dsk8bj.js.map +13 -0
- package/dist/cli/chunk-s5e5jt53.js +227 -0
- package/dist/cli/chunk-s5e5jt53.js.map +11 -0
- package/dist/cli/chunk-sbdqrjbb.js +81 -0
- package/dist/cli/chunk-sbdqrjbb.js.map +10 -0
- package/dist/cli/chunk-tnskyrej.js +117 -0
- package/dist/cli/chunk-tnskyrej.js.map +10 -0
- package/dist/cli/chunk-v2ymm99c.js +1016 -0
- package/dist/cli/chunk-v2ymm99c.js.map +13 -0
- package/dist/cli/chunk-v5mm027v.js +185 -0
- package/dist/cli/chunk-v5mm027v.js.map +11 -0
- package/dist/cli/chunk-vt8fgygt.js +23 -0
- package/dist/cli/chunk-vt8fgygt.js.map +10 -0
- package/dist/cli/chunk-vxv4x1n8.js +17 -0
- package/dist/cli/chunk-vxv4x1n8.js.map +10 -0
- package/dist/cli/chunk-wd27zjcz.js +60 -0
- package/dist/cli/chunk-wd27zjcz.js.map +10 -0
- package/dist/cli/chunk-x66c5yjn.js +23 -0
- package/dist/cli/chunk-x66c5yjn.js.map +10 -0
- package/dist/cli/chunk-xv91q4nm.js +5314 -0
- package/dist/cli/chunk-xv91q4nm.js.map +58 -0
- package/dist/cli/chunk-y3g15rvv.js +679 -0
- package/dist/cli/chunk-y3g15rvv.js.map +15 -0
- package/dist/cli/chunk-ye9zdkgv.js +136 -0
- package/dist/cli/chunk-ye9zdkgv.js.map +10 -0
- package/dist/cli/chunk-ynacq3ev.js +1062 -0
- package/dist/cli/chunk-ynacq3ev.js.map +25 -0
- package/dist/cli/chunk-zr3ygrq3.js +54 -0
- package/dist/cli/chunk-zr3ygrq3.js.map +10 -0
- package/dist/cli/index.js +55 -27597
- package/dist/cli/index.js.map +5 -243
- package/dist/types/ai/ask-context.d.ts +26 -0
- package/dist/types/components/layout/nav-utils.d.ts +33 -1
- package/dist/types/core/code-fences.d.ts +11 -0
- package/dist/types/core/package-root.d.ts +1 -1
- package/dist/types/core/schema.d.ts +70 -0
- package/dist/types/theme/fonts.d.ts +22 -22
- package/docs/02-deployment.mdx +22 -1
- package/docs/configuration/ask-ai.mdx +1 -1
- package/docs/configuration/customization.mdx +2 -9
- package/docs/content/navigation.mdx +2 -0
- package/docs/content/syntax.mdx +1 -1
- package/docs/discoverability/open-graph.mdx +4 -0
- package/docs/reference/cli.mdx +1 -1
- package/package.json +4 -2
- package/src/ai/api/handlers.ts +4 -7
- package/src/ai/api/paths.ts +8 -0
- package/src/ai/api/spec.ts +2 -1
- package/src/ai/ask-context.ts +378 -22
- package/src/astro/generate.ts +161 -28
- package/src/astro/include-hmr.ts +10 -13
- package/src/astro/include-refresh.ts +0 -0
- package/src/astro/index.ts +6 -1
- package/src/astro/integration.ts +280 -53
- package/src/astro/module-types.ts +83 -0
- package/src/astro/templates.ts +256 -108
- package/src/audit/image-size.ts +10 -8
- package/src/cli/command-meta.ts +77 -0
- package/src/cli/commands/add.ts +2 -4
- package/src/cli/commands/audit.ts +2 -4
- package/src/cli/commands/build.ts +70 -346
- package/src/cli/commands/check.ts +2 -4
- package/src/cli/commands/dev.ts +31 -42
- package/src/cli/commands/doctor.ts +2 -4
- package/src/cli/commands/eject.ts +3 -41
- package/src/cli/commands/eval.ts +2 -5
- package/src/cli/commands/init.ts +2 -4
- package/src/cli/commands/mcp-stdio.ts +2 -5
- package/src/cli/commands/preview.ts +3 -5
- package/src/cli/commands/sync.ts +2 -4
- package/src/cli/commands/translate.ts +2 -5
- package/src/cli/commands/validate.ts +2 -4
- package/src/cli/commands/version.ts +2 -4
- package/src/cli/eject-scripts.ts +0 -45
- package/src/cli/host-args.ts +16 -0
- package/src/cli/index.ts +84 -35
- package/src/cli/lazy-command.ts +47 -0
- package/src/components/Icon.astro +24 -0
- package/src/components/content/GithubInfo.astro +4 -1
- package/src/components/icon-sprite-middleware.ts +41 -0
- package/src/components/icon-sprite.ts +93 -0
- package/src/components/layout/IconSprite.astro +11 -0
- package/src/components/layout/NavTree.astro +156 -188
- package/src/components/layout/NavTreeCache.astro +45 -0
- package/src/components/layout/NavTreeScript.astro +256 -0
- package/src/components/layout/PageActions.astro +11 -5
- package/src/components/layout/PageLayout.astro +21 -3
- package/src/components/layout/ReferenceLayout.astro +21 -4
- package/src/components/layout/RootLayout.astro +44 -6
- package/src/components/layout/nav-cache.ts +49 -0
- package/src/components/layout/nav-utils.ts +69 -1
- package/src/components/layout/page-locale.ts +29 -0
- package/src/core/api-name.ts +18 -0
- package/src/core/code-fences.ts +48 -0
- package/src/core/content-assets.ts +3 -7
- package/src/core/includes.ts +3 -7
- package/src/core/package-root.ts +1 -1
- package/src/core/schema.ts +19 -0
- package/src/core/sources/normalize.ts +2 -37
- package/src/core/sources/obsidian.ts +3 -2
- package/src/core/svg-dimensions.ts +97 -0
- package/src/core/version-cut.ts +2 -2
- package/src/deploy/artifacts.ts +370 -0
- package/src/deploy/cloudflare-negotiation.ts +97 -32
- package/src/deploy/function-bundle.ts +66 -20
- package/src/deploy/sitemap.ts +6 -0
- package/src/deploy/vercel-negotiation.ts +8 -30
- package/src/markdown/language-icon.ts +64 -20
- package/src/markdown/mermaid.ts +11 -0
- package/src/og/cache.ts +236 -0
- package/src/og/card.ts +18 -16
- package/src/og/index.ts +8 -1
- package/src/openapi/render-mdx.ts +9 -5
- package/src/registry/eject.ts +23 -10
- package/src/theme/entry.ts +41 -7
- package/src/theme/fonts.ts +30 -23
package/src/astro/integration.ts
CHANGED
|
@@ -1,10 +1,18 @@
|
|
|
1
1
|
import type { IncomingMessage, ServerResponse } from "node:http";
|
|
2
|
+
import { fileURLToPath } from "node:url";
|
|
2
3
|
|
|
3
4
|
import type { AstroIntegration } from "astro";
|
|
5
|
+
import { join, relative, resolve } from "pathe";
|
|
4
6
|
|
|
7
|
+
import { loadEnvFiles } from "../cli/env.ts";
|
|
5
8
|
import { enrichDiagnostic } from "../core/diagnostics.ts";
|
|
9
|
+
import { scanProject } from "../core/project-graph.ts";
|
|
10
|
+
import type { BlumeProject } from "../core/project-graph.ts";
|
|
6
11
|
import type { Diagnostic } from "../core/types.ts";
|
|
12
|
+
import { publishBuildArtifacts } from "../deploy/artifacts.ts";
|
|
13
|
+
import type { ArtifactLogger } from "../deploy/artifacts.ts";
|
|
7
14
|
import { markdownVariantUrl, prefersMarkdown } from "./markdown-negotiation.ts";
|
|
15
|
+
import { runtimeModuleDeclarations } from "./module-types.ts";
|
|
8
16
|
|
|
9
17
|
/** The `{ type: "error" }` payload Vite's browser overlay renders. */
|
|
10
18
|
interface OverlayErrorPayload {
|
|
@@ -26,13 +34,150 @@ interface OverlayServer {
|
|
|
26
34
|
ws?: OverlayChannel;
|
|
27
35
|
}
|
|
28
36
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
37
|
+
/** The parameters Astro hands `astro:server:setup`. */
|
|
38
|
+
type ServerSetupParams = Parameters<
|
|
39
|
+
NonNullable<AstroIntegration["hooks"]["astro:server:setup"]>
|
|
40
|
+
>[0];
|
|
33
41
|
|
|
34
|
-
|
|
35
|
-
|
|
42
|
+
/**
|
|
43
|
+
* Astro's `refreshContent`: re-runs every content-layer loader against the
|
|
44
|
+
* live store, the sanctioned way to re-sync content that changed outside
|
|
45
|
+
* Astro's own file watcher.
|
|
46
|
+
*/
|
|
47
|
+
type RefreshContent = NonNullable<ServerSetupParams["refreshContent"]>;
|
|
48
|
+
|
|
49
|
+
/** What the dev negotiation middleware needs per request. */
|
|
50
|
+
interface DevNegotiation {
|
|
51
|
+
/** Page routes that have a raw-Markdown variant (the content manifest). */
|
|
52
|
+
contentRoutes: ReadonlySet<string>;
|
|
53
|
+
/** Homepage agent-discovery `Link` header, when the site has one. */
|
|
54
|
+
homeLinkHeader?: string;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* The state shared between the CLI and the integration: the live dev server
|
|
59
|
+
* (for the browser error overlay), Astro's `refreshContent` (so the CLI's
|
|
60
|
+
* regeneration can re-sync the content store instead of restarting the
|
|
61
|
+
* server), the negotiation inputs the CLI publishes on every regeneration (so
|
|
62
|
+
* a content-route change never rewrites the generated config, which would
|
|
63
|
+
* restart the server in place), and the scanned project a `blume build`
|
|
64
|
+
* hands over so `astro:build:done` can write the deploy artifacts.
|
|
65
|
+
*
|
|
66
|
+
* Kept on `globalThis` rather than in module state, for the same reason as
|
|
67
|
+
* the runtime-module registry (see `runtime-modules.ts`): on a published
|
|
68
|
+
* install the CLI bundle (`dist/cli`) carries its own copy of this module,
|
|
69
|
+
* while the hooks run in the copy Vite loads from `blume/astro` for the
|
|
70
|
+
* generated config. A module-level variable is set in one copy and read in
|
|
71
|
+
* the other, so the overlay never showed anything outside this repository.
|
|
72
|
+
*/
|
|
73
|
+
interface DevServerRegistry {
|
|
74
|
+
buildProject: BlumeProject | null;
|
|
75
|
+
negotiation: DevNegotiation | null;
|
|
76
|
+
overlay: OverlayServer | null;
|
|
77
|
+
refreshContent: RefreshContent | null;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const REGISTRY_KEY = Symbol.for("blume.integration");
|
|
81
|
+
|
|
82
|
+
type RegistryHost = typeof globalThis & {
|
|
83
|
+
[REGISTRY_KEY]?: DevServerRegistry;
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
const registry = (): DevServerRegistry => {
|
|
87
|
+
// SAFETY: the registry is stashed on globalThis under a well-known symbol so
|
|
88
|
+
// every copy of this module in the process shares it; the intersection only
|
|
89
|
+
// names that slot.
|
|
90
|
+
const host = globalThis as RegistryHost;
|
|
91
|
+
host[REGISTRY_KEY] ??= {
|
|
92
|
+
buildProject: null,
|
|
93
|
+
negotiation: null,
|
|
94
|
+
overlay: null,
|
|
95
|
+
refreshContent: null,
|
|
96
|
+
};
|
|
97
|
+
return host[REGISTRY_KEY];
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Hand the scanned project to the integration ahead of `build()`, so its
|
|
102
|
+
* `astro:build:done` hook can write the deploy artifacts without scanning
|
|
103
|
+
* again. `blume build` publishes it for a real build and nothing for an
|
|
104
|
+
* isolated verify build, which produces no artifacts. `null` withdraws it.
|
|
105
|
+
*/
|
|
106
|
+
export const publishBuildProject = (project: BlumeProject | null): void => {
|
|
107
|
+
registry().buildProject = project;
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* The project an `astro build` with no CLI in front of it (an ejected app)
|
|
112
|
+
* writes artifacts for: scanned from `buildArtifactsRoot`, resolved against
|
|
113
|
+
* the Astro root recorded on `astro:config:done`. Scan diagnostics surface as
|
|
114
|
+
* warnings — there is no `--strict` to honor here, and the build itself has
|
|
115
|
+
* already succeeded. `null` when nothing asked for a scan.
|
|
116
|
+
*/
|
|
117
|
+
const scanForArtifacts = async (
|
|
118
|
+
astroRoot: URL | null,
|
|
119
|
+
artifactsRoot: string | undefined,
|
|
120
|
+
logger: ArtifactLogger
|
|
121
|
+
): Promise<BlumeProject | null> => {
|
|
122
|
+
if (!(astroRoot && artifactsRoot)) {
|
|
123
|
+
return null;
|
|
124
|
+
}
|
|
125
|
+
const root = resolve(fileURLToPath(astroRoot), artifactsRoot);
|
|
126
|
+
// Remote sources read their tokens from the environment during the scan.
|
|
127
|
+
loadEnvFiles(root);
|
|
128
|
+
const project = await scanProject(root, { mode: "build" });
|
|
129
|
+
for (const diagnostic of project.diagnostics) {
|
|
130
|
+
logger.warn(`[${diagnostic.code}] ${diagnostic.message}`);
|
|
131
|
+
}
|
|
132
|
+
return project;
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Publish the dev negotiation inputs for the running server: the content
|
|
137
|
+
* routes with a Markdown variant and the homepage `Link` header. Called by
|
|
138
|
+
* `generateRuntime` on every pass, so the middleware follows a route rename
|
|
139
|
+
* without the generated config changing. `null` withdraws a publication, so
|
|
140
|
+
* the middleware falls back to the options baked into the integration call
|
|
141
|
+
* (an ejected project has no CLI to publish).
|
|
142
|
+
*/
|
|
143
|
+
export const publishDevNegotiation = (
|
|
144
|
+
negotiation: {
|
|
145
|
+
contentRoutes: readonly string[];
|
|
146
|
+
homeLinkHeader?: string;
|
|
147
|
+
} | null
|
|
148
|
+
): void => {
|
|
149
|
+
registry().negotiation = negotiation
|
|
150
|
+
? {
|
|
151
|
+
contentRoutes: new Set(negotiation.contentRoutes),
|
|
152
|
+
homeLinkHeader: negotiation.homeLinkHeader,
|
|
153
|
+
}
|
|
154
|
+
: null;
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Re-run Astro's content-layer loaders against the live dev server. Returns
|
|
159
|
+
* `false` when no server has registered one — before the first
|
|
160
|
+
* `astro:server:setup`, or outside `blume dev` — so the caller can fall back.
|
|
161
|
+
* A route-set change (a page added, removed, or a folder renamed) is what
|
|
162
|
+
* needs this: Astro's glob watcher misses directory renames, and its in-place
|
|
163
|
+
* config restart never re-globs, so without a re-sync `getEntry` reads a
|
|
164
|
+
* stale store and the moved page 404s.
|
|
165
|
+
*/
|
|
166
|
+
export const refreshBlumeContent = async (): Promise<boolean> => {
|
|
167
|
+
const { refreshContent } = registry();
|
|
168
|
+
if (!refreshContent) {
|
|
169
|
+
return false;
|
|
170
|
+
}
|
|
171
|
+
// No loader filter: every collection re-syncs (the docs glob and any
|
|
172
|
+
// staged collection alike).
|
|
173
|
+
await refreshContent({});
|
|
174
|
+
return true;
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
const overlayChannel = (): OverlayChannel | undefined => {
|
|
178
|
+
const { overlay } = registry();
|
|
179
|
+
return overlay?.ws ?? overlay?.hot;
|
|
180
|
+
};
|
|
36
181
|
|
|
37
182
|
/**
|
|
38
183
|
* Surface Blume's own diagnostics (config/frontmatter/content errors) in the
|
|
@@ -85,36 +230,41 @@ export interface BlumePageRoute {
|
|
|
85
230
|
|
|
86
231
|
export interface BlumeIntegrationOptions {
|
|
87
232
|
pages: BlumePageRoute[];
|
|
88
|
-
/**
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
233
|
+
/**
|
|
234
|
+
* Page routes that have a raw-Markdown variant (the content manifest). The
|
|
235
|
+
* hidden runtime leaves this out: the CLI publishes the live set through
|
|
236
|
+
* `publishDevNegotiation` on every regeneration, so a route change never
|
|
237
|
+
* rewrites the generated config. An ejected project, which has no CLI,
|
|
238
|
+
* bakes it in here.
|
|
239
|
+
*/
|
|
240
|
+
contentRoutes?: string[];
|
|
92
241
|
/**
|
|
93
242
|
* Homepage `Link` header value for agent discovery (see
|
|
94
243
|
* `ai/link-headers.ts`); the dev-server counterpart of the `_headers` /
|
|
95
244
|
* Vercel-config emission, so `curl -I` against `blume dev` shows what the
|
|
96
|
-
* deployed site will send.
|
|
245
|
+
* deployed site will send. Published the same way as `contentRoutes`.
|
|
97
246
|
*/
|
|
98
247
|
homeLinkHeader?: string;
|
|
248
|
+
/**
|
|
249
|
+
* Blume project root to scan on `astro:build:done` for the deploy artifacts
|
|
250
|
+
* (search index, llms.txt, sitemap, …) when no CLI has published the
|
|
251
|
+
* project — an ejected app running plain `astro build`. Relative to the
|
|
252
|
+
* Astro root. The hidden runtime leaves it unset: `blume build` publishes
|
|
253
|
+
* its already-scanned project instead.
|
|
254
|
+
*/
|
|
255
|
+
buildArtifactsRoot?: string;
|
|
99
256
|
}
|
|
100
257
|
|
|
101
258
|
/**
|
|
102
259
|
* Whether a dev-server request URL is the homepage: the path (query dropped,
|
|
103
|
-
*
|
|
260
|
+
* trailing slash tolerated) is the root.
|
|
104
261
|
*/
|
|
105
|
-
const isHomeUrl = (rawUrl: string | undefined
|
|
262
|
+
const isHomeUrl = (rawUrl: string | undefined): boolean => {
|
|
106
263
|
if (!rawUrl) {
|
|
107
264
|
return false;
|
|
108
265
|
}
|
|
109
266
|
const queryIndex = rawUrl.indexOf("?");
|
|
110
|
-
|
|
111
|
-
const prefix = base && base !== "/" ? base.replace(/\/$/u, "") : "";
|
|
112
|
-
if (prefix) {
|
|
113
|
-
if (path !== prefix && !path.startsWith(`${prefix}/`)) {
|
|
114
|
-
return false;
|
|
115
|
-
}
|
|
116
|
-
path = path.slice(prefix.length);
|
|
117
|
-
}
|
|
267
|
+
const path = queryIndex === -1 ? rawUrl : rawUrl.slice(0, queryIndex);
|
|
118
268
|
return path === "" || path === "/";
|
|
119
269
|
};
|
|
120
270
|
|
|
@@ -134,16 +284,26 @@ const isHomeUrl = (rawUrl: string | undefined, base?: string): boolean => {
|
|
|
134
284
|
* page (see `markdownRoutePaths`). The same
|
|
135
285
|
* middleware also stamps the homepage agent-discovery `Link` header, mirroring
|
|
136
286
|
* what the deployed site sends via `_headers` / the Vercel routing config.
|
|
287
|
+
*
|
|
288
|
+
* Request URLs arrive base-less: Astro unshifts its own dev middlewares (base,
|
|
289
|
+
* trailing slash, route guard) ahead of this one from its post-`configureServer`
|
|
290
|
+
* hook, and its base middleware has already rewritten `/<base>/guide` to
|
|
291
|
+
* `/guide`. Stripping `deployment.base` here a second time would leave no
|
|
292
|
+
* request matching a content route.
|
|
137
293
|
*/
|
|
138
294
|
const negotiateMarkdown =
|
|
139
|
-
(
|
|
295
|
+
(fallback: DevNegotiation) =>
|
|
140
296
|
(req: IncomingMessage, res: ServerResponse, next: () => void): void => {
|
|
297
|
+
// Read per request: the CLI republishes on every regeneration, so a page
|
|
298
|
+
// renamed while the server runs negotiates under its new route.
|
|
299
|
+
const { contentRoutes, homeLinkHeader } =
|
|
300
|
+
registry().negotiation ?? fallback;
|
|
141
301
|
if (req.method === "GET" || req.method === "HEAD") {
|
|
142
|
-
if (homeLinkHeader && isHomeUrl(req.url
|
|
302
|
+
if (homeLinkHeader && isHomeUrl(req.url)) {
|
|
143
303
|
res.setHeader("Link", homeLinkHeader);
|
|
144
304
|
}
|
|
145
305
|
if (prefersMarkdown(req.headers.accept)) {
|
|
146
|
-
const variant = markdownVariantUrl(req.url,
|
|
306
|
+
const variant = markdownVariantUrl(req.url, contentRoutes);
|
|
147
307
|
if (variant) {
|
|
148
308
|
res.setHeader("Vary", "Accept");
|
|
149
309
|
req.url = variant;
|
|
@@ -153,40 +313,107 @@ const negotiateMarkdown =
|
|
|
153
313
|
next();
|
|
154
314
|
};
|
|
155
315
|
|
|
316
|
+
/** The `.d.ts` the integration injects for the `blume:*` virtual modules. */
|
|
317
|
+
const MODULE_TYPES_FILE = "modules.d.ts";
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* Where Astro writes an integration's injected types when the integration
|
|
321
|
+
* never asked for its codegen dir (a config run whose `astro:config:setup`
|
|
322
|
+
* was skipped — the test fixtures). Mirrors Astro's own convention.
|
|
323
|
+
*/
|
|
324
|
+
const defaultCodegenDir = (root: URL): URL =>
|
|
325
|
+
new URL(".astro/integrations/blume/", root);
|
|
326
|
+
|
|
156
327
|
/**
|
|
157
328
|
* Blume's Astro integration. Mounts user-authored pages from `pages/` into the
|
|
158
329
|
* generated runtime via `injectRoute`, keeping each file in its original
|
|
159
|
-
* location so relative imports and `getStaticPaths` keep working
|
|
160
|
-
* the
|
|
330
|
+
* location so relative imports and `getStaticPaths` keep working; declares
|
|
331
|
+
* the `blume:*` virtual modules' types through `injectTypes`; and teaches the
|
|
332
|
+
* dev server to honor `Accept: text/markdown`.
|
|
161
333
|
*/
|
|
162
334
|
export const blumeIntegration = (
|
|
163
335
|
options: BlumeIntegrationOptions
|
|
164
|
-
): AstroIntegration =>
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
336
|
+
): AstroIntegration => {
|
|
337
|
+
// Astro hands out the codegen dir on `astro:config:setup`; the types are
|
|
338
|
+
// injected on `astro:config:done`, once `srcDir` is final, and the
|
|
339
|
+
// `blume:examples` declaration needs the path between the two.
|
|
340
|
+
let codegenDir: URL | null = null;
|
|
341
|
+
// The Astro root, kept for the build-artifacts scan: `astro:build:done`
|
|
342
|
+
// receives no config.
|
|
343
|
+
let astroRoot: URL | null = null;
|
|
344
|
+
return {
|
|
345
|
+
hooks: {
|
|
346
|
+
"astro:build:done": async ({ dir, logger }) => {
|
|
347
|
+
const project =
|
|
348
|
+
registry().buildProject ??
|
|
349
|
+
(await scanForArtifacts(
|
|
350
|
+
astroRoot,
|
|
351
|
+
options.buildArtifactsRoot,
|
|
352
|
+
logger
|
|
353
|
+
));
|
|
354
|
+
if (!project) {
|
|
355
|
+
return;
|
|
356
|
+
}
|
|
357
|
+
// `dir` is what Astro reports as the client output — `dist/`, or
|
|
358
|
+
// `dist/client` for a server build — which is what the platform
|
|
359
|
+
// serves (the Vercel adapter copies it into its Build Output static
|
|
360
|
+
// tree in a later hook).
|
|
361
|
+
await publishBuildArtifacts(project, fileURLToPath(dir), logger);
|
|
362
|
+
},
|
|
363
|
+
"astro:config:done": ({ config, injectTypes }) => {
|
|
364
|
+
astroRoot = config.root;
|
|
365
|
+
const from = fileURLToPath(
|
|
366
|
+
codegenDir ?? defaultCodegenDir(config.root)
|
|
367
|
+
);
|
|
368
|
+
const examplesModule = relative(
|
|
369
|
+
from,
|
|
370
|
+
join(fileURLToPath(config.srcDir), "generated", "examples.ts")
|
|
371
|
+
);
|
|
372
|
+
injectTypes({
|
|
373
|
+
content: runtimeModuleDeclarations(examplesModule),
|
|
374
|
+
filename: MODULE_TYPES_FILE,
|
|
172
375
|
});
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
)
|
|
187
|
-
|
|
188
|
-
|
|
376
|
+
},
|
|
377
|
+
"astro:config:setup": ({
|
|
378
|
+
addMiddleware,
|
|
379
|
+
createCodegenDir,
|
|
380
|
+
injectRoute,
|
|
381
|
+
}) => {
|
|
382
|
+
codegenDir = createCodegenDir();
|
|
383
|
+
// Splices each page's icon sprite in once the page has rendered (see
|
|
384
|
+
// components/icon-sprite-middleware.ts). Innermost, so a project's
|
|
385
|
+
// own middleware sees the finished HTML.
|
|
386
|
+
addMiddleware({
|
|
387
|
+
entrypoint: "blume/components/icon-sprite-middleware.ts",
|
|
388
|
+
order: "post",
|
|
389
|
+
});
|
|
390
|
+
for (const page of options.pages) {
|
|
391
|
+
injectRoute({
|
|
392
|
+
entrypoint: page.entrypoint,
|
|
393
|
+
pattern: page.pattern,
|
|
394
|
+
prerender: true,
|
|
395
|
+
});
|
|
396
|
+
}
|
|
397
|
+
},
|
|
398
|
+
"astro:server:setup": ({ refreshContent, server }) => {
|
|
399
|
+
// Keep a handle on the dev server so Blume diagnostics can be pushed
|
|
400
|
+
// to its browser error overlay (see `showBlumeErrorOverlay`), and on
|
|
401
|
+
// Astro's content re-sync so a route-set change needs no restart
|
|
402
|
+
// (see `refreshBlumeContent`).
|
|
403
|
+
const shared = registry();
|
|
404
|
+
shared.overlay = server;
|
|
405
|
+
shared.refreshContent = refreshContent ?? null;
|
|
406
|
+
// Prepend so the rewrite happens before Astro's own request handler,
|
|
407
|
+
// letting the rewritten URL resolve to the `.md` endpoint.
|
|
408
|
+
server.middlewares.stack.unshift({
|
|
409
|
+
handle: negotiateMarkdown({
|
|
410
|
+
contentRoutes: new Set(options.contentRoutes),
|
|
411
|
+
homeLinkHeader: options.homeLinkHeader,
|
|
412
|
+
}),
|
|
413
|
+
route: "",
|
|
414
|
+
});
|
|
415
|
+
},
|
|
189
416
|
},
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
}
|
|
417
|
+
name: "blume",
|
|
418
|
+
};
|
|
419
|
+
};
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ambient declarations for the `blume:*` virtual modules the generated pages
|
|
3
|
+
* and Blume's own layouts import. The integration hands them to Astro through
|
|
4
|
+
* `injectTypes` on `astro:config:done`, so they land under
|
|
5
|
+
* `.astro/integrations/blume/` and are referenced from Astro's generated
|
|
6
|
+
* `types.d.ts` — the same channel `astro:content` and `astro:env` use — rather
|
|
7
|
+
* than a hand-written `src/env.d.ts` the generator and eject each had to
|
|
8
|
+
* write. Every project that mounts the integration gets the types with it.
|
|
9
|
+
*
|
|
10
|
+
* `examplesModule` is the path of the generated `examples.ts` relative to the
|
|
11
|
+
* declaration file: the `blume:examples` map is typed from that module's
|
|
12
|
+
* literal export so `<Component path>` lookups stay typed per project.
|
|
13
|
+
*/
|
|
14
|
+
export const runtimeModuleDeclarations = (
|
|
15
|
+
examplesModule: string
|
|
16
|
+
): string => `declare module "blume:ask" {
|
|
17
|
+
const Ask: typeof import("blume/components/islands/AskAI.astro").default;
|
|
18
|
+
export default Ask;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
declare module "blume:data" {
|
|
22
|
+
const data: import("blume").BlumeData;
|
|
23
|
+
export default data;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
declare module "blume:ask-data" {
|
|
27
|
+
const askData: import("blume/ai/ask-context.ts").AskData;
|
|
28
|
+
export default askData;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
declare module "blume:content-assets" {
|
|
32
|
+
const assets: Record<string, string>;
|
|
33
|
+
export default assets;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
declare module "blume:mcp-data" {
|
|
37
|
+
const data: import("blume/ai/mcp/data.ts").McpData;
|
|
38
|
+
export default data;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
declare module "blume:raw-markdown" {
|
|
42
|
+
const raw: Record<string, import("blume/ai/markdown.ts").RawMarkdownEntry>;
|
|
43
|
+
export default raw;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
declare module "blume:rss" {
|
|
47
|
+
const feeds: Record<string, string>;
|
|
48
|
+
export default feeds;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
declare module "blume:search-index" {
|
|
52
|
+
const documents: import("blume/search/documents.ts").SearchDocument[];
|
|
53
|
+
export default documents;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
declare module "blume:examples" {
|
|
57
|
+
type Examples = typeof import(${JSON.stringify(examplesModule)}).examples;
|
|
58
|
+
export const examples: Record<string, Examples[keyof Examples]>;
|
|
59
|
+
export const examplesBase: string;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
declare module "blume:examples-theme";
|
|
63
|
+
|
|
64
|
+
declare module "blume:openapi" {
|
|
65
|
+
const specs: import("blume/openapi/model.ts").OpenApiData;
|
|
66
|
+
export default specs;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
declare module "blume:features" {
|
|
70
|
+
/** Registers the <blume-mermaid> element; null when no page has a mermaid fence. */
|
|
71
|
+
export const loadMermaid: (() => Promise<unknown>) | null;
|
|
72
|
+
/** The EPUB generator's browser bundle; null when export.epub is off. */
|
|
73
|
+
export const loadEpub:
|
|
74
|
+
| (() => Promise<typeof import("epub-gen-memory/bundle")>)
|
|
75
|
+
| null;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
declare module "blume:search-client" {
|
|
79
|
+
export const createSearch: () =>
|
|
80
|
+
| import("blume/components/layout/search/types.ts").SearchFn
|
|
81
|
+
| Promise<import("blume/components/layout/search/types.ts").SearchFn>;
|
|
82
|
+
}
|
|
83
|
+
`;
|