docs: support since, deprecated, and feature flag tags in dml reference (#13741)

This commit is contained in:
Shahed Nasser
2025-10-13 13:31:48 +03:00
committed by GitHub
parent 958b003a11
commit 9a5ca86b76
16 changed files with 274 additions and 5 deletions
@@ -0,0 +1,32 @@
import React from "react"
import { Badge, Tooltip } from "@/components"
export type DeprecatedNoticeProps = {
description?: string
tooltipTextClassName?: string
badgeClassName?: string
badgeContent?: React.ReactNode
}
export const DeprecatedNotice = ({
description,
tooltipTextClassName,
badgeClassName,
badgeContent = `Deprecated`,
}: DeprecatedNoticeProps) => {
return (
<Tooltip
tooltipChildren={
<span className={tooltipTextClassName}>
{description ||
"This feature is deprecated and may be removed in future releases."}
</span>
}
clickable
>
<Badge variant="neutral" className={badgeClassName}>
{badgeContent}
</Badge>
</Tooltip>
)
}
@@ -0,0 +1,37 @@
import React from "react"
import { Badge, Tooltip } from "@/components"
export type VersionNoticeProps = {
version: string
tooltipTextClassName?: string
badgeClassName?: string
badgeContent?: React.ReactNode
}
export const VersionNotice = ({
version,
tooltipTextClassName,
badgeClassName,
badgeContent = `v${version}`,
}: VersionNoticeProps) => {
return (
<Tooltip
tooltipChildren={
<span className={tooltipTextClassName}>
This is available starting from
<br />
<a
href={`https://github.com/medusajs/medusa/releases/tag/${version}`}
>
Medusa v{version}
</a>
</span>
}
clickable
>
<Badge variant="blue" className={badgeClassName}>
{badgeContent}
</Badge>
</Tooltip>
)
}
@@ -22,6 +22,8 @@ import {
import { decodeStr, isInView } from "@/utils" import { decodeStr, isInView } from "@/utils"
import { usePathname } from "next/navigation" import { usePathname } from "next/navigation"
import { useIsBrowser, useSiteConfig } from "../../.." import { useIsBrowser, useSiteConfig } from "../../.."
import { VersionNotice } from "../../Notices/VersionNotice"
import { DeprecatedNotice } from "../../Notices/DeprecatedNotice"
type CommonProps = ParentCommonProps & { type CommonProps = ParentCommonProps & {
level?: number level?: number
@@ -236,6 +238,15 @@ const TypeListItem = ({
badgeContent={<ArrowsPointingOutMini />} badgeContent={<ArrowsPointingOutMini />}
/> />
)} )}
{item.since && (
<VersionNotice
version={item.since}
badgeClassName="!p-0 leading-none"
/>
)}
{item.deprecated?.is_deprecated && (
<DeprecatedNotice description={item.deprecated?.description} />
)}
</div> </div>
</div> </div>
</DetailsSummary> </DetailsSummary>
@@ -17,6 +17,11 @@ export type Type = {
featureFlag?: string featureFlag?: string
expandable: boolean expandable: boolean
children?: Type[] children?: Type[]
deprecated?: {
is_deprecated: boolean
description?: string
}
since?: string
} }
type ParameterTypesType = { type ParameterTypesType = {
+4 -2
View File
@@ -22,8 +22,10 @@ export * from "./Details/Summary"
export * from "./DetailsList" export * from "./DetailsList"
export * from "./DottedSeparator" export * from "./DottedSeparator"
export * from "./EditButton" export * from "./EditButton"
export * from "./ExpandableNotice" export * from "./Notices/ExpandableNotice"
export * from "./FeatureFlagNotice" export * from "./Notices/FeatureFlagNotice"
export * from "./Notices/DeprecatedNotice"
export * from "./Notices/VersionNotice"
export * from "./Feedback" export * from "./Feedback"
export * from "./Feedback/Solutions" export * from "./Feedback/Solutions"
export * from "./Footer" export * from "./Footer"
@@ -606,14 +606,22 @@ class DefaultKindGenerator<T extends ts.Node = ts.Node> {
deprecatedTag: ts.JSDocTag | undefined deprecatedTag: ts.JSDocTag | undefined
sinceTag: ts.JSDocTag | undefined sinceTag: ts.JSDocTag | undefined
featureFlagTag: ts.JSDocTag | undefined featureFlagTag: ts.JSDocTag | undefined
summary: string | undefined
} { } {
const nodeComments = ts.getJSDocCommentsAndTags(node) const nodeComments = ts.getJSDocCommentsAndTags(node)
let deprecatedTag: ts.JSDocTag | undefined let deprecatedTag: ts.JSDocTag | undefined
let sinceTag: ts.JSDocTag | undefined let sinceTag: ts.JSDocTag | undefined
let featureFlagTag: ts.JSDocTag | undefined let featureFlagTag: ts.JSDocTag | undefined
let summary: string | undefined
nodeComments.forEach((comment) => { nodeComments.forEach((comment) => {
if (!("tags" in comment)) { if (!("tags" in comment)) {
if (ts.isJSDoc(comment) && comment.comment) {
summary =
typeof comment.comment === "string"
? comment.comment
: comment.comment.map((part) => part.text).join(" ")
}
return return
} }
@@ -636,9 +644,22 @@ class DefaultKindGenerator<T extends ts.Node = ts.Node> {
deprecatedTag, deprecatedTag,
sinceTag, sinceTag,
featureFlagTag, featureFlagTag,
summary,
} }
} }
formatJSDocTag(tag: ts.JSDocTag | undefined): string | undefined {
if (!tag) {
return undefined
}
if (typeof tag.comment === "string") {
return tag.comment
}
return tag.comment?.map((part) => part.text).join(" ")
}
/** /**
* Check if a node is ignored. * Check if a node is ignored.
* *
@@ -101,10 +101,24 @@ class DmlKindGenerator extends DefaultKindGenerator<ts.CallExpression> {
dataModelName, dataModelName,
}) })
/**
* Use parent to get tags like since, deprecated, featureFlag
*/
const parent = node.parent?.parent?.parent
const { sinceTag, deprecatedTag, featureFlagTag } =
this.getInformationFromTags(parent)
const dmlFile: DmlFile = { const dmlFile: DmlFile = {
[dataModelName]: { [dataModelName]: {
filePath: getBasePath(node.getSourceFile().fileName), filePath: getBasePath(node.getSourceFile().fileName),
properties, properties,
since: this.formatJSDocTag(sinceTag),
deprecated: {
is_deprecated: !!deprecatedTag,
description: this.formatJSDocTag(deprecatedTag),
},
featureFlag: this.formatJSDocTag(featureFlagTag),
}, },
} }
@@ -229,8 +243,11 @@ class DmlKindGenerator extends DefaultKindGenerator<ts.CallExpression> {
) )
const isBoolean = propertyTypeStr.includes("BooleanProperty") const isBoolean = propertyTypeStr.includes("BooleanProperty")
const relationName = isRelation ? camelToWords(propertyName) : undefined const relationName = isRelation ? camelToWords(propertyName) : undefined
const { summary, sinceTag, deprecatedTag, featureFlagTag } =
this.getInformationFromTags(propertyNode)
let propertyDescription = let propertyDescription =
summary ||
this.knowledgeBaseFactory.tryToGetObjectPropertySummary({ this.knowledgeBaseFactory.tryToGetObjectPropertySummary({
retrieveOptions: { retrieveOptions: {
str: propertyName, str: propertyName,
@@ -251,6 +268,24 @@ class DmlKindGenerator extends DefaultKindGenerator<ts.CallExpression> {
propertyDescription += `\n\n@expandable` propertyDescription += `\n\n@expandable`
} }
if (sinceTag) {
propertyDescription += `\n\n@since ${
this.formatJSDocTag(sinceTag) ?? ""
}`
}
if (featureFlagTag) {
propertyDescription += `\n\n@featureFlag ${
this.formatJSDocTag(featureFlagTag) ?? ""
}`
}
if (deprecatedTag) {
propertyDescription += `\n\n@deprecated ${
this.formatJSDocTag(deprecatedTag) ?? ""
}`
}
properties[propertyName] = propertyDescription properties[propertyName] = propertyDescription
}) })
@@ -3,6 +3,7 @@ import path from "path"
import { import {
Application, Application,
Comment, Comment,
CommentTag,
Context, Context,
Converter, Converter,
DeclarationReflection, DeclarationReflection,
@@ -12,6 +13,9 @@ import { getDirname, getDmlProperties, isDmlEntity } from "utils"
import { DmlFile } from "types" import { DmlFile } from "types"
const FILE_NAME_REGEX = /packages\/modules\/(?<module>[a-z-]+)/ const FILE_NAME_REGEX = /packages\/modules\/(?<module>[a-z-]+)/
const SINCE_REGEX = /@since\s+([\d.]+)/
const DEPRECATED_REGEX = /@deprecated\s+(.+)/
const FEATURE_FLAG_REGEX = /@featureFlag\s+(\S+)/
export function load(app: Application) { export function load(app: Application) {
app.converter.on( app.converter.on(
@@ -76,6 +80,41 @@ function getDescriptionsFromJson(
if (!jsonFileContent[reflection.name]) { if (!jsonFileContent[reflection.name]) {
return return
} }
const comment = reflection.comment || new Comment()
if (jsonFileContent[reflection.name].since) {
comment.blockTags.push(
new CommentTag("@since", [
{
kind: "text",
text: jsonFileContent[reflection.name].since!,
},
])
)
}
if (jsonFileContent[reflection.name].deprecated?.is_deprecated) {
comment.blockTags.push(
new CommentTag("@deprecated", [
{
kind: "text",
text: jsonFileContent[reflection.name].deprecated!.description || "",
},
])
)
}
if (jsonFileContent[reflection.name].featureFlag) {
comment.blockTags.push(
new CommentTag("@featureFlag", [
{
kind: "text",
text: jsonFileContent[reflection.name].featureFlag!,
},
])
)
}
reflection.comment = comment
Object.entries(jsonFileContent[reflection.name].properties).forEach( Object.entries(jsonFileContent[reflection.name].properties).forEach(
([propertyName, description]) => { ([propertyName, description]) => {
@@ -90,16 +129,57 @@ function getDescriptionsFromJson(
const comment = propertyReflection.comment || new Comment() const comment = propertyReflection.comment || new Comment()
const isExpandable = description.includes("@expandable") const isExpandable = description.includes("@expandable")
const sinceMatch = description.match(SINCE_REGEX)
const featureFlagMatch = description.match(FEATURE_FLAG_REGEX)
const deprecatedMatch = description.match(DEPRECATED_REGEX)
comment.summary.push({ comment.summary.push({
kind: "text", kind: "text",
text: description.replace("@expandable", "").trim(), text: description
.replace("@expandable", "")
.replace(SINCE_REGEX, "")
.replace(FEATURE_FLAG_REGEX, "")
.replace(DEPRECATED_REGEX, "")
.trim(),
}) })
if (isExpandable) { if (isExpandable) {
comment.modifierTags.add("@expandable") comment.modifierTags.add("@expandable")
} }
if (sinceMatch) {
comment.blockTags.push(
new CommentTag("@since", [
{
kind: "text",
text: sinceMatch[1],
},
])
)
}
if (featureFlagMatch) {
comment.blockTags.push(
new CommentTag("@featureFlag", [
{
kind: "text",
text: featureFlagMatch[1],
},
])
)
}
if (deprecatedMatch) {
comment.blockTags.push(
new CommentTag("@deprecated", [
{
kind: "text",
text: deprecatedMatch[1],
},
])
)
}
propertyReflection.comment = comment propertyReflection.comment = comment
} }
) )
@@ -13,6 +13,6 @@ export default function () {
const tagContent = sinceTag.content.map((content) => content.text).join("") const tagContent = sinceTag.content.map((content) => content.text).join("")
return `:::note\n\nThis is available starting from Medusa \`v${tagContent}\`.\n\n:::` return `:::note\n\nThis is available starting from [Medusa v${tagContent}](https://github.com/medusajs/medusa/releases/tag/v${tagContent}).\n\n:::`
}) })
} }
@@ -4,6 +4,8 @@
{{/if}} {{/if}}
{{{version this}}}
{{{sourceCodeLink}}} {{{sourceCodeLink}}}
{{{dmlProperties}}} {{{dmlProperties}}}
@@ -34,4 +34,9 @@ export type Parameter = {
featureFlag?: string featureFlag?: string
expandable: boolean expandable: boolean
children?: Parameter[] children?: Parameter[]
since?: string
deprecated?: {
is_deprecated: boolean
description?: string
}
} }
@@ -133,6 +133,10 @@ export function reflectionComponentFormatter({
reflection.flags.isOptional || reflection.flags.isOptional ||
reflection.kind === ReflectionKind.EnumMember reflection.kind === ReflectionKind.EnumMember
const comments = getComments(reflection) const comments = getComments(reflection)
const deprecatedTag = comments?.blockTags.find(
(tag) => tag.tag === "@deprecated"
)
const sinceTag = comments?.blockTags.find((tag) => tag.tag === "@since")
const componentItem: Parameter = { const componentItem: Parameter = {
name: reflection.name, name: reflection.name,
type: reflection.type type: reflection.type
@@ -160,6 +164,17 @@ export function reflectionComponentFormatter({
children: [], children: [],
} }
if (sinceTag) {
componentItem.since = sinceTag.content.map((c) => c.text).join("")
}
if (deprecatedTag) {
componentItem.deprecated = {
is_deprecated: true,
description: deprecatedTag?.content.map((c) => c.text).join(""),
}
}
if (level + 1 > (maxLevel || MarkdownTheme.MAX_LEVEL)) { if (level + 1 > (maxLevel || MarkdownTheme.MAX_LEVEL)) {
return componentItem return componentItem
} }
@@ -66,7 +66,12 @@ export function getReflectionTypeParameters({
description = loadComment(typeName, project) description = loadComment(typeName, project)
} }
return { const deprecatedTag = comment?.blockTags.find(
(tag) => tag.tag === "@deprecated"
)
const sinceTag = comment?.blockTags.find((tag) => tag.tag === "@since")
const parameter: Parameter = {
name: "name" in reflectionType ? reflectionType.name : typeName, name: "name" in reflectionType ? reflectionType.name : typeName,
type, type,
optional: optional:
@@ -84,6 +89,19 @@ export function getReflectionTypeParameters({
featureFlag: Handlebars.helpers.featureFlag(comment), featureFlag: Handlebars.helpers.featureFlag(comment),
children: [], children: [],
} }
if (sinceTag) {
parameter.since = sinceTag.content.map((c) => c.text).join("")
}
if (deprecatedTag) {
parameter.deprecated = {
is_deprecated: true,
description: deprecatedTag?.content.map((c) => c.text).join(""),
}
}
return parameter
} }
const componentItem: Parameter[] = [] const componentItem: Parameter[] = []
+6
View File
@@ -307,6 +307,12 @@ export declare type DmlFile = {
[k: string]: { [k: string]: {
filePath: string filePath: string
properties: DmlObject properties: DmlObject
since?: string
deprecated?: {
is_deprecated: boolean
description?: string
}
featureFlag?: string
} }
} }