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:
@@ -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).
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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}`
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user