blume 1.1.1 → 1.1.3
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 +21 -0
- package/dist/cli/index.js +175 -26
- package/dist/cli/index.js.map +16 -16
- package/dist/types/ai/component-markdown.d.ts +10 -0
- package/dist/types/core/config-input.d.ts +50 -5
- package/dist/types/core/i18n-ui.d.ts +24 -24
- package/dist/types/core/schema.d.ts +301 -132
- package/dist/types/core/types.d.ts +21 -0
- package/dist/types/markdown/themes.d.ts +21 -0
- package/dist/types/openapi/references.d.ts +5 -0
- package/docs/advanced/api-reference.mdx +20 -0
- package/docs/configuration/index.mdx +28 -1
- package/docs/content/components.mdx +1 -1
- package/docs/content/syntax.mdx +14 -0
- package/package.json +1 -1
- package/src/ai/component-markdown.ts +28 -0
- package/src/ai/llms.ts +11 -2
- package/src/ai/markdown.ts +12 -6
- package/src/astro/examples.ts +13 -0
- package/src/astro/generate.ts +95 -14
- package/src/astro/templates.ts +48 -7
- package/src/components/content/Component.astro +99 -6
- package/src/components/content/diff.ts +53 -4
- package/src/components/layout/Logo.astro +2 -2
- package/src/components/layout/RootLayout.astro +23 -6
- package/src/components/layout/nav-utils.ts +18 -7
- package/src/components/openapi/ApiTagOperations.astro +17 -8
- package/src/core/config-input.ts +51 -5
- package/src/core/date-format.ts +17 -0
- package/src/core/navigation.ts +11 -7
- package/src/core/project-graph.ts +9 -0
- package/src/core/schema.ts +96 -2
- package/src/core/types.ts +23 -0
- package/src/markdown/index.ts +2 -0
- package/src/markdown/inline-code.ts +1 -1
- package/src/markdown/themes.ts +7 -2
- package/src/openapi/references.ts +6 -0
- package/src/openapi/scalar.ts +4 -0
- package/src/registry/eject.ts +6 -3
- package/src/theme/entry.ts +7 -0
- package/src/theme/twoslash.ts +10 -0
package/src/core/schema.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
|
|
3
3
|
import type { ComponentMarkdown } from "../ai/component-markdown.ts";
|
|
4
|
+
import type { CodeTheme } from "../markdown/themes.ts";
|
|
4
5
|
import { normalizeRoute } from "../openapi/references.ts";
|
|
5
6
|
import { normalizeXHandle } from "../seo/x-handle.ts";
|
|
6
7
|
import { FONT_SLUGS, isFontSlug } from "../theme/fonts.ts";
|
|
@@ -950,9 +951,38 @@ const githubConfigSchema = z.strictObject({
|
|
|
950
951
|
repo: z.string(),
|
|
951
952
|
});
|
|
952
953
|
|
|
954
|
+
const codeThemeSchema = z.custom<CodeTheme>((value) => {
|
|
955
|
+
if (typeof value === "string") {
|
|
956
|
+
return true;
|
|
957
|
+
}
|
|
958
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
959
|
+
return false;
|
|
960
|
+
}
|
|
961
|
+
// Token rules live in `settings` (Shiki's canonical field, also the TextMate
|
|
962
|
+
// form themes like createCssVariablesTheme() produce) or `tokenColors` (the
|
|
963
|
+
// VS Code spelling Shiki falls back to). A colors-only theme (editor fg/bg,
|
|
964
|
+
// no token rules) is also valid — Shiki renders it from `colors` alone. Each
|
|
965
|
+
// field present must have the right shape, and at least one must be present.
|
|
966
|
+
const theme = value as Record<string, unknown>;
|
|
967
|
+
const settingsValid =
|
|
968
|
+
theme.settings === undefined || Array.isArray(theme.settings);
|
|
969
|
+
const tokenColorsValid =
|
|
970
|
+
theme.tokenColors === undefined || Array.isArray(theme.tokenColors);
|
|
971
|
+
const colorsValid =
|
|
972
|
+
theme.colors === undefined ||
|
|
973
|
+
(typeof theme.colors === "object" &&
|
|
974
|
+
theme.colors !== null &&
|
|
975
|
+
!Array.isArray(theme.colors));
|
|
976
|
+
const hasContent =
|
|
977
|
+
theme.settings !== undefined ||
|
|
978
|
+
theme.tokenColors !== undefined ||
|
|
979
|
+
theme.colors !== undefined;
|
|
980
|
+
return settingsValid && tokenColorsValid && colorsValid && hasContent;
|
|
981
|
+
}, "Expected a Shiki theme name or custom theme object");
|
|
982
|
+
|
|
953
983
|
const codeBlockThemeSchema = z.strictObject({
|
|
954
|
-
dark:
|
|
955
|
-
light:
|
|
984
|
+
dark: codeThemeSchema.default("github-dark"),
|
|
985
|
+
light: codeThemeSchema.default("github-light"),
|
|
956
986
|
});
|
|
957
987
|
|
|
958
988
|
const codeBlocksConfigSchema = z.strictObject({
|
|
@@ -998,6 +1028,49 @@ const lastModifiedConfigSchema = z.union([
|
|
|
998
1028
|
z.strictObject({ type: z.enum(["git", "frontmatter"]).default("git") }),
|
|
999
1029
|
]);
|
|
1000
1030
|
|
|
1031
|
+
/**
|
|
1032
|
+
* How the "last updated" stamp and the changelog timeline render their dates —
|
|
1033
|
+
* a curated pass-through to `Intl.DateTimeFormat`, shared by both surfaces so
|
|
1034
|
+
* they read alike. Defaults to `{ dateStyle: "long" }`. Both stamps format in
|
|
1035
|
+
* UTC unless a `timeZone` is given, so a date reads the same regardless of the
|
|
1036
|
+
* build machine's zone. `dateStyle` is a preset that can't be combined with the
|
|
1037
|
+
* individual component fields (`year`, `month`, …), matching `Intl`'s own rule.
|
|
1038
|
+
*/
|
|
1039
|
+
const dateFormatConfigSchema = z
|
|
1040
|
+
.strictObject({
|
|
1041
|
+
/** Calendar system (e.g. `japanese`, `buddhist`). */
|
|
1042
|
+
calendar: z.string().optional(),
|
|
1043
|
+
/** Preset date length; mutually exclusive with the component fields. */
|
|
1044
|
+
dateStyle: z.enum(["full", "long", "medium", "short"]).optional(),
|
|
1045
|
+
/** Day representation. */
|
|
1046
|
+
day: z.enum(["numeric", "2-digit"]).optional(),
|
|
1047
|
+
/** Era representation (e.g. the Japanese imperial era). */
|
|
1048
|
+
era: z.enum(["long", "short", "narrow"]).optional(),
|
|
1049
|
+
/** Month representation. */
|
|
1050
|
+
month: z.enum(["numeric", "2-digit", "long", "short", "narrow"]).optional(),
|
|
1051
|
+
/** Numbering system (e.g. `latn`, `arab`). */
|
|
1052
|
+
numberingSystem: z.string().optional(),
|
|
1053
|
+
/** IANA time zone (e.g. `Asia/Tokyo`). Defaults to `UTC`. */
|
|
1054
|
+
timeZone: z.string().optional(),
|
|
1055
|
+
/** Weekday representation. */
|
|
1056
|
+
weekday: z.enum(["long", "short", "narrow"]).optional(),
|
|
1057
|
+
/** Year representation. */
|
|
1058
|
+
year: z.enum(["numeric", "2-digit"]).optional(),
|
|
1059
|
+
})
|
|
1060
|
+
.refine(
|
|
1061
|
+
(value) =>
|
|
1062
|
+
value.dateStyle === undefined ||
|
|
1063
|
+
(value.weekday === undefined &&
|
|
1064
|
+
value.era === undefined &&
|
|
1065
|
+
value.year === undefined &&
|
|
1066
|
+
value.month === undefined &&
|
|
1067
|
+
value.day === undefined),
|
|
1068
|
+
{
|
|
1069
|
+
message:
|
|
1070
|
+
"dateFormat.dateStyle can't be combined with weekday/era/year/month/day; use one or the other.",
|
|
1071
|
+
}
|
|
1072
|
+
);
|
|
1073
|
+
|
|
1001
1074
|
/** Code-block rendering options (`markdown.code`). */
|
|
1002
1075
|
const codeConfigSchema = z.strictObject({
|
|
1003
1076
|
/**
|
|
@@ -1055,6 +1128,16 @@ const openapiSourceSchema = z.strictObject({
|
|
|
1055
1128
|
|
|
1056
1129
|
export type OpenApiSource = z.infer<typeof openapiSourceSchema>;
|
|
1057
1130
|
|
|
1131
|
+
/**
|
|
1132
|
+
* Arbitrary Scalar API-reference options forwarded verbatim to the generated
|
|
1133
|
+
* `<ScalarComponent>` (Scalar renderer only). A passthrough map — Blume doesn't
|
|
1134
|
+
* mirror Scalar's full config surface — so keys like `localization`, `agent`,
|
|
1135
|
+
* `hideTestRequestButton`, or `orderSchemaPropertiesBy` all flow through. These
|
|
1136
|
+
* take precedence over Blume's own derived config (spec, theme), so this is a
|
|
1137
|
+
* full escape hatch; the dedicated `theme` field is the ergonomic shorthand.
|
|
1138
|
+
*/
|
|
1139
|
+
const scalarConfigSchema = z.record(z.string(), z.unknown()).optional();
|
|
1140
|
+
|
|
1058
1141
|
/**
|
|
1059
1142
|
* OpenAPI reference. By default (`renderer: "blume"`) Blume parses the spec with
|
|
1060
1143
|
* Scalar's parser and renders its own UI: one real page per operation, grouped
|
|
@@ -1072,6 +1155,8 @@ const openapiConfigSchema = z.strictObject({
|
|
|
1072
1155
|
renderer: z.enum(["blume", "scalar"]).default("blume"),
|
|
1073
1156
|
/** Where the reference mounts. */
|
|
1074
1157
|
route: z.string().default("/reference"),
|
|
1158
|
+
/** Extra Scalar config forwarded to `<ScalarComponent>` (Scalar renderer only). */
|
|
1159
|
+
scalar: scalarConfigSchema,
|
|
1075
1160
|
/** One or more specs; each renders on its own route by default. */
|
|
1076
1161
|
sources: z.array(openapiSourceSchema).default([]),
|
|
1077
1162
|
/** Shorthand for a single source: `sources: [{ spec }]`. */
|
|
@@ -1087,6 +1172,8 @@ const openapiConfigSchema = z.strictObject({
|
|
|
1087
1172
|
const asyncapiConfigSchema = z.strictObject({
|
|
1088
1173
|
enabled: z.boolean().default(false),
|
|
1089
1174
|
route: z.string().default("/events"),
|
|
1175
|
+
/** Extra Scalar config forwarded to `<ScalarComponent>`. */
|
|
1176
|
+
scalar: scalarConfigSchema,
|
|
1090
1177
|
sources: z.array(openapiSourceSchema).default([]),
|
|
1091
1178
|
spec: z.string().optional(),
|
|
1092
1179
|
theme: z.string().optional(),
|
|
@@ -1175,6 +1262,11 @@ export const blumeConfigSchema = z.strictObject({
|
|
|
1175
1262
|
.optional()
|
|
1176
1263
|
.transform((value) => normalizeBasePath(value)),
|
|
1177
1264
|
content: contentConfigSchema.default({}),
|
|
1265
|
+
/**
|
|
1266
|
+
* Date presentation for the "last updated" stamp and the changelog timeline.
|
|
1267
|
+
* Pass-through `Intl.DateTimeFormat` options; defaults to `{ dateStyle: "long" }`.
|
|
1268
|
+
*/
|
|
1269
|
+
dateFormat: dateFormatConfigSchema.default({ dateStyle: "long" }),
|
|
1178
1270
|
deployment: deploymentConfigSchema.default({}),
|
|
1179
1271
|
description: z.string().optional(),
|
|
1180
1272
|
/**
|
|
@@ -1207,6 +1299,8 @@ export const blumeConfigSchema = z.strictObject({
|
|
|
1207
1299
|
|
|
1208
1300
|
/** Resolved config: every field present after defaults are applied. */
|
|
1209
1301
|
export type ResolvedConfig = z.infer<typeof blumeConfigSchema>;
|
|
1302
|
+
/** Resolved `dateFormat`: the `Intl.DateTimeFormat` options both date stamps share. */
|
|
1303
|
+
export type ResolvedDateFormat = z.infer<typeof dateFormatConfigSchema>;
|
|
1210
1304
|
/** Resolved `frontmatter.extend`: custom key → user-supplied schema. */
|
|
1211
1305
|
export type FrontmatterExtend = Record<string, StandardSchema>;
|
|
1212
1306
|
/** Resolved i18n block (present only when the project opts into i18n). */
|
package/src/core/types.ts
CHANGED
|
@@ -31,6 +31,22 @@ export interface Diagnostic {
|
|
|
31
31
|
docsUrl?: string;
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
+
/** One discovered `examples/` file reduced to what Markdown downleveling needs. */
|
|
35
|
+
export interface ExampleMarkdownEntry {
|
|
36
|
+
/** Shiki language for the fenced block — the file's extension. */
|
|
37
|
+
lang: string;
|
|
38
|
+
/** Raw example source, shown verbatim in the agent-facing code fence. */
|
|
39
|
+
source: string;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Discovered examples keyed by their `<Component path>` (the file's location
|
|
44
|
+
* under `examples/`, sans extension). Lets the agent-facing Markdown downlevel
|
|
45
|
+
* `<Component path="…" />` to the example's source, since the live preview
|
|
46
|
+
* can't survive the trip to plain Markdown.
|
|
47
|
+
*/
|
|
48
|
+
export type ExampleLookup = Record<string, ExampleMarkdownEntry>;
|
|
49
|
+
|
|
34
50
|
/** A heading extracted from page content, used for the TOC and search. */
|
|
35
51
|
export interface Heading {
|
|
36
52
|
depth: number;
|
|
@@ -227,6 +243,13 @@ export interface Navigation {
|
|
|
227
243
|
tabs: NavTab[];
|
|
228
244
|
selectors: NavSelector[];
|
|
229
245
|
sidebar: NavNode[];
|
|
246
|
+
/**
|
|
247
|
+
* The tree root in final path space — localized and based (`/`, `/en`,
|
|
248
|
+
* `/docs`). Tab paths arrive in the same space, so the tab sitting at this
|
|
249
|
+
* path spans the whole tree and must be scoped as the root tab, not as a
|
|
250
|
+
* section tab. Absent on older serialized graphs; treat as `/`.
|
|
251
|
+
*/
|
|
252
|
+
root?: string;
|
|
230
253
|
/** Pinned links shown above the sidebar sections, unscoped by tab. */
|
|
231
254
|
featured: FeaturedLink[];
|
|
232
255
|
/** Repo URL for the header link, or null when hidden (`navigation.repo`). */
|
package/src/markdown/index.ts
CHANGED
|
@@ -21,6 +21,8 @@ import { tableWrapPlugin } from "./table-wrap.ts";
|
|
|
21
21
|
import { DEFAULT_CODE_THEMES } from "./themes.ts";
|
|
22
22
|
import type { CodeThemes } from "./themes.ts";
|
|
23
23
|
|
|
24
|
+
export type { CodeTheme, CodeThemes } from "./themes.ts";
|
|
25
|
+
|
|
24
26
|
/** A Shiki transformer, derived from the upstream factories' return type. */
|
|
25
27
|
type ShikiTransformer = ReturnType<typeof transformerNotationDiff>;
|
|
26
28
|
|
package/src/markdown/themes.ts
CHANGED
|
@@ -6,6 +6,11 @@
|
|
|
6
6
|
* single home for the github fallback used when nothing is configured.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
+
import type { ThemeRegistrationAny } from "shiki";
|
|
10
|
+
|
|
11
|
+
/** A bundled Shiki theme name or an inline custom Shiki theme definition. */
|
|
12
|
+
export type CodeTheme = string | ThemeRegistrationAny;
|
|
13
|
+
|
|
9
14
|
/**
|
|
10
15
|
* A light/dark Shiki theme pair (`markdown.codeBlocks.theme`). A `type` (not an
|
|
11
16
|
* `interface`) so it keeps the implicit index signature Shiki's `themes`
|
|
@@ -13,8 +18,8 @@
|
|
|
13
18
|
*/
|
|
14
19
|
// oxlint-disable-next-line typescript/consistent-type-definitions -- interface loses the implicit index signature Shiki's `themes` param needs
|
|
15
20
|
export type CodeThemes = {
|
|
16
|
-
dark:
|
|
17
|
-
light:
|
|
21
|
+
dark: CodeTheme;
|
|
22
|
+
light: CodeTheme;
|
|
18
23
|
};
|
|
19
24
|
|
|
20
25
|
/** The default pair, used when `markdown.codeBlocks.theme` is unset. */
|
|
@@ -42,6 +42,11 @@ export interface ReferenceSource {
|
|
|
42
42
|
spec: string;
|
|
43
43
|
/** Per-block Scalar theme name override, if any (Scalar renderer only). */
|
|
44
44
|
theme?: string;
|
|
45
|
+
/**
|
|
46
|
+
* Arbitrary Scalar config forwarded to `<ScalarComponent>` (Scalar renderer
|
|
47
|
+
* only). Takes precedence over Blume's derived spec/theme config.
|
|
48
|
+
*/
|
|
49
|
+
scalar?: Record<string, unknown>;
|
|
45
50
|
/** Display options carried through to the Blume renderer. */
|
|
46
51
|
display: ReferenceDisplay;
|
|
47
52
|
/**
|
|
@@ -117,6 +122,7 @@ const referencesFor = (
|
|
|
117
122
|
label,
|
|
118
123
|
renderer,
|
|
119
124
|
route,
|
|
125
|
+
scalar: block.scalar,
|
|
120
126
|
slug: routeSlug(route),
|
|
121
127
|
spec: source.spec,
|
|
122
128
|
theme: block.theme,
|
package/src/openapi/scalar.ts
CHANGED
|
@@ -161,6 +161,10 @@ export const buildReferenceFiles = async (options: {
|
|
|
161
161
|
configuration: {
|
|
162
162
|
...spec.config,
|
|
163
163
|
...themeConfiguration(config, ref.theme),
|
|
164
|
+
// Author-supplied Scalar options win outright — a full escape hatch
|
|
165
|
+
// over Blume's derived spec/theme config (localization, agent,
|
|
166
|
+
// hideTestRequestButton, orderSchemaPropertiesBy, and the rest).
|
|
167
|
+
...ref.scalar,
|
|
164
168
|
},
|
|
165
169
|
dataImport: `${"../".repeat(depth + 1)}generated/data.json`,
|
|
166
170
|
route: ref.route,
|
package/src/registry/eject.ts
CHANGED
|
@@ -9,7 +9,7 @@ import { buildRawMarkdown } from "../ai/markdown.ts";
|
|
|
9
9
|
import { buildMcpData } from "../ai/mcp/data.ts";
|
|
10
10
|
import { buildMcpDiscovery, buildMcpServerCard } from "../ai/mcp/discovery.ts";
|
|
11
11
|
import { planComponentSlots } from "../astro/component-slots.ts";
|
|
12
|
-
import { discoverExamples } from "../astro/examples.ts";
|
|
12
|
+
import { discoverExamples, exampleMarkdownLookup } from "../astro/examples.ts";
|
|
13
13
|
import {
|
|
14
14
|
buildRuntimeData,
|
|
15
15
|
collectStaged,
|
|
@@ -292,6 +292,11 @@ export const eject = async (
|
|
|
292
292
|
const exportPdf = config.export.pdf;
|
|
293
293
|
const exportEpub = config.export.epub;
|
|
294
294
|
|
|
295
|
+
// Discover examples first and expose them on the project, so the agent-facing
|
|
296
|
+
// Markdown built below (raw `.md`, MCP) downlevels `<Component>` to its source.
|
|
297
|
+
const examples = await discoverExamples(root, config.examples.source);
|
|
298
|
+
project.examples = exampleMarkdownLookup(examples.examples);
|
|
299
|
+
|
|
295
300
|
const [
|
|
296
301
|
pages,
|
|
297
302
|
needsReactRaw,
|
|
@@ -300,7 +305,6 @@ export const eject = async (
|
|
|
300
305
|
userExamplesCss,
|
|
301
306
|
rawMarkdown,
|
|
302
307
|
islands,
|
|
303
|
-
examples,
|
|
304
308
|
] = await Promise.all([
|
|
305
309
|
context.pagesRoot ? discoverPages(context.pagesRoot) : Promise.resolve([]),
|
|
306
310
|
detectNeedsReact(root),
|
|
@@ -311,7 +315,6 @@ export const eject = async (
|
|
|
311
315
|
readExamplesCss(root, config.examples.css),
|
|
312
316
|
buildRawMarkdown(project),
|
|
313
317
|
discoverIslands(root),
|
|
314
|
-
discoverExamples(root, config.examples.source),
|
|
315
318
|
]);
|
|
316
319
|
// Island/example frameworks drive which Astro renderers the ejected config
|
|
317
320
|
// wires in; React also switches on for project `.tsx`/`.jsx` and Ask AI.
|
package/src/theme/entry.ts
CHANGED
|
@@ -247,6 +247,13 @@ ${THEME_MAPPING}
|
|
|
247
247
|
letter-spacing: 0;
|
|
248
248
|
}
|
|
249
249
|
|
|
250
|
+
/* A heading can carry one long unbreakable token — an OpenAPI operation's title
|
|
251
|
+
is \`METHOD /very/long/{path}\` when the spec sets no summary — which would run
|
|
252
|
+
off the content column. Break it across lines instead of overflowing. */
|
|
253
|
+
.prose :where(h1, h2, h3, h4, h5, h6) {
|
|
254
|
+
overflow-wrap: break-word;
|
|
255
|
+
}
|
|
256
|
+
|
|
250
257
|
.prose :where(h1) {
|
|
251
258
|
font-size: 3rem;
|
|
252
259
|
line-height: 1.1;
|
package/src/theme/twoslash.ts
CHANGED
|
@@ -43,6 +43,16 @@ const OVERRIDES = `
|
|
|
43
43
|
padding-right: 1.25rem;
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
+
/* Twoslash blocks keep \`overflow: visible\` so popups can escape, which means
|
|
47
|
+
long lines can't scroll — they'd push past the viewport on narrow screens.
|
|
48
|
+
Wrap them on mobile instead; hover popups still position against the token. */
|
|
49
|
+
@media (max-width: 640px) {
|
|
50
|
+
.prose pre.twoslash code {
|
|
51
|
+
white-space: pre-wrap;
|
|
52
|
+
overflow-wrap: anywhere;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
46
56
|
/* The rich renderer renders each popup's type signature as a nested Shiki
|
|
47
57
|
pre. Strip the code-block chrome (border, radius, padding, background) so it
|
|
48
58
|
sits flush inside the popup, which owns the frame. */
|