diff --git a/apps/developer-hub/content/docs/openapi/fortuna/chain_ids.mdx b/apps/developer-hub/content/docs/api-reference/entropy/fortuna/chain_ids.mdx similarity index 63% rename from apps/developer-hub/content/docs/openapi/fortuna/chain_ids.mdx rename to apps/developer-hub/content/docs/api-reference/entropy/fortuna/chain_ids.mdx index 48444d5524..ff9dae3b8c 100644 --- a/apps/developer-hub/content/docs/openapi/fortuna/chain_ids.mdx +++ b/apps/developer-hub/content/docs/api-reference/entropy/fortuna/chain_ids.mdx @@ -1,5 +1,5 @@ --- -title: Get the list of supported chain ids +title: "/v1/chains" description: Get the list of supported chain ids full: true _openapi: @@ -14,9 +14,4 @@ _openapi: {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} - + \ No newline at end of file diff --git a/apps/developer-hub/content/docs/openapi/fortuna/explorer.mdx b/apps/developer-hub/content/docs/api-reference/entropy/fortuna/explorer.mdx similarity index 81% rename from apps/developer-hub/content/docs/openapi/fortuna/explorer.mdx rename to apps/developer-hub/content/docs/api-reference/entropy/fortuna/explorer.mdx index 8e8b4fd387..afee2b8223 100644 --- a/apps/developer-hub/content/docs/openapi/fortuna/explorer.mdx +++ b/apps/developer-hub/content/docs/api-reference/entropy/fortuna/explorer.mdx @@ -1,5 +1,5 @@ --- -title: Returns the logs of all requests captured by the keeper. +title: "/v1/logs" description: >- Returns the logs of all requests captured by the keeper. @@ -32,9 +32,4 @@ _openapi: {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} - + \ No newline at end of file diff --git a/apps/developer-hub/content/docs/api-reference/entropy/fortuna/index.mdx b/apps/developer-hub/content/docs/api-reference/entropy/fortuna/index.mdx new file mode 100644 index 0000000000..63a2965035 --- /dev/null +++ b/apps/developer-hub/content/docs/api-reference/entropy/fortuna/index.mdx @@ -0,0 +1,11 @@ +--- +title: Overview +--- + +{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} + + + + + + diff --git a/apps/developer-hub/content/docs/api-reference/entropy/fortuna/meta.json b/apps/developer-hub/content/docs/api-reference/entropy/fortuna/meta.json new file mode 100644 index 0000000000..13ec2badc5 --- /dev/null +++ b/apps/developer-hub/content/docs/api-reference/entropy/fortuna/meta.json @@ -0,0 +1,9 @@ +{ + "title": "Fortuna", + "pages": [ + "index", + "chain_ids", + "revelation", + "explorer" + ] +} diff --git a/apps/developer-hub/content/docs/openapi/fortuna/revelation.mdx b/apps/developer-hub/content/docs/api-reference/entropy/fortuna/revelation.mdx similarity index 82% rename from apps/developer-hub/content/docs/openapi/fortuna/revelation.mdx rename to apps/developer-hub/content/docs/api-reference/entropy/fortuna/revelation.mdx index a3e8711342..4d14f92027 100644 --- a/apps/developer-hub/content/docs/openapi/fortuna/revelation.mdx +++ b/apps/developer-hub/content/docs/api-reference/entropy/fortuna/revelation.mdx @@ -1,5 +1,5 @@ --- -title: Reveal the random value for a given sequence number and blockchain. +title: "/v1/chains/{chain_id}/revelations/{sequence}" description: >- Reveal the random value for a given sequence number and blockchain. @@ -44,11 +44,4 @@ _openapi: {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} - + \ No newline at end of file diff --git a/apps/developer-hub/content/docs/api-reference/entropy/meta.json b/apps/developer-hub/content/docs/api-reference/entropy/meta.json new file mode 100644 index 0000000000..2c9bc438b8 --- /dev/null +++ b/apps/developer-hub/content/docs/api-reference/entropy/meta.json @@ -0,0 +1,6 @@ +{ + "title": "Entropy", + "pages": [ + "fortuna" + ] +} diff --git a/apps/developer-hub/content/docs/api-reference/meta.json b/apps/developer-hub/content/docs/api-reference/meta.json new file mode 100644 index 0000000000..cbf2de12c2 --- /dev/null +++ b/apps/developer-hub/content/docs/api-reference/meta.json @@ -0,0 +1,9 @@ +{ + "root": true, + "title": "API Reference", + "icon": "Code", + "pages": [ + "entropy", + "pyth-core" + ] +} diff --git a/apps/developer-hub/content/docs/api-reference/pyth-core/hermes/get_price_feed.mdx b/apps/developer-hub/content/docs/api-reference/pyth-core/hermes/get_price_feed.mdx new file mode 100644 index 0000000000..9f7af043ad --- /dev/null +++ b/apps/developer-hub/content/docs/api-reference/pyth-core/hermes/get_price_feed.mdx @@ -0,0 +1,33 @@ +--- +title: "/api/get_price_feed" +description: >- + **Deprecated: use /v2/updates/price/{publish_time} instead** + + + Get a price update for a price feed with a specific timestamp + + + Given a price feed id and timestamp, retrieve the Pyth price update closest to + that timestamp. +full: true +_openapi: + method: GET + route: /api/get_price_feed + toc: [] + structuredData: + headings: [] + contents: + - content: >- + **Deprecated: use /v2/updates/price/{publish_time} instead** + + + Get a price update for a price feed with a specific timestamp + + + Given a price feed id and timestamp, retrieve the Pyth price update + closest to that timestamp. +--- + +{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} + + \ No newline at end of file diff --git a/apps/developer-hub/content/docs/api-reference/pyth-core/hermes/get_vaa.mdx b/apps/developer-hub/content/docs/api-reference/pyth-core/hermes/get_vaa.mdx new file mode 100644 index 0000000000..63c0d18297 --- /dev/null +++ b/apps/developer-hub/content/docs/api-reference/pyth-core/hermes/get_vaa.mdx @@ -0,0 +1,33 @@ +--- +title: "/api/get_vaa" +description: >- + **Deprecated: use /v2/updates/price/{publish_time} instead** + + + Get a VAA for a price feed with a specific timestamp + + + Given a price feed id and timestamp, retrieve the Pyth price update closest to + that timestamp. +full: true +_openapi: + method: GET + route: /api/get_vaa + toc: [] + structuredData: + headings: [] + contents: + - content: >- + **Deprecated: use /v2/updates/price/{publish_time} instead** + + + Get a VAA for a price feed with a specific timestamp + + + Given a price feed id and timestamp, retrieve the Pyth price update + closest to that timestamp. +--- + +{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} + + \ No newline at end of file diff --git a/apps/developer-hub/content/docs/api-reference/pyth-core/hermes/get_vaa_ccip.mdx b/apps/developer-hub/content/docs/api-reference/pyth-core/hermes/get_vaa_ccip.mdx new file mode 100644 index 0000000000..a5945007bf --- /dev/null +++ b/apps/developer-hub/content/docs/api-reference/pyth-core/hermes/get_vaa_ccip.mdx @@ -0,0 +1,39 @@ +--- +title: "/api/get_vaa_ccip" +description: >- + **Deprecated: use /v2/updates/price/{publish_time} instead** + + + Get a VAA for a price feed using CCIP + + + This endpoint accepts a single argument which is a hex-encoded byte string of + the following form: + + ` ` +full: true +_openapi: + method: GET + route: /api/get_vaa_ccip + toc: [] + structuredData: + headings: [] + contents: + - content: >- + **Deprecated: use /v2/updates/price/{publish_time} instead** + + + Get a VAA for a price feed using CCIP + + + This endpoint accepts a single argument which is a hex-encoded byte + string of the following form: + + ` ` +--- + +{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} + + \ No newline at end of file diff --git a/apps/developer-hub/content/docs/api-reference/pyth-core/hermes/index.mdx b/apps/developer-hub/content/docs/api-reference/pyth-core/hermes/index.mdx new file mode 100644 index 0000000000..5c5153daa8 --- /dev/null +++ b/apps/developer-hub/content/docs/api-reference/pyth-core/hermes/index.mdx @@ -0,0 +1,20 @@ +--- +title: Overview +--- + +{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} + + + + + + + + + + + + + + + diff --git a/apps/developer-hub/content/docs/api-reference/pyth-core/hermes/latest_price_feeds.mdx b/apps/developer-hub/content/docs/api-reference/pyth-core/hermes/latest_price_feeds.mdx new file mode 100644 index 0000000000..3d4b3fa5af --- /dev/null +++ b/apps/developer-hub/content/docs/api-reference/pyth-core/hermes/latest_price_feeds.mdx @@ -0,0 +1,33 @@ +--- +title: "/api/latest_price_feeds" +description: >- + **Deprecated: use /v2/updates/price/latest instead** + + + Get the latest price updates by price feed id. + + + Given a collection of price feed ids, retrieve the latest Pyth price for each + price feed. +full: true +_openapi: + method: GET + route: /api/latest_price_feeds + toc: [] + structuredData: + headings: [] + contents: + - content: >- + **Deprecated: use /v2/updates/price/latest instead** + + + Get the latest price updates by price feed id. + + + Given a collection of price feed ids, retrieve the latest Pyth price + for each price feed. +--- + +{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} + + \ No newline at end of file diff --git a/apps/developer-hub/content/docs/api-reference/pyth-core/hermes/latest_price_updates.mdx b/apps/developer-hub/content/docs/api-reference/pyth-core/hermes/latest_price_updates.mdx new file mode 100644 index 0000000000..7efae3ad15 --- /dev/null +++ b/apps/developer-hub/content/docs/api-reference/pyth-core/hermes/latest_price_updates.mdx @@ -0,0 +1,27 @@ +--- +title: "/v2/updates/price/latest" +description: >- + Get the latest price updates by price feed id. + + + Given a collection of price feed ids, retrieve the latest Pyth price for each + price feed. +full: true +_openapi: + method: GET + route: /v2/updates/price/latest + toc: [] + structuredData: + headings: [] + contents: + - content: >- + Get the latest price updates by price feed id. + + + Given a collection of price feed ids, retrieve the latest Pyth price + for each price feed. +--- + +{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} + + \ No newline at end of file diff --git a/apps/developer-hub/content/docs/api-reference/pyth-core/hermes/latest_publisher_stake_caps.mdx b/apps/developer-hub/content/docs/api-reference/pyth-core/hermes/latest_publisher_stake_caps.mdx new file mode 100644 index 0000000000..0a80abd6aa --- /dev/null +++ b/apps/developer-hub/content/docs/api-reference/pyth-core/hermes/latest_publisher_stake_caps.mdx @@ -0,0 +1,17 @@ +--- +title: "/v2/updates/publisher_stake_caps/latest" +description: Get the most recent publisher stake caps update data. +full: true +_openapi: + method: GET + route: /v2/updates/publisher_stake_caps/latest + toc: [] + structuredData: + headings: [] + contents: + - content: Get the most recent publisher stake caps update data. +--- + +{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} + + \ No newline at end of file diff --git a/apps/developer-hub/content/docs/api-reference/pyth-core/hermes/latest_twaps.mdx b/apps/developer-hub/content/docs/api-reference/pyth-core/hermes/latest_twaps.mdx new file mode 100644 index 0000000000..b2f61c001a --- /dev/null +++ b/apps/developer-hub/content/docs/api-reference/pyth-core/hermes/latest_twaps.mdx @@ -0,0 +1,27 @@ +--- +title: "/v2/updates/twap/{window_seconds}/latest" +description: >- + Get the latest TWAP by price feed id with a custom time window. + + + Given a collection of price feed ids, retrieve the latest Pyth TWAP price for + each price feed. +full: true +_openapi: + method: GET + route: /v2/updates/twap/{window_seconds}/latest + toc: [] + structuredData: + headings: [] + contents: + - content: >- + Get the latest TWAP by price feed id with a custom time window. + + + Given a collection of price feed ids, retrieve the latest Pyth TWAP + price for each price feed. +--- + +{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} + + \ No newline at end of file diff --git a/apps/developer-hub/content/docs/api-reference/pyth-core/hermes/latest_vaas.mdx b/apps/developer-hub/content/docs/api-reference/pyth-core/hermes/latest_vaas.mdx new file mode 100644 index 0000000000..dc35023480 --- /dev/null +++ b/apps/developer-hub/content/docs/api-reference/pyth-core/hermes/latest_vaas.mdx @@ -0,0 +1,43 @@ +--- +title: "/api/latest_vaas" +description: >- + **Deprecated: use /v2/updates/price/latest instead** + + + Get VAAs for a set of price feed ids. + + + Given a collection of price feed ids, retrieve the latest VAA for each. The + returned VAA(s) can + + be submitted to the Pyth contract to update the on-chain price. If VAAs are + not found for every + + provided price ID the call will fail. +full: true +_openapi: + method: GET + route: /api/latest_vaas + toc: [] + structuredData: + headings: [] + contents: + - content: >- + **Deprecated: use /v2/updates/price/latest instead** + + + Get VAAs for a set of price feed ids. + + + Given a collection of price feed ids, retrieve the latest VAA for + each. The returned VAA(s) can + + be submitted to the Pyth contract to update the on-chain price. If + VAAs are not found for every + + provided price ID the call will fail. +--- + +{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} + + \ No newline at end of file diff --git a/apps/developer-hub/content/docs/api-reference/pyth-core/hermes/meta.json b/apps/developer-hub/content/docs/api-reference/pyth-core/hermes/meta.json new file mode 100644 index 0000000000..8787df25f3 --- /dev/null +++ b/apps/developer-hub/content/docs/api-reference/pyth-core/hermes/meta.json @@ -0,0 +1,18 @@ +{ + "title": "Hermes", + "pages": [ + "index", + "get_price_feed", + "get_vaa", + "get_vaa_ccip", + "latest_price_feeds", + "latest_vaas", + "price_feed_ids", + "price_feeds_metadata", + "latest_price_updates", + "price_stream_sse_handler", + "timestamp_price_updates", + "latest_publisher_stake_caps", + "latest_twaps" + ] +} diff --git a/apps/developer-hub/content/docs/api-reference/pyth-core/hermes/price_feed_ids.mdx b/apps/developer-hub/content/docs/api-reference/pyth-core/hermes/price_feed_ids.mdx new file mode 100644 index 0000000000..9a07572bbc --- /dev/null +++ b/apps/developer-hub/content/docs/api-reference/pyth-core/hermes/price_feed_ids.mdx @@ -0,0 +1,33 @@ +--- +title: "/api/price_feed_ids" +description: >- + **Deprecated: use /v2/price_feeds instead** + + + Get the set of price feed IDs. + + + This endpoint fetches all of the price feed IDs for which price updates can be + retrieved. +full: true +_openapi: + method: GET + route: /api/price_feed_ids + toc: [] + structuredData: + headings: [] + contents: + - content: >- + **Deprecated: use /v2/price_feeds instead** + + + Get the set of price feed IDs. + + + This endpoint fetches all of the price feed IDs for which price + updates can be retrieved. +--- + +{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} + + \ No newline at end of file diff --git a/apps/developer-hub/content/docs/api-reference/pyth-core/hermes/price_feeds_metadata.mdx b/apps/developer-hub/content/docs/api-reference/pyth-core/hermes/price_feeds_metadata.mdx new file mode 100644 index 0000000000..d12730307e --- /dev/null +++ b/apps/developer-hub/content/docs/api-reference/pyth-core/hermes/price_feeds_metadata.mdx @@ -0,0 +1,31 @@ +--- +title: "/v2/price_feeds" +description: >- + Get the set of price feeds. + + + This endpoint fetches all price feeds from the Pyth network. It can be + filtered by asset type + + and query string. +full: true +_openapi: + method: GET + route: /v2/price_feeds + toc: [] + structuredData: + headings: [] + contents: + - content: >- + Get the set of price feeds. + + + This endpoint fetches all price feeds from the Pyth network. It can be + filtered by asset type + + and query string. +--- + +{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} + + \ No newline at end of file diff --git a/apps/developer-hub/content/docs/api-reference/pyth-core/hermes/price_stream_sse_handler.mdx b/apps/developer-hub/content/docs/api-reference/pyth-core/hermes/price_stream_sse_handler.mdx new file mode 100644 index 0000000000..21dceff295 --- /dev/null +++ b/apps/developer-hub/content/docs/api-reference/pyth-core/hermes/price_stream_sse_handler.mdx @@ -0,0 +1,33 @@ +--- +title: "/v2/updates/price/stream" +description: >- + SSE route handler for streaming price updates. + + + The connection will automatically close after 24 hours to prevent resource + leaks. + + Clients should implement reconnection logic to maintain continuous price + updates. +full: true +_openapi: + method: GET + route: /v2/updates/price/stream + toc: [] + structuredData: + headings: [] + contents: + - content: >- + SSE route handler for streaming price updates. + + + The connection will automatically close after 24 hours to prevent + resource leaks. + + Clients should implement reconnection logic to maintain continuous + price updates. +--- + +{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} + + \ No newline at end of file diff --git a/apps/developer-hub/content/docs/api-reference/pyth-core/hermes/timestamp_price_updates.mdx b/apps/developer-hub/content/docs/api-reference/pyth-core/hermes/timestamp_price_updates.mdx new file mode 100644 index 0000000000..297a41392a --- /dev/null +++ b/apps/developer-hub/content/docs/api-reference/pyth-core/hermes/timestamp_price_updates.mdx @@ -0,0 +1,27 @@ +--- +title: "/v2/updates/price/{publish_time}" +description: >- + Get the latest price updates by price feed id. + + + Given a collection of price feed ids, retrieve the latest Pyth price for each + price feed. +full: true +_openapi: + method: GET + route: /v2/updates/price/{publish_time} + toc: [] + structuredData: + headings: [] + contents: + - content: >- + Get the latest price updates by price feed id. + + + Given a collection of price feed ids, retrieve the latest Pyth price + for each price feed. +--- + +{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} + + \ No newline at end of file diff --git a/apps/developer-hub/content/docs/api-reference/pyth-core/meta.json b/apps/developer-hub/content/docs/api-reference/pyth-core/meta.json new file mode 100644 index 0000000000..34888f85e1 --- /dev/null +++ b/apps/developer-hub/content/docs/api-reference/pyth-core/meta.json @@ -0,0 +1,6 @@ +{ + "title": "Pyth Core", + "pages": [ + "hermes" + ] +} diff --git a/apps/developer-hub/content/docs/meta.json b/apps/developer-hub/content/docs/meta.json index 0d89d9cdc8..6af1ac7229 100644 --- a/apps/developer-hub/content/docs/meta.json +++ b/apps/developer-hub/content/docs/meta.json @@ -1,3 +1,3 @@ { - "pages": ["price-feeds", "express-relay", "entropy"] + "pages": ["price-feeds", "express-relay", "entropy", "api-reference"] } diff --git a/apps/developer-hub/content/docs/openapi/fortuna/index.mdx b/apps/developer-hub/content/docs/openapi/fortuna/index.mdx deleted file mode 100644 index 483df2b0b0..0000000000 --- a/apps/developer-hub/content/docs/openapi/fortuna/index.mdx +++ /dev/null @@ -1,23 +0,0 @@ ---- -title: Overview ---- - -{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} - - - - - - diff --git a/apps/developer-hub/scripts/generate-docs.ts b/apps/developer-hub/scripts/generate-docs.ts index 03e0a9ac5f..5e30e11be7 100644 --- a/apps/developer-hub/scripts/generate-docs.ts +++ b/apps/developer-hub/scripts/generate-docs.ts @@ -1,50 +1,314 @@ +import * as fs from "node:fs/promises"; +import path from "node:path"; + import { generateFiles } from "fumadocs-openapi"; +import { createOpenAPI } from "fumadocs-openapi/server"; + +import { products } from "../src/lib/openapi"; -import { openapi, products } from "../src/lib/openapi"; +const outDir = "./content/docs/api-reference/"; -const outDir = "./content/docs/openapi/"; +// Track generated files for each service to build meta.json +const generatedEndpoints: Record = {}; export async function generateDocs() { - await generateFiles({ - input: openapi, - output: outDir, - per: "operation", - name: (output, document) => { - // Extract product name from the OpenAPI document info - const productName = getProductName(document.info.title); - - if (output.type === "operation") { - const operation = - document.paths?.[output.item.path]?.[output.item.method]; - const operationId = - operation?.operationId ?? - output.item.path.replaceAll(/[^a-zA-Z0-9]/g, "_"); - return `${productName}/${operationId}`; - } + // Generate docs separately for each service to ensure index files only contain + // endpoints from that specific service + for (const [serviceName, config] of Object.entries(products)) { + // eslint-disable-next-line no-console + console.log(`\nGenerating docs for ${serviceName}...`); + + generatedEndpoints[serviceName] = []; + + // Create a separate OpenAPI instance for this service + const serviceOpenapi = createOpenAPI({ + input: [config.openApiUrl], + }); - return `${productName}/webhooks/${output.item.name}`; - }, - index: { - url: { - baseUrl: "/openapi/", - contentDir: "./content/docs/openapi", + await generateFiles({ + input: serviceOpenapi, + output: outDir, + per: "operation", + name: (output, document) => { + if (output.type === "operation") { + const operation = + document.paths?.[output.item.path]?.[output.item.method]; + const operationId = + operation?.operationId ?? + output.item.path.replaceAll(/[^a-zA-Z0-9]/g, "_"); + + // Track generated endpoints + generatedEndpoints[serviceName]?.push(operationId); + + // eslint-disable-next-line no-console + console.log(` - ${operationId}`); + return `${config.product}/${serviceName}/${operationId}`; + } + + return `${config.product}/${serviceName}/webhooks/${output.item.name}`; + }, + frontmatter: (context) => { + // Use endpoint path as title for navigation + const ctx = context as { type?: string; path?: string }; + if (ctx.type === "operation" && ctx.path) { + return { + title: ctx.path, + }; + } + return {}; }, - items: Object.keys(products).map((productName) => ({ - path: `${productName}/index.mdx`, - })), - }, - }); -} - -function getProductName(title: string) { - // Match the title to a product name - const titleLower = title.toLowerCase(); - for (const [name] of Object.entries(products)) { - if (titleLower.includes(name)) { - return name; + index: { + url: { + baseUrl: "/api-reference/", + contentDir: "./content/docs/api-reference", + }, + items: [ + { + path: `${config.product}/${serviceName}/index.mdx`, + }, + ], + }, + }); + } + + // Generate all meta.json files + await generateMetaFiles(); + + // Post-process MDX files to use endpoint paths as titles + await updateMdxTitles(); + + // Rewrite index cards to use path as title and first sentence as description + await updateIndexCards(); +} + +async function generateMetaFiles() { + // eslint-disable-next-line no-console + console.log("\nGenerating meta.json files..."); + + // Group products by their parent product category + const productGroups: Record = {}; + for (const [serviceName, config] of Object.entries(products)) { + productGroups[config.product] ??= []; + productGroups[config.product]?.push(serviceName); + } + + // 1. Generate root api-reference/meta.json + const rootMeta = { + root: true, + title: "API Reference", + icon: "Code", + pages: Object.keys(productGroups), + }; + await writeJson(path.join(outDir, "meta.json"), rootMeta); + // eslint-disable-next-line no-console + console.log(" - api-reference/meta.json"); + + // 2. Generate product meta.json files (e.g., pyth-core, entropy) + for (const [productName, services] of Object.entries(productGroups)) { + const productMeta = { + title: formatProductTitle(productName), + pages: services, + }; + const productDir = path.join(outDir, productName); + await fs.mkdir(productDir, { recursive: true }); + await writeJson(path.join(productDir, "meta.json"), productMeta); + // eslint-disable-next-line no-console + console.log(` - ${productName}/meta.json`); + + // 3. Generate service meta.json files (e.g., hermes, fortuna) + for (const serviceName of services) { + const endpoints = generatedEndpoints[serviceName] ?? []; + const serviceMeta = { + title: formatServiceTitle(serviceName), + pages: ["index", ...endpoints], + }; + const serviceDir = path.join(productDir, serviceName); + await writeJson(path.join(serviceDir, "meta.json"), serviceMeta); + // eslint-disable-next-line no-console + console.log(` - ${productName}/${serviceName}/meta.json`); + } + } +} + +function formatProductTitle(productName: string): string { + // Convert "pyth-core" to "Pyth Core", "entropy" to "Entropy" + return productName + .split("-") + .map((word) => word.charAt(0).toUpperCase() + word.slice(1)) + .join(" "); +} + +function formatServiceTitle(serviceName: string): string { + // Capitalize first letter: "hermes" -> "Hermes" + return serviceName.charAt(0).toUpperCase() + serviceName.slice(1); +} + +async function writeJson(filePath: string, data: object): Promise { + await fs.writeFile(filePath, JSON.stringify(data, undefined, 2) + "\n"); +} + +async function updateMdxTitles(): Promise { + // eslint-disable-next-line no-console + console.log("\nUpdating MDX titles to use endpoint paths..."); + + // Process all service directories + for (const [serviceName, config] of Object.entries(products)) { + const serviceDir = path.join(outDir, config.product, serviceName); + + try { + const files = await fs.readdir(serviceDir); + + for (const file of files) { + if (!file.endsWith(".mdx") || file === "index.mdx") continue; + + const filePath = path.join(serviceDir, file); + const content = await fs.readFile(filePath, "utf8"); + + // Extract the route from _openapi.route in frontmatter + const routeRegex = /route:\s*([^\n]+)/; + const routeMatch = routeRegex.exec(content); + if (!routeMatch?.[1]) continue; + + const route = routeMatch[1].trim(); + + // Replace the title in frontmatter with the route + const updatedContent = content.replace( + /^---\ntitle:\s*[^\n]+/, + `---\ntitle: "${route}"`, + ); + + if (updatedContent !== content) { + await fs.writeFile(filePath, updatedContent); + // eslint-disable-next-line no-console + console.log(` - Updated: ${config.product}/${serviceName}/${file}`); + } + } + } catch { + // Directory might not exist yet } } - return "unknown"; +} + +async function updateIndexCards(): Promise { + // eslint-disable-next-line no-console + console.log( + "\nUpdating index cards to use path titles + first-sentence descriptions...", + ); + + for (const [serviceName, config] of Object.entries(products)) { + const serviceDir = path.join(outDir, config.product, serviceName); + const indexPath = path.join(serviceDir, "index.mdx"); + + try { + await fs.access(indexPath); + } catch { + continue; + } + + // Read all endpoint MDX files to extract route, method, and description + const endpoints = generatedEndpoints[serviceName] ?? []; + const cardData: { + href: string; + route: string; + method: string; + description: string; + }[] = []; + + for (const operationId of endpoints) { + const mdxPath = path.join(serviceDir, `${operationId}.mdx`); + try { + const content = await fs.readFile(mdxPath, "utf8"); + + // Extract route + const routeMatch = /route:\s*([^\n]+)/.exec(content); + const route = routeMatch?.[1]?.trim() ?? operationId; + + // Extract method + const methodMatch = /method:\s*([^\n]+)/.exec(content); + const method = methodMatch?.[1]?.trim().toUpperCase() ?? "GET"; + + // Extract description (handle both single-line and multiline formats) + let descText = ""; + + // Try multiline format first: description: >-\n text + const multilineMatch = /description:\s*>-\s*\n((?:\s{2}.*\n)+)/.exec( + content, + ); + if (multilineMatch?.[1]) { + descText = multilineMatch[1] + .split("\n") + .map((line) => line.trim()) + .filter(Boolean) + .join(" "); + } else { + // Try single-line format: description: text + const singleMatch = /description:\s*([^\n]+)/.exec(content); + if (singleMatch?.[1]) { + descText = singleMatch[1].trim(); + } + } + + const firstSentence = getFirstSentence(descText); + + cardData.push({ + href: `/api-reference/${config.product}/${serviceName}/${operationId}`, + route, + method, + description: firstSentence, + }); + } catch { + // Skip if file doesn't exist + } + } + + // Build new index content with APICards grid and APICard components + const cards = cardData + .map( + (card) => + ` `, + ) + .join("\n"); + + const newIndex = `--- +title: Overview +--- + +{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} + + +${cards} + +`; + + await fs.writeFile(indexPath, newIndex); + // eslint-disable-next-line no-console + console.log( + ` - Updated index: ${config.product}/${serviceName}/index.mdx`, + ); + } +} + +function getFirstSentence(text: string): string { + if (!text) return ""; + + // Remove markdown formatting (bold, italic, etc.) + let cleaned = text.replaceAll(/\*\*[^*]+\*\*/g, "").trim(); + + // If text starts with "Deprecated..." or similar, skip to the actual description + if (cleaned.toLowerCase().startsWith("deprecated")) { + const afterDeprecated = cleaned.indexOf(")"); + if (afterDeprecated > 0) { + cleaned = cleaned.slice(afterDeprecated + 1).trim(); + } + } + + const sentenceEnd = cleaned.search(/[.!?](\s|$)/); + if (sentenceEnd === -1) return cleaned.trim(); + return cleaned.slice(0, sentenceEnd + 1).trim(); +} + +function escapeQuotes(text: string): string { + return text.replaceAll('"', String.raw`\"`); } await generateDocs(); diff --git a/apps/developer-hub/src/components/APICard/index.module.scss b/apps/developer-hub/src/components/APICard/index.module.scss new file mode 100644 index 0000000000..8b12c895a0 --- /dev/null +++ b/apps/developer-hub/src/components/APICard/index.module.scss @@ -0,0 +1,77 @@ +@use "@pythnetwork/component-library/theme"; + +.grid { + display: grid; + grid-template-columns: repeat(2, 1fr); + gap: theme.spacing(4); + + @include theme.breakpoint("md") { + grid-template-columns: repeat(2, 1fr); + } + + @media (width <= 768px) { + grid-template-columns: 1fr; + } +} + +.card { + display: flex; + flex-direction: column; + gap: theme.spacing(2); + padding: theme.spacing(5); + border-radius: theme.border-radius("lg"); + background-color: theme.color("background", "secondary"); + text-decoration: none; + transition: background-color 200ms ease-out; + + &:hover { + background-color: theme.color("background", "card-highlight"); + } +} + +.title { + display: flex; + align-items: center; + gap: theme.spacing(2); + flex-wrap: wrap; +} + +.name { + font-size: theme.font-size("sm"); + font-weight: theme.font-weight("semibold"); + color: theme.color("foreground"); +} + +.badge { + font-size: theme.font-size("xxs"); + font-weight: theme.font-weight("semibold"); + text-transform: uppercase; + letter-spacing: theme.letter-spacing("wide"); +} + +.get { + color: theme.pallette-color("green", 400); +} + +.post { + color: theme.pallette-color("amber", 400); +} + +.put { + color: theme.pallette-color("blue", 400); +} + +.patch { + color: theme.pallette-color("purple", 400); +} + +.delete { + color: theme.pallette-color("red", 400); +} + +.description { + font-size: theme.font-size("sm"); + font-weight: theme.font-weight("normal"); + line-height: 1.6; + color: theme.color("muted"); +} diff --git a/apps/developer-hub/src/components/APICard/index.tsx b/apps/developer-hub/src/components/APICard/index.tsx new file mode 100644 index 0000000000..3cbefe0294 --- /dev/null +++ b/apps/developer-hub/src/components/APICard/index.tsx @@ -0,0 +1,45 @@ +import clsx from "clsx"; +import Link from "next/link"; +import type { ReactNode } from "react"; + +import styles from "./index.module.scss"; + +type APICardProps = { + href: string; + title: string; + method: string; + description?: string; +}; + +type APICardsProps = { + children: ReactNode; +}; + +export function APICards({ children }: APICardsProps) { + return
{children}
; +} + +export function APICard({ href, title, method, description }: APICardProps) { + const methodLower = method.toLowerCase(); + + return ( + +
+ {title} + + {method} + +
+ {description &&

{description}

} + + ); +} diff --git a/apps/developer-hub/src/components/Pages/BasePage/index.tsx b/apps/developer-hub/src/components/Pages/BasePage/index.tsx index e9e517356d..e8e9dc5fa5 100644 --- a/apps/developer-hub/src/components/Pages/BasePage/index.tsx +++ b/apps/developer-hub/src/components/Pages/BasePage/index.tsx @@ -20,6 +20,9 @@ export async function BasePage(props: { params: { slug: string[] } }) { const title = page.data.title; const url = page.url; + // Hide PageActions for api-reference pages + const isApiReference = url.startsWith("/api-reference"); + return ( {page.data.title} {page.data.description} - + {!isApiReference && ( + + )} diff --git a/apps/developer-hub/src/components/Root/global.css b/apps/developer-hub/src/components/Root/global.css index 7af62f670a..8dabe45a95 100644 --- a/apps/developer-hub/src/components/Root/global.css +++ b/apps/developer-hub/src/components/Root/global.css @@ -22,6 +22,7 @@ @import "tailwindcss"; @import "fumadocs-ui/css/neutral.css"; @import "fumadocs-ui/css/preset.css"; +@import "fumadocs-openapi/css/preset.css"; /* @import "./theme.css"; this overrides the default colors to match the pyth branding */ /* @import "./fumadocs-global-style-overrides.css"; */ diff --git a/apps/developer-hub/src/lib/openapi.ts b/apps/developer-hub/src/lib/openapi.ts index 5ffdb8629d..e2a7dd8ab5 100644 --- a/apps/developer-hub/src/lib/openapi.ts +++ b/apps/developer-hub/src/lib/openapi.ts @@ -3,8 +3,14 @@ import { createOpenAPI } from "fumadocs-openapi/server"; export const products = { fortuna: { name: "fortuna", + product: "entropy", openApiUrl: "https://fortuna-staging.dourolabs.app/docs/openapi.json", }, + hermes: { + name: "hermes", + product: "pyth-core", + openApiUrl: "https://hermes.pyth.network/docs/openapi.json", + }, }; export const openapi = createOpenAPI({ diff --git a/apps/developer-hub/src/mdx-components.tsx b/apps/developer-hub/src/mdx-components.tsx index 20240cb85e..886d04ac6b 100644 --- a/apps/developer-hub/src/mdx-components.tsx +++ b/apps/developer-hub/src/mdx-components.tsx @@ -5,6 +5,7 @@ import { Tab, Tabs } from "fumadocs-ui/components/tabs"; import defaultMdxComponents from "fumadocs-ui/mdx"; import type { MDXComponents } from "mdx/types"; +import { APICard, APICards } from "./components/APICard"; import { openapi } from "./lib/openapi"; export function getMDXComponents(components?: MDXComponents): MDXComponents { @@ -13,6 +14,8 @@ export function getMDXComponents(components?: MDXComponents): MDXComponents { APIPage: (props: ApiPageProps) => ( ), + APICard, + APICards, Tabs, Tab, ...components,