docs: prep for v2 documentation (#6710)
This PR includes documentation that preps for v2 docs (but doesn't introduce new docs). _Note: The number of file changes in the PR is due to find-and-replace within the `references` which is unavoidable. Let me know if I should move it to another PR._ ## Changes - Change Medusa version in base OAS used for v2. - Fix to docblock generator related to not catching all path parameters. - Added typedoc plugin that generates ER Diagrams, which will be used specifically for data model references in commerce modules. - Changed OAS tool to output references in `www/apps/api-reference/specs-v2` directory when the `--v2` option is used. - Added a version switcher to the API reference to switch between V1 and V2. This switcher is enabled by an environment variable, so it won't be visible/usable at the moment. - Upgraded docusaurus to v3.0.1 - Added new Vale rules to ensure correct spelling of Medusa Admin and module names. - Added new components to the `docs-ui` package that will be used in future documentation changes.
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
openapi: 3.0.0
|
||||
info:
|
||||
version: 1.0.0
|
||||
version: 2.0.0
|
||||
title: Medusa Admin API
|
||||
license:
|
||||
name: MIT
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
openapi: 3.0.0
|
||||
info:
|
||||
version: 1.0.0
|
||||
version: 2.0.0
|
||||
title: Medusa Storefront API
|
||||
license:
|
||||
name: MIT
|
||||
|
||||
@@ -28,7 +28,7 @@ import OasSchemaHelper from "../helpers/oas-schema.js"
|
||||
import formatOas from "../../utils/format-oas.js"
|
||||
import { DEFAULT_OAS_RESPONSES } from "../../constants.js"
|
||||
|
||||
export const API_ROUTE_PARAM_REGEX = /\[(.+)\]/g
|
||||
export const API_ROUTE_PARAM_REGEX = /\[(.+?)\]/g
|
||||
const RES_STATUS_REGEX = /^res[\s\S]*\.status\((\d+)\)/
|
||||
|
||||
type SchemaDescriptionOptions = {
|
||||
@@ -112,9 +112,12 @@ class OasKindGenerator extends FunctionKindGenerator {
|
||||
// and the second of type `MedusaResponse`
|
||||
return (
|
||||
(functionNode.parameters.length === 2 &&
|
||||
functionNode.parameters[0].type
|
||||
(functionNode.parameters[0].type
|
||||
?.getText()
|
||||
.startsWith("MedusaRequest") &&
|
||||
.startsWith("MedusaRequest") ||
|
||||
functionNode.parameters[0].type
|
||||
?.getText()
|
||||
.startsWith("AuthenticatedMedusaRequest")) &&
|
||||
functionNode.parameters[1].type
|
||||
?.getText()
|
||||
.startsWith("MedusaResponse")) ||
|
||||
@@ -988,26 +991,32 @@ class OasKindGenerator extends FunctionKindGenerator {
|
||||
*/
|
||||
tagName?: string
|
||||
}): OpenAPIV3.ParameterObject[] {
|
||||
const pathParameters = API_ROUTE_PARAM_REGEX.exec(oasPath)?.slice(1)
|
||||
// reset regex manually
|
||||
API_ROUTE_PARAM_REGEX.lastIndex = 0
|
||||
let pathParameters: string[] | undefined
|
||||
const parameters: OpenAPIV3.ParameterObject[] = []
|
||||
|
||||
if (pathParameters?.length) {
|
||||
pathParameters.forEach((parameter) =>
|
||||
parameters.push(
|
||||
this.getParameterObject({
|
||||
type: "path",
|
||||
name: parameter,
|
||||
description: this.getSchemaDescription({
|
||||
typeStr: parameter,
|
||||
parentName: tagName,
|
||||
}),
|
||||
required: true,
|
||||
schema: {
|
||||
type: "string",
|
||||
},
|
||||
})
|
||||
while (
|
||||
(pathParameters = API_ROUTE_PARAM_REGEX.exec(oasPath)?.slice(1)) !==
|
||||
undefined
|
||||
) {
|
||||
if (pathParameters.length) {
|
||||
pathParameters.forEach((parameter) =>
|
||||
parameters.push(
|
||||
this.getParameterObject({
|
||||
type: "path",
|
||||
name: parameter,
|
||||
description: this.getSchemaDescription({
|
||||
typeStr: parameter,
|
||||
parentName: tagName,
|
||||
}),
|
||||
required: true,
|
||||
schema: {
|
||||
type: "string",
|
||||
},
|
||||
})
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return parameters
|
||||
|
||||
@@ -71,10 +71,8 @@ module.exports = {
|
||||
showCommentsAsHeader: true,
|
||||
sections: baseSectionsOptions,
|
||||
parameterStyle: "component",
|
||||
parameterComponent: "ParameterTypes",
|
||||
mdxImports: [
|
||||
`import ParameterTypes from "@site/src/components/ParameterTypes"`,
|
||||
],
|
||||
parameterComponent: "TypeList",
|
||||
mdxImports: [`import TypeList from "@site/src/components/TypeList"`],
|
||||
},
|
||||
internal: {
|
||||
maxLevel: 1,
|
||||
@@ -231,7 +229,7 @@ npx medusa develop
|
||||
reflectionTitle: {
|
||||
kind: false,
|
||||
typeParameters: false,
|
||||
suffix: " Reference",
|
||||
suffix: "Reference",
|
||||
},
|
||||
},
|
||||
|
||||
@@ -638,7 +636,7 @@ npx medusa develop
|
||||
reflectionTitle: {
|
||||
kind: false,
|
||||
typeParameters: false,
|
||||
suffix: " Reference",
|
||||
suffix: "Reference",
|
||||
},
|
||||
},
|
||||
|
||||
@@ -681,7 +679,7 @@ npx medusa develop
|
||||
reflectionTitle: {
|
||||
kind: false,
|
||||
typeParameters: false,
|
||||
suffix: " Reference",
|
||||
suffix: "Reference",
|
||||
},
|
||||
},
|
||||
|
||||
@@ -780,7 +778,7 @@ npx medusa develop
|
||||
reflectionTitle: {
|
||||
kind: false,
|
||||
typeParameters: false,
|
||||
suffix: " Reference",
|
||||
suffix: "Reference",
|
||||
},
|
||||
},
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import { load as parseOasSchemaPlugin } from "./parse-oas-schema-plugin"
|
||||
import { load as apiIgnorePlugin } from "./api-ignore"
|
||||
import { load as eslintExamplePlugin } from "./eslint-example"
|
||||
import { load as signatureModifierPlugin } from "./signature-modifier"
|
||||
import { MermaidDiagramGenerator } from "./mermaid-diagram-generator"
|
||||
import { load as parentIgnorePlugin } from "./parent-ignore"
|
||||
import { GenerateNamespacePlugin } from "./generate-namespace"
|
||||
|
||||
@@ -18,4 +19,5 @@ export function load(app: Application) {
|
||||
parentIgnorePlugin(app)
|
||||
|
||||
new GenerateNamespacePlugin(app)
|
||||
new MermaidDiagramGenerator(app)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,302 @@
|
||||
import path from "path"
|
||||
import {
|
||||
Application,
|
||||
Comment,
|
||||
Context,
|
||||
Converter,
|
||||
DeclarationReflection,
|
||||
ParameterType,
|
||||
ReferenceType,
|
||||
Reflection,
|
||||
ReflectionKind,
|
||||
TypeDocOptionMap,
|
||||
} from "typedoc"
|
||||
import ts from "typescript"
|
||||
|
||||
type RelationType =
|
||||
| "one-to-one"
|
||||
| "one-to-many"
|
||||
| "many-to-one"
|
||||
| "many-to-many"
|
||||
|
||||
type Relations = Map<
|
||||
string,
|
||||
{
|
||||
target: string
|
||||
left: RelationType
|
||||
right?: RelationType
|
||||
name: string
|
||||
}[]
|
||||
>
|
||||
|
||||
type PluginOptions = Pick<
|
||||
TypeDocOptionMap,
|
||||
"generateModelsDiagram" | "diagramAddToFile"
|
||||
>
|
||||
|
||||
export class MermaidDiagramGenerator {
|
||||
private app: Application
|
||||
private options?: PluginOptions
|
||||
private mainFileReflection?: Reflection
|
||||
|
||||
constructor(app: Application) {
|
||||
this.app = app
|
||||
this.app.options.addDeclaration({
|
||||
name: "generateModelsDiagram",
|
||||
help: "Whether to generate a Mermaid.js class diagram for data models in the reference.",
|
||||
type: ParameterType.Boolean,
|
||||
defaultValue: false,
|
||||
})
|
||||
this.app.options.addDeclaration({
|
||||
name: "diagramAddToFile",
|
||||
help: "The file to add the mermaid diagram to. The diagram is added as a package comment.",
|
||||
type: ParameterType.String,
|
||||
})
|
||||
app.converter.on(
|
||||
Converter.EVENT_CREATE_DECLARATION,
|
||||
this.setMainFile.bind(this)
|
||||
)
|
||||
app.converter.on(
|
||||
Converter.EVENT_RESOLVE_BEGIN,
|
||||
this.findRelations.bind(this)
|
||||
)
|
||||
}
|
||||
|
||||
getPluginOptions(): PluginOptions {
|
||||
if (this.options) {
|
||||
return this.options
|
||||
}
|
||||
|
||||
this.options = {
|
||||
generateModelsDiagram: this.app.options.getValue("generateModelsDiagram"),
|
||||
diagramAddToFile: this.app.options.getValue("diagramAddToFile"),
|
||||
}
|
||||
|
||||
return this.options
|
||||
}
|
||||
|
||||
setMainFile(context: Context) {
|
||||
const options = this.getPluginOptions()
|
||||
if (
|
||||
this.mainFileReflection ||
|
||||
!options.generateModelsDiagram ||
|
||||
!options.diagramAddToFile
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
const mainFilePath = options.diagramAddToFile.startsWith("packages")
|
||||
? path.resolve("..", "..", "..", options.diagramAddToFile)
|
||||
: options.diagramAddToFile
|
||||
|
||||
const mainFileSource = context.program.getSourceFile(mainFilePath)
|
||||
if (!mainFileSource) {
|
||||
// console.error(
|
||||
// `Couldn't fine the main source file ${options.diagramAddToFile}`
|
||||
// )
|
||||
return
|
||||
}
|
||||
const mainFileSymbol = context.checker.getSymbolAtLocation(mainFileSource)
|
||||
if (!mainFileSymbol) {
|
||||
// console.error(`Couldn't fine the main file's symbol`)
|
||||
return
|
||||
}
|
||||
|
||||
this.mainFileReflection =
|
||||
context.project.getReflectionFromSymbol(mainFileSymbol)
|
||||
|
||||
if (!this.mainFileReflection) {
|
||||
// console.error(`Couldn't fine the main file's reflection`)
|
||||
}
|
||||
}
|
||||
|
||||
findRelations(context: Context) {
|
||||
const options = this.getPluginOptions()
|
||||
if (
|
||||
!this.mainFileReflection ||
|
||||
!options.generateModelsDiagram ||
|
||||
!options.diagramAddToFile
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
const relations: Relations = new Map()
|
||||
|
||||
for (const reflection of context.project.getReflectionsByKind(
|
||||
ReflectionKind.Class
|
||||
)) {
|
||||
if (!(reflection instanceof DeclarationReflection)) {
|
||||
return
|
||||
}
|
||||
|
||||
// find relations of that reflection
|
||||
reflection.children?.forEach((child) => {
|
||||
let referenceType: ReferenceType | undefined
|
||||
// check that the child field references another reflection
|
||||
if (child.type?.type === "reference") {
|
||||
referenceType = child.type
|
||||
} else if (child.type?.type === "union") {
|
||||
referenceType = child.type.types.find(
|
||||
(unionType) => unionType.type === "reference"
|
||||
) as ReferenceType
|
||||
}
|
||||
|
||||
if (!referenceType) {
|
||||
return
|
||||
}
|
||||
|
||||
// If type is Collection from mikro-orm, get the type argument's reflection
|
||||
// otherwise, use the reflection as-is
|
||||
const targetReflection =
|
||||
this.isMikroOrmCollection(referenceType) &&
|
||||
referenceType.typeArguments?.length &&
|
||||
referenceType.typeArguments[0].type === "reference" &&
|
||||
referenceType.typeArguments[0].reflection
|
||||
? referenceType.typeArguments[0].reflection
|
||||
: referenceType.reflection
|
||||
|
||||
if (!targetReflection) {
|
||||
return
|
||||
}
|
||||
|
||||
// if entry already exists in relation, don't add anything
|
||||
const exists =
|
||||
relations
|
||||
.get(reflection.name)
|
||||
?.some((relation) => relation.target === targetReflection.name) ||
|
||||
relations
|
||||
.get(targetReflection.name)
|
||||
?.some((relation) => relation.target === reflection.name)
|
||||
|
||||
if (exists) {
|
||||
return
|
||||
}
|
||||
|
||||
// figure out relation type from decorators
|
||||
const relationType = this.getRelationFromDecorators(
|
||||
this.getReflectionDecorators(context, child)
|
||||
)
|
||||
if (!relationType) {
|
||||
return
|
||||
}
|
||||
|
||||
if (!relations.has(reflection.name)) {
|
||||
relations.set(reflection.name, [])
|
||||
}
|
||||
relations.get(reflection.name)?.push({
|
||||
target: targetReflection.name,
|
||||
left: relationType,
|
||||
right: this.getReverseRelationType(relationType),
|
||||
name: child.name,
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
if (!relations.size) {
|
||||
return
|
||||
}
|
||||
|
||||
this.mainFileReflection.comment = new Comment([
|
||||
{
|
||||
text: "## Relations Overview\n\n",
|
||||
kind: "text",
|
||||
},
|
||||
{
|
||||
text: this.buildMermaidDiagram(relations),
|
||||
kind: "code",
|
||||
},
|
||||
])
|
||||
}
|
||||
|
||||
isMikroOrmCollection(referenceType: ReferenceType) {
|
||||
return (
|
||||
referenceType.name === "Collection" &&
|
||||
referenceType.package?.includes("@mikro-orm")
|
||||
)
|
||||
}
|
||||
|
||||
getReflectionDecorators(context: Context, reflection: Reflection): string[] {
|
||||
const symbol = context.project.getSymbolFromReflection(reflection)
|
||||
const decorators: string[] = []
|
||||
|
||||
symbol?.declarations?.forEach((declaration) => {
|
||||
const modifiers =
|
||||
"modifiers" in declaration && declaration.modifiers
|
||||
? (declaration.modifiers as ts.NodeArray<ts.Modifier>)
|
||||
: []
|
||||
|
||||
modifiers.forEach((modifier) => {
|
||||
if (!ts.isDecorator(modifier)) {
|
||||
return
|
||||
}
|
||||
|
||||
;(modifier as ts.Decorator).forEachChild((childNode) => {
|
||||
if (!ts.isCallExpression(childNode)) {
|
||||
return
|
||||
}
|
||||
|
||||
const childNodeExpression = (childNode as ts.CallExpression)
|
||||
.expression
|
||||
if (!ts.isIdentifier(childNodeExpression)) {
|
||||
return
|
||||
}
|
||||
|
||||
decorators.push(childNodeExpression.escapedText.toString())
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
return decorators
|
||||
}
|
||||
|
||||
getRelationFromDecorators(decorators: string[]): RelationType | undefined {
|
||||
switch (true) {
|
||||
case decorators.includes("OneToOne"):
|
||||
return "one-to-one"
|
||||
case decorators.includes("OneToMany"):
|
||||
return "one-to-many"
|
||||
case decorators.includes("ManyToOne"):
|
||||
return "many-to-one"
|
||||
case decorators.includes("ManyToMany"):
|
||||
return "many-to-many"
|
||||
}
|
||||
}
|
||||
|
||||
getReverseRelationType(relationType: RelationType): RelationType {
|
||||
return relationType.split("-").reverse().join("-") as RelationType
|
||||
}
|
||||
|
||||
buildMermaidDiagram(relations: Relations): string {
|
||||
const linePrefix = `\t`
|
||||
const lineSuffix = `\n`
|
||||
let diagram = `erDiagram${lineSuffix}`
|
||||
relations.forEach((itemRelations, itemName) => {
|
||||
itemRelations.forEach((itemRelation) => {
|
||||
diagram += `${linePrefix}${itemName} ${this.getRelationTypeSymbol(
|
||||
itemRelation.left,
|
||||
"left"
|
||||
)}--${this.getRelationTypeSymbol(itemRelation.right!, "right")} ${
|
||||
itemRelation.target
|
||||
} : ${itemRelation.name}${lineSuffix}`
|
||||
})
|
||||
})
|
||||
|
||||
return "```mermaid\n" + diagram + "\n```"
|
||||
}
|
||||
|
||||
getRelationTypeSymbol(
|
||||
relationType: RelationType,
|
||||
direction: "left" | "right"
|
||||
): string {
|
||||
switch (relationType) {
|
||||
case "one-to-one":
|
||||
return "||"
|
||||
case "one-to-many":
|
||||
return direction === "left" ? "||" : "|{"
|
||||
case "many-to-many":
|
||||
return direction === "left" ? "}|" : "|{"
|
||||
case "many-to-one":
|
||||
return direction === "left" ? "}|" : "||"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -54,7 +54,7 @@ export function formatParameterComponent({
|
||||
}
|
||||
// reorder component items to show required items first
|
||||
componentItems = sortComponentItems(componentItems)
|
||||
return `<${parameterComponent} parameters={${JSON.stringify(
|
||||
return `<${parameterComponent} types={${JSON.stringify(
|
||||
componentItems
|
||||
)}} ${extraPropsArr.join(" ")} sectionTitle="${sectionTitle}"/>`
|
||||
}
|
||||
|
||||
8
docs-util/packages/types/lib/index.d.ts
vendored
8
docs-util/packages/types/lib/index.d.ts
vendored
@@ -217,5 +217,13 @@ export declare module "typedoc" {
|
||||
* @defaultValue false
|
||||
*/
|
||||
checkVariables: boolean
|
||||
/**
|
||||
* Whether to generate a Mermaid.js class diagram for data models in the reference.
|
||||
*/
|
||||
generateModelsDiagram: boolean
|
||||
/**
|
||||
* The file to add the mermaid diagram to. The diagram is added as a package comment.
|
||||
*/
|
||||
diagramAddToFile: string
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user