blume 1.4.2 → 1.5.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 +71 -0
- package/dist/cli/index.js +2260 -1100
- package/dist/cli/index.js.map +123 -117
- package/dist/types/ai/component-markdown.d.ts +14 -4
- package/dist/types/core/base-path.d.ts +8 -0
- package/dist/types/core/config-input.d.ts +87 -27
- package/dist/types/core/config.d.ts +2 -1
- package/dist/types/core/data.d.ts +16 -1
- package/dist/types/core/diagnostics.d.ts +5 -1
- package/dist/types/core/i18n-ui.d.ts +12 -0
- package/dist/types/core/schema.d.ts +116 -15
- package/dist/types/core/sources/types.d.ts +11 -1
- package/dist/types/core/standard-schema.d.ts +7 -3
- package/dist/types/core/types.d.ts +43 -2
- package/dist/types/core/ui-packs/index.d.ts +9 -1
- package/dist/types/openapi/references.d.ts +14 -7
- package/dist/types/seo/x-handle.d.ts +3 -2
- package/docs/advanced/api-reference.mdx +8 -6
- package/docs/configuration/ai.mdx +26 -8
- package/docs/configuration/search.mdx +2 -0
- package/docs/configuration/seo.mdx +1 -1
- package/docs/content/i18n.mdx +1 -1
- package/docs/content/meta.mdx +2 -1
- package/docs/content/meta.ts +1 -0
- package/docs/content/navigation.mdx +35 -1
- package/docs/content/sources.mdx +1 -1
- package/docs/content/versioning.mdx +106 -0
- package/docs/reference/cli.mdx +1 -0
- package/docs/reference/frontmatter.mdx +3 -0
- package/package.json +13 -1
- package/skills/blume-migrate/SKILL.md +2 -2
- package/skills/blume-migrate/references/docusaurus.md +1 -1
- package/skills/blume-migrate/references/fumadocs.md +1 -1
- package/skills/blume-migrate/references/mintlify.md +1 -1
- package/src/ai/agent-readability.ts +40 -12
- package/src/ai/api-catalog.ts +2 -2
- package/src/ai/ask-context.ts +49 -12
- package/src/ai/ask.ts +10 -1
- package/src/ai/component-markdown.ts +80 -43
- package/src/ai/llms.ts +42 -17
- package/src/ai/mcp/data.ts +48 -12
- package/src/ai/mcp/discovery.ts +52 -16
- package/src/ai/mcp/server.ts +280 -125
- package/src/ai/mcp/tools.ts +3 -3
- package/src/ai/skills.ts +32 -9
- package/src/ai/tar.ts +29 -70
- package/src/ai/visibility.ts +2 -2
- package/src/astro/component-slots.ts +2 -0
- package/src/astro/examples.ts +13 -5
- package/src/astro/generate.ts +113 -63
- package/src/astro/integration.ts +13 -2
- package/src/astro/islands.ts +23 -12
- package/src/astro/templates.ts +185 -41
- package/src/audit/agent.ts +16 -31
- package/src/audit/checks/content.ts +26 -11
- package/src/audit/checks/dns-aid.ts +3 -0
- package/src/audit/checks/indexability.ts +24 -6
- package/src/audit/checks/llms.ts +9 -4
- package/src/audit/checks/network.ts +2 -0
- package/src/audit/checks/social.ts +18 -10
- package/src/audit/crawl.ts +78 -25
- package/src/audit/report.ts +20 -19
- package/src/audit/run.ts +15 -5
- package/src/audit/snapshot.ts +29 -6
- package/src/audit/types.ts +25 -3
- package/src/blume-modules.d.ts +5 -1
- package/src/cli/commands/audit.ts +21 -21
- package/src/cli/commands/build.ts +30 -16
- package/src/cli/commands/dev.ts +15 -15
- package/src/cli/commands/doctor.ts +2 -0
- package/src/cli/commands/eject.ts +4 -4
- package/src/cli/commands/eval.ts +24 -30
- package/src/cli/commands/init.ts +9 -9
- package/src/cli/commands/mcp-stdio.ts +3 -0
- package/src/cli/commands/translate.ts +14 -3
- package/src/cli/commands/version.ts +85 -0
- package/src/cli/dev-lock.ts +31 -10
- package/src/cli/eject-scripts.ts +17 -2
- package/src/cli/env.ts +13 -30
- package/src/cli/index.ts +2 -0
- package/src/cli/init/questions.ts +1 -1
- package/src/cli/init/scaffold.ts +41 -13
- package/src/cli/internal-error.ts +1 -0
- package/src/cli/report-format.ts +22 -0
- package/src/components/content/AccordionItem.astro +2 -9
- package/src/components/content/ColorItem.astro +5 -13
- package/src/components/content/Component.astro +12 -8
- package/src/components/content/Frame.astro +2 -12
- package/src/components/content/Prompt.astro +12 -31
- package/src/components/content/Tab.astro +2 -9
- package/src/components/content/Tooltip.astro +1 -9
- package/src/components/content/Update.astro +2 -9
- package/src/components/content/auto-type-table.ts +3 -0
- package/src/components/content/diff.ts +9 -5
- package/src/components/content/github-info.ts +2 -0
- package/src/components/content/inline-markdown.ts +28 -0
- package/src/components/copy-feedback.ts +96 -0
- package/src/components/islands/ask-ai.tsx +111 -34
- package/src/components/islands/hooks.ts +5 -1
- package/src/components/islands/webmcp.ts +49 -12
- package/src/components/layout/Header.astro +25 -1
- package/src/components/layout/NavSelector.astro +11 -2
- package/src/components/layout/NavTree.astro +4 -2
- package/src/components/layout/PageActions.astro +20 -32
- package/src/components/layout/PageLayout.astro +8 -28
- package/src/components/layout/RootLayout.astro +24 -48
- package/src/components/layout/Search.astro +133 -22
- package/src/components/layout/VersionBanner.astro +39 -0
- package/src/components/layout/analytics-client.ts +8 -5
- package/src/components/layout/drawer-inert.ts +31 -0
- package/src/components/layout/hydration-hint.ts +1 -1
- package/src/components/layout/nav-utils.ts +1 -4
- package/src/components/layout/overrides.ts +25 -12
- package/src/components/layout/search/algolia.ts +18 -5
- package/src/components/layout/search/endpoint.ts +3 -0
- package/src/components/layout/search/flexsearch.ts +23 -7
- package/src/components/layout/search/orama-cloud.ts +1 -1
- package/src/components/layout/search/orama.ts +4 -1
- package/src/components/layout/search/pagefind.ts +8 -5
- package/src/components/layout/search/types.ts +45 -1
- package/src/components/layout/search/typesense.ts +19 -3
- package/src/components/openapi/ApiOverview.astro +32 -6
- package/src/components/openapi/AsyncApiOperation.astro +237 -0
- package/src/components/openapi/Bindings.astro +89 -0
- package/src/components/openapi/MethodBadge.astro +3 -0
- package/src/components/openapi/Operation.astro +7 -2
- package/src/components/openapi/PanelTabs.astro +131 -0
- package/src/components/openapi/ParametersTable.astro +2 -0
- package/src/components/openapi/RequestPanel.astro +12 -119
- package/src/components/openapi/async-snippets.ts +174 -0
- package/src/components/openapi/async.ts +348 -0
- package/src/components/openapi/helpers.ts +52 -20
- package/src/components/openapi/panel.ts +11 -8
- package/src/components/openapi/security.ts +102 -29
- package/src/components/openapi/snippets.ts +11 -11
- package/src/components/raf-throttle.ts +21 -0
- package/src/components/slug.ts +14 -0
- package/src/core/base-path.ts +18 -1
- package/src/core/component-overrides.ts +28 -23
- package/src/core/config-input.ts +96 -27
- package/src/core/config.ts +20 -7
- package/src/core/content.ts +3 -1
- package/src/core/data.ts +16 -1
- package/src/core/define-components.ts +5 -0
- package/src/core/diagnostics.ts +46 -38
- package/src/core/frontmatter.ts +74 -4
- package/src/core/graph.ts +137 -53
- package/src/core/i18n-ui.ts +15 -0
- package/src/core/i18n.ts +16 -8
- package/src/core/load-module.ts +1 -0
- package/src/core/manifest.ts +92 -3
- package/src/core/meta.ts +44 -14
- package/src/core/nav-diagnostics.ts +3 -3
- package/src/core/navigation.ts +247 -67
- package/src/core/probe.ts +7 -19
- package/src/core/project-graph.ts +27 -4
- package/src/core/schema.ts +219 -67
- package/src/core/site-url.ts +27 -0
- package/src/core/sources/assets.ts +2 -0
- package/src/core/sources/cache.ts +16 -8
- package/src/core/sources/github-releases.ts +39 -11
- package/src/core/sources/mdx-remote.ts +4 -0
- package/src/core/sources/normalize.ts +93 -22
- package/src/core/sources/notion.ts +76 -22
- package/src/core/sources/portable-text.ts +48 -12
- package/src/core/sources/resolve.ts +1 -0
- package/src/core/sources/sanity.ts +68 -14
- package/src/core/sources/types.ts +17 -1
- package/src/core/sources/watch.ts +1 -1
- package/src/core/standard-schema.ts +9 -3
- package/src/core/text-width.ts +26 -0
- package/src/core/tsconfig-aliases.ts +9 -5
- package/src/core/types.ts +45 -2
- package/src/core/ui-packs/index.ts +9 -1
- package/src/core/version-cut.ts +301 -0
- package/src/core/version.ts +2 -0
- package/src/core/versions.ts +170 -0
- package/src/deploy/adapter-output.ts +5 -2
- package/src/deploy/cloudflare-negotiation.ts +40 -11
- package/src/deploy/robots.ts +2 -1
- package/src/deploy/rss.ts +2 -1
- package/src/deploy/sitemap.ts +89 -8
- package/src/deploy/vercel-negotiation.ts +11 -4
- package/src/eval/agents.ts +13 -10
- package/src/eval/report.ts +5 -18
- package/src/eval/run.ts +2 -2
- package/src/eval/schema.ts +1 -1
- package/src/markdown/base-links.ts +6 -6
- package/src/markdown/directives.ts +7 -1
- package/src/markdown/heading-anchors.ts +17 -6
- package/src/markdown/index.ts +73 -24
- package/src/markdown/inline-code.ts +14 -2
- package/src/markdown/language-icon.ts +6 -2
- package/src/markdown/mdast.ts +18 -4
- package/src/markdown/package-commands.ts +63 -58
- package/src/markdown/table-wrap.ts +4 -1
- package/src/markdown/twoslash.ts +2 -0
- package/src/og/card.ts +50 -33
- package/src/og/derive.ts +43 -27
- package/src/openapi/asyncapi.ts +366 -0
- package/src/openapi/model.ts +135 -66
- package/src/openapi/parse.ts +166 -33
- package/src/openapi/references.ts +47 -22
- package/src/openapi/render-mdx.ts +137 -59
- package/src/openapi/scalar.ts +8 -10
- package/src/openapi/source.ts +126 -29
- package/src/registry/eject.ts +7 -2
- package/src/search/documents.ts +103 -39
- package/src/search/facets.ts +7 -5
- package/src/search/orama-index.ts +117 -32
- package/src/search/popular.ts +10 -5
- package/src/search/providers.ts +2 -2
- package/src/search/sync/index.ts +2 -0
- package/src/search/sync/typesense.ts +4 -2
- package/src/seo/jsonld.ts +24 -6
- package/src/seo/x-handle.ts +8 -3
- package/src/theme/chrome-icons.ts +7 -2
- package/src/theme/fonts.ts +8 -4
- package/src/theme/icons.ts +4 -2
- package/src/theme/palette.ts +27 -15
- package/src/translate/ledger.ts +4 -2
- package/src/translate/meta.ts +15 -6
- package/src/translate/report.ts +10 -19
- package/src/translate/run.ts +29 -38
- package/src/translate/validate.ts +52 -17
- package/src/translate/work-list.ts +0 -0
- package/src/cli/coalesce.ts +0 -43
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
AsyncApiChannelObject,
|
|
3
|
+
AsyncApiDocument,
|
|
4
|
+
AsyncApiOperationObject,
|
|
5
|
+
AsyncApiRefLike,
|
|
6
|
+
AsyncApiServerObject,
|
|
7
|
+
AsyncApiSpecValue,
|
|
8
|
+
} from "../../openapi/asyncapi.ts";
|
|
9
|
+
import type { ParameterLike, SchemaLike } from "./helpers.ts";
|
|
10
|
+
import { resolveComponentRef } from "./helpers.ts";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Runtime helpers for the AsyncAPI components — the async counterpart of
|
|
14
|
+
* `helpers.ts`. These operate on the normalized 3.x document behind the
|
|
15
|
+
* `blume:openapi` alias, resolving the ref shapes AsyncAPI adds on top of
|
|
16
|
+
* `#/components/*`: operations point at channels, channel messages may `$ref`
|
|
17
|
+
* `#/components/messages`, and operation messages point *into* a channel
|
|
18
|
+
* (`#/channels/<id>/messages/<name>`). Browser-safe like the rest of the set.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
/** A permissive view of an AsyncAPI message — only the fields we render. */
|
|
22
|
+
export interface AsyncApiMessageLike extends AsyncApiRefLike {
|
|
23
|
+
name?: string;
|
|
24
|
+
title?: string;
|
|
25
|
+
summary?: string;
|
|
26
|
+
description?: string;
|
|
27
|
+
contentType?: string;
|
|
28
|
+
payload?: AsyncApiSpecValue;
|
|
29
|
+
headers?: AsyncApiSpecValue;
|
|
30
|
+
examples?: {
|
|
31
|
+
name?: string;
|
|
32
|
+
summary?: string;
|
|
33
|
+
payload?: AsyncApiSpecValue;
|
|
34
|
+
headers?: AsyncApiSpecValue;
|
|
35
|
+
}[];
|
|
36
|
+
bindings?: AsyncApiChannelObject["bindings"];
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** A message paired with its channel-map key (the fallback display name). */
|
|
40
|
+
export interface NamedMessage {
|
|
41
|
+
key: string;
|
|
42
|
+
message: AsyncApiMessageLike;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const isObject = (
|
|
46
|
+
value: AsyncApiSpecValue
|
|
47
|
+
): value is Record<string, AsyncApiSpecValue> =>
|
|
48
|
+
typeof value === "object" && value !== null && !Array.isArray(value);
|
|
49
|
+
|
|
50
|
+
/** Permissive spec nodes may lie about declared string fields; verify first. */
|
|
51
|
+
const isString = (value: AsyncApiSpecValue): value is string =>
|
|
52
|
+
typeof value === "string";
|
|
53
|
+
|
|
54
|
+
/** Decode a JSON-pointer token: `user~1signedup` -> `user/signedup`. */
|
|
55
|
+
const unescapePointer = (token: string): string =>
|
|
56
|
+
token.replaceAll("~1", "/").replaceAll("~0", "~");
|
|
57
|
+
|
|
58
|
+
const CHANNEL_MESSAGE_REF =
|
|
59
|
+
/^#\/channels\/(?<channel>.+)\/messages\/(?<name>[^/]+)$/u;
|
|
60
|
+
|
|
61
|
+
type Components = AsyncApiDocument["components"];
|
|
62
|
+
|
|
63
|
+
/** Resolve a channel-map message (possibly a components `$ref`) to its object. */
|
|
64
|
+
const channelMessage = (
|
|
65
|
+
raw: AsyncApiRefLike | undefined,
|
|
66
|
+
components: Components
|
|
67
|
+
): AsyncApiMessageLike | undefined => {
|
|
68
|
+
if (!isObject(raw)) {
|
|
69
|
+
return undefined;
|
|
70
|
+
}
|
|
71
|
+
const resolved = resolveComponentRef(raw, components, "messages");
|
|
72
|
+
// Still a bare `$ref` after resolution means it pointed nowhere useful.
|
|
73
|
+
if (isString(resolved.$ref)) {
|
|
74
|
+
return undefined;
|
|
75
|
+
}
|
|
76
|
+
// SAFETY: `resolved` is a plain message object; the permissive message view
|
|
77
|
+
// only narrows the fields the components render, all of them optional.
|
|
78
|
+
return resolved as AsyncApiMessageLike;
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* The messages one operation carries: its own `messages` refs when declared
|
|
83
|
+
* (each pointing into the channel's message map or at a components message),
|
|
84
|
+
* else every message the channel declares. Unresolvable refs are dropped —
|
|
85
|
+
* the schema tables can only render an actual message object.
|
|
86
|
+
*/
|
|
87
|
+
export const operationMessages = (
|
|
88
|
+
operation: AsyncApiOperationObject | undefined,
|
|
89
|
+
channel: AsyncApiChannelObject | undefined,
|
|
90
|
+
document: AsyncApiDocument
|
|
91
|
+
): NamedMessage[] => {
|
|
92
|
+
const { components } = document;
|
|
93
|
+
const messageMap: Record<string, AsyncApiRefLike> = isObject(
|
|
94
|
+
channel?.messages
|
|
95
|
+
)
|
|
96
|
+
? channel.messages
|
|
97
|
+
: {};
|
|
98
|
+
const refs = operation?.messages;
|
|
99
|
+
if (!Array.isArray(refs) || refs.length === 0) {
|
|
100
|
+
const all: NamedMessage[] = [];
|
|
101
|
+
for (const [key, raw] of Object.entries(messageMap)) {
|
|
102
|
+
const message = channelMessage(raw, components);
|
|
103
|
+
if (message) {
|
|
104
|
+
all.push({ key, message });
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return all;
|
|
108
|
+
}
|
|
109
|
+
const named: NamedMessage[] = [];
|
|
110
|
+
for (const ref of refs) {
|
|
111
|
+
if (!isObject(ref)) {
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
const pointer = isString(ref.$ref)
|
|
115
|
+
? CHANNEL_MESSAGE_REF.exec(ref.$ref)?.groups?.name
|
|
116
|
+
: undefined;
|
|
117
|
+
const key = pointer === undefined ? undefined : unescapePointer(pointer);
|
|
118
|
+
// A channel-message pointer resolves through the channel map; anything
|
|
119
|
+
// else (a components ref, an inline message) resolves directly.
|
|
120
|
+
const message =
|
|
121
|
+
key === undefined
|
|
122
|
+
? channelMessage(ref, components)
|
|
123
|
+
: channelMessage(messageMap[key], components);
|
|
124
|
+
if (message) {
|
|
125
|
+
named.push({ key: key ?? message.name ?? "message", message });
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
return named;
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
/** A message's display name: its `name`/`title`, else its channel-map key. */
|
|
132
|
+
export const messageLabel = (named: NamedMessage): string =>
|
|
133
|
+
named.message.title ?? named.message.name ?? named.key;
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* The `schemaFormat` media types whose schemas render as JSON Schema: the
|
|
137
|
+
* AsyncAPI Schema Object (a JSON Schema superset), OpenAPI Schema Objects,
|
|
138
|
+
* and JSON Schema itself, in their bare and `+json`/`+yaml` spellings.
|
|
139
|
+
*/
|
|
140
|
+
const JSON_SCHEMA_FORMAT =
|
|
141
|
+
/^application\/(?:vnd\.aai\.asyncapi|vnd\.oai\.openapi|schema)(?:\+(?:json|yaml))?$/u;
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* The JSON-schema view of a possibly multi-format schema value
|
|
145
|
+
* (`{ schemaFormat, schema }`, allowed on both message payloads and headers).
|
|
146
|
+
* Unwraps when the format is JSON-schema compatible and yields nothing
|
|
147
|
+
* otherwise (an Avro or Protobuf schema can't render as a schema table —
|
|
148
|
+
* callers fall back to a note).
|
|
149
|
+
*/
|
|
150
|
+
export const schemaOf = (value: AsyncApiSpecValue): SchemaLike | undefined => {
|
|
151
|
+
if (!isObject(value)) {
|
|
152
|
+
return undefined;
|
|
153
|
+
}
|
|
154
|
+
if (isString(value.schemaFormat) && "schema" in value) {
|
|
155
|
+
// Match the media type proper (parameters like `;version=…` stripped)
|
|
156
|
+
// against the JSON-Schema-compatible formats AsyncAPI registers. A
|
|
157
|
+
// substring test would misclassify e.g. Avro's `+json` encoding, whose
|
|
158
|
+
// schema is JSON but not JSON Schema.
|
|
159
|
+
const format = (value.schemaFormat.split(";")[0] ?? "")
|
|
160
|
+
.trim()
|
|
161
|
+
.toLowerCase();
|
|
162
|
+
const jsonish = JSON_SCHEMA_FORMAT.test(format);
|
|
163
|
+
if (jsonish && isObject(value.schema)) {
|
|
164
|
+
// SAFETY: a JSON-Schema-format schema object; the permissive SchemaLike
|
|
165
|
+
// view only narrows the fields the schema tables render, all optional.
|
|
166
|
+
return value.schema as SchemaLike;
|
|
167
|
+
}
|
|
168
|
+
return undefined;
|
|
169
|
+
}
|
|
170
|
+
// SAFETY: a bare schema object; the permissive SchemaLike view only narrows
|
|
171
|
+
// the fields the schema tables render, all of them optional.
|
|
172
|
+
return value as SchemaLike;
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
/** The JSON-schema view of a message payload; see {@link schemaOf}. */
|
|
176
|
+
export const payloadSchema = (
|
|
177
|
+
message: AsyncApiMessageLike
|
|
178
|
+
): SchemaLike | undefined => schemaOf(message.payload);
|
|
179
|
+
|
|
180
|
+
/** The channel-parameter fields {@link channelParameters} lowers. */
|
|
181
|
+
interface AsyncApiParameterLike extends AsyncApiRefLike {
|
|
182
|
+
description?: string;
|
|
183
|
+
default?: AsyncApiSpecValue;
|
|
184
|
+
enum?: AsyncApiSpecValue[];
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Channel parameters lowered into the shared parameter-table shape. AsyncAPI
|
|
189
|
+
* 3.x parameters are always strings (`enum`/`default`/`examples`, no schema),
|
|
190
|
+
* and every one is required — an address template can't resolve without it.
|
|
191
|
+
*/
|
|
192
|
+
export const channelParameters = (
|
|
193
|
+
channel: AsyncApiChannelObject | undefined,
|
|
194
|
+
document: AsyncApiDocument
|
|
195
|
+
): ParameterLike[] => {
|
|
196
|
+
const parameters: ParameterLike[] = [];
|
|
197
|
+
const declared: Record<string, AsyncApiRefLike> = channel?.parameters ?? {};
|
|
198
|
+
for (const [name, raw] of Object.entries(declared)) {
|
|
199
|
+
if (!isObject(raw)) {
|
|
200
|
+
continue;
|
|
201
|
+
}
|
|
202
|
+
// SAFETY: the permissive parameter view only narrows the fields lowered
|
|
203
|
+
// below; each one is runtime-checked before use.
|
|
204
|
+
const parameter = resolveComponentRef(
|
|
205
|
+
raw as AsyncApiParameterLike,
|
|
206
|
+
document.components,
|
|
207
|
+
"parameters"
|
|
208
|
+
);
|
|
209
|
+
const schema: SchemaLike = { type: "string" };
|
|
210
|
+
if (Array.isArray(parameter.enum)) {
|
|
211
|
+
schema.enum = parameter.enum;
|
|
212
|
+
}
|
|
213
|
+
if (parameter.default !== undefined) {
|
|
214
|
+
schema.default = parameter.default;
|
|
215
|
+
}
|
|
216
|
+
parameters.push({
|
|
217
|
+
description: isString(parameter.description)
|
|
218
|
+
? parameter.description
|
|
219
|
+
: undefined,
|
|
220
|
+
in: "channel",
|
|
221
|
+
name,
|
|
222
|
+
required: true,
|
|
223
|
+
schema,
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
return parameters;
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
const SERVER_REF = /^#\/servers\/(?<name>[^/]+)$/u;
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* The servers a channel is available on: its `servers` refs when declared,
|
|
233
|
+
* else every server the document declares (the spec's default).
|
|
234
|
+
*/
|
|
235
|
+
export const channelServers = (
|
|
236
|
+
channel: AsyncApiChannelObject | undefined,
|
|
237
|
+
document: AsyncApiDocument
|
|
238
|
+
): AsyncApiServerObject[] => {
|
|
239
|
+
const all = document.servers ?? {};
|
|
240
|
+
const refs = channel?.servers;
|
|
241
|
+
if (!Array.isArray(refs) || refs.length === 0) {
|
|
242
|
+
return Object.values(all).filter(isObject);
|
|
243
|
+
}
|
|
244
|
+
const servers: AsyncApiServerObject[] = [];
|
|
245
|
+
for (const ref of refs) {
|
|
246
|
+
const name = SERVER_REF.exec(
|
|
247
|
+
isObject(ref) && isString(ref.$ref) ? ref.$ref : ""
|
|
248
|
+
)?.groups?.name;
|
|
249
|
+
const server = name === undefined ? undefined : all[unescapePointer(name)];
|
|
250
|
+
if (isObject(server)) {
|
|
251
|
+
servers.push(server);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
return servers;
|
|
255
|
+
};
|
|
256
|
+
|
|
257
|
+
/** Normalize protocol spellings onto the binding key they document. */
|
|
258
|
+
const PROTOCOL_ALIASES = {
|
|
259
|
+
"kafka-secure": "kafka",
|
|
260
|
+
mqtt5: "mqtt",
|
|
261
|
+
mqtts: "mqtt",
|
|
262
|
+
"secure-mqtt": "mqtt",
|
|
263
|
+
wss: "ws",
|
|
264
|
+
};
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* The protocol an operation speaks, for binding-aware code samples: the first
|
|
268
|
+
* operation/channel binding key, else the first relevant server's `protocol`.
|
|
269
|
+
*/
|
|
270
|
+
export const protocolOf = (
|
|
271
|
+
operation: AsyncApiOperationObject | undefined,
|
|
272
|
+
channel: AsyncApiChannelObject | undefined,
|
|
273
|
+
servers: AsyncApiServerObject[]
|
|
274
|
+
): string | undefined => {
|
|
275
|
+
const declared =
|
|
276
|
+
Object.keys(operation?.bindings ?? {})[0] ??
|
|
277
|
+
Object.keys(channel?.bindings ?? {})[0] ??
|
|
278
|
+
servers.find((server) => isString(server.protocol))?.protocol;
|
|
279
|
+
if (!isString(declared) || declared === "") {
|
|
280
|
+
return undefined;
|
|
281
|
+
}
|
|
282
|
+
const lower = declared.toLowerCase();
|
|
283
|
+
// SAFETY: guarded by the `in` check, `lower` is one of the alias keys.
|
|
284
|
+
return lower in PROTOCOL_ALIASES
|
|
285
|
+
? PROTOCOL_ALIASES[lower as keyof typeof PROTOCOL_ALIASES]
|
|
286
|
+
: lower;
|
|
287
|
+
};
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* The security list an operation actually enforces: its own `security` when
|
|
291
|
+
* declared, else the union of its servers' — connecting already requires the
|
|
292
|
+
* server's schemes. Server entries dedupe by `$ref`, so two servers sharing a
|
|
293
|
+
* scheme render it once.
|
|
294
|
+
*/
|
|
295
|
+
export const asyncApiSecurityEntries = (
|
|
296
|
+
operation: AsyncApiOperationObject | undefined,
|
|
297
|
+
servers: AsyncApiServerObject[]
|
|
298
|
+
): AsyncApiRefLike[] => {
|
|
299
|
+
if (Array.isArray(operation?.security)) {
|
|
300
|
+
return operation.security;
|
|
301
|
+
}
|
|
302
|
+
const entries: AsyncApiRefLike[] = [];
|
|
303
|
+
const seen = new Set<string>();
|
|
304
|
+
for (const server of servers) {
|
|
305
|
+
for (const entry of server.security ?? []) {
|
|
306
|
+
if (!isObject(entry)) {
|
|
307
|
+
continue;
|
|
308
|
+
}
|
|
309
|
+
if (isString(entry.$ref)) {
|
|
310
|
+
if (seen.has(entry.$ref)) {
|
|
311
|
+
continue;
|
|
312
|
+
}
|
|
313
|
+
seen.add(entry.$ref);
|
|
314
|
+
}
|
|
315
|
+
entries.push(entry);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
return entries;
|
|
319
|
+
};
|
|
320
|
+
|
|
321
|
+
/** One protocol's binding fields, ready for a key/value table. */
|
|
322
|
+
export interface BindingGroup {
|
|
323
|
+
protocol: string;
|
|
324
|
+
rows: { name: string; value: unknown }[];
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/**
|
|
328
|
+
* Binding maps flattened for display, `bindingVersion` (metadata, not
|
|
329
|
+
* behavior) dropped. Values stay unformatted — the component renders schema-ish
|
|
330
|
+
* objects as nested schema tables and everything else as code.
|
|
331
|
+
*/
|
|
332
|
+
export const bindingGroups = (
|
|
333
|
+
bindings?: AsyncApiChannelObject["bindings"]
|
|
334
|
+
): BindingGroup[] => {
|
|
335
|
+
const groups: BindingGroup[] = [];
|
|
336
|
+
for (const [protocol, fields] of Object.entries(bindings ?? {})) {
|
|
337
|
+
if (!isObject(fields)) {
|
|
338
|
+
continue;
|
|
339
|
+
}
|
|
340
|
+
const rows = Object.entries(fields)
|
|
341
|
+
.filter(([name]) => name !== "bindingVersion")
|
|
342
|
+
.map(([name, value]) => ({ name, value }));
|
|
343
|
+
if (rows.length > 0) {
|
|
344
|
+
groups.push({ protocol, rows });
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
return groups;
|
|
348
|
+
};
|
|
@@ -8,6 +8,22 @@ import { sample } from "openapi-sampler";
|
|
|
8
8
|
* values come from openapi-sampler, which is likewise browser-safe.
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
+
/** Any value a parsed OpenAPI document can hold: JSON, nested schemas included. */
|
|
12
|
+
export type SpecValue =
|
|
13
|
+
| string
|
|
14
|
+
| number
|
|
15
|
+
| boolean
|
|
16
|
+
| null
|
|
17
|
+
| undefined
|
|
18
|
+
| SpecValue[]
|
|
19
|
+
| { [key: string]: SpecValue };
|
|
20
|
+
|
|
21
|
+
const isString = (value: SpecValue): value is string =>
|
|
22
|
+
typeof value === "string";
|
|
23
|
+
|
|
24
|
+
const isNumber = (value: SpecValue): value is number =>
|
|
25
|
+
typeof value === "number";
|
|
26
|
+
|
|
11
27
|
/** A permissive view of an OpenAPI 3.1 schema — only the fields we render. */
|
|
12
28
|
export interface SchemaLike {
|
|
13
29
|
$ref?: string;
|
|
@@ -18,11 +34,11 @@ export interface SchemaLike {
|
|
|
18
34
|
properties?: Record<string, SchemaLike>;
|
|
19
35
|
required?: string[];
|
|
20
36
|
items?: SchemaLike;
|
|
21
|
-
enum?:
|
|
22
|
-
const?:
|
|
23
|
-
default?:
|
|
24
|
-
example?:
|
|
25
|
-
examples?:
|
|
37
|
+
enum?: SpecValue[];
|
|
38
|
+
const?: SpecValue;
|
|
39
|
+
default?: SpecValue;
|
|
40
|
+
example?: SpecValue;
|
|
41
|
+
examples?: SpecValue[];
|
|
26
42
|
allOf?: SchemaLike[];
|
|
27
43
|
oneOf?: SchemaLike[];
|
|
28
44
|
anyOf?: SchemaLike[];
|
|
@@ -38,7 +54,7 @@ export interface SchemaLike {
|
|
|
38
54
|
minItems?: number;
|
|
39
55
|
maxItems?: number;
|
|
40
56
|
pattern?: string;
|
|
41
|
-
[key: string]:
|
|
57
|
+
[key: string]: SpecValue;
|
|
42
58
|
}
|
|
43
59
|
|
|
44
60
|
/** A permissive view of an operation parameter — only the fields we render. */
|
|
@@ -50,9 +66,16 @@ export interface ParameterLike {
|
|
|
50
66
|
required?: boolean;
|
|
51
67
|
deprecated?: boolean;
|
|
52
68
|
schema?: SchemaLike;
|
|
53
|
-
example?:
|
|
69
|
+
example?: SpecValue;
|
|
70
|
+
[key: string]: SpecValue;
|
|
54
71
|
}
|
|
55
72
|
|
|
73
|
+
/** The `components` object of a parsed spec: section name → named-node table. */
|
|
74
|
+
export type ComponentsLike = Record<
|
|
75
|
+
string,
|
|
76
|
+
Record<string, SpecValue> | undefined
|
|
77
|
+
>;
|
|
78
|
+
|
|
56
79
|
const REF_PATTERN = /#\/components\/schemas\/(?<name>[^/]+)$/u;
|
|
57
80
|
|
|
58
81
|
const COMPONENT_REF = /#\/components\/(?<section>[^/]+)\/(?<name>[^/]+)$/u;
|
|
@@ -65,16 +88,18 @@ const COMPONENT_REF = /#\/components\/(?<section>[^/]+)\/(?<name>[^/]+)$/u;
|
|
|
65
88
|
*/
|
|
66
89
|
export const resolveComponentRef = <T extends { $ref?: string }>(
|
|
67
90
|
node: T,
|
|
68
|
-
components:
|
|
91
|
+
components: ComponentsLike | undefined,
|
|
69
92
|
section: string
|
|
70
93
|
): T => {
|
|
71
|
-
if (
|
|
94
|
+
if (!isString(node.$ref)) {
|
|
72
95
|
return node;
|
|
73
96
|
}
|
|
74
97
|
const groups = COMPONENT_REF.exec(node.$ref)?.groups;
|
|
75
98
|
if (groups?.section !== section) {
|
|
76
99
|
return node;
|
|
77
100
|
}
|
|
101
|
+
// SAFETY: a components section table stores nodes of that section's type,
|
|
102
|
+
// and callers always pair `section` with the matching `T`.
|
|
78
103
|
const table = components?.[section] as Record<string, T> | undefined;
|
|
79
104
|
return table?.[groups.name ?? ""] ?? node;
|
|
80
105
|
};
|
|
@@ -88,7 +113,7 @@ export const resolveComponentRef = <T extends { $ref?: string }>(
|
|
|
88
113
|
export const mergeParameters = (
|
|
89
114
|
pathParameters: ParameterLike[] | undefined,
|
|
90
115
|
operationParameters: ParameterLike[] | undefined,
|
|
91
|
-
components?:
|
|
116
|
+
components?: ComponentsLike
|
|
92
117
|
): ParameterLike[] => {
|
|
93
118
|
const merged = new Map<string, ParameterLike>();
|
|
94
119
|
let position = 0;
|
|
@@ -118,7 +143,7 @@ export const resolveSchema = (
|
|
|
118
143
|
if (!schema) {
|
|
119
144
|
return {};
|
|
120
145
|
}
|
|
121
|
-
if (
|
|
146
|
+
if (isString(schema.$ref)) {
|
|
122
147
|
const name = REF_PATTERN.exec(schema.$ref)?.groups?.name;
|
|
123
148
|
if (name && schemas[name]) {
|
|
124
149
|
return schemas[name];
|
|
@@ -140,7 +165,7 @@ const nonNullTypes = (type: string | string[] | undefined): string[] => {
|
|
|
140
165
|
* array items can't recurse forever.
|
|
141
166
|
*/
|
|
142
167
|
export const typeLabel = (schema: SchemaLike): string => {
|
|
143
|
-
if (
|
|
168
|
+
if (isString(schema.$ref)) {
|
|
144
169
|
return refName(schema.$ref);
|
|
145
170
|
}
|
|
146
171
|
if (schema.oneOf || schema.anyOf) {
|
|
@@ -177,11 +202,11 @@ export const constraints = (schema: SchemaLike): string[] => {
|
|
|
177
202
|
];
|
|
178
203
|
for (const [key, label] of numeric) {
|
|
179
204
|
const value = schema[key];
|
|
180
|
-
if (
|
|
205
|
+
if (isNumber(value)) {
|
|
181
206
|
out.push(`${label} ${value}`);
|
|
182
207
|
}
|
|
183
208
|
}
|
|
184
|
-
if (
|
|
209
|
+
if (isString(schema.pattern)) {
|
|
185
210
|
out.push(`matches ${schema.pattern}`);
|
|
186
211
|
}
|
|
187
212
|
if (schema.default !== undefined) {
|
|
@@ -190,6 +215,12 @@ export const constraints = (schema: SchemaLike): string[] => {
|
|
|
190
215
|
return out;
|
|
191
216
|
};
|
|
192
217
|
|
|
218
|
+
/** The merged property list and required set a schema exposes. */
|
|
219
|
+
export interface ObjectPropertySet {
|
|
220
|
+
properties: [string, SchemaLike][];
|
|
221
|
+
required: Set<string>;
|
|
222
|
+
}
|
|
223
|
+
|
|
193
224
|
/**
|
|
194
225
|
* The object properties a schema exposes, merging `allOf` branches so an
|
|
195
226
|
* `allOf`-composed model still lists every field. Returns the properties plus
|
|
@@ -198,7 +229,7 @@ export const constraints = (schema: SchemaLike): string[] => {
|
|
|
198
229
|
export const objectProperties = (
|
|
199
230
|
schema: SchemaLike,
|
|
200
231
|
schemas: Record<string, SchemaLike>
|
|
201
|
-
):
|
|
232
|
+
): ObjectPropertySet => {
|
|
202
233
|
const properties = new Map<string, SchemaLike>();
|
|
203
234
|
const required = new Set<string>();
|
|
204
235
|
// Cycles can only enter through `$ref`s (inline JSON can't self-nest), so
|
|
@@ -206,7 +237,7 @@ export const objectProperties = (
|
|
|
206
237
|
const seen = new Set<string>();
|
|
207
238
|
|
|
208
239
|
const collect = (node: SchemaLike): void => {
|
|
209
|
-
if (
|
|
240
|
+
if (isString(node.$ref)) {
|
|
210
241
|
if (seen.has(node.$ref)) {
|
|
211
242
|
return;
|
|
212
243
|
}
|
|
@@ -239,16 +270,18 @@ export const objectProperties = (
|
|
|
239
270
|
export const exampleValue = (
|
|
240
271
|
schema: SchemaLike | undefined,
|
|
241
272
|
schemas: Record<string, SchemaLike>
|
|
242
|
-
):
|
|
273
|
+
): SpecValue => {
|
|
243
274
|
if (!schema) {
|
|
244
275
|
return null;
|
|
245
276
|
}
|
|
246
277
|
try {
|
|
278
|
+
// SAFETY: SchemaLike structurally covers the JSONSchema7 fields the
|
|
279
|
+
// sampler reads, and the sampler only ever assembles JSON values.
|
|
247
280
|
return sample(
|
|
248
281
|
schema as Parameters<typeof sample>[0],
|
|
249
282
|
{ quiet: true, skipReadOnly: true },
|
|
250
283
|
{ components: { schemas } }
|
|
251
|
-
);
|
|
284
|
+
) as SpecValue;
|
|
252
285
|
} catch {
|
|
253
286
|
// An unresolvable $ref or malformed schema is a spec problem the schema
|
|
254
287
|
// tables already surface; a sample is best-effort.
|
|
@@ -257,5 +290,4 @@ export const exampleValue = (
|
|
|
257
290
|
};
|
|
258
291
|
|
|
259
292
|
/** Pretty-print a JSON value for an example/code block. */
|
|
260
|
-
export const toJson = (value:
|
|
261
|
-
JSON.stringify(value, null, 2);
|
|
293
|
+
export const toJson = <T>(value: T): string => JSON.stringify(value, null, 2);
|
|
@@ -6,6 +6,8 @@
|
|
|
6
6
|
* theme.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
+
import { copyText, createCopyFlash } from "../copy-feedback.ts";
|
|
10
|
+
|
|
9
11
|
class BlumePanelTabs extends HTMLElement {
|
|
10
12
|
connectedCallback() {
|
|
11
13
|
const tabs = [
|
|
@@ -36,18 +38,19 @@ class BlumePanelTabs extends HTMLElement {
|
|
|
36
38
|
}
|
|
37
39
|
|
|
38
40
|
if (copy) {
|
|
41
|
+
const flash = createCopyFlash((copied) => {
|
|
42
|
+
if (copied) {
|
|
43
|
+
copy.dataset.copied = "true";
|
|
44
|
+
} else {
|
|
45
|
+
delete copy.dataset.copied;
|
|
46
|
+
}
|
|
47
|
+
}, "Copied");
|
|
39
48
|
copy.addEventListener("click", async () => {
|
|
40
49
|
const active = panels.find(
|
|
41
50
|
(panel) => !panel.classList.contains("hidden")
|
|
42
51
|
);
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
copy.dataset.copied = "true";
|
|
46
|
-
setTimeout(() => {
|
|
47
|
-
delete copy.dataset.copied;
|
|
48
|
-
}, 1500);
|
|
49
|
-
} catch {
|
|
50
|
-
// Clipboard unavailable (insecure context); silently ignore.
|
|
52
|
+
if (await copyText(active?.textContent ?? "")) {
|
|
53
|
+
flash();
|
|
51
54
|
}
|
|
52
55
|
});
|
|
53
56
|
}
|