Files
supabase/apps/docs/spec/sections/generateMgmtApiSections.cts
T
Paweł Gulbinowiczandcoderabbitai[bot] 032c71c5bf fix(docs): use summary instead of slug for webhooks api spec (#51088)
## I have read the
[CONTRIBUTING.md](https://github.com/supabase/supabase/blob/master/CONTRIBUTING.md)
file.

YES

## What kind of change does this PR introduce?

Bug fix

## What is the current behavior?

Operations names for webhooks in Organizations webhooks and Project
webhooks use titles derived from slug, which produces wrong/incoherent
titles, for example: `Organizations slug webhooks deliveries id get`.

## What is the new behavior?

For Organizations webhooks and Project webhooks use `summary` from the
OpenAPI spec as the title instead of deriving it from the operation id.

## Additional context

Once this PR is merged, an _Update Mgmt Api Docs_ workflow needs to be
run to regenerate the sections and the spec.


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Documentation**
* Management API documentation section titles for organization and
project operations now use the OpenAPI summary when one is available. If
no summary is provided, the title falls back to the existing name-based
format. Titles for other operations continue to use the existing format,
so their presentation is unchanged.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
2026-09-30 17:25:17 +02:00

98 lines
2.8 KiB
TypeScript

import fs from 'fs'
import path from 'path'
function slugToTitle(slug) {
if (!slug) return ''
const prefixRegex = /^v\d+/
const title = slug.replace(prefixRegex, '').replace(/-/g, ' ').trimStart()
return title.charAt(0).toUpperCase() + title.slice(1)
}
function isValidSlug(slug) {
const slugRegex = /^[a-z0-9]+(?:-[a-z0-9]+)*$/
return slugRegex.test(slug)
}
function readJson(filePath: string) {
return JSON.parse(fs.readFileSync(filePath, 'utf8'))
}
function extractSectionsFromOpenApi(filePaths: string[], outputPath: string) {
try {
// Merge paths from all specs, later specs override earlier ones on conflict
const mergedPaths = Object.assign({}, ...filePaths.map((p) => readJson(p).paths ?? {}))
const categories: string[] = []
const sections: Array<{
type: string
title: string
id?: string
slug?: string
items: Array<{
type: string
title: string
id: string
slug: string
}>
}> = []
for (const route in mergedPaths) {
const methods = mergedPaths[route]
for (const method in methods) {
if (methods[method]['x-internal']) {
continue
}
const tag = methods[method].tags?.[0]
const operationId = methods[method].operationId
if (!tag || !isValidSlug(operationId)) continue
if (!categories.includes(tag)) {
categories.push(tag)
sections.push({
type: 'category',
title: tag,
items: [],
})
}
const sectionCate = sections.find((i) => i.title === tag)
sectionCate?.items.push({
id: operationId,
title: operationId.match(/^v\d+-(organizations-slug|projects-ref)-.+$/)
? (methods[method].summary ?? slugToTitle(operationId))
: slugToTitle(operationId),
slug: operationId,
type: 'operation',
})
}
}
sections.sort((a, b) => a.title.localeCompare(b.title))
sections.forEach((i) => i.items.sort((a, b) => a.title.localeCompare(b.title)))
sections.unshift({
title: 'Introduction',
id: 'introduction',
slug: 'introduction',
type: 'markdown',
items: [],
})
fs.writeFileSync(outputPath, JSON.stringify(sections, null, 2), 'utf8')
console.log(`Sections successfully generated!!!`)
} catch (error) {
console.error('Error:', error)
}
}
const args = process.argv.slice(2)
if (args.length < 2) {
console.error(
'Please provide at least one openapi file path and an output file path as arguments.'
)
process.exit(1)
}
// Last arg is output, everything before is input files
const outputFilePath = path.resolve(args[args.length - 1])
const inputFilePaths = args.slice(0, -1).map((p) => path.resolve(p))
extractSectionsFromOpenApi(inputFilePaths, outputFilePath)