AbdulElahGwaith's picture
Upload folder using huggingface_hub
88df9e4 verified
import type { Context, Page } from '@/types'
import type { PageTransformer } from './types'
import type { Operation } from '@/rest/components/types'
import { renderContent } from '@/content-render/index'
import matter from '@gr2m/gray-matter'
import { readFileSync } from 'fs'
import { join, dirname } from 'path'
import { fileURLToPath } from 'url'
import { fastTextOnly } from '@/content-render/unified/text-only'
const __filename = fileURLToPath(import.meta.url)
const __dirname = dirname(__filename)
/**
* Transformer for REST API pages
* Converts REST operations and their data into markdown format using a Liquid template
*/
export class RestTransformer implements PageTransformer {
canTransform(page: Page): boolean {
// Only transform REST pages that are not landing pages
// Landing pages (like /en/rest) will be handled by a separate transformer
return page.autogenerated === 'rest' && !page.relativePath.endsWith('index.md')
}
async transform(
page: Page,
pathname: string,
context: Context,
apiVersion?: string,
): Promise<string> {
// Import getRest dynamically to avoid circular dependencies
const { default: getRest } = await import('@/rest/lib/index')
// Extract version from context
const currentVersion = context.currentVersion!
// Use the provided apiVersion, or fall back to the latest from context
const effectiveApiVersion =
apiVersion ||
(context.currentVersionObj?.apiVersions?.length
? context.currentVersionObj.latestApiVersion
: undefined)
// Parse the category and subcategory from the page path
// e.g. /en/rest/actions/artifacts -> category: actions, subcategory: artifacts
const pathParts = pathname.split('/').filter(Boolean)
const restIndex = pathParts.indexOf('rest')
if (restIndex === -1 || restIndex >= pathParts.length - 1) {
throw new Error(`Invalid REST path: ${pathname}`)
}
const category = pathParts[restIndex + 1]
const subcategory = pathParts[restIndex + 2] // May be undefined for category-only pages
// Get the REST operations data
const restData = await getRest(currentVersion, effectiveApiVersion)
let operations: Operation[] = []
if (subcategory && restData[category]?.[subcategory]) {
operations = restData[category][subcategory]
} else if (category && restData[category]) {
// For categories without subcategories, operations are nested directly
const categoryData = restData[category]
// Flatten all operations from all subcategories
operations = Object.values(categoryData).flat()
}
// Prepare manual content
let manualContent = ''
if (page.markdown) {
const markerIndex = page.markdown.indexOf(
'<!-- Content after this section is automatically generated -->',
)
if (markerIndex > 0) {
const { content } = matter(page.markdown)
const manualContentMarkerIndex = content.indexOf(
'<!-- Content after this section is automatically generated -->',
)
if (manualContentMarkerIndex > 0) {
const rawManualContent = content.substring(0, manualContentMarkerIndex).trim()
if (rawManualContent) {
manualContent = await renderContent(rawManualContent, {
...context,
markdownRequested: true,
})
}
}
}
}
// Prepare data for template
const templateData = await this.prepareTemplateData(
page,
operations,
context,
manualContent,
effectiveApiVersion,
)
// Load and render template
const templatePath = join(__dirname, '../templates/rest-page.template.md')
const templateContent = readFileSync(templatePath, 'utf8')
// Render the template with Liquid
const rendered = await renderContent(templateContent, {
...context,
...templateData,
markdownRequested: true,
})
return rendered
}
/**
* Prepare data for the Liquid template
*/
private async prepareTemplateData(
page: Page,
operations: Operation[],
context: Context,
manualContent: string,
apiVersion?: string,
): Promise<Record<string, any>> {
// Prepare page intro
const intro = page.intro ? await page.renderProp('intro', context, { textOnly: true }) : ''
// Prepare operations for the template
const preparedOperations = await Promise.all(
operations.map(async (operation) => await this.prepareOperation(operation)),
)
return {
page: {
title: page.title,
intro,
},
manualContent,
restOperations: preparedOperations,
apiVersion,
}
}
/**
* Prepare a single operation for template rendering
*/
private async prepareOperation(operation: Operation): Promise<Record<string, any>> {
// Convert HTML description to text
const description = operation.descriptionHTML ? fastTextOnly(operation.descriptionHTML) : ''
// Determine header settings
const needsContentTypeHeader = operation.subcategory === 'inference'
const omitHeaders =
operation.subcategory === 'management-console' || operation.subcategory === 'manage-ghes'
const showHeaders = !omitHeaders
// Check if operation has parameters
const hasParameters =
(operation.parameters?.length || 0) > 0 || (operation.bodyParameters?.length || 0) > 0
// Process status codes to convert HTML descriptions to plain text
const statusCodes = operation.statusCodes?.map((statusCode) => ({
...statusCode,
description: statusCode.description ? fastTextOnly(statusCode.description) : undefined,
}))
// Prepare code examples with processed URLs
const codeExamples =
operation.codeExamples?.map((example) => {
let url = `${operation.serverUrl}${operation.requestPath}`
// Replace path parameters in URL
if (example.request?.parameters && Object.keys(example.request.parameters).length > 0) {
for (const [key, value] of Object.entries(example.request.parameters)) {
url = url.replace(`{${key}}`, String(value))
}
}
return {
request: {
description: example.request?.description
? fastTextOnly(example.request.description)
: '',
url,
acceptHeader: example.request?.acceptHeader,
bodyParameters: example.request?.bodyParameters
? JSON.stringify(example.request.bodyParameters, null, 2)
: null,
},
response: {
statusCode: example.response?.statusCode,
schema: (example.response as any)?.schema
? JSON.stringify((example.response as any).schema, null, 2)
: null,
},
}
}) || []
return {
...operation,
description,
hasParameters,
showHeaders,
needsContentTypeHeader,
statusCodes,
codeExamples,
}
}
}