blume 1.1.0 → 1.1.1
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 +15 -0
- package/dist/cli/index.js +240 -87
- package/dist/cli/index.js.map +18 -18
- package/dist/types/ai/component-markdown.d.ts +12 -1
- package/dist/types/core/config-input.d.ts +12 -3
- package/dist/types/core/schema.d.ts +19 -6
- package/dist/types/core/types.d.ts +7 -0
- package/docs/advanced/changelog.mdx +1 -1
- package/docs/configuration/ai.mdx +2 -2
- package/docs/configuration/seo.mdx +16 -0
- package/docs/content/navigation.mdx +4 -0
- package/docs/reference/cli.mdx +1 -1
- package/docs/reference/frontmatter.mdx +2 -0
- package/package.json +2 -1
- package/src/ai/component-markdown.ts +39 -11
- package/src/ai/llms.ts +4 -2
- package/src/ai/markdown.ts +5 -1
- package/src/astro/generate.ts +75 -32
- package/src/astro/pages.ts +21 -5
- package/src/astro/templates.ts +28 -6
- package/src/audit/checks/llms.ts +4 -1
- package/src/cli/commands/build.ts +13 -1
- package/src/cli/prepare.ts +10 -2
- package/src/core/config-input.ts +12 -3
- package/src/core/diagnostics.ts +2 -0
- package/src/core/graph.ts +23 -4
- package/src/core/navigation.ts +169 -14
- package/src/core/project-graph.ts +54 -28
- package/src/core/schema.ts +7 -0
- package/src/core/sources/github-releases.ts +65 -2
- package/src/core/types.ts +7 -0
- package/src/markdown/index.ts +1 -0
- package/src/markdown/twoslash.ts +60 -0
- package/src/registry/eject.ts +3 -1
- /package/docs/{03-faq.mdx → 07-faq.mdx} +0 -0
package/src/core/schema.ts
CHANGED
|
@@ -870,6 +870,13 @@ const ogConfigSchema = z.strictObject({
|
|
|
870
870
|
logo: z.string().optional(),
|
|
871
871
|
/** Optional generated-card colors. */
|
|
872
872
|
palette: ogPaletteSchema.optional(),
|
|
873
|
+
/**
|
|
874
|
+
* Card headlines for custom `.astro` pages, keyed by route (`"/"`, `"/cli"`).
|
|
875
|
+
* A custom page has no frontmatter to read, so its card is otherwise titled
|
|
876
|
+
* by humanizing its last URL segment (`/cli` → "Cli"); an entry here wins.
|
|
877
|
+
* Content pages always take their card headline from the page title.
|
|
878
|
+
*/
|
|
879
|
+
titles: z.record(z.string(), z.string()).optional(),
|
|
873
880
|
});
|
|
874
881
|
|
|
875
882
|
const rssConfigSchema = z.strictObject({
|
|
@@ -56,6 +56,63 @@ const LEADING_V = /^v/iu;
|
|
|
56
56
|
const NON_SLUG = /[^a-z0-9]+/gu;
|
|
57
57
|
const EDGE_DASHES = /^-+|-+$/gu;
|
|
58
58
|
|
|
59
|
+
// `blume audit` grades meta descriptions against the 110–160 character search
|
|
60
|
+
// snippet range (audit/types.ts thresholds), so the derived summary aims for
|
|
61
|
+
// the longest word-boundary cut under the cap.
|
|
62
|
+
const DESCRIPTION_MAX = 160;
|
|
63
|
+
const DESCRIPTION_MIN = 110;
|
|
64
|
+
|
|
65
|
+
const CODE_FENCE = /```[\s\S]*?```/gu;
|
|
66
|
+
const HEADING_LINE = /^#{1,6}\s.*$/gmu;
|
|
67
|
+
const LIST_MARK = /^\s*(?:[-*+]|\d+[.)])\s+/u;
|
|
68
|
+
// Changesets-generated release bullets open with the changeset's short commit
|
|
69
|
+
// hash (`- cf8fa22: Fix …`) — noise in a search snippet.
|
|
70
|
+
const CHANGESET_HASH = /^[0-9a-f]{7,40}:\s+/u;
|
|
71
|
+
const IMAGE = /!\[[^\]]*\]\([^)]*\)/gu;
|
|
72
|
+
const LINK = /\[(?<text>[^\]]*)\]\([^)]*\)/gu;
|
|
73
|
+
const INLINE_CODE = /`(?<code>[^`]+)`/gu;
|
|
74
|
+
// Tag-shaped only: a bare `<` in prose must not swallow text up to a later `>`.
|
|
75
|
+
const HTML_OR_JSX = /<\/?[a-zA-Z][^\n<>]*>|<\/?>/gu;
|
|
76
|
+
const MARKDOWN_PUNCT = /[*_~>]+/gu;
|
|
77
|
+
const WHITESPACE = /\s+/gu;
|
|
78
|
+
const TRAILING_FRAGMENT = /[\s,;:.—–-]+$/u;
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Derive a meta description from release notes: markdown reduced to plain
|
|
82
|
+
* text — section headings ("### Patch Changes") and changesets' commit-hash
|
|
83
|
+
* bullet prefixes dropped — then cut at a word boundary to fit the search
|
|
84
|
+
* snippet cap. Undefined when the notes have no prose at all.
|
|
85
|
+
*/
|
|
86
|
+
const releaseDescription = (body: string): string | undefined => {
|
|
87
|
+
const text = body
|
|
88
|
+
.replaceAll(CODE_FENCE, " ")
|
|
89
|
+
.replaceAll(HEADING_LINE, "")
|
|
90
|
+
.split("\n")
|
|
91
|
+
.map((line) => line.replace(LIST_MARK, "").replace(CHANGESET_HASH, ""))
|
|
92
|
+
.join("\n")
|
|
93
|
+
.replaceAll(IMAGE, " ")
|
|
94
|
+
.replaceAll(LINK, "$<text>")
|
|
95
|
+
.replaceAll(INLINE_CODE, "$<code>")
|
|
96
|
+
.replaceAll(HTML_OR_JSX, " ")
|
|
97
|
+
.replaceAll(MARKDOWN_PUNCT, " ")
|
|
98
|
+
.replaceAll(WHITESPACE, " ")
|
|
99
|
+
.trim();
|
|
100
|
+
if (!text) {
|
|
101
|
+
return undefined;
|
|
102
|
+
}
|
|
103
|
+
if (text.length <= DESCRIPTION_MAX) {
|
|
104
|
+
return text;
|
|
105
|
+
}
|
|
106
|
+
// Cut before the cap at a word boundary (kept only when it doesn't drop the
|
|
107
|
+
// summary under the minimum), shed any dangling punctuation, and mark the cut.
|
|
108
|
+
const slice = text.slice(0, DESCRIPTION_MAX - 1);
|
|
109
|
+
const boundary = slice.lastIndexOf(" ");
|
|
110
|
+
const head = (
|
|
111
|
+
boundary >= DESCRIPTION_MIN ? slice.slice(0, boundary) : slice
|
|
112
|
+
).replace(TRAILING_FRAGMENT, "");
|
|
113
|
+
return `${head}…`;
|
|
114
|
+
};
|
|
115
|
+
|
|
59
116
|
/** Slugify a tag into a stable, URL-safe source ref (`v1.2.0` -> `v1-2-0`). */
|
|
60
117
|
const slugifyTag = (tag: string): string =>
|
|
61
118
|
tag.toLowerCase().replaceAll(NON_SLUG, "-").replaceAll(EDGE_DASHES, "");
|
|
@@ -71,9 +128,10 @@ const githubHeaders = (): Headers => {
|
|
|
71
128
|
};
|
|
72
129
|
|
|
73
130
|
/**
|
|
74
|
-
* Lower one release to a staged Markdown entry: the notes become the body
|
|
131
|
+
* Lower one release to a staged Markdown entry: the notes become the body,
|
|
75
132
|
* `type: changelog` frontmatter (title/date/version/category) drives the
|
|
76
|
-
* generated `/changelog` timeline and RSS feed
|
|
133
|
+
* generated `/changelog` timeline and RSS feed, and a summary derived from the
|
|
134
|
+
* notes becomes the release page's meta description.
|
|
77
135
|
*/
|
|
78
136
|
const releaseToEntry = (release: GithubRelease): SourceEntry => {
|
|
79
137
|
const version = release.tag_name.replace(LEADING_V, "");
|
|
@@ -81,9 +139,14 @@ const releaseToEntry = (release: GithubRelease): SourceEntry => {
|
|
|
81
139
|
const date = release.published_at ?? release.created_at;
|
|
82
140
|
const category = release.prerelease ? "Prerelease" : "Release";
|
|
83
141
|
const body = (release.body ?? "").replaceAll("\r\n", "\n").trim();
|
|
142
|
+
// A summary in `seo.description` gives each release page a unique meta
|
|
143
|
+
// description (instead of the site-wide fallback) without also rendering the
|
|
144
|
+
// visible lede paragraph a top-level `description` would add.
|
|
145
|
+
const description = releaseDescription(body);
|
|
84
146
|
const data = {
|
|
85
147
|
changelog: { category, version },
|
|
86
148
|
date,
|
|
149
|
+
...(description ? { seo: { description } } : {}),
|
|
87
150
|
title,
|
|
88
151
|
type: "changelog",
|
|
89
152
|
};
|
package/src/core/types.ts
CHANGED
|
@@ -108,6 +108,13 @@ export interface PageRecord {
|
|
|
108
108
|
* `/guides/x`). Pages with the same key are translations of each other.
|
|
109
109
|
*/
|
|
110
110
|
translationKey: string;
|
|
111
|
+
/**
|
|
112
|
+
* True for entries filled in from the fallback locale to pad a locale's
|
|
113
|
+
* navigation for pages it hasn't translated yet. The record's content —
|
|
114
|
+
* title included — belongs to the fallback locale, so per-locale content
|
|
115
|
+
* checks skip these.
|
|
116
|
+
*/
|
|
117
|
+
fallback?: boolean;
|
|
111
118
|
/**
|
|
112
119
|
* Content-relative path with the leading locale directory stripped, used for
|
|
113
120
|
* sidebar grouping so the locale dir is not surfaced as a nav group. Equals
|
package/src/markdown/index.ts
CHANGED
|
@@ -37,6 +37,7 @@ export { calloutTypeFor } from "./directives.ts";
|
|
|
37
37
|
export { headingAnchorPlugin } from "./heading-anchors.ts";
|
|
38
38
|
export { mermaidPlugin } from "./mermaid.ts";
|
|
39
39
|
export { packageInstallPlugin } from "./package-install.ts";
|
|
40
|
+
export { blumeTwoslashTransformer } from "./twoslash.ts";
|
|
40
41
|
|
|
41
42
|
/** Element type of Satteri's `mdastPlugins`, sourced from the (alpha) core. */
|
|
42
43
|
type MdastPlugin = NonNullable<
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Twoslash transformer for fenced code blocks, compiled with Blume's own
|
|
3
|
+
* pinned TypeScript instead of whatever copy the user's project hoists.
|
|
4
|
+
*
|
|
5
|
+
* The stock `transformerTwoslash` from `@shikijs/twoslash` resolves the
|
|
6
|
+
* ambient `typescript` package, which is whatever version the surrounding
|
|
7
|
+
* project installed. Under TypeScript 7 (the native tsgo compiler) the
|
|
8
|
+
* package's main export is a version stub with no classic compiler API — no
|
|
9
|
+
* `ts.sys`, no language service — and its `lib/` directory ships no
|
|
10
|
+
* `lib.*.d.ts` files, so Twoslash breaks the moment a fence uses it. Blume
|
|
11
|
+
* ships a classic `typescript` as its own runtime dependency, so this factory
|
|
12
|
+
* resolves that copy from inside the package and hands it to Twoslash
|
|
13
|
+
* explicitly (`tsModule` for the compiler, `tsLibDirectory` for the default
|
|
14
|
+
* lib files), leaving the user's project free to use any TypeScript version.
|
|
15
|
+
*
|
|
16
|
+
* Composed from the `core` entrypoints of both packages: the main `twoslash`
|
|
17
|
+
* entry eagerly imports the ambient `typescript` (harmless under TS7 — the
|
|
18
|
+
* stub loads fine — but pointless), and its `transformerTwoslash` wrapper
|
|
19
|
+
* forwards `tsModule` to `createTwoslasher` while dropping `tsLibDirectory`.
|
|
20
|
+
* The core entries import no `typescript` at all and take both options.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
import { createRequire } from "node:module";
|
|
24
|
+
import path from "node:path";
|
|
25
|
+
|
|
26
|
+
import { createTransformerFactory, rendererRich } from "@shikijs/twoslash/core";
|
|
27
|
+
import type { ShikiTransformer } from "shiki";
|
|
28
|
+
import { createTwoslasher } from "twoslash/core";
|
|
29
|
+
import type TS from "typescript";
|
|
30
|
+
|
|
31
|
+
const require = createRequire(import.meta.url);
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Twoslash transformer preconfigured for Blume: opt-in per fence via the
|
|
35
|
+
* `twoslash` meta keyword (explicitTrigger), compiling with Blume's own
|
|
36
|
+
* pinned classic TypeScript. The compiler is resolved lazily at call time —
|
|
37
|
+
* this runs once, at Astro config load — so merely importing this module
|
|
38
|
+
* never pays the TypeScript parse cost.
|
|
39
|
+
*/
|
|
40
|
+
export const blumeTwoslashTransformer = (): ShikiTransformer => {
|
|
41
|
+
const tsModule = require("typescript") as typeof TS;
|
|
42
|
+
const twoslasher = createTwoslasher({
|
|
43
|
+
// Match the stock transformer's default: fence snippets are authored
|
|
44
|
+
// bundler-style (extensionless relative imports, package imports).
|
|
45
|
+
compilerOptions: {
|
|
46
|
+
moduleResolution: tsModule.ModuleResolutionKind.Bundler,
|
|
47
|
+
},
|
|
48
|
+
// `require.resolve("typescript")` is the package's main entry,
|
|
49
|
+
// `lib/typescript.js`; its directory holds the `lib.*.d.ts` default libs.
|
|
50
|
+
tsLibDirectory: path.dirname(require.resolve("typescript")),
|
|
51
|
+
tsModule,
|
|
52
|
+
vfsRoot: process.cwd(),
|
|
53
|
+
});
|
|
54
|
+
return createTransformerFactory(
|
|
55
|
+
twoslasher,
|
|
56
|
+
rendererRich()
|
|
57
|
+
)({
|
|
58
|
+
explicitTrigger: true,
|
|
59
|
+
});
|
|
60
|
+
};
|
package/src/registry/eject.ts
CHANGED
|
@@ -472,7 +472,9 @@ export const eject = async (
|
|
|
472
472
|
|
|
473
473
|
if (config.seo.og.enabled) {
|
|
474
474
|
files.push({
|
|
475
|
-
content: ogEndpointTemplate(
|
|
475
|
+
content: ogEndpointTemplate(
|
|
476
|
+
customOgRoutes(pages, config.title, config.seo.og.titles)
|
|
477
|
+
),
|
|
476
478
|
path: join(srcDir, "pages", "og", "[...slug].png.ts"),
|
|
477
479
|
});
|
|
478
480
|
}
|
|
File without changes
|