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

@@ -0,0 +1,156 @@
"use client"
import React from "react"
import { useEffect, useMemo, useState } from "react"
import { useRequestRunner } from "../../../hooks"
import { CodeBlock } from ".."
import { Card } from "../../Card"
import { Button, InputText } from "../../.."
import { ApiMethod, ApiDataOptions, ApiTestingOptions } from "types"
type ApiRunnerProps = {
apiMethod: ApiMethod
apiUrl: string
pathData?: Record<string, unknown>
queryData?: Record<string, unknown>
bodyData?: Record<string, unknown>
}
export const ApiRunner = ({
apiMethod,
apiUrl,
pathData,
bodyData,
queryData,
}: ApiRunnerProps) => {
// assemble api testing options
const [apiTestingOptions, setApiTestingOptions] = useState<ApiTestingOptions>(
{
method: apiMethod,
url: apiUrl,
pathData,
bodyData,
queryData,
}
)
const [isRunning, setIsRunning] = useState(false)
const [ran, setRan] = useState(false)
const hasData = (data?: Record<string, unknown>): boolean =>
data !== undefined && Object.keys(data).length > 0
// TODO change to be based on whether auth/data needed
const manualTestTrigger = useMemo(
() =>
hasData(apiTestingOptions.pathData) ||
hasData(apiTestingOptions.queryData) ||
hasData(apiTestingOptions.bodyData),
[apiTestingOptions]
)
const [responseLogs, setResponseLogs] = useState<string[]>([])
const pushMessage = (...message: string[]) =>
setResponseLogs((prev) => [...prev, ...message])
const { runRequest } = useRequestRunner({
pushLog: pushMessage,
onFinish: () => setIsRunning(false),
replaceLog: (message) => setResponseLogs([message]),
})
useEffect(() => {
if (!isRunning && !manualTestTrigger && !ran) {
setIsRunning(true)
}
}, [apiTestingOptions, manualTestTrigger, isRunning, ran])
useEffect(() => {
if (isRunning && !ran) {
setRan(true)
setResponseLogs(["Sending request..."])
runRequest(apiTestingOptions)
}
}, [isRunning, ran])
const getParamsElms = ({
data,
title,
nameInApiOptions,
}: {
data: ApiDataOptions
title: string
nameInApiOptions: "pathData" | "bodyData" | "queryData"
}) => (
<div className="flex flex-col gap-docs_0.5">
<span className="text-compact-medium-plus text-medusa-fg-base">
{title}
</span>
<div className="flex gap-docs_0.5">
{Object.keys(data).map((pathParam, index) => (
<InputText
name={pathParam}
onChange={(e) =>
setApiTestingOptions((prev) => ({
...prev,
[nameInApiOptions]: {
...prev[nameInApiOptions],
[pathParam]: e.target.value,
},
}))
}
key={index}
placeholder={pathParam}
value={
typeof data[pathParam] === "string"
? (data[pathParam] as string)
: typeof data[pathParam] === "number"
? (data[pathParam] as number)
: `${data[pathParam]}`
}
/>
))}
</div>
</div>
)
return (
<>
{manualTestTrigger && (
<Card className="font-base mb-docs_1" contentClassName="gap-docs_0.5">
{apiTestingOptions.pathData &&
getParamsElms({
data: apiTestingOptions.pathData,
title: "Path Parameters",
nameInApiOptions: "pathData",
})}
{apiTestingOptions.bodyData &&
getParamsElms({
data: apiTestingOptions.bodyData,
title: "Request Body Parameters",
nameInApiOptions: "bodyData",
})}
{apiTestingOptions.queryData &&
getParamsElms({
data: apiTestingOptions.queryData,
title: "Request Query Parameters",
nameInApiOptions: "queryData",
})}
<Button
onClick={() => {
setIsRunning(true)
setRan(false)
}}
>
Send Request
</Button>
</Card>
)}
{(isRunning || ran) && (
<CodeBlock
source={responseLogs.join("\n")}
lang="json"
title="Testing Result"
collapsed={true}
blockStyle="subtle"
noReport={true}
/>
)}
</>
)
}

View File

@@ -0,0 +1,84 @@
"use client"
import React, { useMemo } from "react"
import clsx from "clsx"
import { CodeBlockStyle } from ".."
import { useColorMode } from "@/providers"
import { Badge, BadgeVariant } from "@/components"
export type CodeBlockHeaderMeta = {
badgeLabel?: string
badgeColor?: BadgeVariant
}
type CodeBlockHeaderProps = {
children?: React.ReactNode
title?: string
blockStyle?: CodeBlockStyle
} & CodeBlockHeaderMeta
export const CodeBlockHeader = ({
children,
title,
blockStyle = "loud",
badgeLabel,
badgeColor,
}: CodeBlockHeaderProps) => {
const { colorMode } = useColorMode()
const borderColor = useMemo(
() =>
clsx(
blockStyle === "loud" && [
colorMode === "light" && "border-medusa-code-border",
colorMode === "dark" && "border-medusa-border-base",
],
blockStyle === "subtle" && [
colorMode === "light" && "border-medusa-border-base",
colorMode === "dark" && "border-medusa-code-border",
]
),
[blockStyle, colorMode]
)
return (
<div
className={clsx(
"py-docs_0.75 rounded-t-docs_DEFAULT px-docs_1 mb-0",
"flex gap-docs_2 items-start justify-between",
blockStyle === "loud" && [
colorMode === "light" && "bg-medusa-code-bg-header",
colorMode === "dark" && "bg-medusa-bg-base",
],
blockStyle === "subtle" && [
colorMode === "light" && "bg-medusa-bg-component",
colorMode === "dark" && "bg-medusa-code-bg-header",
],
borderColor && `border border-b-0 ${borderColor}`
)}
>
{children}
{title && (
<div
className={clsx(
"txt-compact-small-plus",
blockStyle === "loud" && [
colorMode === "light" && "text-medusa-code-text-subtle",
colorMode === "dark" && "text-medusa-fg-muted",
],
blockStyle === "subtle" && [
colorMode === "light" && "text-medusa-fg-subtle",
colorMode === "dark" && "text-medusa-code-text-subtle",
]
)}
>
{title}
</div>
)}
{badgeLabel && (
<Badge variant={badgeColor || "orange"} className="font-base">
{badgeLabel}
</Badge>
)}
</div>
)
}

View File

@@ -0,0 +1,241 @@
import React, { useMemo } from "react"
import { Highlight } from ".."
import { RenderProps, Token } from "prism-react-renderer"
import clsx from "clsx"
import { MarkdownContent, Tooltip } from "@/components"
type CodeBlockLineProps = {
line: Token[]
highlights?: Highlight[]
lineNumber: number
showLineNumber: boolean
bgColorClassName: string
lineNumberColorClassName: string
noLineNumbers?: boolean
} & Pick<RenderProps, "getLineProps" | "getTokenProps">
export const CodeBlockLine = ({
line,
highlights = [],
lineNumber,
getLineProps,
getTokenProps,
showLineNumber,
bgColorClassName,
lineNumberColorClassName,
}: CodeBlockLineProps) => {
const lineProps = getLineProps({ line, key: lineNumber })
// collect highlighted tokens, if there are any
const highlightedTokens: {
start: number
end: number
highlight: Highlight
}[] = []
highlights.forEach((highlight) => {
if (!highlight.text) {
return
}
let startIndex: number | undefined = undefined
let currentPositionInHighlightedText = 0
let endIndex = 0
const found = line.some((token, tokenIndex) => {
if (token.empty || !token.content.length) {
startIndex = undefined
currentPositionInHighlightedText = 0
return false
}
const comparisonLength = Math.min(
token.content.length,
highlight.text!.substring(currentPositionInHighlightedText).length
)
const nextPositionInHighlightedText =
currentPositionInHighlightedText + comparisonLength
const canHighlight =
!highlightedTokens.length ||
!highlightedTokens.some(
(token) => tokenIndex >= token.start && tokenIndex <= token.end
)
if (
token.content.substring(0, comparisonLength) ===
highlight.text?.substring(
currentPositionInHighlightedText,
nextPositionInHighlightedText
) &&
canHighlight
) {
if (startIndex === undefined) {
startIndex = tokenIndex
}
currentPositionInHighlightedText = nextPositionInHighlightedText
}
if (currentPositionInHighlightedText === highlight.text!.length) {
// matching text was found, break loop
endIndex = tokenIndex
return true
}
})
if (found && startIndex !== undefined) {
highlightedTokens.push({
start: startIndex,
end: endIndex,
highlight,
})
}
})
// sort highlighted tokens by their start position
highlightedTokens.sort((tokensA, tokensB) => {
if (tokensA.start < tokensB.start) {
return -1
}
return tokensA.start > tokensB.start ? 1 : 0
})
// if there are highlighted tokens, split tokens in the
// line by segments of not highlighted and highlighted token
// if there are no highlighted tokens, the line is used as-is.
const transformedLine: {
tokens: Token[]
type: "default" | "highlighted"
highlight?: Highlight
}[] = highlightedTokens.length
? []
: [
{
tokens: line,
type: "default",
},
]
let lastIndex = 0
// go through highlighted tokens to add the segments before/after to the
// transformedLines array
highlightedTokens.forEach((highlightedTokensItem, index) => {
if (lastIndex < highlightedTokensItem.start) {
transformedLine.push({
tokens: line.slice(lastIndex, highlightedTokensItem.start),
type: "default",
})
}
transformedLine.push({
tokens: line.slice(
highlightedTokensItem.start,
highlightedTokensItem.end + 1
),
type: "highlighted",
highlight: highlightedTokensItem.highlight,
})
lastIndex = highlightedTokensItem.end + 1
// if this is the last item in `highlightedTokens` and
// its end index is less than the line's length, that means
// there are tokens at the end of the line that aren't highlighted
// and should be pushed as-is to the `transformedLines` array.
if (index === highlightedTokens.length - 1 && lastIndex < line.length - 1) {
transformedLine.push({
tokens: line.slice(lastIndex),
type: "default",
})
}
})
const getTokensElm = ({
tokens,
isHighlighted,
offset,
}: {
tokens: Token[]
isHighlighted: boolean
offset: number
}) => (
<span
className={clsx(
// TODO change code colors and class names based on figma colors
isHighlighted && [
"lg:py-px lg:px-[6px] lg:border-medusa-code-icon lg:rounded-docs_sm",
"lg:bg-medusa-code-border lg:cursor-pointer",
]
)}
>
{tokens.map((token, key) => {
const tokenKey = offset + key
const { className: tokenClassName, ...rest } = getTokenProps({
token,
key: tokenKey,
})
return (
<span key={tokenKey} className={clsx(tokenClassName)} {...rest} />
)
})}
</span>
)
const isHighlightedLine = useMemo(
() => highlights.length && !highlightedTokens.length,
[highlights, highlightedTokens]
)
return (
<span
key={lineNumber}
{...lineProps}
className={clsx(
"table-row",
isHighlightedLine && "bg-medusa-code-bg-header",
lineProps.className
)}
>
{showLineNumber && (
<span
className={clsx(
"mr-docs_1 table-cell select-none",
"sticky left-0 w-[1%] px-docs_1 text-right",
bgColorClassName,
lineNumberColorClassName
)}
>
{lineNumber + 1}
</span>
)}
<span>
{transformedLine.map(({ tokens, type, highlight }, index) => {
const offset =
index === 0 ? 0 : transformedLine[index - 1].tokens.length
const tooltipText =
highlight?.tooltipText ||
(isHighlightedLine
? highlights.find((h) => h.tooltipText !== undefined)?.tooltipText
: undefined)
const isHighlighted = type === "highlighted"
return (
<React.Fragment key={index}>
{tooltipText && (
<Tooltip
text={tooltipText}
tooltipClassName="font-base"
render={({ content }) => (
<MarkdownContent
allowedElements={["a", "strong", "code"]}
unwrapDisallowed={true}
>
{content || ""}
</MarkdownContent>
)}
>
{getTokensElm({ tokens, isHighlighted, offset })}
</Tooltip>
)}
{!tooltipText && getTokensElm({ tokens, isHighlighted, offset })}
</React.Fragment>
)
})}
</span>
</span>
)
}

View File

@@ -1,124 +1,329 @@
"use client"
import React from "react"
import React, { useMemo, useState } from "react"
import clsx from "clsx"
import { HighlightProps, Highlight, themes } from "prism-react-renderer"
import { CopyButton, useColorMode } from "docs-ui"
import { SquareTwoStackSolid } from "@medusajs/icons"
import { CopyButton, Tooltip, LegacyLink } from "@/components"
import { useColorMode } from "@/providers"
import { ExclamationCircle, PlaySolid, SquareTwoStack } from "@medusajs/icons"
import { CodeBlockHeader, CodeBlockHeaderMeta } from "./Header"
import { CodeBlockLine } from "./Line"
import { ApiAuthType, ApiDataOptions, ApiMethod } from "types"
import { CSSTransition } from "react-transition-group"
import { ApiRunner } from "./ApiRunner"
import { GITHUB_ISSUES_PREFIX } from "../.."
export type Highlight = {
line: number
text?: string
tooltipText?: string
}
export type CodeBlockMetaFields = {
title?: string
npm2yarn?: boolean
highlights?: string[][]
apiTesting?: boolean
testApiMethod?: ApiMethod
testApiUrl?: string
testAuthType?: ApiAuthType
testPathParams?: ApiDataOptions
testQueryParams?: ApiDataOptions
testBodyParams?: ApiDataOptions
noCopy?: boolean
noReport?: boolean
noLineNumbers?: boolean
} & CodeBlockHeaderMeta
export type CodeBlockStyle = "loud" | "subtle"
export type CodeBlockProps = {
source: string
lang?: string
className?: string
collapsed?: boolean
} & Omit<HighlightProps, "code" | "language" | "children">
blockStyle?: CodeBlockStyle
children?: React.ReactNode
} & CodeBlockMetaFields &
Omit<HighlightProps, "code" | "language" | "children">
export const CodeBlock = ({
source,
lang = "",
className,
collapsed = false,
title = "",
highlights = [],
apiTesting = false,
blockStyle = "loud",
noCopy = false,
noReport = false,
noLineNumbers = false,
children,
...rest
}: CodeBlockProps) => {
if (!source && typeof children === "string") {
source = children
}
const { colorMode } = useColorMode()
const [showTesting, setShowTesting] = useState(false)
const canShowApiTesting = useMemo(
() => apiTesting && rest.testApiMethod && rest.testApiUrl,
[apiTesting, rest]
)
const bgColor = useMemo(
() =>
clsx(
blockStyle === "loud" && [
colorMode === "light" && "bg-medusa-code-bg-base",
colorMode === "dark" && "bg-medusa-bg-component",
],
blockStyle === "subtle" && [
colorMode === "light" && "bg-medusa-bg-subtle",
colorMode === "dark" && "bg-medusa-code-bg-base",
]
),
[blockStyle, colorMode]
)
const lineNumbersColor = useMemo(
() =>
clsx(
blockStyle === "loud" && [
colorMode === "light" && "text-medusa-code-text-subtle",
colorMode === "dark" && "text-medusa-fg-muted",
],
blockStyle === "subtle" && [
colorMode === "light" && "text-medusa-fg-muted",
colorMode === "dark" && "text-medusa-code-text-subtle",
]
),
[blockStyle, colorMode]
)
const borderColor = useMemo(
() =>
clsx(
blockStyle === "loud" && [
colorMode === "light" && "border-medusa-code-border",
colorMode === "dark" && "border-medusa-border-base",
],
blockStyle === "subtle" && [
colorMode === "light" && "border-medusa-border-base",
colorMode === "dark" && "border-medusa-code-border",
]
),
[blockStyle, colorMode]
)
const iconColor = useMemo(
() =>
clsx(
blockStyle === "loud" && [
colorMode === "light" && "text-medusa-code-icon",
colorMode === "dark" && "text-medusa-fg-muted",
],
blockStyle === "subtle" && [
colorMode === "light" && "text-medusa-fg-muted",
colorMode === "dark" && "text-medusa-code-icon",
]
),
[blockStyle, colorMode]
)
if (!source.length) {
return <></>
}
const transformedHighlights: Highlight[] = highlights
.filter((highlight) => highlight.length !== 0)
.map((highlight) => ({
line: parseInt(highlight[0]),
text: highlight.length >= 2 ? highlight[1] : undefined,
tooltipText: highlight.length >= 3 ? highlight[2] : undefined,
}))
return (
<div
className={clsx(
"bg-medusa-code-bg-base relative mb-docs_1 rounded-docs_DEFAULT",
"border-medusa-code-border w-full max-w-full border",
collapsed && "max-h-[400px] overflow-auto",
className
<>
{title && (
<CodeBlockHeader
title={title}
blockStyle={blockStyle}
badgeLabel={rest.badgeLabel}
badgeColor={rest.badgeColor}
/>
)}
>
<Highlight
theme={{
...themes.vsDark,
plain: {
...themes.vsDark.plain,
backgroundColor: colorMode === "light" ? "#111827" : "#1B1B1F",
},
}}
code={source.trim()}
language={lang.toLowerCase()}
{...rest}
>
{({
className: preClassName,
style,
tokens,
getLineProps,
getTokenProps,
}) => (
<>
<pre
style={{ ...style, fontStretch: "100%" }}
className={clsx(
"xs:max-w-[90%] relative !my-0 break-words bg-transparent !outline-none",
"overflow-auto break-words rounded-docs_DEFAULT p-0",
preClassName
)}
>
<code
className={clsx(
"text-code-body font-monospace table min-w-full pb-docs_1.5 print:whitespace-pre-wrap",
tokens.length > 1 && "pt-docs_1 pr-docs_1",
tokens.length <= 1 && "!py-docs_0.5 px-docs_1"
)}
>
{tokens.map((line, i) => {
const lineProps = getLineProps({ line, key: i })
return (
<span
key={i}
{...lineProps}
className={clsx("table-row", lineProps.className)}
>
{tokens.length > 1 && (
<span
className={clsx(
"text-medusa-code-text-subtle mr-docs_1 table-cell select-none",
"bg-medusa-code-bg-base sticky left-0 w-[1%] px-docs_1 text-right"
)}
>
{i + 1}
</span>
)}
<span>
{line.map((token, key) => (
<span key={key} {...getTokenProps({ token, key })} />
))}
</span>
</span>
)
})}
</code>
</pre>
<div
className={clsx(
"absolute hidden gap-docs_1 md:flex",
"xs:rounded xs:absolute xs:right-0 xs:top-0 xs:w-[calc(10%+24px)] xs:h-full xs:bg-code-fade"
)}
>
<CopyButton
text={source}
tooltipClassName="font-base"
className={clsx(
"absolute",
tokens.length === 1 && "right-docs_0.75 top-[10px]",
tokens.length > 1 && "right-docs_1 top-docs_1"
)}
>
<SquareTwoStackSolid className="text-medusa-code-icon" />
</CopyButton>
</div>
</>
<div
className={clsx(
"relative mb-docs_1 rounded-b-docs_DEFAULT",
"w-full max-w-full border",
bgColor,
borderColor,
collapsed && "max-h-[400px] overflow-auto",
!title && "rounded-t-docs_DEFAULT",
(blockStyle === "loud" || colorMode !== "light") &&
"code-block-highlight-dark",
blockStyle === "subtle" &&
colorMode === "light" &&
"code-block-highlight-light",
className
)}
</Highlight>
</div>
>
<Highlight
theme={
blockStyle === "loud" || colorMode === "dark"
? {
...themes.vsDark,
plain: {
...themes.vsDark.plain,
backgroundColor:
blockStyle === "loud"
? colorMode === "light"
? "#111827"
: "#27282D"
: "#1B1B1F",
},
}
: {
...themes.vsLight,
plain: {
...themes.vsLight.plain,
backgroundColor: "#F9FAFB",
},
}
}
code={source.trim()}
language={lang.toLowerCase()}
{...rest}
>
{({ className: preClassName, style, tokens, ...rest }) => (
<>
<pre
style={{ ...style, fontStretch: "100%" }}
className={clsx(
"relative !my-0 break-words bg-transparent !outline-none",
"overflow-auto break-words rounded-docs_DEFAULT p-0 xs:max-w-[83%]",
preClassName
)}
>
<code
className={clsx(
"text-code-body font-monospace table min-w-full pb-docs_1.5 print:whitespace-pre-wrap",
tokens.length > 1 && "pt-docs_1 pr-docs_1",
tokens.length <= 1 && "!py-docs_0.25 px-[6px]"
)}
>
{tokens.map((line, i) => {
const highlightedLines = transformedHighlights.filter(
(highlight) => highlight.line - 1 === i
)
return (
<CodeBlockLine
line={line}
lineNumber={i}
highlights={highlightedLines}
showLineNumber={!noLineNumbers && tokens.length > 1}
key={i}
bgColorClassName={bgColor}
lineNumberColorClassName={lineNumbersColor}
{...rest}
/>
)
})}
</code>
</pre>
<div
className={clsx(
"absolute hidden md:flex md:justify-end",
"xs:rounded xs:absolute xs:right-0 xs:top-0 xs:w-[calc(10%+24px)] xs:h-full xs:bg-transparent",
tokens.length === 1 && "md:right-[6px] md:top-0",
tokens.length > 1 && "md:right-docs_1 md:top-docs_1"
)}
>
{canShowApiTesting && (
<Tooltip
text="Test API"
tooltipClassName="font-base"
className={clsx(
"h-fit",
tokens.length === 1 && "p-[6px]",
tokens.length > 1 && "px-[6px] pb-[6px]"
)}
>
<PlaySolid
className={clsx("cursor-pointer", iconColor)}
onClick={() => setShowTesting(true)}
/>
</Tooltip>
)}
{!noReport && (
<Tooltip
text="Report Issue"
tooltipClassName="font-base"
className={clsx(
"h-fit",
tokens.length === 1 && "p-[6px]",
tokens.length > 1 && "px-[6px] pb-[6px]"
)}
>
{/* TODO replace with Link once we move away from Docusaurus */}
<LegacyLink
href={`${GITHUB_ISSUES_PREFIX}&title=${encodeURIComponent(
`Docs(Code Issue): `
)}`}
target="_blank"
className={clsx(
blockStyle === "loud" && "hover:bg-medusa-code-bg-base",
"bg-transparent border-none cursor-pointer rounded",
"[&:not(:first-child)]:ml-docs_0.5",
"inline-flex justify-center items-center invisible xs:visible"
)}
rel="noreferrer"
>
<ExclamationCircle className={clsx(iconColor)} />
</LegacyLink>
</Tooltip>
)}
{!noCopy && (
<CopyButton
text={source}
tooltipClassName="font-base"
className={clsx(
"h-fit",
tokens.length === 1 && "p-[6px]",
tokens.length > 1 && "px-[6px] pb-[6px]"
)}
>
<SquareTwoStack className={clsx(iconColor)} />
</CopyButton>
)}
</div>
</>
)}
</Highlight>
</div>
{canShowApiTesting && (
<CSSTransition
unmountOnExit
in={showTesting}
timeout={150}
classNames={{
enter: "animate-fadeIn animate-fastest",
exit: "animate-fadeOut animate-fastest",
}}
>
<ApiRunner
apiMethod={rest.testApiMethod!}
apiUrl={rest.testApiUrl!}
pathData={rest.testPathParams}
bodyData={rest.testBodyParams}
queryData={rest.testQueryParams}
/>
</CSSTransition>
)}
</>
)
}