Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 1 addition & 3 deletions astro.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -149,9 +149,7 @@ export default defineConfig({
tailwind({
applyBaseStyles: false,
}),
liveCode({
layout: "~/components/live-code/Layout.astro",
}),
liveCode({}),
icon(),
sitemap({
filter(page) {
Expand Down
116 changes: 116 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
"devDependencies": {
"@actions/core": "1.11.1",
"@actions/github": "6.0.0",
"@apidevtools/swagger-parser": "10.1.1",
"@astrojs/check": "0.9.4",
"@astrojs/react": "4.2.0",
"@astrojs/rss": "4.0.11",
Expand Down Expand Up @@ -77,6 +78,7 @@
"mdast-util-mdx-expression": "2.0.1",
"mermaid": "11.4.1",
"node-html-parser": "7.0.1",
"openapi-types": "12.1.3",
"parse-duration": "2.1.3",
"prettier": "3.5.2",
"prettier-plugin-astro": "0.14.1",
Expand Down
124 changes: 124 additions & 0 deletions src/components/APIRequest.astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
---
import { z } from "astro:schema";
import { getProperty } from "dot-prop";
import { getSchema } from "~/util/api.ts";
import type { OpenAPIV3 } from "openapi-types";
import CURL from "./CURL.astro";
import Details from "./Details.astro";

type Props = z.input<typeof props>;

const props = z.object({
path: z.string(),
method: z.enum(["GET", "HEAD", "POST", "PUT", "DELETE", "PATCH"]),
json: z.record(z.string(), z.any()).default({}),
});

const { path, method, json } = props.parse(Astro.props);

const schema = await getSchema();

const operation = getProperty(
schema,
`paths.${path}.${method.toLowerCase()}`,
) as unknown as
| OpenAPIV3.OperationObject<{
"x-api-token-group"?: string[];
}>
| undefined;

if (!operation) {
throw new Error(
`[APIRequest] Operation ${method} ${path} not found in schema.`,
);
}

const url = new URL(path, "https://api.cloudflare.com/client/v4");
const headers: Record<string, string> = {};

const segments = url.pathname.split("/").filter(Boolean);
for (const segment of segments) {
const decoded = decodeURIComponent(segment);

if (decoded.startsWith("{") && decoded.endsWith("}")) {
const placeholder = "$" + decoded.slice(1, -1).toUpperCase();

url.pathname = url.pathname.replace(segment, placeholder);
}
}

const security = operation.security as
| OpenAPIV3.SecurityRequirementObject[]
| undefined;

if (security) {
const keys = security.flatMap((requirement) => Object.keys(requirement));

if (keys.includes("api_token")) {
headers["Authorization"] = `Bearer $CLOUDFLARE_API_TOKEN`;
} else if (keys.includes("api_key")) {
headers["X-Auth-Email"] = "$CLOUDFLARE_EMAIL";
headers["X-Auth-Key"] = "$CLOUDFLARE_API_KEY";
}
}

const requestBody = operation?.requestBody as
| OpenAPIV3.RequestBodyObject
| undefined;

const jsonSchema = requestBody?.content?.["application/json"]?.schema as
| OpenAPIV3.SchemaObject
| undefined;

if (!jsonSchema) {
throw new Error(
`[APIRequest] This component currently does not support operations that do not accept JSON bodies.`,
);
}

if (jsonSchema.required) {
const providedProperties = Object.keys(json);
const requiredProperties = jsonSchema.required;

const missingProperties = requiredProperties.filter(
(property) => !providedProperties.includes(property),
);

for (const property of missingProperties) {
const defaultValue =
(jsonSchema.properties?.[property] as OpenAPIV3.SchemaObject).default ??
property;

json[property] = defaultValue;
}
}

const tokenGroups = operation["x-api-token-group"];
---

{
tokenGroups && (
<Details header="Required API token permissions">
<span>
At least one of the following{" "}
<a href="/fundamentals/api/reference/permissions/">token permissions</a>{" "}
is required:
</span>
<ul>
{tokenGroups.map((group) => (
<li>{group}</li>
))}
</ul>
</Details>
)
}

<CURL
url={url.toString()}
method={method}
headers={headers}
json={json}
code={{
title: operation.summary,
}}
/>
38 changes: 38 additions & 0 deletions src/components/CURL.astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
---
import { z } from "astro:schema";
import type { ComponentProps } from "astro/types";
import { Code } from "@astrojs/starlight/components";

type Props = z.input<typeof props>;

const props = z.object({
method: z
.enum(["GET", "HEAD", "POST", "PUT", "DELETE", "OPTIONS", "PATCH"])
.default("GET"),
url: z.string().url(),
headers: z.record(z.string(), z.string()).default({}),
json: z.record(z.string(), z.any()).optional(),
code: z
.custom<Omit<ComponentProps<typeof Code>, "code" | "lang">>()
.optional(),
});

const { method, url, headers, json, code } = props.parse(Astro.props);

const lines = [`curl ${url}`, `\t--request ${method}`];

if (headers) {
for (const [key, value] of Object.entries(headers)) {
lines.push(`\t--header "${key}: ${value}"`);
}
}

if (json) {
const jsonLines = JSON.stringify(json, null, "\t\t").split("\n");
jsonLines[jsonLines.length - 1] = "\t" + jsonLines[jsonLines.length - 1];

lines.push(`\t--json '${jsonLines.join("\n")}'`);
}
---

<Code {...code} lang="bash" code={lines.join(" \\\n")} />
2 changes: 2 additions & 0 deletions src/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@ export { PackageManagers } from "starlight-package-managers";
export { Icon as AstroIcon } from "astro-icon/components";
// Custom components
export { default as AnchorHeading } from "./AnchorHeading.astro";
export { default as APIRequest } from "./APIRequest.astro";
export { default as AvailableNotifications } from "./AvailableNotifications.astro";
export { default as CompatibilityFlag } from "./CompatibilityFlag.astro";
export { default as CompatibilityFlags } from "./CompatibilityFlags.astro";
export { default as CURL } from "./CURL.astro";
export { default as Description } from "./Description.astro";
export { default as Details } from "./Details.astro";
export { default as DirectoryListing } from "./DirectoryListing.astro";
Expand Down
27 changes: 0 additions & 27 deletions src/components/live-code/Layout.astro

This file was deleted.

Loading
Loading