Files
dify/packages/contracts/scripts/generate-api-readiness-readme.mjs
Stephen Zhou b04b4449db chore(api): annotate simple contract responses (#36331)
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: Asuka Minato <i@asukaminato.eu.org>
2026-05-19 06:13:20 +00:00

95 lines
3.7 KiB
JavaScript

import fs from 'node:fs'
import path from 'node:path'
import { fileURLToPath } from 'node:url'
const currentDir = path.dirname(fileURLToPath(import.meta.url))
const packageDir = path.resolve(currentDir, '..')
const readinessStatsPath = path.resolve(packageDir, 'generated/api/readiness.json')
const readmePath = path.resolve(packageDir, 'README.md')
const readinessStartMarker = '<!-- api-openapi-readiness:start -->'
const readinessEndMarker = '<!-- api-openapi-readiness:end -->'
const formatPercent = (ready, total) => {
return total === 0 ? '0.0%' : `${((ready / total) * 100).toFixed(1)}%`
}
const collectStats = () => {
if (!fs.existsSync(readinessStatsPath)) {
throw new Error(
`Missing API readiness stats: ${readinessStatsPath}. Run "pnpm -C packages/contracts gen-api-contract-from-openapi" first.`,
)
}
return JSON.parse(fs.readFileSync(readinessStatsPath, 'utf8'))
}
const tableRow = (surface, ready, notReady, total) => {
return `| ${surface} | ${ready} | ${notReady} | ${total} | ${formatPercent(ready, total)} |`
}
const renderReadinessSection = (stats) => {
const rows = Object.entries(stats.surfaces)
.sort(([left], [right]) => left.localeCompare(right))
.map(([surface, stat]) => tableRow(surface, stat.total - stat.notReady, stat.notReady, stat.total))
const totals = Object.values(stats.surfaces).reduce(
(summary, stat) => {
summary.notReady += stat.notReady
summary.total += stat.total
return summary
},
{ notReady: 0, total: 0 },
)
const totalReady = totals.total - totals.notReady
if (totals.total === 0)
throw new Error(`No API readiness stats found in ${readinessStatsPath}`)
return `${readinessStartMarker}
<!-- This section is auto-generated by scripts/generate-api-readiness-readme.mjs. Do not edit between the markers. -->
Snapshot generated from \`packages/contracts/generated/api/readiness.json\` after running \`pnpm -C packages/contracts gen-api-contract-from-openapi\`.
Are we OpenAPI ready? **No.** Current generated API contracts are **${formatPercent(totalReady, totals.total)} ready**.
| Surface | Ready | Not ready | Total | Ready % |
| --- | ---: | ---: | ---: | ---: |
${rows.join('\n')}
| **total** | **${totalReady}** | **${totals.notReady}** | **${totals.total}** | **${formatPercent(totalReady, totals.total)}** |
Readiness here means the generated contract operation is not marked with:
> ${stats.warning}
Operations marked with that warning should not be migrated to blindly. Prefer fixing backend OpenAPI annotations first so the generated contract has accurate request and response types, then migrate callers endpoint by endpoint.
The current heuristic marks an operation as not ready when a request body or success response that should have a body contains a loose object type, when a mutating controller reads a JSON body that is not documented as a request body, or when an operation has no documented 2xx response. 204, 205, and 304 responses are treated as bodyless when the request type is otherwise accurate.
${readinessEndMarker}
`
}
const updateReadme = (readinessSection) => {
const readme = fs.readFileSync(readmePath, 'utf8')
const startIndex = readme.indexOf(readinessStartMarker)
const endIndex = readme.indexOf(readinessEndMarker)
if (startIndex === -1 || endIndex === -1 || endIndex < startIndex) {
throw new Error(
`Missing readiness markers in ${readmePath}. Expected ${readinessStartMarker} and ${readinessEndMarker}.`,
)
}
const nextReadme = [
readme.slice(0, startIndex),
readinessSection,
readme.slice(endIndex + readinessEndMarker.length),
].join('')
fs.writeFileSync(readmePath, nextReadme)
}
updateReadme(renderReadinessSection(collectStats()))