这是indexloc提供的服务,不要输入任何密码
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
1 change: 1 addition & 0 deletions docs/site/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ content/openapi/artifacts/*

# Generated data files
content/examples-data.json
.openapi.json

.source
/public/*.st
Expand Down
83 changes: 3 additions & 80 deletions docs/site/app/api/remote-cache-spec/route.ts
Original file line number Diff line number Diff line change
@@ -1,85 +1,8 @@
import type { OpenAPIV3 } from "openapi-types";
// This file is generated at build time.
import json from "#/.openapi.json";

export const revalidate = 0;

const VERCEL_OPEN_API = "https://openapi.vercel.sh/";
const API_VERSION = "8";
const API_PREFIX = `/v${API_VERSION}/artifacts`;
const INFO: OpenAPIV3.InfoObject = {
title: "Turborepo Remote Cache API",
description:
"Turborepo is an intelligent build system optimized for JavaScript and TypeScript codebases.",
version: `${API_VERSION}.0.0`,
};
const COMPONENTS: OpenAPIV3.ComponentsObject = {
securitySchemes: {
bearerToken: {
type: "http",
description: "Default authentication mechanism",
scheme: "bearer",
},
},
};

async function fetchVercelOpenAPISchema(): Promise<OpenAPIV3.Document> {
const result = await fetch(VERCEL_OPEN_API);
const json = (await result.json()) as OpenAPIV3.Document;

return json;
}

function formatOpenAPISchema(schema: OpenAPIV3.Document): OpenAPIV3.Document {
const paths: OpenAPIV3.PathsObject = {};
for (const [path, methods] of Object.entries(schema.paths)) {
if (path.startsWith(API_PREFIX)) {
paths[path] = methods;
}
}

// replace the paths, info and components
schema.components = COMPONENTS;
schema.info = INFO;
schema.paths = paths;

return schema;
}

function errorResponse({
status,
message,
}: {
status: 400 | 404 | 500;
message: string;
}): Response {
return new Response(
JSON.stringify({
error: message,
}),
{
status,
}
);
}
export async function GET(): Promise<Response> {
try {
const vercelSchema = await fetchVercelOpenAPISchema();
const remoteCacheSchema = formatOpenAPISchema(vercelSchema);

return new Response(JSON.stringify(remoteCacheSchema), {
status: 200,
headers: {
"content-type": "application/json",
// cache for one day, and allow stale responses for one hour
"cache-control": `public, s-maxage=${String(
60 * 60 * 24
)}, stale-while-revalidate=${String(60 * 60)}`,
},
});
} catch (e) {
const error = e as Error;

// eslint-disable-next-line no-console -- We're alright with this.
console.error(error);
return errorResponse({ status: 500, message: error.message });
}
return Response.json(json);
}
105 changes: 104 additions & 1 deletion docs/site/scripts/generate-docs.mjs
Original file line number Diff line number Diff line change
@@ -1,9 +1,112 @@
// The OpenAPI spec for a self-hosted implementation is generated
// from the Vercel Remote Cache implementation.
// The Vercel Remote Cache spec is more specific to the needs of Vercel
// while the self-hosted spec is more open for anyone to implement.
//
// While the two specifications are related enough to use the Vercel Remote Cache
// as the source of truth, the self-hosted Remote Cache has all
// of the same capabilities. Because of this,
// we do some light processing to make sure that the content
// in the self-hosted spec makes sense for self-hosted users.
//
// You can verify differences for the specs by comparing:
// Vercel Remote Cache: https://vercel.com/docs/rest-api/reference/endpoints/artifacts/record-an-artifacts-cache-usage-event
// Self-hosted: https://turbo.build/api/remote-cache-spec

import { writeFileSync } from "node:fs";
import { generateFiles } from "fumadocs-openapi";

const out = "./content/openapi";

/* The Vercel Remote Cache spec has examples that show Vercel values.
* Removing them makes the self-hosted spec easier to use. */
const removeExamples = (obj) => {
if (!obj || typeof obj !== "object") return obj;

if (Array.isArray(obj)) {
return obj.map((item) => removeExamples(item));
}

const result = {};
for (const [key, value] of Object.entries(obj)) {
if (key !== "example") {
result[key] = removeExamples(value);
}
}

return result;
};

/* The Vercel Remote Cache spec has responses related to billing.
* Self-hosted users don't need these. */
function removeBillingRelated403Responses(spec) {
// Define billing-related phrases to filter out
const billingPhrases = [
"The customer has reached their spend cap limit and has been paused",
"The Remote Caching usage limit has been reached for this account",
"Remote Caching has been disabled for this team or user",
];

// Process all paths
for (const path in spec.paths) {
const pathObj = spec.paths[path];

// Process all methods in each path
for (const method in pathObj) {
const methodObj = pathObj[method];

// Check if the method has responses
if (methodObj?.responses["403"]) {
const description = methodObj.responses["403"].description;

// Split the description by newlines
const descriptionLines = description.split("\n");

// Filter out billing-related lines
const filteredLines = descriptionLines.filter((line) => {
return !billingPhrases.some((phrase) => line.includes(phrase));
});

// If there are remaining lines, join them back together
if (filteredLines.length > 0) {
methodObj.responses["403"].description = filteredLines.join("\n");
} else {
// If all lines were billing-related, set a generic permission message
methodObj.responses["403"].description =
"You do not have permission to access this resource.";
}
}
}
}

return spec;
}

const updateServerDescription = (spec) => {
if (spec.servers && spec.servers.length > 0) {
const serverIndex = spec.servers.findIndex(
(server) => server.url === "https://api.vercel.com"
);

if (serverIndex !== -1) {
spec.servers[serverIndex].description =
"Vercel Remote Cache implementation for reference.";
}
return spec;
}
};

const thing = await fetch("https://turbo.build/api/remote-cache-spec")
.then((res) => res.json())
.then((json) => removeExamples(json))
.then((json) => removeBillingRelated403Responses(json))
.then((json) => updateServerDescription(json));

writeFileSync("./.openapi.json", JSON.stringify(thing, null, 2));

void generateFiles({
input: ["https://turbo.build/api/remote-cache-spec"],
input: ["./.openapi.json"],
addGeneratedComment: true,
output: out,
groupBy: "tag",
});
Loading