docs: add clean markdown version of all documentation pages (#11308)

* added route to book

* added to resources

* added route to ui

* added to user guide
This commit is contained in:
Shahed Nasser
2025-02-05 11:23:13 +02:00
committed by GitHub
parent 87db3f0c45
commit 98236c8262
30 changed files with 1086 additions and 192 deletions
@@ -205,40 +205,40 @@ You can now build your plugin's customizations. The following guide explains how
<CardList
items={[
{
text: "Create a module",
link: "/learn/fundamentals/modules",
title: "Create a module",
href: "/learn/fundamentals/modules",
},
{
text: "Create a module link",
link: "/learn/fundamentals/module-links",
title: "Create a module link",
href: "/learn/fundamentals/module-links",
},
{
text: "Create a workflow",
link: "/learn/fundamentals/workflows",
title: "Create a workflow",
href: "/learn/fundamentals/workflows",
},
{
text: "Add a workflow hook",
link: "/learn/fundamentals/workflows/add-workflow-hook",
title: "Add a workflow hook",
href: "/learn/fundamentals/workflows/add-workflow-hook",
},
{
text: "Create an API route",
link: "/learn/fundamentals/api-routes",
title: "Create an API route",
href: "/learn/fundamentals/api-routes",
},
{
text: "Add a subscriber",
link: "/learn/fundamentals/events-and-subscribers",
title: "Add a subscriber",
href: "/learn/fundamentals/events-and-subscribers",
},
{
text: "Add a scheduled job",
link: "/learn/fundamentals/scheduled-jobs",
title: "Add a scheduled job",
href: "/learn/fundamentals/scheduled-jobs",
},
{
text: "Add an admin widget",
link: "/learn/fundamentals/admin/widgets",
title: "Add an admin widget",
href: "/learn/fundamentals/admin/widgets",
},
{
text: "Add an admin UI route",
link: "/learn/fundamentals/admin/ui-routes",
title: "Add an admin UI route",
href: "/learn/fundamentals/admin/ui-routes",
}
]}
className="mb-1.5"
+1 -1
View File
@@ -72,7 +72,7 @@ Below are some stories from companies that use Medusa:
This documentation introduces you to Medusa's concepts and how they help you build your business use case. The documentation is structured to gradually introduce Medusa's concepts, with easy-to-follow examples along the way.
By following this documentation, youll be able to create custom commerce experiences that would otherwise take large engineering teams months to build.
By following this documentation, you'll be able to create custom commerce experiences that would otherwise take large engineering teams months to build.
### How to use the documentation
@@ -13,11 +13,11 @@ You can build your storefront from scratch with your preferred tech stack, or st
<CardList
items={[
{
text: "Install Next.js Starter Storefront",
title: "Install Next.js Starter Storefront",
href: "!resources!/nextjs-starter"
},
{
text: "Build Custom Storefront",
title: "Build Custom Storefront",
href: "!resources!/storefront-development"
}
]}
@@ -0,0 +1,75 @@
import { getCleanMd } from "docs-utils"
import { existsSync } from "fs"
import { unstable_cache } from "next/cache"
import { notFound } from "next/navigation"
import { NextRequest, NextResponse } from "next/server"
import path from "path"
import {
addUrlToRelativeLink,
crossProjectLinksPlugin,
localLinksRehypePlugin,
} from "remark-rehype-plugins"
import type { Plugin } from "unified"
type Params = {
params: Promise<{ slug: string[] }>
}
export async function GET(req: NextRequest, { params }: Params) {
const { slug } = await params
// keep this so that Vercel keeps the files in deployment
const basePath = path.join(process.cwd(), "app")
const filePath = path.join(basePath, ...slug, "page.mdx")
if (!existsSync(filePath)) {
return notFound()
}
const cleanMdContent = await getCleanMd_(filePath, {
before: [
[
crossProjectLinksPlugin,
{
baseUrl: process.env.NEXT_PUBLIC_BASE_URL,
projectUrls: {
resources: {
url: process.env.NEXT_PUBLIC_RESOURCES_URL,
},
"user-guide": {
url: process.env.NEXT_PUBLIC_RESOURCES_URL,
},
ui: {
url: process.env.NEXT_PUBLIC_RESOURCES_URL,
},
api: {
url: process.env.NEXT_PUBLIC_RESOURCES_URL,
},
},
useBaseUrl:
process.env.NODE_ENV === "production" ||
process.env.VERCEL_ENV === "production",
},
],
[localLinksRehypePlugin],
] as unknown as Plugin[],
after: [
[addUrlToRelativeLink, { url: process.env.NEXT_PUBLIC_BASE_URL }],
] as unknown as Plugin[],
})
return new NextResponse(cleanMdContent, {
headers: {
"Content-Type": "text/markdown",
},
})
}
const getCleanMd_ = unstable_cache(
async (filePath: string, plugins?: { before?: Plugin[]; after?: Plugin[] }) =>
getCleanMd({ filePath, plugins }),
["clean-md"],
{
revalidate: 3600,
}
)
+15
View File
@@ -0,0 +1,15 @@
import { NextResponse } from "next/server"
import type { NextRequest } from "next/server"
export function middleware(request: NextRequest) {
return NextResponse.rewrite(
new URL(
`/md-content${request.nextUrl.pathname.replace("/index.html.md", "")}`,
request.url
)
)
}
export const config = {
matcher: "/:path*/index.html.md",
}
+1
View File
@@ -20,6 +20,7 @@
"@next/mdx": "15.0.4",
"clsx": "^2.1.0",
"docs-ui": "*",
"docs-utils": "*",
"next": "15.0.4",
"react": "rc",
"react-dom": "rc",
@@ -0,0 +1,98 @@
import { getCleanMd } from "docs-utils"
import { existsSync } from "fs"
import { unstable_cache } from "next/cache"
import { notFound } from "next/navigation"
import { NextRequest, NextResponse } from "next/server"
import path from "path"
import {
addUrlToRelativeLink,
crossProjectLinksPlugin,
localLinksRehypePlugin,
} from "remark-rehype-plugins"
import type { Plugin } from "unified"
import { filesMap } from "../../../generated/files-map.mjs"
import { slugChanges } from "../../../generated/slug-changes.mjs"
type Params = {
params: Promise<{ slug: string[] }>
}
export async function GET(req: NextRequest, { params }: Params) {
const { slug = ["/"] } = await params
// keep this so that Vercel keeps the files in deployment
path.join(process.cwd(), "app")
path.join(process.cwd(), "references")
const filePathFromMap = await getFileFromMaps(`/${slug.join("/")}`)
if (!filePathFromMap) {
return notFound()
}
const filePath = path.join(path.resolve("..", "..", ".."), filePathFromMap)
if (!existsSync(filePath)) {
return notFound()
}
const cleanMdContent = await getCleanMd_(filePath, {
before: [
[
crossProjectLinksPlugin,
{
baseUrl: process.env.NEXT_PUBLIC_BASE_URL,
projectUrls: {
docs: {
url: process.env.NEXT_PUBLIC_DOCS_URL,
path: "",
},
"user-guide": {
url: process.env.NEXT_PUBLIC_USER_GUIDE_URL,
},
ui: {
url: process.env.NEXT_PUBLIC_UI_URL,
},
api: {
url: process.env.NEXT_PUBLIC_API_URL,
},
},
useBaseUrl:
process.env.NODE_ENV === "production" ||
process.env.VERCEL_ENV === "production",
},
],
[localLinksRehypePlugin],
] as unknown as Plugin[],
after: [
[addUrlToRelativeLink, { url: process.env.NEXT_PUBLIC_BASE_URL }],
] as unknown as Plugin[],
})
return new NextResponse(cleanMdContent, {
headers: {
"Content-Type": "text/markdown",
},
})
}
const getCleanMd_ = unstable_cache(
async (filePath: string, plugins?: { before?: Plugin[]; after?: Plugin[] }) =>
getCleanMd({ filePath, plugins }),
["clean-md"],
{
revalidate: 3600,
}
)
const getFileFromMaps = unstable_cache(
async (path: string) => {
return (
slugChanges.find((slugChange) => slugChange.newSlug === path)?.filePath ||
filesMap.find((file) => file.pathname === path)?.filePath
)
},
["file-map"],
{
revalidate: 3600,
}
)
+15
View File
@@ -0,0 +1,15 @@
import { NextResponse } from "next/server"
import type { NextRequest } from "next/server"
export function middleware(request: NextRequest) {
return NextResponse.rewrite(
new URL(
`${request.nextUrl.basePath}/md-content${request.nextUrl.pathname.replace("/index.html.md", "")}`,
request.url
)
)
}
export const config = {
matcher: "/:path*/index.html.md",
}
+11 -2
View File
@@ -1,8 +1,9 @@
import "dotenv/config"
import { defineDocumentType, makeSource } from "contentlayer/source-files"
import { rehypeComponent } from "./src/lib/rehype-component"
import rehypeSlug from "rehype-slug"
import { uiRehypePlugin } from "../../packages/remark-rehype-plugins/src"
import { ExampleRegistry } from "./src/registries/example-registry"
export const Doc = defineDocumentType(() => ({
name: "Doc",
@@ -29,7 +30,15 @@ export default makeSource({
contentDirPath: "./src/content",
documentTypes: [Doc],
mdx: {
rehypePlugins: [[rehypeComponent], [rehypeSlug]],
rehypePlugins: [
[
uiRehypePlugin,
{
exampleRegistry: ExampleRegistry,
},
],
[rehypeSlug],
],
mdxOptions: (options) => {
return {
...options,
+2
View File
@@ -24,6 +24,7 @@
"contentlayer": "^0.3.4",
"date-fns": "^3.3.1",
"docs-ui": "*",
"docs-utils": "*",
"mdast-util-toc": "^7.0.0",
"next": "15.0.4",
"next-contentlayer": "^0.3.4",
@@ -47,6 +48,7 @@
"eslint-plugin-prettier": "^5.2.1",
"eslint-plugin-react-hooks": "^5.0.0",
"react-docgen": "^7.1.0",
"remark-rehype-plugins": "*",
"ts-node": "^10.9.1",
"types": "*"
},
@@ -0,0 +1,86 @@
import { getCleanMd } from "docs-utils"
import { existsSync } from "fs"
import { unstable_cache } from "next/cache"
import { notFound } from "next/navigation"
import { NextRequest, NextResponse } from "next/server"
import path from "path"
import { addUrlToRelativeLink } from "remark-rehype-plugins"
import type { Plugin } from "unified"
import * as Icons from "@medusajs/icons"
import * as HookValues from "@/registries/hook-values"
import { colors as allColors } from "@/config/colors"
type Params = {
params: Promise<{ slug: string[] }>
}
export async function GET(req: NextRequest, { params }: Params) {
const { slug = ["/"] } = await params
// keep this so that Vercel keeps the files in deployment
const basePath = path.join(process.cwd(), "src", "content", "docs")
const examplesPath = path.join(process.cwd(), "src", "examples")
const specsPath = path.join(process.cwd(), "src", "specs")
const fileName = slug.length === 1 ? "index" : slug.pop() || "index"
const filePath = path.join(basePath, ...slug, `${fileName}.mdx`)
if (!existsSync(filePath)) {
return notFound()
}
const cleanMdContent = await getCleanMd_(
filePath,
{ examplesPath, specsPath },
{
after: [
[addUrlToRelativeLink, { url: process.env.NEXT_PUBLIC_BASE_URL }],
] as unknown as Plugin[],
}
)
return new NextResponse(cleanMdContent, {
headers: {
"Content-Type": "text/markdown",
},
})
}
const getCleanMd_ = unstable_cache(
async (
filePath: string,
parserOptions: {
examplesPath: string
specsPath: string
},
plugins?: { before?: Plugin[]; after?: Plugin[] }
) => {
const iconNames = Object.keys(Icons).filter((name) => name !== "default")
return getCleanMd({
filePath,
plugins,
parserOptions: {
ComponentExample: {
examplesBasePath: parserOptions.examplesPath,
},
ComponentReference: {
specsPath: parserOptions.specsPath,
},
IconSearch: {
iconNames,
},
HookValues: {
hooksData: HookValues,
},
Colors: {
colors: allColors,
},
},
})
},
["clean-md"],
{
revalidate: 3600,
}
)
+15
View File
@@ -0,0 +1,15 @@
import { NextResponse } from "next/server"
import type { NextRequest } from "next/server"
export function middleware(request: NextRequest) {
return NextResponse.rewrite(
new URL(
`${request.nextUrl.basePath}/md-content${request.nextUrl.pathname.replace("/index.html.md", "")}`,
request.url
)
)
}
export const config = {
matcher: "/:path*/index.html.md",
}
+2 -13
View File
@@ -1,19 +1,8 @@
import { HookTable } from "@/components/hook-table"
import { HookDataMap } from "@/types/hooks"
const useToastValues: HookDataMap = [
{
value: "dialog",
type: {
type: "function",
signature: `async (props: PromptProps): Promise<boolean>`,
},
description: "Async function used to display a new confirmation dialog.",
},
]
import { usePrompt } from "../../registries/hook-values"
const Props = () => {
return <HookTable props={useToastValues} />
return <HookTable props={usePrompt} />
}
export default Props
+2 -14
View File
@@ -1,20 +1,8 @@
import { HookTable } from "@/components/hook-table"
import { HookDataMap } from "@/types/hooks"
const useToggleStateValuesArray: HookDataMap = [
{
value: "state",
type: {
type: "object",
name: "StateData",
shape:
"[\n state: boolean,\n open: () => void,\n close: () => void,\n toggle: () => void\n]",
},
},
]
import { useToggleState } from "../../registries/hook-values"
const Props = () => {
return <HookTable props={useToggleStateValuesArray} />
return <HookTable props={useToggleState} />
}
export default Props
@@ -1,12 +1,7 @@
import * as React from "react"
import { ExampleRegistry as ExampleRegistryType } from "types"
type ExampleType = {
name: string
component: React.LazyExoticComponent<() => React.JSX.Element>
file: string
}
export const ExampleRegistry: Record<string, ExampleType> = {
export const ExampleRegistry: ExampleRegistryType = {
"alert-demo": {
name: "alert-demo",
component: React.lazy(async () => import("@/examples/alert-demo")),
+4 -1
View File
@@ -1,6 +1,9 @@
import { HookRegistryItem } from "@/types/hooks"
import * as React from "react"
export type HookRegistryItem = {
table: React.LazyExoticComponent<React.ComponentType>
}
export const HookRegistry: Record<string, HookRegistryItem> = {
usePrompt: {
table: React.lazy(async () => import("../props/hooks/usePrompt")),
+24
View File
@@ -0,0 +1,24 @@
import { HookDataMap } from "../types/hooks"
export const useToggleState: HookDataMap = [
{
value: "state",
type: {
type: "object",
name: "StateData",
shape:
"[\n state: boolean,\n open: () => void,\n close: () => void,\n toggle: () => void\n]",
},
},
]
export const usePrompt: HookDataMap = [
{
value: "dialog",
type: {
type: "function",
signature: `async (props: PromptProps): Promise<boolean>`,
},
description: "Async function used to display a new confirmation dialog.",
},
]
-5
View File
@@ -1,4 +1,3 @@
import { ComponentType, LazyExoticComponent } from "react"
import { PropType } from "./props"
export type HookData = {
@@ -8,7 +7,3 @@ export type HookData = {
}
export type HookDataMap = HookData[]
export type HookRegistryItem = {
table: LazyExoticComponent<ComponentType>
}
@@ -0,0 +1,76 @@
import { getCleanMd } from "docs-utils"
import { existsSync } from "fs"
import { unstable_cache } from "next/cache"
import { notFound } from "next/navigation"
import { NextRequest, NextResponse } from "next/server"
import path from "path"
import {
addUrlToRelativeLink,
crossProjectLinksPlugin,
localLinksRehypePlugin,
} from "remark-rehype-plugins"
import type { Plugin } from "unified"
type Params = {
params: Promise<{ slug: string[] }>
}
export async function GET(req: NextRequest, { params }: Params) {
const { slug } = await params
// keep this so that Vercel keeps the files in deployment
const basePath = path.join(process.cwd(), "app")
const filePath = path.join(basePath, ...slug, "page.mdx")
if (!existsSync(filePath)) {
return notFound()
}
const cleanMdContent = await getCleanMd_(filePath, {
before: [
[
crossProjectLinksPlugin,
{
baseUrl: process.env.NEXT_PUBLIC_BASE_URL,
projectUrls: {
docs: {
url: process.env.NEXT_PUBLIC_DOCS_URL,
path: "",
},
resources: {
url: process.env.NEXT_PUBLIC_RESOURCES_URL,
},
ui: {
url: process.env.NEXT_PUBLIC_UI_URL,
},
api: {
url: process.env.NEXT_PUBLIC_API_URL,
},
},
useBaseUrl:
process.env.NODE_ENV === "production" ||
process.env.VERCEL_ENV === "production",
},
],
[localLinksRehypePlugin],
] as unknown as Plugin[],
after: [
[addUrlToRelativeLink, { url: process.env.NEXT_PUBLIC_BASE_URL }],
] as unknown as Plugin[],
})
return new NextResponse(cleanMdContent, {
headers: {
"Content-Type": "text/markdown",
},
})
}
const getCleanMd_ = unstable_cache(
async (filePath: string, plugins?: { before?: Plugin[]; after?: Plugin[] }) =>
getCleanMd({ filePath, plugins }),
["clean-md"],
{
revalidate: 3600,
}
)
+15
View File
@@ -0,0 +1,15 @@
import { NextResponse } from "next/server"
import type { NextRequest } from "next/server"
export function middleware(request: NextRequest) {
return NextResponse.rewrite(
new URL(
`${request.nextUrl.basePath}/md-content${request.nextUrl.pathname.replace("/index.html.md", "")}`,
request.url
)
)
}
export const config = {
matcher: "/:path*/index.html.md",
}