docs-util: improvements and fixes to OAS generation (#8855)

- Add to knowledge base summaries for delete-related properties
- Fix long curl examples overflowing the line
- Fix singular / plural usage in some knowledge base descriptions / summaries
- Fix some workflows not picked up
- Remove query params for routes that don't use them.
This commit is contained in:
Shahed Nasser
2024-08-29 11:40:55 +03:00
committed by GitHub
parent 16d8fb1d20
commit 25134d2307
3 changed files with 93 additions and 59 deletions

View File

@@ -523,6 +523,29 @@ class KnowledgeBaseFactory {
template:
"Pass additional custom data to the API route. This data is passed to the underlying workflow under the `additional_data` parameter.",
},
{
exact: "object",
template: (_, options) => {
if (!options?.rawParentName?.includes("DeleteResponse")) {
return
}
return "The name of the deleted object."
},
},
{
exact: "deleted",
template: (_, options) => {
if (
!options?.rawParentName?.includes("DeleteResponse") ||
!options.parentName
) {
return
}
return `Whether the ${singular(options.parentName)} was deleted.`
},
},
{
pattern: /.*/,
template(str, options) {
@@ -799,6 +822,7 @@ class KnowledgeBaseFactory {
: kebabToTitle(splitOasPath[splitOasPath.length - 1])
// retrieve different formatted versions of the entity name for the summary/description
const pluralEndingEntityName = pluralize.plural(endingEntityName)
const singularEndingEntityName = pluralize.singular(endingEntityName)
const lowerEndingEntityName = pluralEndingEntityName.toLowerCase()
const singularLowerEndingEntityName =
pluralize.singular(endingEntityName)
@@ -808,11 +832,19 @@ class KnowledgeBaseFactory {
result.summary = `List ${pluralEndingEntityName}`
result.description = `Retrieve a list of ${lowerEndingEntityName} in a ${singularLowerTag}. The ${lowerEndingEntityName} can be filtered by fields like FILTER FIELDS. The ${lowerEndingEntityName} can also be paginated.`
} else if (httpMethod === "post") {
result.summary = `Add ${pluralEndingEntityName} to ${singularTag}`
result.description = `Add a list of ${lowerEndingEntityName} to a ${singularLowerTag}.`
result.summary = `Add ${
isBulk ? pluralEndingEntityName : singularEndingEntityName
} to ${singularTag}`
result.description = isBulk
? `Add a list of ${lowerEndingEntityName} to a ${singularLowerTag}.`
: `Add a ${singularLowerEndingEntityName} to a ${singularLowerTag}`
} else {
result.summary = `Remove ${pluralEndingEntityName} from ${singularTag}`
result.description = `Remove a list of ${lowerEndingEntityName} from a ${singularLowerTag}. This doesn't delete the ${singularLowerEndingEntityName}, only the association between the ${singularLowerEndingEntityName} and the ${singularLowerTag}.`
result.summary = `Remove ${
isBulk ? pluralEndingEntityName : singularEndingEntityName
} from ${singularTag}`
result.description = isBulk
? `Remove a list of ${lowerEndingEntityName} from a ${singularLowerTag}.`
: `Remove a ${singularLowerEndingEntityName} from a ${singularLowerTag}.`
}
} else {
// the OAS operation is applied on a single entity that is the main entity (denoted by the tag).

View File

@@ -35,6 +35,7 @@ type SchemaDescriptionOptions = {
nodeType?: ts.Type
typeStr: string
parentName?: string
rawParentName?: string
}
export type OasArea = "admin" | "store"
@@ -954,13 +955,15 @@ class OasKindGenerator extends FunctionKindGenerator {
// TODO for now I'll use the type for validatedQuery until
// we have an actual approach to infer query types
const querySymbol = requestType.getProperty("validatedQuery")
if (querySymbol) {
if (querySymbol && this.shouldAddQueryParams(node)) {
const queryType = this.checker.getTypeOfSymbol(querySymbol)
const queryTypeName = this.checker.typeToString(queryType)
queryType.getProperties().forEach((property) => {
const propertyType = this.checker.getTypeOfSymbol(property)
const descriptionOptions: SchemaDescriptionOptions = {
typeStr: property.getName(),
parentName: tagName,
rawParentName: queryTypeName,
node: property.valueDeclaration,
symbol: property,
nodeType: propertyType,
@@ -993,6 +996,7 @@ class OasKindGenerator extends FunctionKindGenerator {
itemType: requestTypeArguments[0],
descriptionOptions: {
parentName: tagName,
rawParentName: this.checker.typeToString(requestTypeArguments[0]),
},
zodObjectTypeName: zodObjectTypeName,
})
@@ -1095,6 +1099,7 @@ class OasKindGenerator extends FunctionKindGenerator {
itemType: responseTypeArguments[0],
descriptionOptions: {
parentName: tagName,
rawParentName: this.checker.typeToString(responseTypeArguments[0]),
},
zodObjectTypeName: getCorrectZodTypeName({
typeReferenceNode: node.parameters[1].type,
@@ -1390,8 +1395,9 @@ class OasKindGenerator extends FunctionKindGenerator {
if (this.isRequired(property)) {
requiredProperties.push(property.name)
}
const propertyType = this.checker.getTypeOfSymbol(property)
properties[property.name] = this.typeToSchema({
itemType: this.checker.getTypeOfSymbol(property),
itemType: propertyType,
level: level + 1,
title: property.name,
descriptionOptions: {
@@ -1453,7 +1459,7 @@ class OasKindGenerator extends FunctionKindGenerator {
node,
nodeType,
typeStr,
parentName,
...templateOptions
}: SchemaDescriptionOptions): string {
if (!symbol && !node && !nodeType) {
// if none of the associated symbol, node, or type are provided,
@@ -1462,9 +1468,7 @@ class OasKindGenerator extends FunctionKindGenerator {
return (
this.knowledgeBaseFactory.tryToGetOasSchemaDescription({
str: typeStr,
templateOptions: {
parentName,
},
templateOptions,
}) || SUMMARY_PLACEHOLDER
)
}
@@ -1882,66 +1886,54 @@ class OasKindGenerator extends FunctionKindGenerator {
getAssociatedWorkflow(node: FunctionNode): string | undefined {
let workflow: string | undefined
if (
(!ts.isFunctionDeclaration(node) && !ts.isArrowFunction(node)) ||
!node.body ||
!ts.isBlock(node.body)
) {
if (!ts.isFunctionDeclaration(node) && !ts.isArrowFunction(node)) {
return
}
const sourceFile = node.getSourceFile()
const fileLocalSymbols: Map<string, ts.Symbol> =
"locals" in sourceFile
? (sourceFile.locals as Map<string, ts.Symbol>)
: new Map()
if (!fileLocalSymbols.size) {
// if a source file doesn't have imports, then it's assumed that it's not using workflows.
if (!("imports" in sourceFile) || !Array.isArray(sourceFile.imports)) {
return
}
node.body.statements.some((statement) => {
let awaitExpression: ts.AwaitExpression | undefined
// retrieve workflows imported from `@medusajs/core-flows`
// since there could be multiple import statements from the
// same package, put them in an array
const coreFlowsImports: ts.ImportClause[] = []
;(sourceFile.imports as ts.Node[]).forEach((importNode) => {
if (
ts.isVariableStatement(statement) &&
statement.declarationList.declarations[0].initializer &&
ts.isAwaitExpression(
statement.declarationList.declarations[0].initializer
)
!importNode.parent ||
!ts.isImportDeclaration(importNode.parent) ||
!importNode.parent.importClause?.namedBindings ||
importNode.getText() !== `"@medusajs/core-flows"`
) {
awaitExpression = statement.declarationList.declarations[0].initializer
} else if (ts.isAwaitExpression(statement)) {
awaitExpression = statement
return
}
if (
!awaitExpression ||
!ts.isCallExpression(awaitExpression.expression) ||
!ts.isPropertyAccessExpression(awaitExpression.expression.expression) ||
!awaitExpression.expression.expression.name.getText().startsWith("run")
) {
return false
}
coreFlowsImports.push(importNode.parent.importClause)
})
const fullExpressionText = awaitExpression.expression.getText()
const parenIndex = fullExpressionText.indexOf("(")
const dotIndex = fullExpressionText.indexOf(".")
const cutOffIndex =
parenIndex !== -1 && dotIndex === -1
? parenIndex
: parenIndex === -1 && dotIndex !== -1
? dotIndex
: parenIndex === -1 && dotIndex === -1
? fullExpressionText.length
: Math.min(parenIndex, dotIndex)
const expressionName = fullExpressionText.substring(0, cutOffIndex)
// if no imports found from `@medusajs/core-flows`, return
if (!coreFlowsImports.length) {
return
}
if (!fileLocalSymbols.has(expressionName)) {
return false
}
// check if any of the imported vars are used in the function node
// const fileWorkflowImports: Map<string, ts.Identifier> = new Map()
const fnText = node.getText()
coreFlowsImports.forEach((coreFlowsImport) => {
coreFlowsImport.namedBindings?.forEachChild((childImport) => {
if (!ts.isImportSpecifier(childImport) || workflow) {
return
}
workflow = expressionName
return true
const workflowName = childImport.name.getText()
if (fnText.includes(workflowName)) {
workflow = workflowName
}
})
})
return workflow
@@ -2031,6 +2023,17 @@ class OasKindGenerator extends FunctionKindGenerator {
writeFileSync(areaYamlPath, stringify(areaYaml))
}
}
shouldAddQueryParams(node: FunctionNode): boolean {
const queryParamsUsageIndicators = [
`req.filterableFields`,
`req.remoteQueryConfig`,
]
const fnText = node.getText()
return queryParamsUsageIndicators.some((indicator) =>
fnText.includes(indicator)
)
}
}
export default OasKindGenerator

View File

@@ -13,8 +13,7 @@ export default function formatOas(
oas: OpenApiOperation | OpenApiSchema,
oasPrefix: string
) {
return `* ${oasPrefix}${DOCBLOCK_NEW_LINE}${stringify(oas).replaceAll(
"\n",
DOCBLOCK_NEW_LINE
)}${DOCBLOCK_END_LINE}`
return `* ${oasPrefix}${DOCBLOCK_NEW_LINE}${stringify(oas, {
lineWidth: 200,
}).replaceAll("\n", DOCBLOCK_NEW_LINE)}${DOCBLOCK_END_LINE}`
}