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:
Shahed Nasser
2024-03-18 09:47:35 +02:00
committed by GitHub
parent 56a6ec0227
commit bb87db8342
2008 changed files with 15716 additions and 10536 deletions

View File

@@ -1,6 +1,6 @@
openapi: 3.0.0
info:
version: 1.0.0
version: 2.0.0
title: Medusa Admin API
license:
name: MIT

View File

@@ -1,6 +1,6 @@
openapi: 3.0.0
info:
version: 1.0.0
version: 2.0.0
title: Medusa Storefront API
license:
name: MIT

View File

@@ -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

View File

@@ -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",
},
},

View File

@@ -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)
}

View File

@@ -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" ? "}|" : "||"
}
}
}

View File

@@ -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}"/>`
}

View File

@@ -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
}
}