blume 0.4.0 → 0.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/dist/cli/index.js +1137 -722
- package/dist/cli/index.js.map +28 -23
- package/dist/types/core/data.d.ts +2 -0
- package/dist/types/core/project.d.ts +12 -2
- package/dist/types/core/schema.d.ts +154 -15
- package/dist/types/core/types.d.ts +7 -0
- package/docs/advanced/api-reference.mdx +33 -23
- package/docs/advanced/bridge.mdx +74 -0
- package/docs/advanced/meta.ts +8 -1
- package/docs/advanced/migrate.mdx +119 -0
- package/docs/configuration/index.mdx +1 -1
- package/docs/content/components.mdx +55 -2
- package/docs/content/i18n.mdx +1 -1
- package/docs/content/syntax.mdx +2 -2
- package/docs/index.mdx +2 -2
- package/docs/reference/cli.mdx +29 -1
- package/docs/reference/frontmatter.mdx +5 -0
- package/package.json +11 -1
- package/src/astro/generate.ts +18 -8
- package/src/astro/templates.ts +28 -4
- package/src/cli/commands/build.ts +107 -63
- package/src/cli/commands/check.ts +20 -0
- package/src/cli/dev-lock.ts +13 -5
- package/src/cli/prepare.ts +3 -0
- package/src/components/BlumePage.astro +6 -0
- package/src/components/Icon.astro +13 -10
- package/src/components/content/ApiField.astro +75 -0
- package/src/components/content/ParamField.astro +39 -0
- package/src/components/content/RequestField.astro +23 -0
- package/src/components/content/ResponseField.astro +23 -0
- package/src/components/content/Step.astro +1 -1
- package/src/components/layout/Breadcrumbs.astro +7 -2
- package/src/components/layout/NavTree.astro +24 -8
- package/src/components/layout/RootLayout.astro +56 -34
- package/src/components/layout/Search.astro +1 -1
- package/src/components/openapi/ApiOverview.astro +84 -0
- package/src/components/openapi/MethodBadge.astro +28 -0
- package/src/components/openapi/Operation.astro +140 -0
- package/src/components/openapi/ParametersTable.astro +97 -0
- package/src/components/openapi/RequestBody.astro +58 -0
- package/src/components/openapi/RequestPanel.astro +169 -0
- package/src/components/openapi/Responses.astro +91 -0
- package/src/components/openapi/SchemaProperty.astro +118 -0
- package/src/components/openapi/SchemaTable.astro +86 -0
- package/src/components/openapi/helpers.ts +238 -0
- package/src/components/openapi/panel.ts +59 -0
- package/src/components/openapi/snippets.ts +201 -0
- package/src/core/builtin-tags.ts +5 -0
- package/src/core/data.ts +2 -0
- package/src/core/project-graph.ts +5 -1
- package/src/core/project.ts +25 -3
- package/src/core/schema.ts +47 -6
- package/src/core/sources/mintlify.ts +1 -1
- package/src/core/sources/resolve.ts +28 -6
- package/src/core/types.ts +7 -0
- package/src/migrate/mintlify/config.ts +153 -1
- package/src/migrate/mintlify/content.ts +8 -2
- package/src/migrate/mintlify/index.ts +58 -1
- package/src/openapi/model.ts +174 -0
- package/src/openapi/parse.ts +48 -0
- package/src/openapi/references.ts +164 -0
- package/src/openapi/render-mdx.ts +76 -0
- package/src/openapi/scalar.ts +15 -103
- package/src/openapi/source.ts +140 -0
- package/src/registry/eject.ts +15 -2
- package/src/theme/chrome-icons.ts +22 -0
- package/src/theme/icons.ts +151 -161
|
@@ -6,6 +6,8 @@ import { defineCommand } from "citty";
|
|
|
6
6
|
import { join } from "pathe";
|
|
7
7
|
|
|
8
8
|
import { buildLlmsFiles } from "../../ai/llms.ts";
|
|
9
|
+
import { ensureGitignore } from "../../core/gitignore.ts";
|
|
10
|
+
import type { BlumeProject } from "../../core/project-graph.ts";
|
|
9
11
|
import type { ResolvedConfig } from "../../core/schema.ts";
|
|
10
12
|
import { serverFeatures } from "../../core/server-features.ts";
|
|
11
13
|
import {
|
|
@@ -169,6 +171,82 @@ const enforceBudget = async (
|
|
|
169
171
|
return passed ? "pass" : "fail";
|
|
170
172
|
};
|
|
171
173
|
|
|
174
|
+
/**
|
|
175
|
+
* Run every deploy post-step of a real (non-isolated) build: the search index +
|
|
176
|
+
* hosted-provider sync, llms.txt, sitemap/robots, redirect files, the summary
|
|
177
|
+
* box, and the optional bundle report / budget gate. Exits non-zero if a budget
|
|
178
|
+
* is exceeded. Isolated verify builds skip all of this.
|
|
179
|
+
*/
|
|
180
|
+
const publishBuildArtifacts = async (
|
|
181
|
+
project: BlumeProject,
|
|
182
|
+
distDir: string,
|
|
183
|
+
args: { analyze?: boolean; "budget-css"?: string; "budget-js"?: string }
|
|
184
|
+
): Promise<void> => {
|
|
185
|
+
if (project.config.search.provider === "pagefind") {
|
|
186
|
+
logger.start("Building search index");
|
|
187
|
+
const indexed = await buildSearchIndex(distDir);
|
|
188
|
+
logger.success(`Indexed ${indexed} page(s) for search`);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Upload the index to a hosted provider (Algolia, Orama Cloud, Typesense).
|
|
192
|
+
// Skipped with a warning when its admin key isn't configured.
|
|
193
|
+
await syncSearchProvider(project, {
|
|
194
|
+
start: (message) => logger.start(message),
|
|
195
|
+
success: (message) => logger.success(message),
|
|
196
|
+
warn: (message) => logger.warn(message),
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
if (project.config.ai.llmsTxt) {
|
|
200
|
+
const { index, full } = await buildLlmsFiles(project);
|
|
201
|
+
await Promise.all([
|
|
202
|
+
writeFile(join(distDir, "llms.txt"), index, "utf-8"),
|
|
203
|
+
writeFile(join(distDir, "llms-full.txt"), full, "utf-8"),
|
|
204
|
+
]);
|
|
205
|
+
logger.success("Generated llms.txt and llms-full.txt");
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// A user's own public/ file (copied into dist by Astro) always wins.
|
|
209
|
+
const sitemap = buildSitemap(project);
|
|
210
|
+
if (sitemap && !existsSync(join(distDir, "sitemap.xml"))) {
|
|
211
|
+
await writeFile(join(distDir, "sitemap.xml"), sitemap, "utf-8");
|
|
212
|
+
logger.success("Generated sitemap.xml");
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
const robots = buildRobots(project);
|
|
216
|
+
if (robots && !existsSync(join(distDir, "robots.txt"))) {
|
|
217
|
+
await writeFile(join(distDir, "robots.txt"), robots, "utf-8");
|
|
218
|
+
logger.success("Generated robots.txt");
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
await emitRedirectFiles(project.config, distDir);
|
|
222
|
+
|
|
223
|
+
const { config } = project;
|
|
224
|
+
const features = serverFeatures(config);
|
|
225
|
+
logger.box(
|
|
226
|
+
[
|
|
227
|
+
`Output ${config.deployment.output}`,
|
|
228
|
+
`Adapter ${config.deployment.adapter ?? "none"}`,
|
|
229
|
+
`Site ${config.deployment.site ?? "not set"}`,
|
|
230
|
+
`Search ${config.search.provider}`,
|
|
231
|
+
`Redirects ${config.redirects.length}`,
|
|
232
|
+
`Sitemap ${sitemap ? "yes" : "no (set deployment.site)"}`,
|
|
233
|
+
`Robots ${robots ? "yes" : "no"}`,
|
|
234
|
+
`LLM files ${config.ai.llmsTxt ? "yes" : "no"}`,
|
|
235
|
+
`Server features ${features.length > 0 ? features.join(", ") : "none"}`,
|
|
236
|
+
].join("\n")
|
|
237
|
+
);
|
|
238
|
+
|
|
239
|
+
if (args.analyze) {
|
|
240
|
+
await reportBundleSizes(distDir);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
if ((await enforceBudget(distDir, args)) === "fail") {
|
|
244
|
+
process.exit(1);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
logger.success(`Built to ${distDir}`);
|
|
248
|
+
};
|
|
249
|
+
|
|
172
250
|
export const buildCommand = defineCommand({
|
|
173
251
|
args: {
|
|
174
252
|
adapter: {
|
|
@@ -191,6 +269,11 @@ export const buildCommand = defineCommand({
|
|
|
191
269
|
description: "Fail if total client JavaScript exceeds this many kB.",
|
|
192
270
|
type: "string",
|
|
193
271
|
},
|
|
272
|
+
isolated: {
|
|
273
|
+
description:
|
|
274
|
+
"Build into an isolated .blume-verify runtime (and its own dist) so a running dev server and the real dist/ are untouched. For verifying changes while `blume dev` runs.",
|
|
275
|
+
type: "boolean",
|
|
276
|
+
},
|
|
194
277
|
output: {
|
|
195
278
|
description: "Output mode: static | server.",
|
|
196
279
|
type: "string",
|
|
@@ -207,7 +290,18 @@ export const buildCommand = defineCommand({
|
|
|
207
290
|
},
|
|
208
291
|
async run({ args }) {
|
|
209
292
|
const root = process.cwd();
|
|
210
|
-
|
|
293
|
+
|
|
294
|
+
// `--isolated` (or BLUME_RUNTIME_DIR) relocates the whole runtime to a
|
|
295
|
+
// sibling dir so this build never touches a live dev server's `.blume/` or
|
|
296
|
+
// the user's real `dist/`. A non-default runtime dir has no dev lock, so the
|
|
297
|
+
// refusal below lets it proceed; a plain build still refuses.
|
|
298
|
+
const runtimeDir = args.isolated
|
|
299
|
+
? ".blume-verify"
|
|
300
|
+
: process.env.BLUME_RUNTIME_DIR;
|
|
301
|
+
refuseIfDevRunning(root, "building", runtimeDir);
|
|
302
|
+
if (args.isolated) {
|
|
303
|
+
await ensureGitignore(root, [".blume-verify/"]);
|
|
304
|
+
}
|
|
211
305
|
|
|
212
306
|
if (args.output && args.output !== "static" && args.output !== "server") {
|
|
213
307
|
logger.error(`Invalid --output "${args.output}" (use static | server).`);
|
|
@@ -230,6 +324,7 @@ export const buildCommand = defineCommand({
|
|
|
230
324
|
},
|
|
231
325
|
preview: args.preview,
|
|
232
326
|
root,
|
|
327
|
+
runtimeDir,
|
|
233
328
|
strict: args.strict,
|
|
234
329
|
});
|
|
235
330
|
|
|
@@ -242,70 +337,19 @@ export const buildCommand = defineCommand({
|
|
|
242
337
|
root: project.context.outDir,
|
|
243
338
|
});
|
|
244
339
|
|
|
245
|
-
const distDir = join(root, "dist");
|
|
246
|
-
|
|
247
|
-
if (project.config.search.provider === "pagefind") {
|
|
248
|
-
logger.start("Building search index");
|
|
249
|
-
const indexed = await buildSearchIndex(distDir);
|
|
250
|
-
logger.success(`Indexed ${indexed} page(s) for search`);
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
// Upload the index to a hosted provider (Algolia, Orama Cloud, Typesense).
|
|
254
|
-
// Skipped with a warning when its admin key isn't configured.
|
|
255
|
-
await syncSearchProvider(project, {
|
|
256
|
-
start: (message) => logger.start(message),
|
|
257
|
-
success: (message) => logger.success(message),
|
|
258
|
-
warn: (message) => logger.warn(message),
|
|
259
|
-
});
|
|
260
|
-
|
|
261
|
-
if (project.config.ai.llmsTxt) {
|
|
262
|
-
const { index, full } = await buildLlmsFiles(project);
|
|
263
|
-
await Promise.all([
|
|
264
|
-
writeFile(join(distDir, "llms.txt"), index, "utf-8"),
|
|
265
|
-
writeFile(join(distDir, "llms-full.txt"), full, "utf-8"),
|
|
266
|
-
]);
|
|
267
|
-
logger.success("Generated llms.txt and llms-full.txt");
|
|
268
|
-
}
|
|
340
|
+
const distDir = project.context.distDir ?? join(root, "dist");
|
|
269
341
|
|
|
270
|
-
//
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
await writeFile(join(distDir, "robots.txt"), robots, "utf-8");
|
|
280
|
-
logger.success("Generated robots.txt");
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
await emitRedirectFiles(project.config, distDir);
|
|
284
|
-
|
|
285
|
-
const { config } = project;
|
|
286
|
-
const features = serverFeatures(config);
|
|
287
|
-
logger.box(
|
|
288
|
-
[
|
|
289
|
-
`Output ${config.deployment.output}`,
|
|
290
|
-
`Adapter ${config.deployment.adapter ?? "none"}`,
|
|
291
|
-
`Site ${config.deployment.site ?? "not set"}`,
|
|
292
|
-
`Search ${config.search.provider}`,
|
|
293
|
-
`Redirects ${config.redirects.length}`,
|
|
294
|
-
`Sitemap ${sitemap ? "yes" : "no (set deployment.site)"}`,
|
|
295
|
-
`Robots ${robots ? "yes" : "no"}`,
|
|
296
|
-
`LLM files ${config.ai.llmsTxt ? "yes" : "no"}`,
|
|
297
|
-
`Server features ${features.length > 0 ? features.join(", ") : "none"}`,
|
|
298
|
-
].join("\n")
|
|
299
|
-
);
|
|
300
|
-
|
|
301
|
-
if (args.analyze) {
|
|
302
|
-
await reportBundleSizes(distDir);
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
if ((await enforceBudget(distDir, args)) === "fail") {
|
|
306
|
-
process.exit(1);
|
|
342
|
+
// An isolated build is a throwaway verify: it only needs to confirm the site
|
|
343
|
+
// compiles and renders. Skip the network post-steps (search sync) and
|
|
344
|
+
// deploy artifacts (index/llms/sitemap/robots/redirects) that only matter
|
|
345
|
+
// for a real publish and would push to hosted providers.
|
|
346
|
+
if (runtimeDir) {
|
|
347
|
+
logger.success(
|
|
348
|
+
`Isolated build OK — output at ${distDir} (not published).`
|
|
349
|
+
);
|
|
350
|
+
return;
|
|
307
351
|
}
|
|
308
352
|
|
|
309
|
-
|
|
353
|
+
await publishBuildArtifacts(project, distDir, args);
|
|
310
354
|
},
|
|
311
355
|
});
|
|
@@ -5,11 +5,18 @@ import { sync } from "astro";
|
|
|
5
5
|
import { defineCommand } from "citty";
|
|
6
6
|
import { join } from "pathe";
|
|
7
7
|
|
|
8
|
+
import { ensureGitignore } from "../../core/gitignore.ts";
|
|
9
|
+
import { refuseIfDevRunning } from "../dev-lock.ts";
|
|
8
10
|
import { logger } from "../log.ts";
|
|
9
11
|
import { prepareProject } from "../prepare.ts";
|
|
10
12
|
|
|
11
13
|
export const checkCommand = defineCommand({
|
|
12
14
|
args: {
|
|
15
|
+
isolated: {
|
|
16
|
+
description:
|
|
17
|
+
"Type-check in an isolated .blume-verify runtime so a running dev server is untouched. For verifying changes while `blume dev` runs.",
|
|
18
|
+
type: "boolean",
|
|
19
|
+
},
|
|
13
20
|
preview: {
|
|
14
21
|
description: "Include drafts and unpublished CMS content.",
|
|
15
22
|
type: "boolean",
|
|
@@ -25,10 +32,23 @@ export const checkCommand = defineCommand({
|
|
|
25
32
|
},
|
|
26
33
|
async run({ args }) {
|
|
27
34
|
const root = process.cwd();
|
|
35
|
+
|
|
36
|
+
// `blume check` regenerates `.blume` just like `build`, so it must refuse a
|
|
37
|
+
// live dev server unless isolated. `--isolated` (or BLUME_RUNTIME_DIR)
|
|
38
|
+
// relocates the runtime to `.blume-verify`, which dev never locks.
|
|
39
|
+
const runtimeDir = args.isolated
|
|
40
|
+
? ".blume-verify"
|
|
41
|
+
: process.env.BLUME_RUNTIME_DIR;
|
|
42
|
+
refuseIfDevRunning(root, "checking", runtimeDir);
|
|
43
|
+
if (args.isolated) {
|
|
44
|
+
await ensureGitignore(root, [".blume-verify/"]);
|
|
45
|
+
}
|
|
46
|
+
|
|
28
47
|
const project = await prepareProject({
|
|
29
48
|
mode: "build",
|
|
30
49
|
preview: args.preview,
|
|
31
50
|
root,
|
|
51
|
+
runtimeDir,
|
|
32
52
|
strict: args.strict,
|
|
33
53
|
});
|
|
34
54
|
|
package/src/cli/dev-lock.ts
CHANGED
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
|
|
9
9
|
import { join } from "pathe";
|
|
10
10
|
|
|
11
|
+
import { resolveRuntimeDir } from "../core/project.ts";
|
|
11
12
|
import { logger } from "./log.ts";
|
|
12
13
|
|
|
13
14
|
/**
|
|
@@ -71,13 +72,20 @@ export const acquireDevLock = (outDir: string): (() => void) => {
|
|
|
71
72
|
};
|
|
72
73
|
|
|
73
74
|
/**
|
|
74
|
-
* Exit with an error when a live `blume dev` owns the
|
|
75
|
-
* `action` names the operation being refused (e.g. "building").
|
|
75
|
+
* Exit with an error when a live `blume dev` owns the runtime dir under `root`.
|
|
76
|
+
* `action` names the operation being refused (e.g. "building"). `runtimeDir`
|
|
77
|
+
* relocates the checked dir: an isolated verify (`.blume-verify`) targets a dir
|
|
78
|
+
* dev never locks, so it proceeds; a default or `--runtime-dir .blume` run still
|
|
79
|
+
* refuses.
|
|
76
80
|
*/
|
|
77
|
-
export const refuseIfDevRunning = (
|
|
78
|
-
|
|
81
|
+
export const refuseIfDevRunning = (
|
|
82
|
+
root: string,
|
|
83
|
+
action: string,
|
|
84
|
+
runtimeDir?: string
|
|
85
|
+
): void => {
|
|
86
|
+
if (isDevLocked(resolveRuntimeDir(root, runtimeDir))) {
|
|
79
87
|
logger.error(
|
|
80
|
-
`A \`blume dev\` server is running against .blume; ${action} would corrupt it. Stop the dev server
|
|
88
|
+
`A \`blume dev\` server is running against .blume; ${action} would corrupt it. Stop the dev server, or re-run with --isolated to build/verify against .blume-verify without touching it.`
|
|
81
89
|
);
|
|
82
90
|
process.exit(1);
|
|
83
91
|
}
|
package/src/cli/prepare.ts
CHANGED
|
@@ -24,6 +24,8 @@ export interface PrepareOptions {
|
|
|
24
24
|
refresh?: boolean;
|
|
25
25
|
/** CLI config overrides (e.g. `--output`, `--content-dir`). */
|
|
26
26
|
overrides?: ConfigOverrides;
|
|
27
|
+
/** Relocate the generated runtime (e.g. `.blume-verify` for `--isolated`). */
|
|
28
|
+
runtimeDir?: string;
|
|
27
29
|
}
|
|
28
30
|
|
|
29
31
|
/**
|
|
@@ -45,6 +47,7 @@ export const prepareProject = async (
|
|
|
45
47
|
overrides: options.overrides,
|
|
46
48
|
preview: options.preview,
|
|
47
49
|
refresh: options.refresh,
|
|
50
|
+
runtimeDir: options.runtimeDir,
|
|
48
51
|
});
|
|
49
52
|
} catch (error) {
|
|
50
53
|
if (error instanceof BlumeError) {
|
|
@@ -38,7 +38,10 @@ import FileTree from "./content/FileTree.astro";
|
|
|
38
38
|
import Frame from "./content/Frame.astro";
|
|
39
39
|
import GithubInfo from "./content/GithubInfo.astro";
|
|
40
40
|
import Panel from "./content/Panel.astro";
|
|
41
|
+
import ParamField from "./content/ParamField.astro";
|
|
41
42
|
import Prompt from "./content/Prompt.astro";
|
|
43
|
+
import RequestField from "./content/RequestField.astro";
|
|
44
|
+
import ResponseField from "./content/ResponseField.astro";
|
|
42
45
|
import Step from "./content/Step.astro";
|
|
43
46
|
import Steps from "./content/Steps.astro";
|
|
44
47
|
import Tab from "./content/Tab.astro";
|
|
@@ -87,7 +90,10 @@ const components = {
|
|
|
87
90
|
GithubInfo,
|
|
88
91
|
Icon,
|
|
89
92
|
Panel,
|
|
93
|
+
ParamField,
|
|
90
94
|
Prompt,
|
|
95
|
+
RequestField,
|
|
96
|
+
ResponseField,
|
|
91
97
|
Step,
|
|
92
98
|
Steps,
|
|
93
99
|
Tab,
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
---
|
|
2
|
+
import data from "blume:data";
|
|
2
3
|
import { resolveIcon } from "../theme/icons.ts";
|
|
3
4
|
|
|
4
5
|
interface Props {
|
|
5
6
|
name?: unknown;
|
|
6
7
|
icon?: unknown;
|
|
7
8
|
iconType?: string;
|
|
9
|
+
/** Icon library for a bare name; defaults to the project's `icons.library`. */
|
|
10
|
+
library?: string;
|
|
8
11
|
size?: number;
|
|
9
12
|
class?: string;
|
|
10
13
|
className?: string;
|
|
@@ -16,6 +19,7 @@ const {
|
|
|
16
19
|
color,
|
|
17
20
|
icon,
|
|
18
21
|
iconType,
|
|
22
|
+
library,
|
|
19
23
|
name,
|
|
20
24
|
size = 16,
|
|
21
25
|
class: astroClass,
|
|
@@ -37,8 +41,12 @@ const isImageIcon = (value: string): boolean =>
|
|
|
37
41
|
/\.(?:avif|gif|jpe?g|png|svg|webp)$/iu.test(value);
|
|
38
42
|
const imageSrc = iconName && !rawSvg && isImageIcon(iconName) ? iconName : null;
|
|
39
43
|
const resolvedIcon =
|
|
40
|
-
iconName && !(imageSrc || rawSvg)
|
|
41
|
-
|
|
44
|
+
iconName && !(imageSrc || rawSvg)
|
|
45
|
+
? resolveIcon(iconName, {
|
|
46
|
+
iconType,
|
|
47
|
+
library: library ?? data.config.icons.library,
|
|
48
|
+
})
|
|
49
|
+
: null;
|
|
42
50
|
const resolvedClass = astroClass ?? className;
|
|
43
51
|
const customStyle = `display:inline-flex;width:${size}px;height:${size}px;${
|
|
44
52
|
color ? `color:${color}` : ""
|
|
@@ -76,21 +84,16 @@ const style = color ? `color:${color}` : undefined;
|
|
|
76
84
|
width={size}
|
|
77
85
|
/>
|
|
78
86
|
) : (
|
|
79
|
-
|
|
87
|
+
resolvedIcon && (
|
|
80
88
|
<svg
|
|
81
89
|
aria-hidden={label ? undefined : "true"}
|
|
82
90
|
aria-label={label}
|
|
83
91
|
class={resolvedClass}
|
|
84
|
-
fill="none"
|
|
85
92
|
height={size}
|
|
86
93
|
role={label ? "img" : undefined}
|
|
87
|
-
set:html={
|
|
88
|
-
stroke="currentColor"
|
|
89
|
-
stroke-linecap="round"
|
|
90
|
-
stroke-linejoin="round"
|
|
91
|
-
stroke-width="2"
|
|
94
|
+
set:html={resolvedIcon.body}
|
|
92
95
|
style={style}
|
|
93
|
-
viewBox=
|
|
96
|
+
viewBox={resolvedIcon.viewBox}
|
|
94
97
|
width={size}
|
|
95
98
|
xmlns="http://www.w3.org/2000/svg"
|
|
96
99
|
/>
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
---
|
|
2
|
+
// Shared field row behind the Mintlify-compat <ParamField>/<ResponseField>/
|
|
3
|
+
// <RequestField> components: a labeled name + type + badges, with the field's
|
|
4
|
+
// description rendered from the slot (which may hold rich MDX, including nested
|
|
5
|
+
// <Expandable> or further fields). Styled to match the native OpenAPI reference
|
|
6
|
+
// rows (src/components/openapi/), so a migrated site reads consistently.
|
|
7
|
+
interface Props {
|
|
8
|
+
default?: unknown;
|
|
9
|
+
deprecated?: unknown;
|
|
10
|
+
location?: string;
|
|
11
|
+
name?: string;
|
|
12
|
+
required?: unknown;
|
|
13
|
+
type?: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const {
|
|
17
|
+
default: defaultValue,
|
|
18
|
+
deprecated,
|
|
19
|
+
location,
|
|
20
|
+
name,
|
|
21
|
+
required,
|
|
22
|
+
type,
|
|
23
|
+
} = Astro.props;
|
|
24
|
+
|
|
25
|
+
// Booleans arrive as MDX shorthand (`required`) or strings (`required="true"`).
|
|
26
|
+
const isTrue = (value: unknown): boolean => value === true || value === "true";
|
|
27
|
+
const hasDefault =
|
|
28
|
+
defaultValue !== undefined && defaultValue !== null && defaultValue !== "";
|
|
29
|
+
const hasDescription = Astro.slots.has("default");
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
<div class="not-prose my-2 rounded-blume border border-border px-4 py-3">
|
|
33
|
+
<div class="flex flex-wrap items-baseline gap-x-2 gap-y-1">
|
|
34
|
+
{name && <code class="font-mono text-foreground text-sm">{name}</code>}
|
|
35
|
+
{
|
|
36
|
+
location && (
|
|
37
|
+
<span class="rounded bg-muted px-1.5 py-0.5 font-medium text-[0.625rem] text-muted-foreground uppercase tracking-wide">
|
|
38
|
+
{location}
|
|
39
|
+
</span>
|
|
40
|
+
)
|
|
41
|
+
}
|
|
42
|
+
{type && <span class="text-muted-foreground text-xs">{type}</span>}
|
|
43
|
+
{
|
|
44
|
+
isTrue(required) && (
|
|
45
|
+
<span class="font-medium text-[0.625rem] text-red-600 uppercase tracking-wide dark:text-red-400">
|
|
46
|
+
required
|
|
47
|
+
</span>
|
|
48
|
+
)
|
|
49
|
+
}
|
|
50
|
+
{
|
|
51
|
+
isTrue(deprecated) && (
|
|
52
|
+
<span class="font-medium text-[0.625rem] text-muted-foreground uppercase tracking-wide line-through">
|
|
53
|
+
deprecated
|
|
54
|
+
</span>
|
|
55
|
+
)
|
|
56
|
+
}
|
|
57
|
+
</div>
|
|
58
|
+
{
|
|
59
|
+
hasDefault && (
|
|
60
|
+
<div class="mt-1 text-muted-foreground text-xs">
|
|
61
|
+
Default:{" "}
|
|
62
|
+
<code class="rounded bg-muted px-1 py-0.5 text-foreground">
|
|
63
|
+
{String(defaultValue)}
|
|
64
|
+
</code>
|
|
65
|
+
</div>
|
|
66
|
+
)
|
|
67
|
+
}
|
|
68
|
+
{
|
|
69
|
+
hasDescription && (
|
|
70
|
+
<div class="mt-1.5 text-muted-foreground text-sm [&>:first-child]:mt-0 [&>:last-child]:mb-0">
|
|
71
|
+
<slot />
|
|
72
|
+
</div>
|
|
73
|
+
)
|
|
74
|
+
}
|
|
75
|
+
</div>
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
---
|
|
2
|
+
// Mintlify-compat <ParamField>. Mintlify encodes the parameter's location in the
|
|
3
|
+
// attribute name (`<ParamField path="id" />`, `query`, `header`, `body`); the
|
|
4
|
+
// attribute's value is the field name. Falls back to a plain `name` prop.
|
|
5
|
+
import ApiField from "./ApiField.astro";
|
|
6
|
+
|
|
7
|
+
const {
|
|
8
|
+
body,
|
|
9
|
+
default: defaultValue,
|
|
10
|
+
deprecated,
|
|
11
|
+
header,
|
|
12
|
+
name,
|
|
13
|
+
path,
|
|
14
|
+
query,
|
|
15
|
+
required,
|
|
16
|
+
type,
|
|
17
|
+
} = Astro.props;
|
|
18
|
+
|
|
19
|
+
const locations = [
|
|
20
|
+
{ key: "path", value: path },
|
|
21
|
+
{ key: "query", value: query },
|
|
22
|
+
{ key: "header", value: header },
|
|
23
|
+
{ key: "body", value: body },
|
|
24
|
+
] as const;
|
|
25
|
+
const located = locations.find((entry) => typeof entry.value === "string");
|
|
26
|
+
const fieldName = located ? located.value : name;
|
|
27
|
+
const location = located?.key;
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
<ApiField
|
|
31
|
+
default={defaultValue}
|
|
32
|
+
deprecated={deprecated}
|
|
33
|
+
location={location}
|
|
34
|
+
name={fieldName}
|
|
35
|
+
required={required}
|
|
36
|
+
type={type}
|
|
37
|
+
>
|
|
38
|
+
<slot />
|
|
39
|
+
</ApiField>
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
---
|
|
2
|
+
// Mintlify-compat <RequestField>: same shape as <ResponseField> — a named field
|
|
3
|
+
// (`name`, `type`, `required`, `default`, `deprecated`) with a slotted body.
|
|
4
|
+
import ApiField from "./ApiField.astro";
|
|
5
|
+
|
|
6
|
+
const {
|
|
7
|
+
default: defaultValue,
|
|
8
|
+
deprecated,
|
|
9
|
+
name,
|
|
10
|
+
required,
|
|
11
|
+
type,
|
|
12
|
+
} = Astro.props;
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
<ApiField
|
|
16
|
+
default={defaultValue}
|
|
17
|
+
deprecated={deprecated}
|
|
18
|
+
name={name}
|
|
19
|
+
required={required}
|
|
20
|
+
type={type}
|
|
21
|
+
>
|
|
22
|
+
<slot />
|
|
23
|
+
</ApiField>
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
---
|
|
2
|
+
// Mintlify-compat <ResponseField>: a named field (`name`, `type`, `required`,
|
|
3
|
+
// `default`, `deprecated`) with its description in the slot.
|
|
4
|
+
import ApiField from "./ApiField.astro";
|
|
5
|
+
|
|
6
|
+
const {
|
|
7
|
+
default: defaultValue,
|
|
8
|
+
deprecated,
|
|
9
|
+
name,
|
|
10
|
+
required,
|
|
11
|
+
type,
|
|
12
|
+
} = Astro.props;
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
<ApiField
|
|
16
|
+
default={defaultValue}
|
|
17
|
+
deprecated={deprecated}
|
|
18
|
+
name={name}
|
|
19
|
+
required={required}
|
|
20
|
+
type={type}
|
|
21
|
+
>
|
|
22
|
+
<slot />
|
|
23
|
+
</ApiField>
|
|
@@ -4,9 +4,11 @@ import type { Crumb } from "./nav-utils.ts";
|
|
|
4
4
|
interface Props {
|
|
5
5
|
/** Full breadcrumb trail from the site root to the current page. */
|
|
6
6
|
crumbs: Crumb[];
|
|
7
|
+
/** Left-align full width (for the wide API layout) instead of the prose measure. */
|
|
8
|
+
wide?: boolean;
|
|
7
9
|
}
|
|
8
10
|
|
|
9
|
-
const { crumbs } = Astro.props;
|
|
11
|
+
const { crumbs, wide = false } = Astro.props;
|
|
10
12
|
|
|
11
13
|
// The built-in shows a single "eyebrow" crumb — the parent group — rather than
|
|
12
14
|
// the whole trail. An override receives the complete list and can render more.
|
|
@@ -17,7 +19,10 @@ const eyebrowCrumb = crumbs.length > 1 ? crumbs[crumbs.length - 2] : null;
|
|
|
17
19
|
eyebrowCrumb && (
|
|
18
20
|
<nav
|
|
19
21
|
aria-label="Breadcrumb"
|
|
20
|
-
class=
|
|
22
|
+
class:list={[
|
|
23
|
+
"mb-2 text-muted-foreground text-sm",
|
|
24
|
+
wide ? "max-w-none" : "mx-auto max-w-[42rem]",
|
|
25
|
+
]}
|
|
21
26
|
>
|
|
22
27
|
{eyebrowCrumb.route ? (
|
|
23
28
|
<a class="hover:text-foreground" href={eyebrowCrumb.route}>
|
|
@@ -31,8 +31,24 @@ const {
|
|
|
31
31
|
root = depth === 0,
|
|
32
32
|
} = Astro.props;
|
|
33
33
|
|
|
34
|
-
const
|
|
35
|
-
"shrink-0 rounded-full
|
|
34
|
+
const badgeBase =
|
|
35
|
+
"shrink-0 rounded-full px-1.5 py-0.5 font-medium text-[0.65rem] leading-none";
|
|
36
|
+
const badgeClass = `${badgeBase} bg-muted text-muted-foreground`;
|
|
37
|
+
// HTTP-method badges (from an OpenAPI reference's sidebar) are colour-coded;
|
|
38
|
+
// every other badge keeps the neutral style.
|
|
39
|
+
const METHOD_BADGE: Record<string, string> = {
|
|
40
|
+
DELETE: "bg-red-500/15 text-red-700 dark:text-red-300",
|
|
41
|
+
GET: "bg-green-500/15 text-green-700 dark:text-green-300",
|
|
42
|
+
HEAD: "bg-muted text-muted-foreground",
|
|
43
|
+
OPTIONS: "bg-muted text-muted-foreground",
|
|
44
|
+
PATCH: "bg-yellow-500/20 text-yellow-800 dark:text-yellow-300",
|
|
45
|
+
POST: "bg-blue-500/15 text-blue-700 dark:text-blue-300",
|
|
46
|
+
PUT: "bg-orange-500/15 text-orange-700 dark:text-orange-300",
|
|
47
|
+
};
|
|
48
|
+
const badgeClassFor = (badge: string): string => {
|
|
49
|
+
const method = METHOD_BADGE[badge.toUpperCase()];
|
|
50
|
+
return method ? `${badgeBase} ${method}` : badgeClass;
|
|
51
|
+
};
|
|
36
52
|
const deprecatedClass =
|
|
37
53
|
"shrink-0 rounded-full bg-amber-500/10 px-1.5 py-0.5 font-medium text-[0.65rem] text-amber-700 leading-none dark:text-amber-300";
|
|
38
54
|
|
|
@@ -154,7 +170,7 @@ const initialId =
|
|
|
154
170
|
/>
|
|
155
171
|
)}
|
|
156
172
|
<span class="min-w-0 flex-1 truncate">{item.label}</span>
|
|
157
|
-
{item.badge && <span class={
|
|
173
|
+
{item.badge && <span class={badgeClassFor(item.badge)}>{item.badge}</span>}
|
|
158
174
|
{item.deprecated && (
|
|
159
175
|
<span class={deprecatedClass}>deprecated</span>
|
|
160
176
|
)}
|
|
@@ -188,7 +204,7 @@ const initialId =
|
|
|
188
204
|
/>
|
|
189
205
|
)}
|
|
190
206
|
<span class="min-w-0 flex-1 truncate">{item.label}</span>
|
|
191
|
-
{item.badge && <span class={
|
|
207
|
+
{item.badge && <span class={badgeClassFor(item.badge)}>{item.badge}</span>}
|
|
192
208
|
<Icon
|
|
193
209
|
class="shrink-0 text-muted-foreground"
|
|
194
210
|
name="chevron-right"
|
|
@@ -224,7 +240,7 @@ const initialId =
|
|
|
224
240
|
)}
|
|
225
241
|
<span class="min-w-0 flex-1 truncate">{item.label}</span>
|
|
226
242
|
{item.badge && (
|
|
227
|
-
<span class={
|
|
243
|
+
<span class={badgeClassFor(item.badge)}>{item.badge}</span>
|
|
228
244
|
)}
|
|
229
245
|
</a>
|
|
230
246
|
) : (
|
|
@@ -238,7 +254,7 @@ const initialId =
|
|
|
238
254
|
)}
|
|
239
255
|
<span class="min-w-0 flex-1 truncate">{item.label}</span>
|
|
240
256
|
{item.badge && (
|
|
241
|
-
<span class={
|
|
257
|
+
<span class={badgeClassFor(item.badge)}>{item.badge}</span>
|
|
242
258
|
)}
|
|
243
259
|
</>
|
|
244
260
|
)}
|
|
@@ -279,7 +295,7 @@ const initialId =
|
|
|
279
295
|
)}
|
|
280
296
|
<span class="min-w-0 truncate">{item.label}</span>
|
|
281
297
|
{item.badge && (
|
|
282
|
-
<span class={`ml-auto ${
|
|
298
|
+
<span class={`ml-auto ${badgeClassFor(item.badge)}`}>{item.badge}</span>
|
|
283
299
|
)}
|
|
284
300
|
</a>
|
|
285
301
|
) : (
|
|
@@ -293,7 +309,7 @@ const initialId =
|
|
|
293
309
|
)}
|
|
294
310
|
<span class="min-w-0 truncate">{item.label}</span>
|
|
295
311
|
{item.badge && (
|
|
296
|
-
<span class={`ml-auto ${
|
|
312
|
+
<span class={`ml-auto ${badgeClassFor(item.badge)}`}>{item.badge}</span>
|
|
297
313
|
)}
|
|
298
314
|
</>
|
|
299
315
|
)}
|