blume 0.6.7 → 0.8.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 +618 -0
- package/LICENSE +21 -0
- package/README.md +107 -0
- package/dist/cli/index.js +2609 -1041
- package/dist/cli/index.js.map +110 -103
- package/dist/types/ai/component-markdown.d.ts +34 -0
- package/dist/types/components/content/youtube.d.ts +18 -0
- package/dist/types/core/base-path.d.ts +47 -0
- package/dist/types/core/config-input.d.ts +110 -12
- package/dist/types/core/config.d.ts +6 -4
- package/dist/types/core/data.d.ts +4 -0
- package/dist/types/core/i18n-ui.d.ts +477 -135
- package/dist/types/core/schema.d.ts +309 -195
- package/dist/types/core/sources/types.d.ts +2 -0
- package/dist/types/core/types.d.ts +6 -1
- package/dist/types/index.d.ts +1 -0
- package/dist/types/openapi/references.d.ts +60 -0
- package/docs/01-quickstart.mdx +5 -2
- package/docs/02-deployment.mdx +24 -9
- package/docs/03-faq.mdx +46 -16
- package/docs/advanced/custom-pages.mdx +1 -1
- package/docs/advanced/skills.mdx +1 -1
- package/docs/configuration/ai.mdx +49 -10
- package/docs/configuration/customization.mdx +11 -0
- package/docs/configuration/index.mdx +33 -3
- package/docs/configuration/seo.mdx +2 -2
- package/docs/content/components.mdx +30 -3
- package/docs/content/i18n.mdx +1 -1
- package/docs/content/islands.mdx +8 -0
- package/docs/content/navigation.mdx +3 -3
- package/docs/content/sources.mdx +1 -1
- package/docs/content/syntax.mdx +17 -2
- package/docs/index.mdx +2 -2
- package/docs/reference/cli.mdx +8 -6
- package/package.json +15 -4
- package/skills/blume/SKILL.md +5 -3
- package/skills/blume-update-docs/SKILL.md +3 -2
- package/src/ai/agent-readability.ts +11 -5
- package/src/ai/ask-context.ts +7 -2
- package/src/ai/ask-data.ts +3 -0
- package/src/ai/ask.ts +12 -7
- package/src/ai/component-markdown.ts +461 -0
- package/src/ai/llms.ts +143 -23
- package/src/ai/markdown.ts +35 -6
- package/src/ai/mcp/data.ts +33 -8
- package/src/ai/mcp/discovery.ts +10 -3
- package/src/ai/mcp/server.ts +24 -7
- package/src/ai/visibility.ts +74 -0
- package/src/astro/component-slots.ts +16 -4
- package/src/astro/examples.ts +12 -7
- package/src/astro/generate.ts +393 -189
- package/src/astro/index.ts +5 -1
- package/src/astro/integration.ts +9 -5
- package/src/astro/islands.ts +11 -5
- package/src/astro/markdown-negotiation.ts +2 -2
- package/src/astro/pages.ts +89 -22
- package/src/astro/templates.ts +259 -25
- package/src/blume-modules.d.ts +8 -0
- package/src/cli/commands/build.ts +131 -38
- package/src/cli/commands/check.ts +1 -1
- package/src/cli/commands/dev.ts +71 -17
- package/src/cli/commands/doctor.ts +2 -2
- package/src/cli/commands/eject.ts +47 -19
- package/src/cli/commands/init.ts +120 -180
- package/src/cli/commands/preview.ts +4 -1
- package/src/cli/commands/validate.ts +44 -2
- package/src/cli/dev-lock.ts +34 -19
- package/src/cli/eject-scripts.ts +72 -0
- package/src/cli/env.ts +15 -5
- package/src/cli/init/questions.ts +158 -0
- package/src/cli/init/scaffold.ts +380 -0
- package/src/cli/required-secrets.ts +2 -1
- package/src/components/content/AccordionItem.astro +23 -4
- package/src/components/content/Badge.astro +3 -1
- package/src/components/content/Card.astro +4 -2
- package/src/components/content/CodeBlock.astro +3 -0
- package/src/components/content/Component.astro +30 -16
- package/src/components/content/Diff.astro +3 -1
- package/src/components/content/Step.astro +10 -1
- package/src/components/content/Tabs.astro +15 -3
- package/src/components/content/Tile.astro +2 -1
- package/src/components/content/Tooltip.astro +3 -1
- package/src/components/content/Update.astro +9 -2
- package/src/components/content/auto-type-table.ts +25 -9
- package/src/components/content/base-href.ts +33 -0
- package/src/components/content/changelog-element.ts +9 -2
- package/src/components/content/diff.ts +12 -6
- package/src/components/content/mermaid-element.ts +10 -2
- package/src/components/index.ts +23 -1
- package/src/components/islands/AskAI.astro +5 -2
- package/src/components/islands/ask-ai.tsx +68 -12
- package/src/components/islands/base-path.ts +28 -0
- package/src/components/islands/hooks.ts +44 -9
- package/src/components/layout/Banner.astro +12 -3
- package/src/components/layout/Breadcrumbs.astro +2 -1
- package/src/components/layout/Favicon.astro +3 -2
- package/src/components/layout/Header.astro +15 -5
- package/src/components/layout/LanguageSwitcher.astro +2 -1
- package/src/components/layout/Logo.astro +13 -4
- package/src/components/layout/NavSelector.astro +2 -1
- package/src/components/layout/NavTree.astro +22 -7
- package/src/components/layout/PageActions.astro +25 -10
- package/src/components/layout/PageFeedback.astro +4 -1
- package/src/components/layout/PageLayout.astro +51 -9
- package/src/components/layout/Pagination.astro +3 -2
- package/src/components/layout/ReferenceLayout.astro +8 -1
- package/src/components/layout/RootLayout.astro +74 -13
- package/src/components/layout/Search.astro +107 -27
- package/src/components/layout/nav-utils.ts +18 -10
- package/src/components/layout/search/algolia.ts +11 -2
- package/src/components/layout/search/endpoint.ts +11 -5
- package/src/components/layout/search/orama-cloud.ts +8 -2
- package/src/components/layout/search/pagefind.ts +3 -0
- package/src/components/layout/search/types.ts +5 -1
- package/src/components/layout/search/typesense.ts +4 -1
- package/src/components/layout/toc-element.ts +8 -2
- package/src/components/openapi/ApiTagOperations.astro +2 -1
- package/src/components/openapi/Operation.astro +47 -40
- package/src/components/openapi/RequestPanel.astro +8 -2
- package/src/components/openapi/helpers.ts +71 -3
- package/src/components/openapi/panel.ts +1 -1
- package/src/components/openapi/snippets.ts +25 -11
- package/src/core/base-path.ts +94 -0
- package/src/core/builtin-tags.ts +2 -0
- package/src/core/component-overrides.ts +103 -74
- package/src/core/config-input.ts +118 -17
- package/src/core/config.ts +8 -5
- package/src/core/content.ts +2 -0
- package/src/core/data.ts +4 -0
- package/src/core/diagnostics.ts +54 -34
- package/src/core/gitignore.ts +4 -1
- package/src/core/graph.ts +166 -88
- package/src/core/i18n-ui.ts +63 -3
- package/src/core/last-modified.ts +15 -6
- package/src/core/links.ts +69 -25
- package/src/core/manifest.ts +62 -45
- package/src/core/nav-diagnostics.ts +1 -1
- package/src/core/navigation.ts +144 -58
- package/src/core/package-json.ts +17 -2
- package/src/core/project-graph.ts +25 -15
- package/src/core/schema.ts +605 -620
- package/src/core/sources/assets.ts +6 -1
- package/src/core/sources/filesystem.ts +4 -0
- package/src/core/sources/github-releases.ts +2 -1
- package/src/core/sources/mdx-remote.ts +76 -63
- package/src/core/sources/normalize.ts +236 -91
- package/src/core/sources/notion.ts +27 -18
- package/src/core/sources/types.ts +2 -0
- package/src/core/tsconfig-aliases.ts +59 -30
- package/src/core/types.ts +6 -1
- package/src/core/ui-packs/ar.ts +1 -0
- package/src/core/ui-packs/bg.ts +1 -0
- package/src/core/ui-packs/bn.ts +1 -0
- package/src/core/ui-packs/ca.ts +1 -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 +1 -0
- package/src/core/ui-packs/el.ts +1 -0
- package/src/core/ui-packs/es.ts +1 -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 -1
- 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 +1 -0
- package/src/core/ui-packs/hu.ts +1 -0
- package/src/core/ui-packs/id.ts +1 -0
- package/src/core/ui-packs/it.ts +1 -0
- package/src/core/ui-packs/ja.ts +1 -0
- package/src/core/ui-packs/ko.ts +1 -0
- package/src/core/ui-packs/nl.ts +1 -0
- package/src/core/ui-packs/no.ts +1 -0
- package/src/core/ui-packs/pl.ts +1 -0
- package/src/core/ui-packs/pt-br.ts +1 -0
- package/src/core/ui-packs/pt.ts +1 -0
- package/src/core/ui-packs/ro.ts +1 -0
- package/src/core/ui-packs/ru.ts +1 -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 +1 -0
- package/src/core/ui-packs/th.ts +1 -0
- package/src/core/ui-packs/tr.ts +1 -0
- package/src/core/ui-packs/uk.ts +1 -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/deploy/adapter-output.ts +18 -8
- package/src/deploy/redirects.ts +25 -2
- package/src/deploy/robots.ts +6 -1
- package/src/deploy/rss.ts +10 -3
- package/src/deploy/sitemap.ts +59 -13
- package/src/index.ts +5 -0
- package/src/markdown/base-links.ts +60 -0
- package/src/markdown/code-title.ts +11 -14
- package/src/markdown/index.ts +46 -9
- package/src/markdown/inline-code.ts +14 -4
- package/src/markdown/package-commands.ts +10 -4
- package/src/markdown/themes.ts +24 -0
- package/src/openapi/model.ts +15 -5
- package/src/openapi/parse.ts +21 -0
- package/src/openapi/references.ts +75 -21
- package/src/openapi/render-mdx.ts +11 -6
- package/src/openapi/scalar.ts +32 -16
- package/src/openapi/source.ts +59 -10
- package/src/registry/eject.ts +247 -19
- package/src/registry/registry.ts +0 -3
- package/src/search/build.ts +3 -0
- package/src/search/documents.ts +36 -4
- package/src/search/sync/typesense.ts +6 -4
- package/src/seo/jsonld.ts +28 -17
- package/src/theme/entry.ts +85 -20
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { withBasePath } from "../core/base-path.ts";
|
|
1
2
|
import type { ResolvedConfig } from "../core/schema.ts";
|
|
2
3
|
import type { NavTab } from "../core/types.ts";
|
|
3
4
|
|
|
@@ -30,6 +31,12 @@ export interface ReferenceSource {
|
|
|
30
31
|
slug: string;
|
|
31
32
|
/** Normalized route the reference mounts at, e.g. `/reference`. */
|
|
32
33
|
route: string;
|
|
34
|
+
/**
|
|
35
|
+
* Site-wide `basePath` the rendered pages are mounted under (`""` when
|
|
36
|
+
* none). Kept separate from `route` — the content pipeline applies it to
|
|
37
|
+
* staged entries itself — so consumers prefix only the URLs they emit.
|
|
38
|
+
*/
|
|
39
|
+
basePath: string;
|
|
33
40
|
label: string;
|
|
34
41
|
/** Local path or `http(s)` URL, verbatim from config. */
|
|
35
42
|
spec: string;
|
|
@@ -37,6 +44,11 @@ export interface ReferenceSource {
|
|
|
37
44
|
theme?: string;
|
|
38
45
|
/** Display options carried through to the Blume renderer. */
|
|
39
46
|
display: ReferenceDisplay;
|
|
47
|
+
/**
|
|
48
|
+
* Warnings recorded while deduping — another source's route collided with
|
|
49
|
+
* this one and was dropped. Surfaced as diagnostics when the source loads.
|
|
50
|
+
*/
|
|
51
|
+
collisions?: string[];
|
|
40
52
|
}
|
|
41
53
|
|
|
42
54
|
const NON_SLUG = /[^a-z0-9]+/gu;
|
|
@@ -77,7 +89,8 @@ const referencesFor = (
|
|
|
77
89
|
block: Block,
|
|
78
90
|
defaultLabel: string,
|
|
79
91
|
renderer: ReferenceRenderer,
|
|
80
|
-
display: ReferenceDisplay
|
|
92
|
+
display: ReferenceDisplay,
|
|
93
|
+
basePath: string
|
|
81
94
|
): ReferenceSource[] => {
|
|
82
95
|
if (!block.enabled) {
|
|
83
96
|
return [];
|
|
@@ -101,6 +114,7 @@ const referencesFor = (
|
|
|
101
114
|
}
|
|
102
115
|
|
|
103
116
|
return {
|
|
117
|
+
basePath,
|
|
104
118
|
display,
|
|
105
119
|
kind,
|
|
106
120
|
label,
|
|
@@ -130,41 +144,81 @@ export const resolveReferences = (
|
|
|
130
144
|
{
|
|
131
145
|
codeSamples: config.openapi.codeSamples,
|
|
132
146
|
expandSchemas: config.openapi.expandSchemas,
|
|
133
|
-
}
|
|
147
|
+
},
|
|
148
|
+
config.basePath
|
|
149
|
+
),
|
|
150
|
+
...referencesFor(
|
|
151
|
+
"asyncapi",
|
|
152
|
+
config.asyncapi,
|
|
153
|
+
"Events",
|
|
154
|
+
"scalar",
|
|
155
|
+
NO_DISPLAY,
|
|
156
|
+
config.basePath
|
|
134
157
|
),
|
|
135
|
-
...referencesFor("asyncapi", config.asyncapi, "Events", "scalar", NO_DISPLAY),
|
|
136
158
|
];
|
|
137
159
|
|
|
138
160
|
/** Nav tabs (header links) for every reference, regardless of renderer. */
|
|
139
161
|
export const referenceTabs = (config: ResolvedConfig): NavTab[] =>
|
|
140
162
|
resolveReferences(config).map((ref) => ({
|
|
141
163
|
label: ref.label,
|
|
142
|
-
|
|
164
|
+
// Blume-rendered operation pages flow through the content pipeline and are
|
|
165
|
+
// mounted under `basePath`, so their tab must be too. Scalar references are
|
|
166
|
+
// a single embedded page injected at the raw `route`, left root-anchored.
|
|
167
|
+
path:
|
|
168
|
+
ref.renderer === "blume"
|
|
169
|
+
? withBasePath(config.basePath, ref.route)
|
|
170
|
+
: ref.route,
|
|
143
171
|
}));
|
|
144
172
|
|
|
173
|
+
/**
|
|
174
|
+
* Accept one resolved reference into the deduped Blume-rendered set, or return
|
|
175
|
+
* null to skip it. Mutates `seen`/`usedSlugs` so repeated routes/slugs collapse.
|
|
176
|
+
* A dropped route collision is recorded on the kept reference (mirroring the
|
|
177
|
+
* Scalar path's warning) — losing a whole spec's pages must not be silent.
|
|
178
|
+
*/
|
|
179
|
+
const blumeReferenceOf = (
|
|
180
|
+
ref: ReferenceSource,
|
|
181
|
+
seen: Map<string, ReferenceSource>,
|
|
182
|
+
usedSlugs: Set<string>
|
|
183
|
+
): ReferenceSource | null => {
|
|
184
|
+
if (ref.kind !== "openapi" || ref.renderer !== "blume") {
|
|
185
|
+
return null;
|
|
186
|
+
}
|
|
187
|
+
const kept = seen.get(ref.route);
|
|
188
|
+
if (kept) {
|
|
189
|
+
(kept.collisions ??= []).push(
|
|
190
|
+
`Two API reference sources resolve to ${ref.route}; keeping the first.`
|
|
191
|
+
);
|
|
192
|
+
return null;
|
|
193
|
+
}
|
|
194
|
+
// Distinct routes can slugify identically (`/api/v1` and `/api-v1` both
|
|
195
|
+
// yield `api-v1`). The slug keys the `blume:openapi` data module, so a
|
|
196
|
+
// collision would let one spec silently overwrite the other while the
|
|
197
|
+
// loser's pages still point at the shared key — disambiguate.
|
|
198
|
+
let { slug } = ref;
|
|
199
|
+
let n = 2;
|
|
200
|
+
while (usedSlugs.has(slug)) {
|
|
201
|
+
slug = `${ref.slug}-${n}`;
|
|
202
|
+
n += 1;
|
|
203
|
+
}
|
|
204
|
+
usedSlugs.add(slug);
|
|
205
|
+
const accepted = slug === ref.slug ? ref : { ...ref, slug };
|
|
206
|
+
// Keep the accepted object (not the original) so a later collision's warning
|
|
207
|
+
// lands on the reference the caller actually receives.
|
|
208
|
+
seen.set(ref.route, accepted);
|
|
209
|
+
return accepted;
|
|
210
|
+
};
|
|
211
|
+
|
|
145
212
|
/** Blume-rendered OpenAPI references, deduped by route (first wins). */
|
|
146
213
|
export const blumeReferences = (config: ResolvedConfig): ReferenceSource[] => {
|
|
147
|
-
const seen = new
|
|
214
|
+
const seen = new Map<string, ReferenceSource>();
|
|
148
215
|
const usedSlugs = new Set<string>();
|
|
149
216
|
const result: ReferenceSource[] = [];
|
|
150
217
|
for (const ref of resolveReferences(config)) {
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
if (seen.has(ref.route)) {
|
|
155
|
-
continue;
|
|
156
|
-
}
|
|
157
|
-
seen.add(ref.route);
|
|
158
|
-
// Distinct routes can slugify identically (`/api/v1` and `/api-v1` both
|
|
159
|
-
// yield `api-v1`). The slug keys the `blume:openapi` data module, so a
|
|
160
|
-
// collision would let one spec silently overwrite the other while the
|
|
161
|
-
// loser's pages still point at the shared key — disambiguate.
|
|
162
|
-
let { slug } = ref;
|
|
163
|
-
for (let n = 2; usedSlugs.has(slug); n += 1) {
|
|
164
|
-
slug = `${ref.slug}-${n}`;
|
|
218
|
+
const accepted = blumeReferenceOf(ref, seen, usedSlugs);
|
|
219
|
+
if (accepted) {
|
|
220
|
+
result.push(accepted);
|
|
165
221
|
}
|
|
166
|
-
usedSlugs.add(slug);
|
|
167
|
-
result.push(slug === ref.slug ? ref : { ...ref, slug });
|
|
168
222
|
}
|
|
169
223
|
return result;
|
|
170
224
|
};
|
|
@@ -119,17 +119,22 @@ export const overviewMdx = (spec: ApiSpecData): RenderedPage => {
|
|
|
119
119
|
});
|
|
120
120
|
}
|
|
121
121
|
}
|
|
122
|
-
const tagSections =
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
122
|
+
const tagSections: string[] = [];
|
|
123
|
+
for (const tag of sections) {
|
|
124
|
+
if (!operations.some((operation) => operation.tagSlug === tag.slug)) {
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
const description = tag.description.trim()
|
|
128
|
+
? [mdxSafe(tag.description.trim())]
|
|
129
|
+
: [];
|
|
130
|
+
tagSections.push(
|
|
127
131
|
[
|
|
128
132
|
`## ${mdxSafe(tag.name)}`,
|
|
129
|
-
...
|
|
133
|
+
...description,
|
|
130
134
|
`<ApiTagOperations source="${spec.slug}" tag="${tag.slug}" />`,
|
|
131
135
|
].join("\n\n")
|
|
132
136
|
);
|
|
137
|
+
}
|
|
133
138
|
return {
|
|
134
139
|
body: [
|
|
135
140
|
withDescription(
|
package/src/openapi/scalar.ts
CHANGED
|
@@ -87,6 +87,35 @@ const specConfiguration = async (
|
|
|
87
87
|
}
|
|
88
88
|
};
|
|
89
89
|
|
|
90
|
+
/**
|
|
91
|
+
* Accept one resolved reference into the deduped Scalar set, or return null to
|
|
92
|
+
* skip it (recording a warning for a route collision). Mutates `seen`.
|
|
93
|
+
*/
|
|
94
|
+
const acceptScalarReference = (
|
|
95
|
+
ref: ReferenceSource,
|
|
96
|
+
seen: Set<string>,
|
|
97
|
+
contentRoutes: ReadonlySet<string>,
|
|
98
|
+
warnings: string[]
|
|
99
|
+
): ReferenceSource | null => {
|
|
100
|
+
if (ref.renderer !== "scalar") {
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
if (seen.has(ref.route)) {
|
|
104
|
+
warnings.push(
|
|
105
|
+
`Two API reference sources resolve to ${ref.route}; keeping the first.`
|
|
106
|
+
);
|
|
107
|
+
return null;
|
|
108
|
+
}
|
|
109
|
+
if (contentRoutes.has(ref.route)) {
|
|
110
|
+
warnings.push(
|
|
111
|
+
`API reference route ${ref.route} collides with a content page; skipping the reference there.`
|
|
112
|
+
);
|
|
113
|
+
return null;
|
|
114
|
+
}
|
|
115
|
+
seen.add(ref.route);
|
|
116
|
+
return ref;
|
|
117
|
+
};
|
|
118
|
+
|
|
90
119
|
/**
|
|
91
120
|
* Build the Scalar reference page(s) for the project. Only Scalar-rendered
|
|
92
121
|
* references are emitted here (Blume-rendered OpenAPI is staged content). Reads
|
|
@@ -105,23 +134,10 @@ export const buildReferenceFiles = async (options: {
|
|
|
105
134
|
const seen = new Set<string>();
|
|
106
135
|
const accepted: ReferenceSource[] = [];
|
|
107
136
|
for (const ref of resolveReferences(config)) {
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
if (seen.has(ref.route)) {
|
|
112
|
-
warnings.push(
|
|
113
|
-
`Two API reference sources resolve to ${ref.route}; keeping the first.`
|
|
114
|
-
);
|
|
115
|
-
continue;
|
|
116
|
-
}
|
|
117
|
-
if (contentRoutes.has(ref.route)) {
|
|
118
|
-
warnings.push(
|
|
119
|
-
`API reference route ${ref.route} collides with a content page; skipping the reference there.`
|
|
120
|
-
);
|
|
121
|
-
continue;
|
|
137
|
+
const next = acceptScalarReference(ref, seen, contentRoutes, warnings);
|
|
138
|
+
if (next) {
|
|
139
|
+
accepted.push(next);
|
|
122
140
|
}
|
|
123
|
-
seen.add(ref.route);
|
|
124
|
-
accepted.push(ref);
|
|
125
141
|
}
|
|
126
142
|
|
|
127
143
|
const built = await Promise.all(
|
package/src/openapi/source.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { withBasePath } from "../core/base-path.ts";
|
|
1
2
|
import matter from "../core/frontmatter.ts";
|
|
2
3
|
import { hashText } from "../core/sources/cache.ts";
|
|
3
4
|
import type {
|
|
@@ -9,7 +10,7 @@ import type {
|
|
|
9
10
|
import type { Diagnostic } from "../core/types.ts";
|
|
10
11
|
import { extractOperations } from "./model.ts";
|
|
11
12
|
import type { ApiOperationRef, ApiSpecData, OpenApiData } from "./model.ts";
|
|
12
|
-
import { parseSpec } from "./parse.ts";
|
|
13
|
+
import { InvalidSpecError, parseSpec } from "./parse.ts";
|
|
13
14
|
import type { ReferenceSource } from "./references.ts";
|
|
14
15
|
import { operationMdx, overviewMdx } from "./render-mdx.ts";
|
|
15
16
|
import type { RenderedPage } from "./render-mdx.ts";
|
|
@@ -90,7 +91,11 @@ export const openApiSource = (
|
|
|
90
91
|
ctx.projectRoot,
|
|
91
92
|
{ cacheDir: ctx.cacheDir, refresh: ctx.refresh }
|
|
92
93
|
);
|
|
93
|
-
const {
|
|
94
|
+
const {
|
|
95
|
+
operations,
|
|
96
|
+
tags,
|
|
97
|
+
warnings: extractWarnings,
|
|
98
|
+
} = extractOperations(document, reference.route);
|
|
94
99
|
const info = document.info ?? { title: reference.label, version: "" };
|
|
95
100
|
const spec: ApiSpecData = {
|
|
96
101
|
codeSamples: reference.display.codeSamples,
|
|
@@ -98,8 +103,18 @@ export const openApiSource = (
|
|
|
98
103
|
document,
|
|
99
104
|
expandSchemas: reference.display.expandSchemas,
|
|
100
105
|
label: reference.label,
|
|
106
|
+
// Operation pages flow through the content pipeline, which mounts them
|
|
107
|
+
// under the site-wide `basePath` (staged entry refs below stay
|
|
108
|
+
// base-less); serialize the served route so components link to the
|
|
109
|
+
// pages' real URLs.
|
|
101
110
|
operations: Object.fromEntries(
|
|
102
|
-
operations.map((operation) => [
|
|
111
|
+
operations.map((operation) => [
|
|
112
|
+
operation.key,
|
|
113
|
+
{
|
|
114
|
+
...operation,
|
|
115
|
+
route: withBasePath(reference.basePath, operation.route),
|
|
116
|
+
},
|
|
117
|
+
])
|
|
103
118
|
),
|
|
104
119
|
route: reference.route,
|
|
105
120
|
slug: reference.slug,
|
|
@@ -108,11 +123,32 @@ export const openApiSource = (
|
|
|
108
123
|
version: info.version ?? "",
|
|
109
124
|
};
|
|
110
125
|
return {
|
|
111
|
-
diagnostics:
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
126
|
+
diagnostics: [
|
|
127
|
+
...warnings.map((message) => ({
|
|
128
|
+
code: "BLUME_OPENAPI_STALE",
|
|
129
|
+
message,
|
|
130
|
+
severity: "warning" as const,
|
|
131
|
+
})),
|
|
132
|
+
...extractWarnings.map((message) => ({
|
|
133
|
+
code: "BLUME_OPENAPI_REF_PATH_ITEM",
|
|
134
|
+
message: `In OpenAPI spec "${reference.spec}": ${message}`,
|
|
135
|
+
severity: "warning" as const,
|
|
136
|
+
})),
|
|
137
|
+
// A document with no operations (say, a config file that happens to
|
|
138
|
+
// parse as YAML) would otherwise build a nav tab onto an empty
|
|
139
|
+
// reference with no hint why.
|
|
140
|
+
...(operations.length === 0
|
|
141
|
+
? [
|
|
142
|
+
{
|
|
143
|
+
code: "BLUME_OPENAPI_EMPTY",
|
|
144
|
+
message: `OpenAPI spec "${reference.spec}" for ${reference.route} declares no operations; its API reference is empty.`,
|
|
145
|
+
severity: "warning" as const,
|
|
146
|
+
suggestion:
|
|
147
|
+
"Check the spec points at an OpenAPI document with operations under `paths`.",
|
|
148
|
+
},
|
|
149
|
+
]
|
|
150
|
+
: []),
|
|
151
|
+
],
|
|
116
152
|
entries: specEntries(spec, operations),
|
|
117
153
|
slug: reference.slug,
|
|
118
154
|
spec,
|
|
@@ -125,8 +161,12 @@ export const openApiSource = (
|
|
|
125
161
|
// so fail loudly in build (blocks under --strict) while staying a warning
|
|
126
162
|
// in dev so offline work still runs.
|
|
127
163
|
severity: ctx.mode === "build" ? "error" : "warning",
|
|
164
|
+
// A readable-but-invalid file is a content problem, not a network one;
|
|
165
|
+
// only point at reachability for actual fetch/read failures.
|
|
128
166
|
suggestion:
|
|
129
|
-
|
|
167
|
+
error instanceof InvalidSpecError
|
|
168
|
+
? "Point the spec at an OpenAPI document (a YAML or JSON file with an object at the top level)."
|
|
169
|
+
: "Check the spec URL/path is reachable from the build environment; behind a proxy, set HTTP(S)_PROXY.",
|
|
130
170
|
};
|
|
131
171
|
}
|
|
132
172
|
};
|
|
@@ -134,7 +174,16 @@ export const openApiSource = (
|
|
|
134
174
|
const load = async (): Promise<SourceLoadResult> => {
|
|
135
175
|
const results = await Promise.all(references.map(loadReference));
|
|
136
176
|
const entries: SourceEntry[] = [];
|
|
137
|
-
|
|
177
|
+
// Route collisions recorded while deduping (see `blumeReferences`): a
|
|
178
|
+
// dropped source loses a whole spec's pages, so warn even when the kept
|
|
179
|
+
// spec loads cleanly.
|
|
180
|
+
const diagnostics: Diagnostic[] = references.flatMap((reference) =>
|
|
181
|
+
(reference.collisions ?? []).map((message) => ({
|
|
182
|
+
code: "BLUME_OPENAPI_ROUTE_COLLISION",
|
|
183
|
+
message,
|
|
184
|
+
severity: "warning" as const,
|
|
185
|
+
}))
|
|
186
|
+
);
|
|
138
187
|
const data: OpenApiData = {};
|
|
139
188
|
for (const result of results) {
|
|
140
189
|
if ("severity" in result) {
|