blume 1.6.5 → 1.6.6
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 +22 -0
- package/bin/blume.mjs +3 -2
- package/dist/cli/chunk-0ewz4trd.js +679 -0
- package/dist/cli/chunk-0ewz4trd.js.map +15 -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-3k0kzs6d.js +69 -0
- package/dist/cli/chunk-3k0kzs6d.js.map +11 -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-5hs6gb7n.js +32 -0
- package/dist/cli/chunk-5hs6gb7n.js.map +10 -0
- package/dist/cli/chunk-5yvt556e.js +185 -0
- package/dist/cli/chunk-5yvt556e.js.map +11 -0
- package/dist/cli/chunk-62qsssnh.js +3808 -0
- package/dist/cli/chunk-62qsssnh.js.map +36 -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-9sh49q0h.js +30 -0
- package/dist/cli/chunk-9sh49q0h.js.map +10 -0
- package/dist/cli/chunk-aerwpe14.js +2370 -0
- package/dist/cli/chunk-aerwpe14.js.map +15 -0
- package/dist/cli/chunk-ag1zyr5x.js +176 -0
- package/dist/cli/chunk-ag1zyr5x.js.map +10 -0
- package/dist/cli/chunk-bawgnt8x.js +277 -0
- package/dist/cli/chunk-bawgnt8x.js.map +11 -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-cnvm6k3e.js +96 -0
- package/dist/cli/chunk-cnvm6k3e.js.map +10 -0
- package/dist/cli/chunk-etsqspj6.js +5170 -0
- package/dist/cli/chunk-etsqspj6.js.map +47 -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-f75cqye8.js +76 -0
- package/dist/cli/chunk-f75cqye8.js.map +10 -0
- package/dist/cli/chunk-j00ezcg5.js +259 -0
- package/dist/cli/chunk-j00ezcg5.js.map +11 -0
- package/dist/cli/chunk-jtb45atp.js +467 -0
- package/dist/cli/chunk-jtb45atp.js.map +14 -0
- package/dist/cli/chunk-m3p3wahd.js +117 -0
- package/dist/cli/chunk-m3p3wahd.js.map +10 -0
- package/dist/cli/chunk-n0y172hf.js +387 -0
- package/dist/cli/chunk-n0y172hf.js.map +12 -0
- package/dist/cli/chunk-n4qjabmt.js +1062 -0
- package/dist/cli/chunk-n4qjabmt.js.map +25 -0
- package/dist/cli/chunk-nyqzjdhj.js +111 -0
- package/dist/cli/chunk-nyqzjdhj.js.map +11 -0
- package/dist/cli/chunk-pxj10x8y.js +35 -0
- package/dist/cli/chunk-pxj10x8y.js.map +10 -0
- package/dist/cli/chunk-s4jn7f1q.js +54 -0
- package/dist/cli/chunk-s4jn7f1q.js.map +10 -0
- package/dist/cli/chunk-s4k1pnvf.js +81 -0
- package/dist/cli/chunk-s4k1pnvf.js.map +10 -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-tc89yh2r.js +136 -0
- package/dist/cli/chunk-tc89yh2r.js.map +10 -0
- package/dist/cli/chunk-vt8fgygt.js +23 -0
- package/dist/cli/chunk-vt8fgygt.js.map +10 -0
- package/dist/cli/chunk-vv237fp3.js +1002 -0
- package/dist/cli/chunk-vv237fp3.js.map +13 -0
- package/dist/cli/chunk-vv3f8mb6.js +5314 -0
- package/dist/cli/chunk-vv3f8mb6.js.map +58 -0
- package/dist/cli/chunk-vxv4x1n8.js +17 -0
- package/dist/cli/chunk-vxv4x1n8.js.map +10 -0
- package/dist/cli/chunk-wb067mv3.js +758 -0
- package/dist/cli/chunk-wb067mv3.js.map +13 -0
- package/dist/cli/chunk-wd27zjcz.js +60 -0
- package/dist/cli/chunk-wd27zjcz.js.map +10 -0
- package/dist/cli/chunk-wkq5tbtq.js +1141 -0
- package/dist/cli/chunk-wkq5tbtq.js.map +19 -0
- package/dist/cli/chunk-x1vrdjyk.js +1967 -0
- package/dist/cli/chunk-x1vrdjyk.js.map +34 -0
- package/dist/cli/chunk-x66c5yjn.js +23 -0
- package/dist/cli/chunk-x66c5yjn.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/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/docs/02-deployment.mdx +1 -1
- package/docs/configuration/ask-ai.mdx +1 -1
- package/docs/configuration/customization.mdx +2 -9
- 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 +20 -23
- 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 +269 -53
- package/src/astro/module-types.ts +74 -0
- package/src/astro/templates.ts +85 -97
- 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 +42 -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/content/GithubInfo.astro +4 -1
- package/src/components/layout/PageLayout.astro +14 -3
- package/src/components/layout/ReferenceLayout.astro +14 -4
- package/src/components/layout/RootLayout.astro +14 -4
- 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/og/card.ts +6 -12
- package/src/openapi/render-mdx.ts +9 -5
- package/src/registry/eject.ts +0 -2
package/src/astro/templates.ts
CHANGED
|
@@ -19,7 +19,7 @@ import { hasScalarReferences } from "../openapi/references.ts";
|
|
|
19
19
|
import { searchProviderMeta } from "../search/providers.ts";
|
|
20
20
|
import { buildFontEntries, fontLocaleCodes } from "../theme/fonts.ts";
|
|
21
21
|
import type { ExampleSpec } from "./examples.ts";
|
|
22
|
-
import type { BlumePageRoute } from "./integration.ts";
|
|
22
|
+
import type { BlumeIntegrationOptions, BlumePageRoute } from "./integration.ts";
|
|
23
23
|
import type { IslandSpec } from "./islands.ts";
|
|
24
24
|
import type { OgCustomRoute } from "./pages.ts";
|
|
25
25
|
import { RUNTIME_MODULE_FILES } from "./runtime-modules.ts";
|
|
@@ -125,30 +125,24 @@ const resolveCloudflareAdapterArgs = (context: ProjectContext): string => {
|
|
|
125
125
|
* Without a configured driver, `@astrojs/cloudflare` force-enables KV-backed
|
|
126
126
|
* sessions and declares a `SESSION` kv_namespaces entry in the generated
|
|
127
127
|
* wrangler config — which `wrangler deploy` then requires a real KV namespace
|
|
128
|
-
* for, even though Blume never reads `Astro.session`.
|
|
129
|
-
*
|
|
130
|
-
*
|
|
128
|
+
* for, even though Blume never reads `Astro.session`. Astro's `session: false`
|
|
129
|
+
* opts the project out, and the adapter checks for it before adding the
|
|
130
|
+
* binding. (An in-memory driver used to stand in before the opt-out existed.)
|
|
131
131
|
*/
|
|
132
132
|
const resolveSessionOption = (deployment: {
|
|
133
133
|
adapter: string | null;
|
|
134
134
|
output: string;
|
|
135
135
|
}): string =>
|
|
136
136
|
deployment.output === "server" && deployment.adapter === "cloudflare"
|
|
137
|
-
? "\n session:
|
|
137
|
+
? "\n session: false,"
|
|
138
138
|
: "";
|
|
139
139
|
|
|
140
140
|
/** The named imports the generated config pulls from `astro/config`. */
|
|
141
|
-
const astroConfigImportLine = (options: {
|
|
142
|
-
hasFonts: boolean;
|
|
143
|
-
hasSession: boolean;
|
|
144
|
-
}): string => {
|
|
141
|
+
const astroConfigImportLine = (options: { hasFonts: boolean }): string => {
|
|
145
142
|
const names = ["defineConfig"];
|
|
146
143
|
if (options.hasFonts) {
|
|
147
144
|
names.push("fontProviders");
|
|
148
145
|
}
|
|
149
|
-
if (options.hasSession) {
|
|
150
|
-
names.push("sessionDrivers");
|
|
151
|
-
}
|
|
152
146
|
return `import { ${names.join(", ")} } from "astro/config";`;
|
|
153
147
|
};
|
|
154
148
|
|
|
@@ -458,6 +452,33 @@ const renderRuntimeModuleWiring = (
|
|
|
458
452
|
return { aliasLines, imports: [], pluginEntry: "" };
|
|
459
453
|
};
|
|
460
454
|
|
|
455
|
+
/**
|
|
456
|
+
* The options baked into the generated config's `blumeIntegration(...)` call.
|
|
457
|
+
* The hidden runtime gets the content routes and homepage `Link` header from
|
|
458
|
+
* the CLI in memory (`publishDevNegotiation`, republished on every
|
|
459
|
+
* regeneration), so a content-route change never rewrites the config — Astro
|
|
460
|
+
* restarts the dev server in place on a config change — and its scanned
|
|
461
|
+
* project from `blume build` for the deploy artifacts. An ejected project has
|
|
462
|
+
* no CLI, so the negotiation inputs are baked in and `astro:build:done` scans
|
|
463
|
+
* the project root (the Astro root, after eject) for the artifacts.
|
|
464
|
+
*/
|
|
465
|
+
const blumeIntegrationOptions = (options: {
|
|
466
|
+
config: ResolvedConfig;
|
|
467
|
+
contentRoutes: string[];
|
|
468
|
+
ejected: boolean;
|
|
469
|
+
pages: BlumePageRoute[];
|
|
470
|
+
}): BlumeIntegrationOptions =>
|
|
471
|
+
options.ejected
|
|
472
|
+
? {
|
|
473
|
+
buildArtifactsRoot: ".",
|
|
474
|
+
contentRoutes: options.contentRoutes,
|
|
475
|
+
homeLinkHeader:
|
|
476
|
+
buildHomeLinkHeader(options.config, options.contentRoutes) ??
|
|
477
|
+
undefined,
|
|
478
|
+
pages: options.pages,
|
|
479
|
+
}
|
|
480
|
+
: { pages: options.pages };
|
|
481
|
+
|
|
461
482
|
export const astroConfigTemplate = (options: {
|
|
462
483
|
context: ProjectContext;
|
|
463
484
|
config: ResolvedConfig;
|
|
@@ -567,7 +588,9 @@ export const astroConfigTemplate = (options: {
|
|
|
567
588
|
: "";
|
|
568
589
|
const imageOption = renderImageOption(config);
|
|
569
590
|
|
|
570
|
-
// Astro's native i18n
|
|
591
|
+
// Astro's native i18n resolves `Astro.currentLocale` from the URL, which the
|
|
592
|
+
// document shells fall back to for `<html lang>`/`dir` on pages the content
|
|
593
|
+
// catch-all doesn't drive (custom pages, the 404, the reference shell).
|
|
571
594
|
// Blume owns getStaticPaths and materializes fallback routes in the manifest,
|
|
572
595
|
// so we deliberately omit Astro's `fallback` to keep one source of routing.
|
|
573
596
|
const i18nOption = config.i18n
|
|
@@ -653,7 +676,6 @@ export const astroConfigTemplate = (options: {
|
|
|
653
676
|
: "";
|
|
654
677
|
const defineConfigImport = astroConfigImportLine({
|
|
655
678
|
hasFonts: fontEntries.length > 0,
|
|
656
|
-
hasSession: sessionOption.length > 0,
|
|
657
679
|
});
|
|
658
680
|
|
|
659
681
|
// Framework renderers are only wired in when an island (or Ask AI, for React)
|
|
@@ -710,12 +732,14 @@ export const astroConfigTemplate = (options: {
|
|
|
710
732
|
// up dev-server `Accept: text/markdown` negotiation over the content routes,
|
|
711
733
|
// plus the homepage agent-discovery `Link` header.
|
|
712
734
|
integrations.push(
|
|
713
|
-
`blumeIntegration(${JSON.stringify(
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
735
|
+
`blumeIntegration(${JSON.stringify(
|
|
736
|
+
blumeIntegrationOptions({
|
|
737
|
+
config,
|
|
738
|
+
contentRoutes,
|
|
739
|
+
ejected: generatedModulesDir !== undefined,
|
|
740
|
+
pages,
|
|
741
|
+
})
|
|
742
|
+
)})`
|
|
719
743
|
);
|
|
720
744
|
|
|
721
745
|
const {
|
|
@@ -758,6 +782,14 @@ ${userConfigSetup}export default defineConfig({
|
|
|
758
782
|
},
|
|
759
783
|
},
|
|
760
784
|
devToolbar: { enabled: false },
|
|
785
|
+
// One canonical URL per page: canonicals, the sitemap, and hreflang all use
|
|
786
|
+
// the slashless form, so the slashed spelling is not a second address. Astro
|
|
787
|
+
// applies this itself — its dev server answers a slashed URL with a 404 that
|
|
788
|
+
// names the setting, an on-demand route redirects — and the Vercel adapter
|
|
789
|
+
// turns it into the platform's 308 route, so the Build Output config needs
|
|
790
|
+
// no hand-spliced redirect (see deploy/vercel-negotiation.ts). Static hosts
|
|
791
|
+
// serve the \`index.html\` directory layout as they always did.
|
|
792
|
+
trailingSlash: "never",
|
|
761
793
|
// The layouts render Astro's <ClientRouter />, and its in-place swaps read
|
|
762
794
|
// from the prefetch cache — fetching every link on hover/viewport hides the
|
|
763
795
|
// request latency behind the user's intent, so most navigations swap
|
|
@@ -924,6 +956,7 @@ const staged = defineCollection({
|
|
|
924
956
|
base: ${JSON.stringify(astroGlobBase(stagedBase))},
|
|
925
957
|
generateId: ({ entry }) => entry,
|
|
926
958
|
}),
|
|
959
|
+
schema: pageCollectionSchema,
|
|
927
960
|
});
|
|
928
961
|
`
|
|
929
962
|
: "";
|
|
@@ -932,16 +965,22 @@ const staged = defineCollection({
|
|
|
932
965
|
import { defineCollection } from "astro:content";
|
|
933
966
|
import { glob } from "astro/loaders";
|
|
934
967
|
import { withIncludeRefresh } from "blume/astro";
|
|
968
|
+
import { pageCollectionSchema } from "blume/core/schema.ts";
|
|
935
969
|
|
|
936
970
|
// withIncludeRefresh keeps <include>-bearing pages fresh: plain .md entries
|
|
937
971
|
// are rendered at sync time and digest-cached on the page file alone, so a
|
|
938
972
|
// partial edit (or a warm-cache rebuild after one) would serve stale HTML.
|
|
973
|
+
//
|
|
974
|
+
// pageCollectionSchema is the scan's page front-matter schema, so entry.data
|
|
975
|
+
// is typed and normalized the same way; it passes custom keys through and
|
|
976
|
+
// falls back to defaults for a page the scan already dropped as invalid.
|
|
939
977
|
const docs = defineCollection({
|
|
940
978
|
loader: withIncludeRefresh(glob({
|
|
941
979
|
pattern: ${JSON.stringify(docsPattern)},
|
|
942
980
|
base: ${JSON.stringify(astroGlobBase(collectionBase))},
|
|
943
981
|
generateId: ({ entry }) => entry,
|
|
944
982
|
}), ${JSON.stringify(`${context.outDir}/src/generated/includes.json`)}),
|
|
983
|
+
schema: pageCollectionSchema,
|
|
945
984
|
});
|
|
946
985
|
${stagedBlock}
|
|
947
986
|
export const collections = { docs${options.staged ? ", staged" : ""} };
|
|
@@ -980,26 +1019,38 @@ export const askEndpointTemplate = (
|
|
|
980
1019
|
const fallbackPrompt = instructions
|
|
981
1020
|
? `${ASK_FALLBACK_PROMPT}\n\n${instructions}`
|
|
982
1021
|
: ASK_FALLBACK_PROMPT;
|
|
1022
|
+
// Secrets go through Astro's `getSecret` rather than `process.env`, so each
|
|
1023
|
+
// adapter supplies them its own way (Cloudflare from the Worker's bindings,
|
|
1024
|
+
// Node and Vercel from the environment).
|
|
983
1025
|
const imports = [
|
|
984
1026
|
'import type { APIRoute } from "astro";',
|
|
985
|
-
'import {
|
|
1027
|
+
'import { getSecret } from "astro:env/server";',
|
|
1028
|
+
// The gateway provider reads the key (or Vercel's OIDC token) from the
|
|
1029
|
+
// environment itself; passing the key explicitly lets a binding-backed
|
|
1030
|
+
// secret store reach it too.
|
|
1031
|
+
backend.kind === "gateway"
|
|
1032
|
+
? 'import { createGateway, streamText } from "ai";'
|
|
1033
|
+
: 'import { streamText } from "ai";',
|
|
986
1034
|
];
|
|
987
1035
|
let setup = "";
|
|
988
1036
|
let modelExpr = JSON.stringify(backend.model);
|
|
989
|
-
if (backend.kind === "
|
|
1037
|
+
if (backend.kind === "gateway") {
|
|
1038
|
+
setup = `\nconst gateway = createGateway({ apiKey: getSecret("AI_GATEWAY_API_KEY") });\n`;
|
|
1039
|
+
modelExpr = `gateway(${JSON.stringify(backend.model)})`;
|
|
1040
|
+
} else if (backend.kind === "openrouter") {
|
|
990
1041
|
imports.push(
|
|
991
1042
|
'import { createOpenRouter } from "@openrouter/ai-sdk-provider";'
|
|
992
1043
|
);
|
|
993
|
-
setup = `\nconst openrouter = createOpenRouter({ apiKey:
|
|
1044
|
+
setup = `\nconst openrouter = createOpenRouter({ apiKey: getSecret(${JSON.stringify(
|
|
994
1045
|
backend.apiKeyEnv
|
|
995
|
-
)}
|
|
1046
|
+
)}) });\n`;
|
|
996
1047
|
modelExpr = `openrouter(${JSON.stringify(backend.model)})`;
|
|
997
1048
|
} else if (backend.kind === "openai-compatible") {
|
|
998
1049
|
imports.push(
|
|
999
1050
|
'import { createOpenAICompatible } from "@ai-sdk/openai-compatible";'
|
|
1000
1051
|
);
|
|
1001
1052
|
setup = `\nconst provider = createOpenAICompatible({
|
|
1002
|
-
apiKey:
|
|
1053
|
+
apiKey: getSecret(${JSON.stringify(backend.apiKeyEnv)}),
|
|
1003
1054
|
baseURL: ${JSON.stringify(backend.baseUrl)},
|
|
1004
1055
|
name: ${JSON.stringify(backend.name)},
|
|
1005
1056
|
});\n`;
|
|
@@ -1062,13 +1113,13 @@ export const askEndpointTemplate = (
|
|
|
1062
1113
|
const keyCheck =
|
|
1063
1114
|
backend.kind === "gateway"
|
|
1064
1115
|
? ` // The AI Gateway authenticates with an API key or Vercel's OIDC token.
|
|
1065
|
-
if (!(
|
|
1116
|
+
if (!(getSecret("AI_GATEWAY_API_KEY") || getSecret("VERCEL_OIDC_TOKEN"))) {
|
|
1066
1117
|
return new Response(
|
|
1067
1118
|
"Ask AI is not configured: set AI_GATEWAY_API_KEY (or deploy on Vercel with OIDC).",
|
|
1068
1119
|
{ status: 500 }
|
|
1069
1120
|
);
|
|
1070
1121
|
}`
|
|
1071
|
-
: ` if (!
|
|
1122
|
+
: ` if (!getSecret(${JSON.stringify(backend.apiKeyEnv)})) {
|
|
1072
1123
|
return new Response(
|
|
1073
1124
|
${JSON.stringify(`Ask AI is not configured: set ${backend.apiKeyEnv}.`)},
|
|
1074
1125
|
{ status: 500 }
|
|
@@ -1176,8 +1227,9 @@ const searchClientImport = (module: string): string =>
|
|
|
1176
1227
|
`import { createSearch as create } from "blume/components/layout/search/${module}.ts";\n`;
|
|
1177
1228
|
|
|
1178
1229
|
// Joins a base-relative path onto BASE_URL, which arrives with or without a
|
|
1179
|
-
// trailing slash (Astro
|
|
1180
|
-
//
|
|
1230
|
+
// trailing slash (Astro normalizes `base` by `trailingSlash`, which the
|
|
1231
|
+
// generated config pins to "never" but an owned config may set either way —
|
|
1232
|
+
// naive concatenation of a bare `/docs` would yield `/docsblume-search.json`).
|
|
1181
1233
|
const SEARCH_BASE_IMPORT =
|
|
1182
1234
|
'import { joinBase } from "blume/components/islands/base-path.ts";\n';
|
|
1183
1235
|
|
|
@@ -1291,11 +1343,12 @@ export const createSearch = () => create({ url });
|
|
|
1291
1343
|
export const mixedbreadSearchEndpointTemplate = (storeId: string): string =>
|
|
1292
1344
|
`// Generated by Blume. Do not edit.
|
|
1293
1345
|
import type { APIRoute } from "astro";
|
|
1346
|
+
import { getSecret } from "astro:env/server";
|
|
1294
1347
|
import Mixedbread from "@mixedbread/sdk";
|
|
1295
1348
|
|
|
1296
1349
|
export const prerender = false;
|
|
1297
1350
|
|
|
1298
|
-
const client = new Mixedbread({ apiKey:
|
|
1351
|
+
const client = new Mixedbread({ apiKey: getSecret("MIXEDBREAD_API_KEY") ?? "" });
|
|
1299
1352
|
const STORE_ID = ${JSON.stringify(storeId)};
|
|
1300
1353
|
|
|
1301
1354
|
export const POST: APIRoute = async ({ request }) => {
|
|
@@ -1883,9 +1936,9 @@ const headings =
|
|
|
1883
1936
|
tocHidden.size > 0
|
|
1884
1937
|
? allHeadings.filter((heading) => !tocHidden.has(heading.slug))
|
|
1885
1938
|
: allHeadings;
|
|
1886
|
-
const frontmatter = entry.data
|
|
1939
|
+
const frontmatter = entry.data;
|
|
1887
1940
|
|
|
1888
|
-
const seo = frontmatter.seo
|
|
1941
|
+
const seo = frontmatter.seo;
|
|
1889
1942
|
const base = data.config.site ? data.config.site.replace(/\\/$/, "") : null;
|
|
1890
1943
|
|
|
1891
1944
|
// Percent-encode the route-derived path (the sitemap convention): a Unicode
|
|
@@ -3037,71 +3090,6 @@ const Example = entry.Component;
|
|
|
3037
3090
|
</html>
|
|
3038
3091
|
`;
|
|
3039
3092
|
|
|
3040
|
-
/** Generate `.blume/src/env.d.ts`. */
|
|
3041
|
-
export const envTemplate =
|
|
3042
|
-
(): string => `/// <reference path="../.astro/types.d.ts" />
|
|
3043
|
-
/// <reference types="astro/client" />
|
|
3044
|
-
|
|
3045
|
-
declare module "blume:ask" {
|
|
3046
|
-
const Ask: typeof import("blume/components/islands/AskAI.astro").default;
|
|
3047
|
-
export default Ask;
|
|
3048
|
-
}
|
|
3049
|
-
|
|
3050
|
-
declare module "blume:data" {
|
|
3051
|
-
const data: import("blume").BlumeData;
|
|
3052
|
-
export default data;
|
|
3053
|
-
}
|
|
3054
|
-
|
|
3055
|
-
declare module "blume:ask-data" {
|
|
3056
|
-
const askData: import("blume/ai/ask-context.ts").AskData;
|
|
3057
|
-
export default askData;
|
|
3058
|
-
}
|
|
3059
|
-
|
|
3060
|
-
declare module "blume:content-assets" {
|
|
3061
|
-
const assets: Record<string, string>;
|
|
3062
|
-
export default assets;
|
|
3063
|
-
}
|
|
3064
|
-
|
|
3065
|
-
declare module "blume:mcp-data" {
|
|
3066
|
-
const data: import("blume/ai/mcp/data.ts").McpData;
|
|
3067
|
-
export default data;
|
|
3068
|
-
}
|
|
3069
|
-
|
|
3070
|
-
declare module "blume:raw-markdown" {
|
|
3071
|
-
const raw: Record<string, import("blume/ai/markdown.ts").RawMarkdownEntry>;
|
|
3072
|
-
export default raw;
|
|
3073
|
-
}
|
|
3074
|
-
|
|
3075
|
-
declare module "blume:rss" {
|
|
3076
|
-
const feeds: Record<string, string>;
|
|
3077
|
-
export default feeds;
|
|
3078
|
-
}
|
|
3079
|
-
|
|
3080
|
-
declare module "blume:search-index" {
|
|
3081
|
-
const documents: import("blume/search/documents.ts").SearchDocument[];
|
|
3082
|
-
export default documents;
|
|
3083
|
-
}
|
|
3084
|
-
|
|
3085
|
-
declare module "blume:examples" {
|
|
3086
|
-
type Examples = typeof import("./generated/examples.ts").examples;
|
|
3087
|
-
export const examples: Record<string, Examples[keyof Examples]>;
|
|
3088
|
-
export const examplesBase: string;
|
|
3089
|
-
}
|
|
3090
|
-
|
|
3091
|
-
declare module "blume:examples-theme";
|
|
3092
|
-
|
|
3093
|
-
declare module "blume:openapi" {
|
|
3094
|
-
const specs: import("blume/openapi/model.ts").OpenApiData;
|
|
3095
|
-
export default specs;
|
|
3096
|
-
}
|
|
3097
|
-
|
|
3098
|
-
declare module "blume:search-client" {
|
|
3099
|
-
export const createSearch: () =>
|
|
3100
|
-
| import("blume/components/layout/search/types.ts").SearchFn
|
|
3101
|
-
| Promise<import("blume/components/layout/search/types.ts").SearchFn>;
|
|
3102
|
-
}
|
|
3103
|
-
`;
|
|
3104
|
-
|
|
3105
3093
|
/** Generate `.blume/package.json`. */
|
|
3106
3094
|
export const runtimePackageTemplate = (dependencies: string[] = []): string =>
|
|
3107
3095
|
`${JSON.stringify(
|
package/src/audit/image-size.ts
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
|
-
import
|
|
1
|
+
import sharp from "sharp";
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* Pixel dimensions read from an image header via
|
|
5
|
-
*
|
|
6
|
-
* included
|
|
7
|
-
*
|
|
8
|
-
*
|
|
4
|
+
* Pixel dimensions read from an image header via sharp, which is already the
|
|
5
|
+
* build's image optimizer and so covers every format the pipeline can emit —
|
|
6
|
+
* WebP and AVIF included. libvips reads only the header and rejects malformed
|
|
7
|
+
* input with an error rather than looping on it, which is why the previous
|
|
8
|
+
* pure-JS parser was retired: it could be driven into an infinite loop by a
|
|
9
|
+
* crafted ICNS, HEIF, or JXL file and hang the build. An unrecognized or
|
|
10
|
+
* truncated buffer yields null and its checks simply don't run.
|
|
9
11
|
*/
|
|
10
12
|
export interface ImageSize {
|
|
11
13
|
width: number;
|
|
@@ -13,9 +15,9 @@ export interface ImageSize {
|
|
|
13
15
|
}
|
|
14
16
|
|
|
15
17
|
/** The image's pixel dimensions, or null when the format isn't recognized. */
|
|
16
|
-
export const imageSize = (bytes: Buffer): ImageSize | null => {
|
|
18
|
+
export const imageSize = async (bytes: Buffer): Promise<ImageSize | null> => {
|
|
17
19
|
try {
|
|
18
|
-
const { width, height } =
|
|
20
|
+
const { width, height } = await sharp(bytes).metadata();
|
|
19
21
|
return width > 0 && height > 0 ? { height, width } : null;
|
|
20
22
|
} catch {
|
|
21
23
|
return null;
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import type { CommandMeta } from "citty";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Every command's `meta`, held apart from the command modules themselves.
|
|
5
|
+
*
|
|
6
|
+
* The CLI entry loads each command lazily (see `lazy-command.ts`), but citty
|
|
7
|
+
* still reads every subcommand's `meta` to render `blume --help` and to match
|
|
8
|
+
* an unknown name against aliases. Keeping that table here lets those paths
|
|
9
|
+
* run without importing a single command module — `dev` alone drags in Astro,
|
|
10
|
+
* `mcp-stdio` the MCP SDK. The command modules read their `meta` from this
|
|
11
|
+
* table too, so the entry and the command can't drift.
|
|
12
|
+
*/
|
|
13
|
+
export const commandMeta = {
|
|
14
|
+
add: {
|
|
15
|
+
description: "Install a source component or template from the registry.",
|
|
16
|
+
name: "add",
|
|
17
|
+
},
|
|
18
|
+
audit: {
|
|
19
|
+
description: "Audit the built site for SEO and site-health issues.",
|
|
20
|
+
name: "audit",
|
|
21
|
+
},
|
|
22
|
+
build: {
|
|
23
|
+
description: "Build the docs site for production.",
|
|
24
|
+
name: "build",
|
|
25
|
+
},
|
|
26
|
+
check: {
|
|
27
|
+
description: "Type-check the docs site with astro check.",
|
|
28
|
+
name: "check",
|
|
29
|
+
},
|
|
30
|
+
dev: {
|
|
31
|
+
description: "Start the Blume development server.",
|
|
32
|
+
name: "dev",
|
|
33
|
+
},
|
|
34
|
+
doctor: {
|
|
35
|
+
description: "Diagnose common configuration and content problems.",
|
|
36
|
+
name: "doctor",
|
|
37
|
+
},
|
|
38
|
+
eject: {
|
|
39
|
+
description: "Promote the generated runtime into an owned Astro project.",
|
|
40
|
+
name: "eject",
|
|
41
|
+
},
|
|
42
|
+
eval: {
|
|
43
|
+
description:
|
|
44
|
+
"Test the docs: an agent answers your questions using only the documentation.",
|
|
45
|
+
name: "eval",
|
|
46
|
+
},
|
|
47
|
+
init: {
|
|
48
|
+
description: "Scaffold a minimal Blume project.",
|
|
49
|
+
name: "init",
|
|
50
|
+
},
|
|
51
|
+
"mcp-stdio": {
|
|
52
|
+
description:
|
|
53
|
+
"Serve an MCP data snapshot over stdio (internal, used by `blume eval`).",
|
|
54
|
+
name: "mcp-stdio",
|
|
55
|
+
},
|
|
56
|
+
preview: {
|
|
57
|
+
description: "Preview the last production build.",
|
|
58
|
+
name: "preview",
|
|
59
|
+
},
|
|
60
|
+
sync: {
|
|
61
|
+
description: "Re-fetch remote content sources and regenerate the runtime.",
|
|
62
|
+
name: "sync",
|
|
63
|
+
},
|
|
64
|
+
translate: {
|
|
65
|
+
description:
|
|
66
|
+
"Translate docs into the configured locales with a local agent CLI.",
|
|
67
|
+
name: "translate",
|
|
68
|
+
},
|
|
69
|
+
validate: {
|
|
70
|
+
description: "Validate internal, anchor, asset, and external links.",
|
|
71
|
+
name: "validate",
|
|
72
|
+
},
|
|
73
|
+
version: {
|
|
74
|
+
description: "Freeze the current docs as an archived version.",
|
|
75
|
+
name: "version",
|
|
76
|
+
},
|
|
77
|
+
} satisfies Record<string, CommandMeta>;
|
package/src/cli/commands/add.ts
CHANGED
|
@@ -6,6 +6,7 @@ import { dirname, join } from "pathe";
|
|
|
6
6
|
|
|
7
7
|
import { findItem, packageSrc, registry } from "../../registry/registry.ts";
|
|
8
8
|
import { rewriteImports } from "../../registry/rewrite-imports.ts";
|
|
9
|
+
import { commandMeta } from "../command-meta.ts";
|
|
9
10
|
import { logger } from "../log.ts";
|
|
10
11
|
|
|
11
12
|
export const addCommand = defineCommand({
|
|
@@ -17,10 +18,7 @@ export const addCommand = defineCommand({
|
|
|
17
18
|
type: "positional",
|
|
18
19
|
},
|
|
19
20
|
},
|
|
20
|
-
meta:
|
|
21
|
-
description: "Install a source component or template from the registry.",
|
|
22
|
-
name: "add",
|
|
23
|
-
},
|
|
21
|
+
meta: commandMeta.add,
|
|
24
22
|
async run({ args }) {
|
|
25
23
|
const root = process.cwd();
|
|
26
24
|
|
|
@@ -13,6 +13,7 @@ import type { AuditResult } from "../../audit/run.ts";
|
|
|
13
13
|
import { BlumeError } from "../../core/diagnostics.ts";
|
|
14
14
|
import { scanProject } from "../../core/project-graph.ts";
|
|
15
15
|
import type { DiagnosticSeverity } from "../../core/types.ts";
|
|
16
|
+
import { commandMeta } from "../command-meta.ts";
|
|
16
17
|
import { reportInternalError } from "../internal-error.ts";
|
|
17
18
|
import { flushStdout, logger } from "../log.ts";
|
|
18
19
|
|
|
@@ -115,10 +116,7 @@ export const auditCommand = defineCommand({
|
|
|
115
116
|
type: "boolean",
|
|
116
117
|
},
|
|
117
118
|
},
|
|
118
|
-
meta:
|
|
119
|
-
description: "Audit the built site for SEO and site-health issues.",
|
|
120
|
-
name: "audit",
|
|
121
|
-
},
|
|
119
|
+
meta: commandMeta.audit,
|
|
122
120
|
async run({ args }) {
|
|
123
121
|
if (args["list-checks"]) {
|
|
124
122
|
process.stdout.write(formatCatalog());
|