blume 1.5.2 → 1.6.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 +85 -0
- package/dist/cli/index.js +3639 -1377
- package/dist/cli/index.js.map +103 -91
- package/dist/types/ai/component-markdown.d.ts +79 -0
- package/dist/types/components/layout/nav-utils.d.ts +60 -0
- package/dist/types/core/base-path.d.ts +9 -0
- package/dist/types/core/config-input.d.ts +206 -4
- package/dist/types/core/config.d.ts +6 -4
- package/dist/types/core/data.d.ts +23 -1
- package/dist/types/core/github.d.ts +35 -0
- package/dist/types/core/i18n-ui.d.ts +8 -0
- package/dist/types/core/navigation.d.ts +69 -0
- package/dist/types/core/schema.d.ts +117 -1
- package/dist/types/core/sources/types.d.ts +31 -6
- package/dist/types/core/types.d.ts +23 -2
- package/dist/types/markdown/features.d.ts +21 -0
- package/dist/types/openapi/references.d.ts +21 -1
- package/dist/types/seo/jsonld.d.ts +105 -0
- package/dist/types/theme/fonts.d.ts +34 -4
- package/docs/_snippets/include-demo.mdx +7 -0
- package/docs/advanced/api-reference.mdx +3 -3
- package/docs/advanced/custom-pages.mdx +1 -1
- package/docs/advanced/graphql.mdx +84 -0
- package/docs/advanced/meta.ts +8 -1
- package/docs/configuration/ai.mdx +21 -3
- package/docs/configuration/index.mdx +24 -0
- package/docs/configuration/search.mdx +13 -1
- package/docs/configuration/seo.mdx +27 -0
- package/docs/configuration/theming.mdx +17 -0
- package/docs/content/components.mdx +7 -0
- package/docs/content/includes.mdx +68 -0
- package/docs/content/meta.ts +1 -0
- package/docs/content/navigation.mdx +25 -0
- package/docs/content/sources.mdx +42 -1
- package/docs/content/syntax.mdx +69 -1
- package/docs/content/versioning.mdx +15 -9
- package/docs/reference/cli.mdx +2 -1
- package/package.json +23 -14
- package/skills/blume-migrate/SKILL.md +16 -7
- package/skills/blume-migrate/references/docusaurus.md +5 -3
- package/skills/blume-migrate/references/fumadocs.md +10 -2
- package/skills/blume-migrate/references/mintlify.md +3 -2
- package/skills/blume-migrate/references/nextra.md +2 -2
- package/skills/blume-migrate/references/starlight.md +1 -1
- package/src/ai/agent-readability.ts +2 -1
- package/src/ai/ask-data.ts +2 -1
- package/src/ai/component-markdown.ts +199 -36
- package/src/ai/llms.ts +93 -6
- package/src/ai/markdown.ts +2 -2
- package/src/ai/mcp/discovery.ts +10 -2
- package/src/ai/mcp/server.ts +74 -2
- package/src/astro/generate.ts +183 -116
- package/src/astro/include-hmr.ts +81 -0
- package/src/astro/include-refresh.ts +0 -0
- package/src/astro/index.ts +3 -5
- package/src/astro/templates.ts +125 -76
- package/src/cli/commands/build.ts +84 -15
- package/src/cli/init/questions.ts +1 -0
- package/src/cli/init/scaffold.ts +27 -4
- package/src/components/colors.ts +142 -0
- package/src/components/content/Badge.astro +5 -12
- package/src/components/content/Callout.astro +19 -36
- package/src/components/content/Card.astro +15 -21
- package/src/components/content/Component.astro +10 -1
- package/src/components/content/GithubInfo.astro +28 -9
- package/src/components/content/Tabs.astro +27 -5
- package/src/components/content/github-info.ts +20 -5
- package/src/components/dropdown-dismiss.ts +122 -0
- package/src/components/layout/Fonts.astro +15 -8
- package/src/components/layout/Header.astro +44 -0
- package/src/components/layout/LanguageSwitcher.astro +9 -1
- package/src/components/layout/NavSelector.astro +12 -3
- package/src/components/layout/NavTree.astro +6 -18
- package/src/components/layout/PageActions.astro +29 -8
- package/src/components/layout/PageLayout.astro +10 -1
- package/src/components/layout/ReferenceLayout.astro +6 -1
- package/src/components/layout/RootLayout.astro +46 -15
- package/src/components/layout/Search.astro +36 -4
- package/src/components/layout/TableOfContents.astro +8 -2
- package/src/components/layout/head-scripts.ts +53 -1
- package/src/components/openapi/ApiOverview.astro +13 -3
- package/src/components/openapi/AsyncApiOperation.astro +7 -14
- package/src/components/openapi/GraphqlChip.astro +33 -0
- package/src/components/openapi/GraphqlFieldsTable.astro +111 -0
- package/src/components/openapi/GraphqlOperation.astro +186 -0
- package/src/components/openapi/GraphqlType.astro +154 -0
- package/src/components/openapi/MethodBadge.astro +3 -14
- package/src/components/openapi/Operation.astro +12 -5
- package/src/components/openapi/OperationPanel.astro +43 -0
- package/src/components/openapi/RequestPanel.astro +5 -10
- package/src/components/openapi/Responses.astro +1 -16
- package/src/components/openapi/graphql-helpers.ts +466 -0
- package/src/components/openapi/playground-client.ts +15 -0
- package/src/components/openapi/sample-panels.ts +45 -0
- package/src/components/openapi/snippets.ts +13 -35
- package/src/core/base-path.ts +11 -0
- package/src/core/config-input.ts +209 -2
- package/src/core/config.ts +6 -4
- package/src/core/content-assets.ts +15 -4
- package/src/core/data.ts +18 -2
- package/src/core/diagnostics.ts +8 -0
- package/src/core/frontmatter.ts +20 -8
- package/src/core/github.ts +71 -0
- package/src/core/graph.ts +22 -8
- package/src/core/heading-markers.ts +96 -0
- package/src/core/i18n-ui.ts +11 -0
- package/src/core/includes.ts +632 -0
- package/src/core/last-modified.ts +36 -11
- package/src/core/links.ts +79 -13
- package/src/core/meta.ts +2 -1
- package/src/core/nav-diagnostics.ts +11 -2
- package/src/core/navigation.ts +27 -6
- package/src/core/project-graph.ts +61 -9
- package/src/core/schema.ts +226 -35
- package/src/core/server-features.ts +5 -9
- package/src/core/sources/github-releases.ts +2 -2
- package/src/core/sources/normalize.ts +502 -115
- package/src/core/sources/notion.ts +43 -8
- package/src/core/sources/obsidian.ts +1038 -0
- package/src/core/sources/read.ts +36 -1
- package/src/core/sources/resolve.ts +34 -1
- package/src/core/sources/types.ts +28 -6
- package/src/core/sources/watch.ts +12 -8
- package/src/core/tsconfig-aliases.ts +48 -35
- package/src/core/types.ts +25 -2
- package/src/core/ui-packs/ar.ts +1 -0
- package/src/core/ui-packs/bg.ts +2 -0
- package/src/core/ui-packs/bn.ts +1 -0
- package/src/core/ui-packs/ca.ts +2 -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 +2 -0
- package/src/core/ui-packs/el.ts +2 -0
- package/src/core/ui-packs/es.ts +2 -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 -0
- 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 +2 -0
- package/src/core/ui-packs/hu.ts +2 -0
- package/src/core/ui-packs/id.ts +2 -0
- package/src/core/ui-packs/it.ts +1 -0
- package/src/core/ui-packs/ja.ts +2 -0
- package/src/core/ui-packs/ko.ts +2 -0
- package/src/core/ui-packs/nl.ts +2 -0
- package/src/core/ui-packs/no.ts +2 -0
- package/src/core/ui-packs/pl.ts +2 -0
- package/src/core/ui-packs/pt-br.ts +2 -0
- package/src/core/ui-packs/pt.ts +2 -0
- package/src/core/ui-packs/ro.ts +2 -0
- package/src/core/ui-packs/ru.ts +2 -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 +2 -0
- package/src/core/ui-packs/th.ts +1 -0
- package/src/core/ui-packs/tr.ts +2 -0
- package/src/core/ui-packs/uk.ts +2 -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/core/version-cut.ts +21 -3
- package/src/core/yaml.ts +26 -0
- package/src/deploy/function-bundle.ts +251 -0
- package/src/eval/schema.ts +3 -1
- package/src/markdown/code-title.ts +22 -16
- package/src/markdown/features.ts +21 -0
- package/src/markdown/fence-meta.ts +50 -0
- package/src/markdown/heading-anchors.ts +198 -37
- package/src/markdown/include.ts +247 -0
- package/src/markdown/index.ts +43 -34
- package/src/markdown/language-icon.ts +2 -2
- package/src/markdown/mdast.ts +7 -3
- package/src/markdown/ts2js.ts +264 -0
- package/src/openapi/asyncapi.ts +4 -1
- package/src/openapi/graphql-build.ts +293 -0
- package/src/openapi/graphql.ts +212 -0
- package/src/openapi/model.ts +38 -5
- package/src/openapi/parse.ts +34 -0
- package/src/openapi/proxy.ts +30 -5
- package/src/openapi/references.ts +89 -13
- package/src/openapi/render-mdx.ts +48 -8
- package/src/openapi/scalar.ts +5 -12
- package/src/openapi/source.ts +91 -23
- package/src/registry/eject.ts +11 -0
- package/src/search/documents.ts +229 -37
- package/src/search/orama-index.ts +9 -5
- package/src/seo/jsonld.ts +293 -51
- package/src/theme/code-block-padding.ts +16 -0
- package/src/theme/entry.ts +65 -11
- package/src/theme/fonts.ts +189 -16
- package/src/translate/prompts.ts +2 -0
- package/src/translate/run.ts +7 -0
- package/src/translate/work-list.ts +0 -0
package/src/markdown/index.ts
CHANGED
|
@@ -12,7 +12,9 @@ import { codeToHtml } from "shiki";
|
|
|
12
12
|
import { baseLinksPlugin } from "./base-links.ts";
|
|
13
13
|
import { codeTitleTransformer } from "./code-title.ts";
|
|
14
14
|
import { directiveToCalloutPlugin } from "./directives.ts";
|
|
15
|
+
import { MARKDOWN_FEATURES, MDX_FEATURES } from "./features.ts";
|
|
15
16
|
import { headingAnchorPlugin } from "./heading-anchors.ts";
|
|
17
|
+
import { includePlugin } from "./include.ts";
|
|
16
18
|
import { inlineCodeHighlightPlugin } from "./inline-code.ts";
|
|
17
19
|
import { languageIconTransformer } from "./language-icon.ts";
|
|
18
20
|
import { mathPlugin } from "./math.ts";
|
|
@@ -21,6 +23,7 @@ import { packageInstallPlugin } from "./package-install.ts";
|
|
|
21
23
|
import { tableWrapPlugin } from "./table-wrap.ts";
|
|
22
24
|
import { DEFAULT_CODE_THEMES } from "./themes.ts";
|
|
23
25
|
import type { CodeThemes } from "./themes.ts";
|
|
26
|
+
import { ts2jsPlugin } from "./ts2js.ts";
|
|
24
27
|
|
|
25
28
|
export type { CodeTheme, CodeThemes } from "./themes.ts";
|
|
26
29
|
|
|
@@ -38,8 +41,10 @@ export {
|
|
|
38
41
|
} from "./code-title.ts";
|
|
39
42
|
export { calloutTypeFor } from "./directives.ts";
|
|
40
43
|
export { headingAnchorPlugin } from "./heading-anchors.ts";
|
|
44
|
+
export { includePlugin } from "./include.ts";
|
|
41
45
|
export { mermaidPlugin } from "./mermaid.ts";
|
|
42
46
|
export { packageInstallPlugin } from "./package-install.ts";
|
|
47
|
+
export { ts2jsPlugin } from "./ts2js.ts";
|
|
43
48
|
export { blumeTwoslashTransformer } from "./twoslash.ts";
|
|
44
49
|
|
|
45
50
|
/** Element type of Satteri's `mdastPlugins`, sourced from the (alpha) core. */
|
|
@@ -77,21 +82,18 @@ const asShikiTransformer = (transformer: { name: string }): ShikiTransformer =>
|
|
|
77
82
|
/**
|
|
78
83
|
* Hast plugins enabled by config. Inline `` `code`{:lang} `` highlighting is
|
|
79
84
|
* always on: it only fires on an explicit trailing `{:lang}` marker, so plain
|
|
80
|
-
* inline code is untouched and there's nothing to opt out of.
|
|
81
|
-
*
|
|
82
|
-
*
|
|
83
|
-
*
|
|
85
|
+
* inline code is untouched and there's nothing to opt out of. The heading
|
|
86
|
+
* plugin also always runs — it owns heading ids and the trailing markers
|
|
87
|
+
* (`[#custom-id]`, `[!toc]`, `[toc]`), which must parse regardless of config —
|
|
88
|
+
* while `markdown.headingAnchors: false` only turns off the self-linking
|
|
89
|
+
* anchor wrap on `<h2>`–`<h6>`. Inline code runs first so the anchor wrap
|
|
90
|
+
* re-refs already-highlighted code.
|
|
84
91
|
*/
|
|
85
|
-
const blumeHastPlugins = (options: BlumeMarkdownOptions): HastPlugin[] =>
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
if (options.headingAnchors !== false) {
|
|
91
|
-
plugins.push(asHastPlugin(headingAnchorPlugin()));
|
|
92
|
-
}
|
|
93
|
-
return plugins;
|
|
94
|
-
};
|
|
92
|
+
const blumeHastPlugins = (options: BlumeMarkdownOptions): HastPlugin[] => [
|
|
93
|
+
asHastPlugin(inlineCodeHighlightPlugin(options.codeThemes)),
|
|
94
|
+
asHastPlugin(tableWrapPlugin()),
|
|
95
|
+
asHastPlugin(headingAnchorPlugin({ wrap: options.headingAnchors !== false })),
|
|
96
|
+
];
|
|
95
97
|
|
|
96
98
|
/**
|
|
97
99
|
* Shiki transformers enabled by default for every code block. The four upstream
|
|
@@ -242,13 +244,6 @@ export const highlightCode = async (
|
|
|
242
244
|
}
|
|
243
245
|
};
|
|
244
246
|
|
|
245
|
-
/**
|
|
246
|
-
* Sätteri Markdown features Blume enables beyond Astro's defaults. GFM,
|
|
247
|
-
* frontmatter, and smart punctuation are already on; this adds superscript
|
|
248
|
-
* (`^text^`) and subscript (`~text~`), which render to native `<sup>`/`<sub>`.
|
|
249
|
-
*/
|
|
250
|
-
const FEATURES = { subscript: true, superscript: true };
|
|
251
|
-
|
|
252
247
|
/** Options shared by both processors. */
|
|
253
248
|
export interface BlumeMarkdownOptions {
|
|
254
249
|
/**
|
|
@@ -273,12 +268,17 @@ export interface BlumeMarkdownOptions {
|
|
|
273
268
|
* `basePath` link isn't double-prefixed (see `withComposedBasePath`).
|
|
274
269
|
*/
|
|
275
270
|
deployBase?: string;
|
|
271
|
+
/**
|
|
272
|
+
* The docs content root, bounding `<include>` target resolution (and
|
|
273
|
+
* anchoring `/`-leading include paths). When unset, relative includes still
|
|
274
|
+
* resolve from the including file.
|
|
275
|
+
*/
|
|
276
|
+
contentRoot?: string;
|
|
276
277
|
}
|
|
277
278
|
|
|
278
279
|
/**
|
|
279
|
-
* MDAST plugins that apply to both `.md` and `.mdx
|
|
280
|
-
*
|
|
281
|
-
* configured.
|
|
280
|
+
* MDAST plugins that apply to both `.md` and `.mdx`: the base-path link
|
|
281
|
+
* rewrite (added only when a `basePath` or `deployBase` is configured).
|
|
282
282
|
*/
|
|
283
283
|
const blumeSharedMdastPlugins = (
|
|
284
284
|
options: BlumeMarkdownOptions
|
|
@@ -291,20 +291,32 @@ const blumeSharedMdastPlugins = (
|
|
|
291
291
|
]
|
|
292
292
|
: [];
|
|
293
293
|
|
|
294
|
+
/**
|
|
295
|
+
* The `<include>` splice. Always first: its mutations apply before the next
|
|
296
|
+
* plugin runs, so spliced content flows through the rest of the chain
|
|
297
|
+
* (callouts, mermaid, math, base links) like inline content.
|
|
298
|
+
*/
|
|
299
|
+
const blumeIncludePlugin = (options: BlumeMarkdownOptions): MdastPlugin =>
|
|
300
|
+
asMdastPlugin(includePlugin({ contentRoot: options.contentRoot }));
|
|
301
|
+
|
|
294
302
|
/** Sätteri processor for plain `.md`, with Blume's curated feature set. */
|
|
295
303
|
export const blumeMarkdownProcessor = (options: BlumeMarkdownOptions = {}) =>
|
|
296
304
|
satteri({
|
|
297
|
-
features: { ...
|
|
305
|
+
features: { ...MARKDOWN_FEATURES },
|
|
298
306
|
hastPlugins: blumeHastPlugins(options),
|
|
299
|
-
mdastPlugins:
|
|
307
|
+
mdastPlugins: [
|
|
308
|
+
blumeIncludePlugin(options),
|
|
309
|
+
...blumeSharedMdastPlugins(options),
|
|
310
|
+
],
|
|
300
311
|
});
|
|
301
312
|
|
|
302
313
|
export type BlumeMdxOptions = BlumeMarkdownOptions;
|
|
303
314
|
|
|
304
315
|
/**
|
|
305
316
|
* Sätteri MDX processor: Blume's feature set plus the MDAST plugins that target
|
|
306
|
-
* components — `package-install` → package-manager tabs,
|
|
307
|
-
* `<Callout>`, ` ```mermaid ` → a
|
|
317
|
+
* components — `package-install` → package-manager tabs, ` ```ts ts2js ` →
|
|
318
|
+
* TypeScript/JavaScript tabs, `:::note` → `<Callout>`, ` ```mermaid ` → a
|
|
319
|
+
* `<blume-mermaid>` element, and block math
|
|
308
320
|
* (`$$…$$`) → the `<Math>` component. Used as the `processor` for
|
|
309
321
|
* `@astrojs/mdx` so these apply to `.mdx` only (plain `.md` uses
|
|
310
322
|
* {@link blumeMarkdownProcessor}).
|
|
@@ -319,15 +331,12 @@ export type BlumeMdxOptions = BlumeMarkdownOptions;
|
|
|
319
331
|
*/
|
|
320
332
|
export const blumeMdxProcessor = (options: BlumeMdxOptions = {}) =>
|
|
321
333
|
satteri({
|
|
322
|
-
features: {
|
|
323
|
-
...FEATURES,
|
|
324
|
-
directive: true,
|
|
325
|
-
// Block-only: `$$…$$` parses, a bare `$` stays literal text.
|
|
326
|
-
math: { singleDollarTextMath: false },
|
|
327
|
-
},
|
|
334
|
+
features: { ...MDX_FEATURES },
|
|
328
335
|
hastPlugins: blumeHastPlugins(options),
|
|
329
336
|
mdastPlugins: [
|
|
337
|
+
blumeIncludePlugin(options),
|
|
330
338
|
asMdastPlugin(packageInstallPlugin()),
|
|
339
|
+
asMdastPlugin(ts2jsPlugin()),
|
|
331
340
|
asMdastPlugin(directiveToCalloutPlugin()),
|
|
332
341
|
asMdastPlugin(mermaidPlugin()),
|
|
333
342
|
asMdastPlugin(mathPlugin()),
|
|
@@ -15,7 +15,7 @@ import {
|
|
|
15
15
|
siAstro,
|
|
16
16
|
siC,
|
|
17
17
|
siCplusplus,
|
|
18
|
-
|
|
18
|
+
siCss,
|
|
19
19
|
siDart,
|
|
20
20
|
siDocker,
|
|
21
21
|
siGnubash,
|
|
@@ -66,7 +66,7 @@ const LANGUAGE_ICONS: LanguageIcons = {
|
|
|
66
66
|
"c++": siCplusplus,
|
|
67
67
|
cjs: siJavascript,
|
|
68
68
|
cpp: siCplusplus,
|
|
69
|
-
css:
|
|
69
|
+
css: siCss,
|
|
70
70
|
cts: siTypescript,
|
|
71
71
|
dart: siDart,
|
|
72
72
|
docker: siDocker,
|
package/src/markdown/mdast.ts
CHANGED
|
@@ -53,10 +53,14 @@ export const jsxTextElement = (
|
|
|
53
53
|
children: MdastValue[] = []
|
|
54
54
|
) => ({ attributes, children, name, type: "mdxJsxTextElement" });
|
|
55
55
|
|
|
56
|
-
/** Build a fenced code block node. */
|
|
57
|
-
export const codeBlock = (
|
|
56
|
+
/** Build a fenced code block node, optionally with a fence-meta string. */
|
|
57
|
+
export const codeBlock = (
|
|
58
|
+
lang: string,
|
|
59
|
+
value: string,
|
|
60
|
+
meta: string | null = null
|
|
61
|
+
) => ({
|
|
58
62
|
lang,
|
|
59
|
-
meta
|
|
63
|
+
meta,
|
|
60
64
|
type: "code",
|
|
61
65
|
value,
|
|
62
66
|
});
|
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Satteri MDAST plugin that turns a TypeScript fence carrying the `ts2js`
|
|
3
|
+
* meta keyword (```ts ts2js) into a `<Tabs>` group with the original
|
|
4
|
+
* TypeScript and an auto-generated JavaScript variant, so authors maintain
|
|
5
|
+
* one snippet and readers pick their dialect. The pair carries its own
|
|
6
|
+
* `syncKey`, so every ts2js pair on a page switches together without
|
|
7
|
+
* dragging along authored tab groups that happen to contain a tab titled
|
|
8
|
+
* "JavaScript".
|
|
9
|
+
*
|
|
10
|
+
* Types are stripped with Sucrase rather than the TypeScript compiler:
|
|
11
|
+
* `ts.transpileModule` re-prints the file (collapsed blank lines, four-space
|
|
12
|
+
* indentation, a `"use strict"` prologue on import-less snippets), while
|
|
13
|
+
* Sucrase erases type syntax token-by-token and leaves the author's
|
|
14
|
+
* formatting — and, crucially for {@link tidy}, the line structure — intact.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { createRequire } from "node:module";
|
|
18
|
+
|
|
19
|
+
import type { transform } from "sucrase";
|
|
20
|
+
|
|
21
|
+
import { isLineRange, metaTokens } from "./fence-meta.ts";
|
|
22
|
+
import { codeBlock, jsxAttribute, jsxFlowElement } from "./mdast.ts";
|
|
23
|
+
import type { MdastNode, MdastVisitorContext } from "./mdast.ts";
|
|
24
|
+
|
|
25
|
+
interface CodeNode extends MdastNode {
|
|
26
|
+
lang?: string | null;
|
|
27
|
+
meta?: string | null;
|
|
28
|
+
value: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** Re-join filtered meta tokens, or `null` when nothing survives. */
|
|
32
|
+
const joinMeta = (tokens: string[]): string | null =>
|
|
33
|
+
tokens.length > 0 ? tokens.join(" ") : null;
|
|
34
|
+
|
|
35
|
+
const require = createRequire(import.meta.url);
|
|
36
|
+
|
|
37
|
+
interface Sucrase {
|
|
38
|
+
transform: typeof transform;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** How the plugin obtains Sucrase; injectable so tests can fail the load. */
|
|
42
|
+
export type SucraseLoader = () => Sucrase;
|
|
43
|
+
|
|
44
|
+
// SAFETY: this resolves Blume's own `sucrase` dependency, whose CJS entry
|
|
45
|
+
// exports the `transform` function the interface describes.
|
|
46
|
+
const defaultLoader: SucraseLoader = () => require("sucrase") as Sucrase;
|
|
47
|
+
|
|
48
|
+
/** Triggering fence languages, mapped to their generated tab's language. */
|
|
49
|
+
const JS_LANG = new Map([
|
|
50
|
+
["ts", "js"],
|
|
51
|
+
["tsx", "jsx"],
|
|
52
|
+
["typescript", "js"],
|
|
53
|
+
]);
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Shiki's twoslash transformer triggers on this pattern over the *raw* fence
|
|
57
|
+
* meta (`RE_TWOSLASH` under `explicitTrigger`), so the keyword matches even
|
|
58
|
+
* inside a quoted attribute (`title="the twoslash guide"`). Mirror it
|
|
59
|
+
* exactly: any fence Shiki will twoslash-render is left alone, because hover
|
|
60
|
+
* data can't carry over to the generated JavaScript.
|
|
61
|
+
*/
|
|
62
|
+
const TWOSLASH = /\btwoslash\b/u;
|
|
63
|
+
|
|
64
|
+
/** A line holding nothing but a Shiki notation comment (`// [!code …]`). */
|
|
65
|
+
const NOTATION_COMMENT = /^\s*\/\/\s*\[!code[^\]]*\]\s*$/u;
|
|
66
|
+
|
|
67
|
+
/** A line holding nothing but a `//` comment. */
|
|
68
|
+
const COMMENT_ONLY = /^\s*\/\//u;
|
|
69
|
+
|
|
70
|
+
const isBlank = (text: string): boolean => text.trim().length === 0;
|
|
71
|
+
|
|
72
|
+
interface TidyLine {
|
|
73
|
+
/** The line is blank because erasure removed its code. */
|
|
74
|
+
erased: boolean;
|
|
75
|
+
text: string;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Clean up Sucrase's erasure artifacts by comparing output to source line by
|
|
80
|
+
* line — erasure never adds or removes lines, so the two align (CRLF input
|
|
81
|
+
* included; the source's line endings are preserved). Untouched lines pass
|
|
82
|
+
* through verbatim, which keeps multi-line template literal interiors —
|
|
83
|
+
* whose blank runs and trailing spaces are string *content* — byte-exact.
|
|
84
|
+
* Lines erasure changed are cleaned:
|
|
85
|
+
*
|
|
86
|
+
* - the stray space a removed trailing type operator leaves before `;` (and
|
|
87
|
+
* any trailing whitespace) is dropped;
|
|
88
|
+
* - a comment stranded by its erased code — including a trailing Shiki
|
|
89
|
+
* notation marker (`// [!code highlight]`), which would otherwise
|
|
90
|
+
* re-anchor to the next line — is removed along with the code it
|
|
91
|
+
* annotated, as is an own-line notation marker whose target line was
|
|
92
|
+
* erased;
|
|
93
|
+
* - blank runs erasure created collapse: a run that is entirely erasure
|
|
94
|
+
* closes up, a run mixing erased and authored blanks keeps a single blank
|
|
95
|
+
* line, and either is dropped at the snippet's edges.
|
|
96
|
+
*/
|
|
97
|
+
const tidy = (js: string, source: string): string => {
|
|
98
|
+
const eol = source.includes("\r\n") ? "\r\n" : "\n";
|
|
99
|
+
const srcLines = source.split(/\r?\n/u);
|
|
100
|
+
const srcLine = (index: number): string => srcLines[index] ?? "";
|
|
101
|
+
const lines = js.split(/\r?\n/u).map((line, index): TidyLine => {
|
|
102
|
+
const src = srcLine(index);
|
|
103
|
+
if (line === src) {
|
|
104
|
+
return { erased: false, text: line };
|
|
105
|
+
}
|
|
106
|
+
if (COMMENT_ONLY.test(line) && !COMMENT_ONLY.test(src)) {
|
|
107
|
+
return { erased: true, text: "" };
|
|
108
|
+
}
|
|
109
|
+
const text = line.replace(/[ \t]+;$/u, ";").replace(/[ \t]+$/u, "");
|
|
110
|
+
return { erased: text.length === 0, text };
|
|
111
|
+
});
|
|
112
|
+
// An own-line notation marker anchors to the line below it; if erasure
|
|
113
|
+
// blanked that target, the marker would highlight whatever ends up there
|
|
114
|
+
// instead, so it goes too.
|
|
115
|
+
for (const [index, line] of lines.entries()) {
|
|
116
|
+
const next = lines[index + 1];
|
|
117
|
+
if (
|
|
118
|
+
next &&
|
|
119
|
+
NOTATION_COMMENT.test(line.text) &&
|
|
120
|
+
isBlank(next.text) &&
|
|
121
|
+
!isBlank(srcLine(index + 1))
|
|
122
|
+
) {
|
|
123
|
+
line.erased = true;
|
|
124
|
+
line.text = "";
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
const out: string[] = [];
|
|
128
|
+
let blankRun: TidyLine[] = [];
|
|
129
|
+
let sawCode = false;
|
|
130
|
+
const flushBlanks = (atEnd: boolean) => {
|
|
131
|
+
if (blankRun.some((line) => line.erased)) {
|
|
132
|
+
if (sawCode && !atEnd && blankRun.some((line) => !line.erased)) {
|
|
133
|
+
out.push("");
|
|
134
|
+
}
|
|
135
|
+
} else {
|
|
136
|
+
out.push(...blankRun.map((line) => line.text));
|
|
137
|
+
}
|
|
138
|
+
blankRun = [];
|
|
139
|
+
};
|
|
140
|
+
for (const line of lines) {
|
|
141
|
+
if (isBlank(line.text)) {
|
|
142
|
+
blankRun.push(line);
|
|
143
|
+
} else {
|
|
144
|
+
flushBlanks(false);
|
|
145
|
+
out.push(line.text);
|
|
146
|
+
sawCode = true;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
flushBlanks(true);
|
|
150
|
+
return out.join(eol);
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
/** Build the `<Tab>` holding one dialect's code fence. */
|
|
154
|
+
const tabNode = (
|
|
155
|
+
title: string,
|
|
156
|
+
lang: string,
|
|
157
|
+
value: string,
|
|
158
|
+
meta: string | null
|
|
159
|
+
) =>
|
|
160
|
+
jsxFlowElement(
|
|
161
|
+
"Tab",
|
|
162
|
+
[jsxAttribute("title", title)],
|
|
163
|
+
[codeBlock(lang, value, meta)]
|
|
164
|
+
);
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* The `ts2js` fence plugin. The generated JavaScript tab drops `{1,3-5}` line
|
|
168
|
+
* ranges from its meta (line numbers shift once types are gone) but keeps the
|
|
169
|
+
* rest (`title="..."`, `lineNumbers`); the TypeScript tab keeps everything.
|
|
170
|
+
* The keyword itself is stripped from both, which also guarantees the emitted
|
|
171
|
+
* fences can never re-trigger the plugin.
|
|
172
|
+
*
|
|
173
|
+
* The `loadSucrase` parameter exists for tests; production callers use the
|
|
174
|
+
* default, which resolves Blume's own dependency.
|
|
175
|
+
*/
|
|
176
|
+
export const ts2jsPlugin = (loadSucrase: SucraseLoader = defaultLoader) => {
|
|
177
|
+
// Resolved lazily on the first triggered fence, so importing this module
|
|
178
|
+
// (at Astro config load) never pays Sucrase's parse cost for ts2js-free
|
|
179
|
+
// sites. `null` records a failed load: every fence degrades to its
|
|
180
|
+
// authored form instead of failing the build, and the load is never
|
|
181
|
+
// retried.
|
|
182
|
+
let sucrase: Sucrase | null | undefined;
|
|
183
|
+
|
|
184
|
+
const stripTypes = (code: string, lang: string): string | undefined => {
|
|
185
|
+
if (sucrase === undefined) {
|
|
186
|
+
try {
|
|
187
|
+
sucrase = loadSucrase();
|
|
188
|
+
} catch {
|
|
189
|
+
sucrase = null;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
if (sucrase === null) {
|
|
193
|
+
return undefined;
|
|
194
|
+
}
|
|
195
|
+
try {
|
|
196
|
+
return tidy(
|
|
197
|
+
sucrase.transform(code, {
|
|
198
|
+
// Keep modern syntax (no downleveling) and the authored JSX.
|
|
199
|
+
disableESTransforms: true,
|
|
200
|
+
jsxRuntime: "preserve",
|
|
201
|
+
// An import kept only for the reader's context must survive:
|
|
202
|
+
// without this, Sucrase drops any import with no value-position
|
|
203
|
+
// reference, and the JavaScript tab would show code whose bindings
|
|
204
|
+
// are undefined when copied. Type-only imports (`import type`,
|
|
205
|
+
// `{ type T }`) are still elided.
|
|
206
|
+
keepUnusedImports: true,
|
|
207
|
+
// The `jsx` transform only for `tsx`: with it enabled, a `ts`
|
|
208
|
+
// angle-bracket assertion (`<number>value`) would parse as JSX and
|
|
209
|
+
// fail.
|
|
210
|
+
transforms: lang === "tsx" ? ["typescript", "jsx"] : ["typescript"],
|
|
211
|
+
}).code,
|
|
212
|
+
code
|
|
213
|
+
);
|
|
214
|
+
} catch {
|
|
215
|
+
// Sucrase throws on code it can't parse (pseudocode, deliberate
|
|
216
|
+
// fragments). Render the fence as authored instead of failing the
|
|
217
|
+
// build.
|
|
218
|
+
return undefined;
|
|
219
|
+
}
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
return {
|
|
223
|
+
code(node: CodeNode, ctx: MdastVisitorContext) {
|
|
224
|
+
const lang = node.lang ?? "";
|
|
225
|
+
const jsLang = JS_LANG.get(lang);
|
|
226
|
+
if (!jsLang) {
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
const tokens = metaTokens(node.meta);
|
|
230
|
+
if (!tokens.includes("ts2js") || TWOSLASH.test(node.meta ?? "")) {
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
const js = stripTypes(node.value, lang);
|
|
234
|
+
// Empty output means a types-only snippet; a blank JavaScript tab helps
|
|
235
|
+
// nobody, so keep the TypeScript fence as-is.
|
|
236
|
+
if (!js) {
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
const kept = tokens.filter((token) => token !== "ts2js");
|
|
240
|
+
const jsMeta = kept.filter((token) => !isLineRange(token));
|
|
241
|
+
ctx.replaceNode(
|
|
242
|
+
node,
|
|
243
|
+
jsxFlowElement(
|
|
244
|
+
"Tabs",
|
|
245
|
+
[
|
|
246
|
+
// hash off: picking a dialect must not rewrite the page hash
|
|
247
|
+
// (clobbering the heading anchor the reader arrived with).
|
|
248
|
+
jsxAttribute("hash", "false"),
|
|
249
|
+
// Scope syncing to generated pairs: without a key, picking
|
|
250
|
+
// "JavaScript" here would also yank any authored group that has a
|
|
251
|
+
// same-titled tab (an SDK-language switcher, say) — one-way,
|
|
252
|
+
// since "TypeScript" wouldn't match to flip it back.
|
|
253
|
+
jsxAttribute("syncKey", "ts2js"),
|
|
254
|
+
],
|
|
255
|
+
[
|
|
256
|
+
tabNode("TypeScript", lang, node.value, joinMeta(kept)),
|
|
257
|
+
tabNode("JavaScript", jsLang, js, joinMeta(jsMeta)),
|
|
258
|
+
]
|
|
259
|
+
)
|
|
260
|
+
);
|
|
261
|
+
},
|
|
262
|
+
name: "blume-ts2js",
|
|
263
|
+
};
|
|
264
|
+
};
|
package/src/openapi/asyncapi.ts
CHANGED
|
@@ -157,8 +157,11 @@ const inlineComponentRefs = (document: AsyncApiDocument): void => {
|
|
|
157
157
|
const resolved = isObject(table)
|
|
158
158
|
? table[unescapePointer(groups.name ?? "")]
|
|
159
159
|
: undefined;
|
|
160
|
+
// A shallow copy per alias: trait merging mutates its node in place
|
|
161
|
+
// (fields written, `traits` deleted), and two entries pointing at the
|
|
162
|
+
// same component — or the component table itself — must not share it.
|
|
160
163
|
if (isObject(resolved) && !isString(resolved.$ref)) {
|
|
161
|
-
map[id] = resolved;
|
|
164
|
+
map[id] = { ...resolved };
|
|
162
165
|
}
|
|
163
166
|
}
|
|
164
167
|
}
|