docs-util: add support for workflows in markdown theme (#8485)
* add template for workflows * initial changes * added support for parallel steps * added support for when * added merge options * fix merge options * fix to tooltip * clean up * remove redirects * fixes * theme fixes + added merge options * generate hook examples + fixes * changed namespaces * add custom autogenerator * change type of additional data
This commit is contained in:
@@ -5,6 +5,7 @@ import { notFound } from "next/navigation"
|
||||
import {
|
||||
typeListLinkFixerPlugin,
|
||||
localLinksRehypePlugin,
|
||||
workflowDiagramLinkFixerPlugin,
|
||||
} from "remark-rehype-plugins"
|
||||
import MDXComponents from "@/components/MDXComponents"
|
||||
import mdxOptions from "../../../mdx-options.mjs"
|
||||
@@ -47,7 +48,20 @@ export default async function ReferencesPage({ params }: PageProps) {
|
||||
mdxOptions: {
|
||||
rehypePlugins: [
|
||||
...mdxOptions.options.rehypePlugins,
|
||||
[typeListLinkFixerPlugin, pluginOptions],
|
||||
[
|
||||
typeListLinkFixerPlugin,
|
||||
{
|
||||
...pluginOptions,
|
||||
checkLinksType: "md",
|
||||
},
|
||||
],
|
||||
[
|
||||
workflowDiagramLinkFixerPlugin,
|
||||
{
|
||||
...pluginOptions,
|
||||
checkLinksType: "value",
|
||||
},
|
||||
],
|
||||
[localLinksRehypePlugin, pluginOptions],
|
||||
],
|
||||
remarkPlugins: mdxOptions.options.remarkPlugins,
|
||||
|
||||
@@ -1,10 +1,16 @@
|
||||
import type { MDXComponents as MDXComponentsType } from "mdx/types"
|
||||
import { Link, MDXComponents as UiMdxComponents, TypeList } from "docs-ui"
|
||||
import {
|
||||
Link,
|
||||
MDXComponents as UiMdxComponents,
|
||||
TypeList,
|
||||
WorkflowDiagram,
|
||||
} from "docs-ui"
|
||||
|
||||
const MDXComponents: MDXComponentsType = {
|
||||
...UiMdxComponents,
|
||||
a: Link,
|
||||
TypeList,
|
||||
WorkflowDiagram,
|
||||
}
|
||||
|
||||
export default MDXComponents
|
||||
|
||||
@@ -3,6 +3,7 @@ import {
|
||||
brokenLinkCheckerPlugin,
|
||||
localLinksRehypePlugin,
|
||||
typeListLinkFixerPlugin,
|
||||
workflowDiagramLinkFixerPlugin,
|
||||
} from "remark-rehype-plugins"
|
||||
import { slugChanges } from "./generated/slug-changes.mjs"
|
||||
import mdxPluginOptions from "./mdx-options.mjs"
|
||||
@@ -15,6 +16,7 @@ const withMDX = mdx({
|
||||
[brokenLinkCheckerPlugin],
|
||||
[localLinksRehypePlugin],
|
||||
[typeListLinkFixerPlugin],
|
||||
[workflowDiagramLinkFixerPlugin],
|
||||
],
|
||||
remarkPlugins: mdxPluginOptions.options.remarkPlugins,
|
||||
jsx: true,
|
||||
@@ -29,14 +31,16 @@ const nextConfig = {
|
||||
transpilePackages: ["docs-ui"],
|
||||
|
||||
basePath: process.env.NEXT_PUBLIC_BASE_PATH || "/v2/resources",
|
||||
async redirects() {
|
||||
// redirect original file paths to the rewrite
|
||||
return slugChanges.map((item) => ({
|
||||
source: item.origSlug,
|
||||
destination: item.newSlug,
|
||||
permanent: true,
|
||||
}))
|
||||
},
|
||||
// Redirects shouldn't be necessary anymore since we have remark / rehype
|
||||
// plugins that fix links. But leaving this here in case we need it again.
|
||||
// async redirects() {
|
||||
// // redirect original file paths to the rewrite
|
||||
// return slugChanges.map((item) => ({
|
||||
// source: item.origSlug,
|
||||
// destination: item.newSlug,
|
||||
// permanent: true,
|
||||
// }))
|
||||
// },
|
||||
}
|
||||
|
||||
export default withMDX(nextConfig)
|
||||
|
||||
@@ -1888,6 +1888,13 @@ export const sidebar = sidebarAttachHrefCommonOptions([
|
||||
isChildSidebar: true,
|
||||
autogenerate_path: "/references/helper_steps/functions",
|
||||
},
|
||||
// TODO uncomment once available.
|
||||
// {
|
||||
// title: "Medusa Workflows Reference",
|
||||
// path: "/medusa-workflows-reference",
|
||||
// isChildSidebar: true,
|
||||
// custom_autogenerate: "core-flows",
|
||||
// },
|
||||
],
|
||||
},
|
||||
{
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
import type { RawSidebarItemType } from "types"
|
||||
import findPageHeading from "./utils/find-page-heading.js"
|
||||
import { existsSync, mkdirSync, readFileSync, readdirSync, statSync } from "fs"
|
||||
import { existsSync, mkdirSync, readdirSync, statSync } from "fs"
|
||||
import path from "path"
|
||||
import { sidebarAttachHrefCommonOptions } from "./index.js"
|
||||
import { getFrontMatterUtil } from "remark-rehype-plugins"
|
||||
import findMetadataTitle from "./utils/find-metadata-title.js"
|
||||
import { getSidebarItemLink, sidebarAttachHrefCommonOptions } from "./index.js"
|
||||
import getCoreFlowsRefSidebarChildren from "./utils/get-core-flows-ref-sidebar-children.js"
|
||||
|
||||
type ItemsToAdd = RawSidebarItemType & {
|
||||
export type ItemsToAdd = RawSidebarItemType & {
|
||||
sidebar_position?: number
|
||||
}
|
||||
|
||||
const customGenerators: Record<string, () => Promise<ItemsToAdd[]>> = {
|
||||
"core-flows": getCoreFlowsRefSidebarChildren,
|
||||
}
|
||||
|
||||
async function getSidebarItems(
|
||||
dir: string,
|
||||
nested = false
|
||||
@@ -52,32 +54,17 @@ async function getSidebarItems(
|
||||
continue
|
||||
}
|
||||
|
||||
const frontmatter = await getFrontMatterUtil(filePath)
|
||||
if (frontmatter.sidebar_autogenerate_exclude) {
|
||||
const newItem = await getSidebarItemLink({
|
||||
filePath,
|
||||
basePath,
|
||||
fileBasename,
|
||||
})
|
||||
|
||||
if (!newItem) {
|
||||
continue
|
||||
}
|
||||
|
||||
const fileContent = frontmatter.sidebar_label
|
||||
? ""
|
||||
: readFileSync(filePath, "utf-8")
|
||||
|
||||
const newItem = sidebarAttachHrefCommonOptions([
|
||||
{
|
||||
path:
|
||||
frontmatter.slug ||
|
||||
filePath.replace(basePath, "").replace(`/${fileBasename}`, ""),
|
||||
title:
|
||||
frontmatter.sidebar_label ||
|
||||
findMetadataTitle(fileContent) ||
|
||||
findPageHeading(fileContent) ||
|
||||
"",
|
||||
},
|
||||
])[0]
|
||||
|
||||
items.push({
|
||||
...newItem,
|
||||
sidebar_position: frontmatter.sidebar_position,
|
||||
})
|
||||
items.push(newItem)
|
||||
|
||||
mainPageIndex = items.length - 1
|
||||
}
|
||||
@@ -108,9 +95,12 @@ async function checkItem(
|
||||
return child
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
if (item.children) {
|
||||
} else if (
|
||||
item.custom_autogenerate &&
|
||||
Object.hasOwn(customGenerators, item.custom_autogenerate)
|
||||
) {
|
||||
item.children = await customGenerators[item.custom_autogenerate]()
|
||||
} else if (item.children) {
|
||||
item.children = await Promise.all(
|
||||
item.children.map(async (childItem) => await checkItem(childItem))
|
||||
)
|
||||
|
||||
@@ -2,4 +2,6 @@ export * from "./generate-sidebar.js"
|
||||
|
||||
export * from "./utils/find-metadata-title.js"
|
||||
export * from "./utils/find-page-heading.js"
|
||||
export * from "./utils/get-core-flows-ref-sidebar-children.js"
|
||||
export * from "./utils/get-sidebar-item-link.js"
|
||||
export * from "./utils/sidebar-attach-href-common-options.js"
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
import { getSidebarItemLink, ItemsToAdd } from "build-scripts"
|
||||
import { existsSync, readdirSync } from "fs"
|
||||
import path from "path"
|
||||
|
||||
export default async function getCoreFlowsRefSidebarChildren(): Promise<
|
||||
ItemsToAdd[]
|
||||
> {
|
||||
const projPath = path.resolve()
|
||||
const basePath = path.join(projPath, "references", "core_flows")
|
||||
|
||||
const directories = readdirSync(basePath, {
|
||||
withFileTypes: true,
|
||||
})
|
||||
|
||||
const sidebarItems: ItemsToAdd[] = []
|
||||
|
||||
for (const directory of directories) {
|
||||
if (
|
||||
!directory.isDirectory() ||
|
||||
directory.name.startsWith("core_flows.") ||
|
||||
directory.name === "types" ||
|
||||
directory.name === "interfaces"
|
||||
) {
|
||||
continue
|
||||
}
|
||||
|
||||
const namespaceBasePath = path.join(basePath, directory.name, "functions")
|
||||
|
||||
if (!existsSync(namespaceBasePath)) {
|
||||
continue
|
||||
}
|
||||
|
||||
const childDirs = readdirSync(namespaceBasePath, {
|
||||
withFileTypes: true,
|
||||
})
|
||||
|
||||
const workflowItems: ItemsToAdd[] = []
|
||||
const stepItems: ItemsToAdd[] = []
|
||||
|
||||
for (const childDir of childDirs) {
|
||||
if (!childDir.isDirectory()) {
|
||||
continue
|
||||
}
|
||||
|
||||
const childDirPath = path.join(namespaceBasePath, childDir.name)
|
||||
const childFile = readdirSync(childDirPath)
|
||||
|
||||
const sidebarItem = await getSidebarItemLink({
|
||||
filePath: path.join(childDirPath, childFile[0]),
|
||||
basePath: projPath,
|
||||
fileBasename: childFile[0],
|
||||
})
|
||||
|
||||
if (sidebarItem) {
|
||||
if (childDir.name.endsWith("Workflow")) {
|
||||
workflowItems.push(sidebarItem)
|
||||
} else {
|
||||
stepItems.push(sidebarItem)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (workflowItems.length || stepItems.length) {
|
||||
const item: ItemsToAdd = {
|
||||
title: directory.name.replaceAll("_", " "),
|
||||
children: [],
|
||||
}
|
||||
|
||||
if (workflowItems.length) {
|
||||
item.children!.push({
|
||||
title: "Workflows",
|
||||
children: workflowItems,
|
||||
})
|
||||
}
|
||||
|
||||
if (stepItems.length) {
|
||||
item.children!.push({
|
||||
title: "Steps",
|
||||
children: stepItems,
|
||||
})
|
||||
}
|
||||
|
||||
sidebarItems.push(item)
|
||||
}
|
||||
}
|
||||
|
||||
return sidebarItems
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
import { getFrontMatterUtil } from "remark-rehype-plugins"
|
||||
import { ItemsToAdd, sidebarAttachHrefCommonOptions } from "../index.js"
|
||||
import { readFileSync } from "fs"
|
||||
import findMetadataTitle from "./find-metadata-title.js"
|
||||
import findPageHeading from "./find-page-heading.js"
|
||||
|
||||
export async function getSidebarItemLink({
|
||||
filePath,
|
||||
basePath,
|
||||
fileBasename,
|
||||
}: {
|
||||
filePath: string
|
||||
basePath: string
|
||||
fileBasename: string
|
||||
}): Promise<ItemsToAdd | undefined> {
|
||||
const frontmatter = await getFrontMatterUtil(filePath)
|
||||
if (frontmatter.sidebar_autogenerate_exclude) {
|
||||
return
|
||||
}
|
||||
|
||||
const fileContent = frontmatter.sidebar_label
|
||||
? ""
|
||||
: readFileSync(filePath, "utf-8")
|
||||
|
||||
const newItem = sidebarAttachHrefCommonOptions([
|
||||
{
|
||||
path:
|
||||
frontmatter.slug ||
|
||||
filePath.replace(basePath, "").replace(`/${fileBasename}`, ""),
|
||||
title:
|
||||
frontmatter.sidebar_label ||
|
||||
findMetadataTitle(fileContent) ||
|
||||
findPageHeading(fileContent) ||
|
||||
"",
|
||||
},
|
||||
])[0]
|
||||
|
||||
return {
|
||||
...newItem,
|
||||
sidebar_position: frontmatter.sidebar_position,
|
||||
}
|
||||
}
|
||||
@@ -68,6 +68,7 @@
|
||||
"@uidotdev/usehooks": "^2.4.1",
|
||||
"algoliasearch": "^4.20.0",
|
||||
"copy-text-to-clipboard": "^3.2.0",
|
||||
"framer-motion": "^11.3.21",
|
||||
"mermaid": "^10.9.0",
|
||||
"npm-to-yarn": "^2.1.0",
|
||||
"prism-react-renderer": "2.3.1",
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
import React from "react"
|
||||
import { NavbarIconButton, NavbarIconButtonProps } from "../../IconButton"
|
||||
import clsx from "clsx"
|
||||
import { Sidebar, XMark } from "@medusajs/icons"
|
||||
import { SidebarLeft, XMark } from "@medusajs/icons"
|
||||
|
||||
export type NavbarMobileMenuButtonProps = {
|
||||
buttonProps?: NavbarIconButtonProps
|
||||
@@ -28,7 +28,7 @@ export const NavbarMobileMenuButton = ({
|
||||
}
|
||||
}}
|
||||
>
|
||||
{!mobileSidebarOpen && <Sidebar className="text-medusa-fg-muted" />}
|
||||
{!mobileSidebarOpen && <SidebarLeft className="text-medusa-fg-muted" />}
|
||||
{mobileSidebarOpen && <XMark className="text-medusa-fg-muted" />}
|
||||
</NavbarIconButton>
|
||||
)
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
import React from "react"
|
||||
|
||||
export const WorkflowDiagramArrowEnd = () => {
|
||||
return (
|
||||
<svg
|
||||
width="22"
|
||||
height="38"
|
||||
viewBox="0 0 22 38"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className="-mt-[6px]"
|
||||
>
|
||||
<path
|
||||
d="M21.5284 32.5303C21.8213 32.2374 21.8213 31.7626 21.5284 31.4697L16.7554 26.6967C16.4625 26.4038 15.9876 26.4038 15.6947 26.6967C15.4019 26.9896 15.4019 27.4645 15.6947 27.7574L19.9374 32L15.6947 36.2426C15.4019 36.5355 15.4019 37.0104 15.6947 37.3033C15.9876 37.5962 16.4625 37.5962 16.7554 37.3033L21.5284 32.5303ZM0.249878 0L0.249878 28H1.74988L1.74988 0H0.249878ZM4.99988 32.75L20.998 32.75V31.25L4.99988 31.25V32.75ZM0.249878 28C0.249878 30.6234 2.37653 32.75 4.99988 32.75V31.25C3.20495 31.25 1.74988 29.7949 1.74988 28H0.249878Z"
|
||||
fill="var(--docs-border-strong)"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
import React from "react"
|
||||
|
||||
export const WorkflowDiagramArrowHorizontal = () => {
|
||||
return (
|
||||
<svg
|
||||
width="42"
|
||||
height="12"
|
||||
viewBox="0 0 42 12"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M41.5303 6.53033C41.8232 6.23744 41.8232 5.76256 41.5303 5.46967L36.7574 0.696699C36.4645 0.403806 35.9896 0.403806 35.6967 0.696699C35.4038 0.989593 35.4038 1.46447 35.6967 1.75736L39.9393 6L35.6967 10.2426C35.4038 10.5355 35.4038 11.0104 35.6967 11.3033C35.9896 11.5962 36.4645 11.5962 36.7574 11.3033L41.5303 6.53033ZM0.999996 5.25C0.585785 5.25 0.249996 5.58579 0.249996 6C0.249996 6.41421 0.585785 6.75 0.999996 6.75V5.25ZM41 5.25L0.999996 5.25V6.75L41 6.75V5.25Z"
|
||||
fill="var(--docs-border-strong)"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import React from "react"
|
||||
|
||||
export const WorkflowDiagramArrowMiddle = () => {
|
||||
return (
|
||||
<svg
|
||||
width="22"
|
||||
height="38"
|
||||
viewBox="0 0 22 38"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className="-mt-[6px]"
|
||||
>
|
||||
<path
|
||||
d="M0.999878 32H0.249878V32.75H0.999878V32ZM21.5284 32.5303C21.8213 32.2374 21.8213 31.7626 21.5284 31.4697L16.7554 26.6967C16.4625 26.4038 15.9876 26.4038 15.6947 26.6967C15.4019 26.9896 15.4019 27.4645 15.6947 27.7574L19.9374 32L15.6947 36.2426C15.4019 36.5355 15.4019 37.0104 15.6947 37.3033C15.9876 37.5962 16.4625 37.5962 16.7554 37.3033L21.5284 32.5303ZM0.249878 0L0.249878 32H1.74988L1.74988 0H0.249878ZM0.999878 32.75L20.998 32.75V31.25L0.999878 31.25V32.75Z"
|
||||
fill="var(--docs-border-strong)"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
import React from "react"
|
||||
import { WorkflowDiagramArrowHorizontal } from "./Horizontal"
|
||||
import { WorkflowDiagramArrowEnd } from "./End"
|
||||
import { WorkflowDiagramArrowMiddle } from "./Middle"
|
||||
|
||||
export type WorkflowDiagramArrowProps = {
|
||||
depth: number
|
||||
}
|
||||
|
||||
export const WorkflowDiagramArrow = ({ depth }: WorkflowDiagramArrowProps) => {
|
||||
if (depth === 1) {
|
||||
return <WorkflowDiagramArrowHorizontal />
|
||||
}
|
||||
|
||||
if (depth === 2) {
|
||||
return (
|
||||
<div className="flex flex-col items-end">
|
||||
<WorkflowDiagramArrowHorizontal />
|
||||
<WorkflowDiagramArrowEnd />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const inbetween = Array.from({ length: depth - 2 }).map((_, index) => (
|
||||
<WorkflowDiagramArrowMiddle key={index} />
|
||||
))
|
||||
|
||||
return (
|
||||
<div className="flex flex-col items-end">
|
||||
<WorkflowDiagramArrowHorizontal />
|
||||
{inbetween}
|
||||
<WorkflowDiagramArrowEnd />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
"use client"
|
||||
|
||||
import React from "react"
|
||||
import { WorkflowStepUi } from "types"
|
||||
import { WorkflowDiagramStepNode } from "../Node"
|
||||
import { WorkflowDiagramLine } from "../Line"
|
||||
|
||||
export type WorkflowDiagramDepthProps = {
|
||||
cluster: WorkflowStepUi[]
|
||||
next: WorkflowStepUi[]
|
||||
}
|
||||
|
||||
export const WorkflowDiagramDepth = ({
|
||||
cluster,
|
||||
next,
|
||||
}: WorkflowDiagramDepthProps) => {
|
||||
return (
|
||||
<div className="flex items-start">
|
||||
<div className="flex flex-col justify-center gap-y-docs_0.5">
|
||||
{cluster.map((step) => (
|
||||
<WorkflowDiagramStepNode key={step.name} step={step} />
|
||||
))}
|
||||
</div>
|
||||
<WorkflowDiagramLine step={next} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
import React from "react"
|
||||
import { WorkflowDiagramArrow } from "../Arrow"
|
||||
import { WorkflowStepUi } from "types"
|
||||
|
||||
export type WorkflowDiagramLineProps = {
|
||||
step: WorkflowStepUi[]
|
||||
}
|
||||
|
||||
export const WorkflowDiagramLine = ({ step }: WorkflowDiagramLineProps) => {
|
||||
if (!step) {
|
||||
return <></>
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="ml-0 -mr-[7px] w-[60px] pr-[7px]">
|
||||
<div className="flex min-h-[24px] w-full items-start">
|
||||
<div className="flex h-docs_1.5 w-[10px] items-center justify-center">
|
||||
<div className="bg-medusa-button-neutral shadow-borders-base size-[10px] shrink-0 rounded-full" />
|
||||
</div>
|
||||
<div className="pt-[6px]">
|
||||
<WorkflowDiagramArrow depth={step.length} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
"use client"
|
||||
|
||||
import { Text } from "@medusajs/ui"
|
||||
import clsx from "clsx"
|
||||
import Link from "next/link"
|
||||
import React from "react"
|
||||
import { WorkflowStepUi } from "types"
|
||||
import { InlineCode, MarkdownContent, Tooltip } from "../../.."
|
||||
import { Bolt, InformationCircle } from "@medusajs/icons"
|
||||
|
||||
export type WorkflowDiagramNodeProps = {
|
||||
step: WorkflowStepUi
|
||||
}
|
||||
|
||||
export const WorkflowDiagramStepNode = ({ step }: WorkflowDiagramNodeProps) => {
|
||||
const stepId = step.name.split(".").pop()
|
||||
|
||||
return (
|
||||
<Tooltip
|
||||
tooltipClassName="!text-left max-w-full text-pretty overflow-scroll"
|
||||
tooltipChildren={
|
||||
<>
|
||||
<h4 className="text-compact-x-small-plus">{step.name}</h4>
|
||||
{step.when && (
|
||||
<span className="block py-docs_0.25">
|
||||
Runs only if a <InlineCode>when</InlineCode> condition is
|
||||
satisfied.
|
||||
</span>
|
||||
)}
|
||||
{step.description && (
|
||||
<MarkdownContent
|
||||
allowedElements={["a", "strong", "code"]}
|
||||
unwrapDisallowed={true}
|
||||
>
|
||||
{step.description}
|
||||
</MarkdownContent>
|
||||
)}
|
||||
</>
|
||||
}
|
||||
clickable={true}
|
||||
>
|
||||
<Link
|
||||
href={step.link || `#${step.name}`}
|
||||
className="focus-visible:shadow-borders-focus transition-fg rounded-docs_sm outline-none"
|
||||
>
|
||||
<div
|
||||
className={clsx(
|
||||
"shadow-borders-base flex min-w-[120px] bg-medusa-bg-base",
|
||||
"items-center rounded-docs_sm py-docs_0.125 px-docs_0.5",
|
||||
(step.type === "hook" || step.when) && "gap-x-docs_0.125"
|
||||
)}
|
||||
data-step-id={step.name}
|
||||
>
|
||||
{step.type === "hook" && (
|
||||
<div className="flex size-[20px] items-center justify-center text-medusa-tag-orange-icon">
|
||||
<Bolt />
|
||||
</div>
|
||||
)}
|
||||
{step.when && (
|
||||
<div className="flex size-[20px] items-center justify-center text-medusa-tag-green-icon">
|
||||
<InformationCircle />
|
||||
</div>
|
||||
)}
|
||||
<Text
|
||||
size="xsmall"
|
||||
leading="compact"
|
||||
weight="plus"
|
||||
className="select-none"
|
||||
>
|
||||
{stepId}
|
||||
</Text>
|
||||
</div>
|
||||
</Link>
|
||||
</Tooltip>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,218 @@
|
||||
"use client"
|
||||
|
||||
import clsx from "clsx"
|
||||
import {
|
||||
useAnimationControls,
|
||||
useDragControls,
|
||||
useMotionValue,
|
||||
motion,
|
||||
} from "framer-motion"
|
||||
import React, { Suspense, useEffect, useRef, useState } from "react"
|
||||
import { ArrowPathMini, MinusMini, PlusMini } from "@medusajs/icons"
|
||||
import { DropdownMenu, Text } from "@medusajs/ui"
|
||||
import { createNodeClusters, getNextCluster } from "../../utils"
|
||||
import { Workflow } from "types"
|
||||
import { WorkflowDiagramDepth } from "./Depth"
|
||||
import { Loading } from "../.."
|
||||
|
||||
export type WorkflowDiagramProps = {
|
||||
workflow: Workflow
|
||||
}
|
||||
|
||||
type ZoomScale = 0.5 | 0.75 | 1
|
||||
|
||||
const defaultState = {
|
||||
x: -1000,
|
||||
y: -1020,
|
||||
scale: 1,
|
||||
}
|
||||
|
||||
const MAX_ZOOM = 1.5
|
||||
const MIN_ZOOM = 0.5
|
||||
const ZOOM_STEP = 0.25
|
||||
|
||||
const WorkflowDiagramContent = ({ workflow }: WorkflowDiagramProps) => {
|
||||
const [zoom, setZoom] = useState<number>(1)
|
||||
const [isDragging, setIsDragging] = useState(false)
|
||||
|
||||
const scale = useMotionValue(defaultState.scale)
|
||||
const x = useMotionValue(defaultState.x)
|
||||
const y = useMotionValue(defaultState.y)
|
||||
|
||||
const controls = useAnimationControls()
|
||||
|
||||
const dragControls = useDragControls()
|
||||
const dragConstraints = useRef<HTMLDivElement>(null)
|
||||
|
||||
const canZoomIn = zoom < MAX_ZOOM
|
||||
const canZoomOut = zoom > MIN_ZOOM
|
||||
|
||||
useEffect(() => {
|
||||
const unsubscribe = scale.on("change", (latest) => {
|
||||
setZoom(latest as ZoomScale)
|
||||
})
|
||||
|
||||
return () => {
|
||||
unsubscribe()
|
||||
}
|
||||
}, [scale])
|
||||
|
||||
// TODO pass steps here.
|
||||
const clusters = createNodeClusters(workflow.steps)
|
||||
|
||||
function scaleXandY(
|
||||
prevScale: number,
|
||||
newScale: number,
|
||||
x: number,
|
||||
y: number
|
||||
) {
|
||||
const scaleRatio = newScale / prevScale
|
||||
return {
|
||||
x: x * scaleRatio,
|
||||
y: y * scaleRatio,
|
||||
}
|
||||
}
|
||||
|
||||
const changeZoom = (newScale: number) => {
|
||||
const { x: newX, y: newY } = scaleXandY(zoom, newScale, x.get(), y.get())
|
||||
|
||||
setZoom(newScale)
|
||||
controls.set({ scale: newScale, x: newX, y: newY })
|
||||
}
|
||||
|
||||
const zoomIn = () => {
|
||||
const curr = scale.get()
|
||||
|
||||
if (curr < 1.5) {
|
||||
const newScale = curr + ZOOM_STEP
|
||||
changeZoom(newScale)
|
||||
}
|
||||
}
|
||||
|
||||
const zoomOut = () => {
|
||||
const curr = scale.get()
|
||||
|
||||
if (curr > 0.5) {
|
||||
const newScale = curr - ZOOM_STEP
|
||||
changeZoom(newScale)
|
||||
}
|
||||
}
|
||||
|
||||
const resetCanvas = async () => await controls.start(defaultState)
|
||||
|
||||
return (
|
||||
<div className="h-[200px] w-full rounded-docs_DEFAULT">
|
||||
<div
|
||||
ref={dragConstraints}
|
||||
className="relative size-full rounded-docs_DEFAULT"
|
||||
>
|
||||
<div className="relative size-full overflow-hidden object-contain rounded-docs_DEFAULT shadow-elevation-card-rest">
|
||||
<div>
|
||||
<motion.div
|
||||
onMouseDown={() => setIsDragging(true)}
|
||||
onMouseUp={() => setIsDragging(false)}
|
||||
drag
|
||||
dragConstraints={dragConstraints}
|
||||
dragElastic={0}
|
||||
dragMomentum={false}
|
||||
dragControls={dragControls}
|
||||
initial={false}
|
||||
animate={controls}
|
||||
transition={{ duration: 0.25 }}
|
||||
style={{
|
||||
x,
|
||||
y,
|
||||
scale,
|
||||
}}
|
||||
className={clsx(
|
||||
"bg-medusa-bg-subtle relative size-[500rem] origin-top-left items-start justify-start overflow-hidden",
|
||||
"bg-[radial-gradient(var(--docs-border-base)_1.5px,transparent_0)] bg-[length:20px_20px] bg-repeat",
|
||||
!isDragging && "cursor-grab",
|
||||
isDragging && "cursor-grabbing"
|
||||
)}
|
||||
>
|
||||
<main className="size-full">
|
||||
<div className="absolute left-[1100px] top-[1100px] flex select-none items-start">
|
||||
{Object.entries(clusters).map(([depth, cluster]) => {
|
||||
const next = getNextCluster(clusters, Number(depth))
|
||||
|
||||
return (
|
||||
<WorkflowDiagramDepth
|
||||
cluster={cluster}
|
||||
next={next}
|
||||
key={depth}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</main>
|
||||
</motion.div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="bg-medusa-bg-base shadow-borders-base text-medusa-fg-subtle absolute bottom-docs_1 left-docs_1.5 flex h-[28px] items-center overflow-hidden rounded-docs_sm">
|
||||
<div className="flex items-center">
|
||||
<button
|
||||
onClick={zoomIn}
|
||||
type="button"
|
||||
disabled={!canZoomIn}
|
||||
aria-label="Zoom in"
|
||||
className="disabled:text-medusa-fg-disabled transition-fg hover:bg-medusa-bg-base-hover active:bg-medusa-bg-base-pressed focus-visible:bg-medusa-bg-base-pressed border-r p-docs_0.25 outline-none"
|
||||
>
|
||||
<PlusMini />
|
||||
</button>
|
||||
<div>
|
||||
<DropdownMenu>
|
||||
<DropdownMenu.Trigger className="disabled:text-medusa-fg-disabled transition-fg hover:bg-medusa-bg-base-hover active:bg-medusa-bg-base-pressed focus-visible:bg-medusa-bg-base-pressed flex w-[50px] items-center justify-center border-r p-docs_0.25 outline-none">
|
||||
<Text
|
||||
as="span"
|
||||
size="xsmall"
|
||||
leading="compact"
|
||||
className="select-none tabular-nums"
|
||||
>
|
||||
{Math.round(zoom * 100)}%
|
||||
</Text>
|
||||
</DropdownMenu.Trigger>
|
||||
<DropdownMenu.Content className="bg-medusa-bg-base">
|
||||
{[50, 75, 100, 125, 150].map((value) => (
|
||||
<DropdownMenu.Item
|
||||
key={value}
|
||||
onClick={() => changeZoom(value / 100)}
|
||||
className="px-docs_0.5 py-[6px]"
|
||||
>
|
||||
{value}%
|
||||
</DropdownMenu.Item>
|
||||
))}
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
<button
|
||||
onClick={zoomOut}
|
||||
type="button"
|
||||
disabled={!canZoomOut}
|
||||
aria-label="Zoom out"
|
||||
className="disabled:text-medusa-fg-disabled transition-fg hover:bg-medusa-bg-base-hover active:bg-medusa-bg-base-pressed focus-visible:bg-medusa-bg-base-pressed border-r p-docs_0.25 outline-none"
|
||||
>
|
||||
<MinusMini />
|
||||
</button>
|
||||
</div>
|
||||
<button
|
||||
onClick={resetCanvas}
|
||||
type="button"
|
||||
aria-label="Reset canvas"
|
||||
className="disabled:text-medusa-fg-disabled transition-fg hover:bg-medusa-bg-base-hover active:bg-medusa-bg-base-pressed focus-visible:bg-medusa-bg-base-pressed p-docs_0.25 outline-none"
|
||||
>
|
||||
<ArrowPathMini />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export const WorkflowDiagram = (props: WorkflowDiagramProps) => {
|
||||
return (
|
||||
<Suspense fallback={<Loading />}>
|
||||
<WorkflowDiagramContent {...props} />
|
||||
</Suspense>
|
||||
)
|
||||
}
|
||||
@@ -74,4 +74,5 @@ export * from "./ThemeImage"
|
||||
export * from "./Toggle"
|
||||
export * from "./Tooltip"
|
||||
export * from "./TypeList"
|
||||
export * from "./WorkflowDiagram"
|
||||
export * from "./ZoomImg"
|
||||
|
||||
@@ -15,3 +15,4 @@ export * from "./learning-paths"
|
||||
export * from "./set-obj-value"
|
||||
export * from "./sidebar-attach-href-common-options"
|
||||
export * from "./swr-fetcher"
|
||||
export * from "./workflow-diagram-utils"
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
import { WorkflowSteps, WorkflowStepUi } from "types"
|
||||
|
||||
export const createNodeClusters = (steps: WorkflowSteps) => {
|
||||
const clusters: Record<number, WorkflowStepUi[]> = {}
|
||||
|
||||
steps.forEach((step) => {
|
||||
if (!clusters[step.depth]) {
|
||||
clusters[step.depth] = []
|
||||
}
|
||||
|
||||
if (step.type === "when") {
|
||||
const whenSteps = step.steps.map((whenStep) => ({
|
||||
...whenStep,
|
||||
depth: step.depth,
|
||||
when: step,
|
||||
}))
|
||||
clusters[step.depth].push(...whenSteps)
|
||||
} else {
|
||||
clusters[step.depth].push(step)
|
||||
}
|
||||
})
|
||||
|
||||
return clusters
|
||||
}
|
||||
|
||||
export const getNextCluster = (
|
||||
clusters: Record<number, WorkflowStepUi[]>,
|
||||
depth: number
|
||||
) => {
|
||||
const nextDepth = depth + 1
|
||||
return clusters[nextDepth]
|
||||
}
|
||||
@@ -5,6 +5,7 @@ export * from "./local-links.js"
|
||||
export * from "./page-number.js"
|
||||
export * from "./resolve-admonitions.js"
|
||||
export * from "./type-list-link-fixer.js"
|
||||
export * from "./workflow-diagram-link-fixer.js"
|
||||
|
||||
export * from "./utils/fix-link.js"
|
||||
export * from "./utils/get-file-slug.js"
|
||||
|
||||
@@ -1,126 +1,9 @@
|
||||
import path from "path"
|
||||
import { Transformer } from "unified"
|
||||
import {
|
||||
ExpressionJsVar,
|
||||
TypeListLinkFixerOptions,
|
||||
UnistNodeWithData,
|
||||
UnistTree,
|
||||
} from "./types/index.js"
|
||||
import { FixLinkOptions, fixLinkUtil } from "./index.js"
|
||||
import getAttribute from "./utils/get-attribute.js"
|
||||
import { estreeToJs } from "./utils/estree-to-js.js"
|
||||
import {
|
||||
isExpressionJsVarLiteral,
|
||||
isExpressionJsVarObj,
|
||||
} from "./utils/expression-is-utils.js"
|
||||
|
||||
const LINK_REGEX = /\[(.*?)\]\((?<link>.*?)\)/gm
|
||||
|
||||
function matchLinks(
|
||||
str: string,
|
||||
linkOptions: Omit<FixLinkOptions, "linkedPath">
|
||||
) {
|
||||
let linkMatches
|
||||
while ((linkMatches = LINK_REGEX.exec(str)) !== null) {
|
||||
if (!linkMatches.groups?.link) {
|
||||
return
|
||||
}
|
||||
|
||||
const newUrl = fixLinkUtil({
|
||||
...linkOptions,
|
||||
linkedPath: linkMatches.groups.link,
|
||||
})
|
||||
|
||||
str = str.replace(linkMatches.groups.link, newUrl)
|
||||
}
|
||||
|
||||
return str
|
||||
}
|
||||
|
||||
function traverseTypes(
|
||||
types: ExpressionJsVar[] | ExpressionJsVar,
|
||||
linkOptions: Omit<FixLinkOptions, "linkedPath">
|
||||
) {
|
||||
if (Array.isArray(types)) {
|
||||
types.forEach((item) => traverseTypes(item, linkOptions))
|
||||
} else if (isExpressionJsVarLiteral(types)) {
|
||||
types.original.value = matchLinks(
|
||||
types.original.value as string,
|
||||
linkOptions
|
||||
)
|
||||
types.original.raw = JSON.stringify(types.original.value)
|
||||
} else {
|
||||
Object.values(types).forEach((value) => {
|
||||
if (Array.isArray(value) || isExpressionJsVarObj(value)) {
|
||||
return traverseTypes(value, linkOptions)
|
||||
}
|
||||
|
||||
if (!isExpressionJsVarLiteral(value)) {
|
||||
return
|
||||
}
|
||||
|
||||
value.original.value = matchLinks(
|
||||
value.original.value as string,
|
||||
linkOptions
|
||||
)
|
||||
value.original.raw = JSON.stringify(value.original.value)
|
||||
})
|
||||
}
|
||||
}
|
||||
import { ComponentLinkFixerOptions } from "./types/index.js"
|
||||
import { componentLinkFixer } from "./utils/component-link-fixer.js"
|
||||
|
||||
export function typeListLinkFixerPlugin(
|
||||
options?: TypeListLinkFixerOptions
|
||||
options?: ComponentLinkFixerOptions
|
||||
): Transformer {
|
||||
const { filePath, basePath } = options || {}
|
||||
return async (tree, file) => {
|
||||
if (!file.cwd) {
|
||||
return
|
||||
}
|
||||
|
||||
if (!file.history.length) {
|
||||
if (!filePath) {
|
||||
return
|
||||
}
|
||||
|
||||
file.history.push(filePath)
|
||||
}
|
||||
|
||||
const { visit } = await import("unist-util-visit")
|
||||
|
||||
const currentPageFilePath = file.history[0].replace(
|
||||
`/${path.basename(file.history[0])}`,
|
||||
""
|
||||
)
|
||||
const appsPath = basePath || path.join(file.cwd, "app")
|
||||
visit(tree as UnistTree, "mdxJsxFlowElement", (node: UnistNodeWithData) => {
|
||||
if (node.name !== "TypeList") {
|
||||
return
|
||||
}
|
||||
|
||||
const typesAttribute = getAttribute(node, "types")
|
||||
|
||||
if (
|
||||
!typesAttribute ||
|
||||
typeof typesAttribute.value === "string" ||
|
||||
!typesAttribute.value.data?.estree
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
const linkOptions = {
|
||||
currentPageFilePath,
|
||||
appsPath,
|
||||
}
|
||||
|
||||
// let newItems: Record<string, unknown>[]
|
||||
|
||||
const typesJsVar = estreeToJs(typesAttribute.value.data.estree)
|
||||
|
||||
if (!typesJsVar) {
|
||||
return
|
||||
}
|
||||
|
||||
traverseTypes(typesJsVar, linkOptions)
|
||||
})
|
||||
}
|
||||
return componentLinkFixer("TypeList", "types", options)
|
||||
}
|
||||
|
||||
@@ -118,9 +118,12 @@ export declare type CrossProjectLinksOptions = {
|
||||
useBaseUrl?: boolean
|
||||
}
|
||||
|
||||
export declare type TypeListLinkFixerOptions = {
|
||||
export declare type ComponentLinkFixerLinkType = "md" | "value"
|
||||
|
||||
export declare type ComponentLinkFixerOptions = {
|
||||
filePath?: string
|
||||
basePath?: string
|
||||
checkLinksType: ComponentLinkFixerLinkType
|
||||
}
|
||||
|
||||
export declare type LocalLinkOptions = {
|
||||
|
||||
@@ -0,0 +1,138 @@
|
||||
import path from "path"
|
||||
import { Transformer } from "unified"
|
||||
import {
|
||||
ComponentLinkFixerLinkType,
|
||||
ExpressionJsVar,
|
||||
UnistNodeWithData,
|
||||
UnistTree,
|
||||
} from "../types/index.js"
|
||||
import { FixLinkOptions, fixLinkUtil } from "../index.js"
|
||||
import getAttribute from "../utils/get-attribute.js"
|
||||
import { estreeToJs } from "../utils/estree-to-js.js"
|
||||
import {
|
||||
isExpressionJsVarLiteral,
|
||||
isExpressionJsVarObj,
|
||||
} from "../utils/expression-is-utils.js"
|
||||
import { ComponentLinkFixerOptions } from "../types/index.js"
|
||||
|
||||
const MD_LINK_REGEX = /\[(.*?)\]\((?<link>.*?)\)/gm
|
||||
const VALUE_LINK_REGEX = /^(![a-z]+!|\.)/gm
|
||||
|
||||
function matchMdLinks(
|
||||
str: string,
|
||||
linkOptions: Omit<FixLinkOptions, "linkedPath">
|
||||
) {
|
||||
let linkMatches
|
||||
while ((linkMatches = MD_LINK_REGEX.exec(str)) !== null) {
|
||||
if (!linkMatches.groups?.link) {
|
||||
return
|
||||
}
|
||||
|
||||
const newUrl = fixLinkUtil({
|
||||
...linkOptions,
|
||||
linkedPath: linkMatches.groups.link,
|
||||
})
|
||||
|
||||
str = str.replace(linkMatches.groups.link, newUrl)
|
||||
}
|
||||
|
||||
return str
|
||||
}
|
||||
|
||||
function matchValueLink(
|
||||
str: string,
|
||||
linkOptions: Omit<FixLinkOptions, "linkedPath">
|
||||
) {
|
||||
if (!VALUE_LINK_REGEX.exec(str)) {
|
||||
return str
|
||||
}
|
||||
|
||||
return fixLinkUtil({
|
||||
...linkOptions,
|
||||
linkedPath: str,
|
||||
})
|
||||
}
|
||||
|
||||
function traverseJsVar(
|
||||
item: ExpressionJsVar[] | ExpressionJsVar,
|
||||
linkOptions: Omit<FixLinkOptions, "linkedPath">,
|
||||
checkLinksType: ComponentLinkFixerLinkType
|
||||
) {
|
||||
const linkFn = checkLinksType === "md" ? matchMdLinks : matchValueLink
|
||||
if (Array.isArray(item)) {
|
||||
item.forEach((item) => traverseJsVar(item, linkOptions, checkLinksType))
|
||||
} else if (isExpressionJsVarLiteral(item)) {
|
||||
item.original.value = linkFn(item.original.value as string, linkOptions)
|
||||
item.original.raw = JSON.stringify(item.original.value)
|
||||
} else {
|
||||
Object.values(item).forEach((value) => {
|
||||
if (Array.isArray(value) || isExpressionJsVarObj(value)) {
|
||||
return traverseJsVar(value, linkOptions, checkLinksType)
|
||||
}
|
||||
|
||||
if (!isExpressionJsVarLiteral(value)) {
|
||||
return
|
||||
}
|
||||
|
||||
value.original.value = linkFn(value.original.value as string, linkOptions)
|
||||
value.original.raw = JSON.stringify(value.original.value)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export function componentLinkFixer(
|
||||
componentName: string,
|
||||
attributeName: string,
|
||||
options?: ComponentLinkFixerOptions
|
||||
): Transformer {
|
||||
const { filePath, basePath, checkLinksType = "md" } = options || {}
|
||||
return async (tree, file) => {
|
||||
if (!file.cwd) {
|
||||
return
|
||||
}
|
||||
|
||||
if (!file.history.length) {
|
||||
if (!filePath) {
|
||||
return
|
||||
}
|
||||
|
||||
file.history.push(filePath)
|
||||
}
|
||||
|
||||
const { visit } = await import("unist-util-visit")
|
||||
|
||||
const currentPageFilePath = file.history[0].replace(
|
||||
`/${path.basename(file.history[0])}`,
|
||||
""
|
||||
)
|
||||
const appsPath = basePath || path.join(file.cwd, "app")
|
||||
visit(tree as UnistTree, "mdxJsxFlowElement", (node: UnistNodeWithData) => {
|
||||
if (node.name !== componentName) {
|
||||
return
|
||||
}
|
||||
|
||||
const workflowAttribute = getAttribute(node, attributeName)
|
||||
|
||||
if (
|
||||
!workflowAttribute ||
|
||||
typeof workflowAttribute.value === "string" ||
|
||||
!workflowAttribute.value.data?.estree
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
const linkOptions = {
|
||||
currentPageFilePath,
|
||||
appsPath,
|
||||
}
|
||||
|
||||
const itemJsVar = estreeToJs(workflowAttribute.value.data.estree)
|
||||
|
||||
if (!itemJsVar) {
|
||||
return
|
||||
}
|
||||
|
||||
traverseJsVar(itemJsVar, linkOptions, checkLinksType)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import { Transformer } from "unified"
|
||||
import { ComponentLinkFixerOptions } from "./types/index.js"
|
||||
import { componentLinkFixer } from "./utils/component-link-fixer.js"
|
||||
|
||||
export function workflowDiagramLinkFixerPlugin(
|
||||
options?: ComponentLinkFixerOptions
|
||||
): Transformer {
|
||||
return componentLinkFixer("WorkflowDiagram", "workflow", options)
|
||||
}
|
||||
@@ -2,3 +2,4 @@ export * from "./api-testing.js"
|
||||
export * from "./config.js"
|
||||
export * from "./general.js"
|
||||
export * from "./sidebar.js"
|
||||
export * from "./workflow.js"
|
||||
|
||||
@@ -26,5 +26,6 @@ export type SidebarSectionItemsType = {
|
||||
|
||||
export type RawSidebarItemType = SidebarItemType & {
|
||||
autogenerate_path?: string
|
||||
custom_autogenerate?: string
|
||||
number?: string
|
||||
}
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
export type WorkflowStep = {
|
||||
type: "step" | "workflow" | "hook"
|
||||
name: string
|
||||
description?: string
|
||||
link?: string
|
||||
depth: number
|
||||
}
|
||||
|
||||
export type WorkflowWhenSteps = {
|
||||
type: "when"
|
||||
condition: string
|
||||
steps: WorkflowStep[]
|
||||
depth: number
|
||||
}
|
||||
|
||||
export type WorkflowStepUi = WorkflowStep & {
|
||||
when?: WorkflowWhenSteps
|
||||
}
|
||||
|
||||
export type WorkflowSteps = (WorkflowStepUi | WorkflowWhenSteps)[]
|
||||
|
||||
export type Workflow = {
|
||||
name: string
|
||||
steps: WorkflowSteps
|
||||
}
|
||||
@@ -8,6 +8,7 @@ import { baseOptions } from "./base-options.js"
|
||||
import path from "path"
|
||||
import { rootPathPrefix } from "./general.js"
|
||||
import { modules } from "./references.js"
|
||||
import { getCoreFlowNamespaces } from "../utils/get-namespaces.js"
|
||||
|
||||
const customOptions: Record<string, Partial<TypeDocOptions>> = {
|
||||
"core-flows": getOptions({
|
||||
@@ -18,20 +19,7 @@ const customOptions: Record<string, Partial<TypeDocOptions>> = {
|
||||
enableWorkflowsPlugins: true,
|
||||
enableNamespaceGenerator: true,
|
||||
// @ts-expect-error there's a typing issue in typedoc
|
||||
generateNamespaces: [
|
||||
{
|
||||
name: "Workflows",
|
||||
description:
|
||||
"Workflows listed here are created by Medusa and can be imported from `@medusajs/core-flows`.",
|
||||
pathPattern: "**/packages/core/core-flows/**/workflows/**",
|
||||
},
|
||||
{
|
||||
name: "Steps",
|
||||
description:
|
||||
"Steps listed here are created by Medusa and can be imported from `@medusajs/core-flows`.",
|
||||
pathPattern: "**/packages/core/core-flows/**/steps/**",
|
||||
},
|
||||
],
|
||||
generateNamespaces: getCoreFlowNamespaces(),
|
||||
}),
|
||||
"auth-provider": getOptions({
|
||||
entryPointPath: "packages/core/utils/src/auth/abstract-auth-provider.ts",
|
||||
|
||||
+77
@@ -0,0 +1,77 @@
|
||||
import { FormattingOptionsType } from "types"
|
||||
import baseSectionsOptions from "../base-section-options.js"
|
||||
|
||||
const coreFlowsOptions: FormattingOptionsType = {
|
||||
"^core_flows": {
|
||||
expandMembers: true,
|
||||
sections: {
|
||||
...baseSectionsOptions,
|
||||
member_getterSetter: false,
|
||||
},
|
||||
workflowDiagramComponent: "WorkflowDiagram",
|
||||
mdxImports: [`import { TypeList, WorkflowDiagram } from "docs-ui"`],
|
||||
},
|
||||
"^modules/core_flows/page\\.mdx": {
|
||||
reflectionDescription:
|
||||
"This section of the documentation provides a reference to Medusa's workflows and steps that you can use in your customizations.",
|
||||
reflectionGroups: {
|
||||
Namespaces: true,
|
||||
Enumerations: false,
|
||||
Classes: false,
|
||||
Interfaces: false,
|
||||
"Type Aliases": false,
|
||||
Variables: false,
|
||||
"Enumeration Members": false,
|
||||
Functions: false,
|
||||
},
|
||||
hideTocHeaders: true,
|
||||
frontmatterData: {
|
||||
slug: "/references/medusa-workflows",
|
||||
},
|
||||
reflectionTitle: {
|
||||
fullReplacement: "Medusa Workflows API Reference",
|
||||
},
|
||||
},
|
||||
"^core_flows/.*/.*(Workflows|Steps)/page\\.mdx": {
|
||||
expandMembers: false,
|
||||
reflectionGroups: {
|
||||
Variables: false,
|
||||
Properties: false,
|
||||
"Type Literals": false,
|
||||
},
|
||||
sections: {
|
||||
...baseSectionsOptions,
|
||||
member_getterSetter: false,
|
||||
members_categories: false,
|
||||
},
|
||||
hideTocHeaders: true,
|
||||
},
|
||||
"^core_flows/.*Workflows/functions/.*/page\\.mdx": {
|
||||
reflectionDescription:
|
||||
"This documentation provides a reference to the `{{alias}}`. It belongs to the `@medusajs/core-flows` package.",
|
||||
frontmatterData: {
|
||||
slug: "/references/medusa-workflows/{{alias}}",
|
||||
sidebar_label: "{{alias}}",
|
||||
},
|
||||
reflectionTitle: {
|
||||
kind: false,
|
||||
typeParameters: false,
|
||||
suffix: "- Medusa Workflows API Reference",
|
||||
},
|
||||
},
|
||||
"^core_flows/.*Steps/functions/.*/page\\.mdx": {
|
||||
reflectionDescription:
|
||||
"This documentation provides a reference to the `{{alias}}`. It belongs to the `@medusajs/core-flows` package.",
|
||||
frontmatterData: {
|
||||
slug: "/references/medusa-workflows/steps/{{alias}}",
|
||||
sidebar_label: "{{alias}}",
|
||||
},
|
||||
reflectionTitle: {
|
||||
kind: false,
|
||||
typeParameters: false,
|
||||
suffix: "- Medusa Workflows API Reference",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export default coreFlowsOptions
|
||||
+2
@@ -11,9 +11,11 @@ import searchOptions from "./search.js"
|
||||
import taxProviderOptions from "./tax-provider.js"
|
||||
import workflowsOptions from "./workflows.js"
|
||||
import dmlOptions from "./dml.js"
|
||||
import coreFlowsOptions from "./core-flows.js"
|
||||
|
||||
const mergerCustomOptions: FormattingOptionsType = {
|
||||
...authProviderOptions,
|
||||
...coreFlowsOptions,
|
||||
...dmlOptions,
|
||||
...fileOptions,
|
||||
...fulfillmentProviderOptions,
|
||||
|
||||
@@ -12,6 +12,10 @@ import { FormattingOptionType } from "types"
|
||||
import { kebabToCamel, kebabToPascal, kebabToSnake, kebabToTitle } from "utils"
|
||||
import baseSectionsOptions from "./base-section-options.js"
|
||||
import mergerCustomOptions from "./merger-custom-options/index.js"
|
||||
import {
|
||||
getCoreFlowNamespaces,
|
||||
getNamespaceNames,
|
||||
} from "../utils/get-namespaces.js"
|
||||
|
||||
const mergerOptions: Partial<TypeDocOptions> = {
|
||||
...baseOptions,
|
||||
@@ -36,7 +40,9 @@ const mergerOptions: Partial<TypeDocOptions> = {
|
||||
"helper-steps",
|
||||
"workflows",
|
||||
],
|
||||
allReflectionsHaveOwnDocumentInNamespace: ["Utilities"],
|
||||
allReflectionsHaveOwnDocumentInNamespace: [
|
||||
...getNamespaceNames(getCoreFlowNamespaces()),
|
||||
],
|
||||
formatting: {
|
||||
"*": {
|
||||
showCommentsAsHeader: true,
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
import { readdirSync } from "fs"
|
||||
import { rootPathPrefix } from "../constants/general.js"
|
||||
import { NamespaceGenerateDetails } from "types"
|
||||
import { capitalize, kebabToTitle } from "utils"
|
||||
import path from "path"
|
||||
|
||||
export function getCoreFlowNamespaces(): NamespaceGenerateDetails[] {
|
||||
const namespaces: NamespaceGenerateDetails[] = []
|
||||
const rootFlowsPath = path.join(
|
||||
rootPathPrefix,
|
||||
"packages",
|
||||
"core",
|
||||
"core-flows",
|
||||
"src"
|
||||
)
|
||||
|
||||
// retrieve directories
|
||||
const directories = readdirSync(rootFlowsPath, {
|
||||
withFileTypes: true,
|
||||
})
|
||||
|
||||
directories.forEach((directory) => {
|
||||
if (!directory.isDirectory()) {
|
||||
return
|
||||
}
|
||||
|
||||
const namespaceName = kebabToTitle(directory.name)
|
||||
const pathPattern = `**/packages/core/core-flows/src/${directory.name}/**`
|
||||
|
||||
const namespace: NamespaceGenerateDetails = {
|
||||
name: namespaceName,
|
||||
pathPattern,
|
||||
children: [],
|
||||
}
|
||||
|
||||
const subDirs = readdirSync(path.join(rootFlowsPath, directory.name), {
|
||||
withFileTypes: true,
|
||||
})
|
||||
|
||||
subDirs.forEach((dir) => {
|
||||
if (
|
||||
!dir.isDirectory() ||
|
||||
(dir.name !== "workflows" && dir.name !== "steps")
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
namespace.children!.push({
|
||||
name: `${capitalize(dir.name)}_${namespaceName}`,
|
||||
pathPattern: `**/packages/core/core-flows/src/${directory.name}/${dir.name}`,
|
||||
})
|
||||
})
|
||||
|
||||
namespaces.push(namespace)
|
||||
})
|
||||
|
||||
return namespaces
|
||||
}
|
||||
|
||||
export function getNamespaceNames(
|
||||
namespaces: NamespaceGenerateDetails[]
|
||||
): string[] {
|
||||
const names: string[] = []
|
||||
|
||||
namespaces.forEach((namespace) => {
|
||||
names.push(namespace.name)
|
||||
|
||||
if (namespace.children) {
|
||||
names.push(...getNamespaceNames(namespace.children))
|
||||
}
|
||||
})
|
||||
|
||||
return names
|
||||
}
|
||||
@@ -2,6 +2,7 @@ import { minimatch } from "minimatch"
|
||||
import {
|
||||
Application,
|
||||
Comment,
|
||||
Context,
|
||||
Converter,
|
||||
DeclarationReflection,
|
||||
ParameterType,
|
||||
@@ -34,25 +35,26 @@ export function load(app: Application) {
|
||||
"generateNamespaces"
|
||||
) as unknown as NamespaceGenerateDetails[]
|
||||
|
||||
namespaces.forEach((namespace) => {
|
||||
const genNamespace = context.createDeclarationReflection(
|
||||
ReflectionKind.Namespace,
|
||||
void 0,
|
||||
void 0,
|
||||
namespace.name
|
||||
)
|
||||
const generateNamespaces = (ns: NamespaceGenerateDetails[]) => {
|
||||
const createdNamespaces: DeclarationReflection[] = []
|
||||
ns.forEach((namespace) => {
|
||||
const genNamespace = createNamespace(context, namespace)
|
||||
|
||||
if (namespace.description) {
|
||||
genNamespace.comment = new Comment([
|
||||
{
|
||||
kind: "text",
|
||||
text: namespace.description,
|
||||
},
|
||||
])
|
||||
}
|
||||
generatedNamespaces.set(namespace.pathPattern, genNamespace)
|
||||
|
||||
generatedNamespaces.set(namespace.pathPattern, genNamespace)
|
||||
})
|
||||
if (namespace.children) {
|
||||
generateNamespaces(namespace.children).forEach((child) =>
|
||||
genNamespace.addChild(child)
|
||||
)
|
||||
}
|
||||
|
||||
createdNamespaces.push(genNamespace)
|
||||
})
|
||||
|
||||
return createdNamespaces
|
||||
}
|
||||
|
||||
generateNamespaces(namespaces)
|
||||
})
|
||||
|
||||
app.converter.on(
|
||||
@@ -69,13 +71,61 @@ export function load(app: Application) {
|
||||
return
|
||||
}
|
||||
|
||||
generatedNamespaces.forEach((namespace, pathPattern) => {
|
||||
if (!minimatch(filePath, pathPattern)) {
|
||||
return
|
||||
}
|
||||
const namespaces = app.options.getValue(
|
||||
"generateNamespaces"
|
||||
) as unknown as NamespaceGenerateDetails[]
|
||||
|
||||
namespace.addChild(reflection)
|
||||
})
|
||||
const findNamespace = (
|
||||
ns: NamespaceGenerateDetails[]
|
||||
): DeclarationReflection | undefined => {
|
||||
let found: DeclarationReflection | undefined
|
||||
ns.some((namespace) => {
|
||||
if (namespace.children) {
|
||||
// give priorities to children
|
||||
found = findNamespace(namespace.children)
|
||||
if (found) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
if (!minimatch(filePath, namespace.pathPattern)) {
|
||||
return false
|
||||
}
|
||||
|
||||
found = generatedNamespaces.get(namespace.pathPattern)
|
||||
|
||||
return found !== undefined
|
||||
})
|
||||
|
||||
return found
|
||||
}
|
||||
|
||||
const namespace = findNamespace(namespaces)
|
||||
|
||||
namespace?.addChild(reflection)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
function createNamespace(
|
||||
context: Context,
|
||||
namespace: NamespaceGenerateDetails
|
||||
): DeclarationReflection {
|
||||
const genNamespace = context.createDeclarationReflection(
|
||||
ReflectionKind.Namespace,
|
||||
void 0,
|
||||
void 0,
|
||||
namespace.name
|
||||
)
|
||||
|
||||
if (namespace.description) {
|
||||
genNamespace.comment = new Comment([
|
||||
{
|
||||
kind: "text",
|
||||
text: namespace.description,
|
||||
},
|
||||
])
|
||||
}
|
||||
|
||||
return genNamespace
|
||||
}
|
||||
|
||||
@@ -69,6 +69,11 @@ import dmlPropertiesHelper from "./resources/helpers/dml-properties"
|
||||
import ifWorkflowStepHelper from "./resources/helpers/if-workflow-step"
|
||||
import stepInputHelper from "./resources/helpers/step-input"
|
||||
import stepOutputHelper from "./resources/helpers/step-output"
|
||||
import ifWorkflowHelper from "./resources/helpers/if-workflow"
|
||||
import workflowInputHelper from "./resources/helpers/workflow-input"
|
||||
import workflowOutputHelper from "./resources/helpers/workflow-output"
|
||||
import workflowDiagramHelper from "./resources/helpers/workflow-diagram"
|
||||
import workflowHooksHelper from "./resources/helpers/workflow-hooks"
|
||||
import { MarkdownTheme } from "./theme"
|
||||
|
||||
const TEMPLATE_PATH = path.join(__dirname, "resources", "templates")
|
||||
@@ -166,4 +171,9 @@ export function registerHelpers(theme: MarkdownTheme) {
|
||||
ifWorkflowStepHelper()
|
||||
stepInputHelper(theme)
|
||||
stepOutputHelper(theme)
|
||||
ifWorkflowHelper()
|
||||
workflowInputHelper(theme)
|
||||
workflowOutputHelper(theme)
|
||||
workflowDiagramHelper(theme)
|
||||
workflowHooksHelper(theme)
|
||||
}
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
import * as Handlebars from "handlebars"
|
||||
import { Reflection, SignatureReflection } from "typedoc"
|
||||
import { isWorkflowStep } from "utils"
|
||||
import { isWorkflow, isWorkflowStep } from "utils"
|
||||
|
||||
export default function () {
|
||||
Handlebars.registerHelper("example", function (reflection: Reflection) {
|
||||
const isStep =
|
||||
const isWorkflowOrStep =
|
||||
reflection.variant === "signature" &&
|
||||
isWorkflowStep(reflection as SignatureReflection)
|
||||
(isWorkflowStep(reflection as SignatureReflection) ||
|
||||
isWorkflow(reflection as SignatureReflection))
|
||||
const targetReflection =
|
||||
isStep && reflection.parent ? reflection.parent : reflection
|
||||
isWorkflowOrStep && reflection.parent ? reflection.parent : reflection
|
||||
const exampleTag = targetReflection.comment?.blockTags.find(
|
||||
(tag) => tag.tag === "@example"
|
||||
)
|
||||
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
import * as Handlebars from "handlebars"
|
||||
import { SignatureReflection } from "typedoc"
|
||||
import { isWorkflow } from "utils"
|
||||
|
||||
export default function () {
|
||||
Handlebars.registerHelper(
|
||||
"ifWorkflow",
|
||||
function (this: SignatureReflection, options: Handlebars.HelperOptions) {
|
||||
return isWorkflow(this) ? options.fn(this) : options.inverse(this)
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -24,6 +24,7 @@ export default function (theme: MarkdownTheme) {
|
||||
reflectionType: inputType,
|
||||
project: this.project || options.data.theme.project,
|
||||
maxLevel,
|
||||
wrapObject: true,
|
||||
})
|
||||
|
||||
if (!input.length) {
|
||||
|
||||
@@ -24,6 +24,7 @@ export default function (theme: MarkdownTheme) {
|
||||
reflectionType: outputType,
|
||||
project: this.project || options.data.theme.project,
|
||||
maxLevel,
|
||||
wrapObject: true,
|
||||
})
|
||||
|
||||
if (!output.length) {
|
||||
|
||||
@@ -3,6 +3,7 @@ import {
|
||||
DeclarationReflection,
|
||||
ProjectReflection,
|
||||
ReflectionGroup,
|
||||
ReflectionKind,
|
||||
} from "typedoc"
|
||||
import { MarkdownTheme } from "../../theme"
|
||||
import { escapeChars } from "utils"
|
||||
@@ -13,13 +14,18 @@ export default function (theme: MarkdownTheme) {
|
||||
function (this: ProjectReflection | DeclarationReflection) {
|
||||
const md: string[] = []
|
||||
|
||||
const { hideInPageTOC } = theme
|
||||
const { hideInPageTOC, allReflectionsHaveOwnDocumentInNamespace } = theme
|
||||
const { hideTocHeaders, reflectionGroupRename = {} } =
|
||||
theme.getFormattingOptionsForLocation()
|
||||
|
||||
const isVisible = this.groups?.some((group) =>
|
||||
group.allChildrenHaveOwnDocument()
|
||||
)
|
||||
const isNamespaceVisible =
|
||||
this.kind === ReflectionKind.Namespace &&
|
||||
allReflectionsHaveOwnDocumentInNamespace.includes(this.name)
|
||||
const isVisible =
|
||||
isNamespaceVisible ||
|
||||
this.groups?.some((group) => {
|
||||
return group.allChildrenHaveOwnDocument()
|
||||
})
|
||||
|
||||
function pushGroup(group: ReflectionGroup, md: string[]) {
|
||||
const children = group.children.map(
|
||||
@@ -47,7 +53,7 @@ export default function (theme: MarkdownTheme) {
|
||||
md.push("\n")
|
||||
})
|
||||
} else {
|
||||
if (!hideInPageTOC || group.allChildrenHaveOwnDocument()) {
|
||||
if (!hideInPageTOC || isVisible) {
|
||||
if (!hideTocHeaders) {
|
||||
md.push(`${headingLevel} ${groupTitle}\n\n`)
|
||||
}
|
||||
|
||||
+113
@@ -0,0 +1,113 @@
|
||||
import { MarkdownTheme } from "../../theme"
|
||||
import * as Handlebars from "handlebars"
|
||||
import { DocumentReflection, SignatureReflection } from "typedoc"
|
||||
import { formatWorkflowDiagramComponent } from "../../utils/format-workflow-diagram-component"
|
||||
import { getProjectChild } from "utils"
|
||||
import { getWorkflowReflectionFromNamespace } from "../../utils/workflow-utils"
|
||||
|
||||
export default function (theme: MarkdownTheme) {
|
||||
Handlebars.registerHelper(
|
||||
"workflowDiagram",
|
||||
function (this: SignatureReflection): string {
|
||||
const { workflowDiagramComponent } =
|
||||
theme.getFormattingOptionsForLocation()
|
||||
if (!this.parent?.documents?.length) {
|
||||
return ""
|
||||
}
|
||||
|
||||
const steps: Record<string, unknown>[] = []
|
||||
|
||||
this.parent.documents.forEach((document, index) => {
|
||||
if (document.name === "when") {
|
||||
const condition = getDocumentTagValue(document, "@whenCondition")
|
||||
const depth = getDocumentTagValue(document, "@workflowDepth")
|
||||
|
||||
const whenStep = {
|
||||
type: "when",
|
||||
condition,
|
||||
depth,
|
||||
steps: [] as Record<string, unknown>[],
|
||||
}
|
||||
|
||||
document.children?.forEach((childDocument) => {
|
||||
whenStep.steps.push(
|
||||
getStep({
|
||||
document: childDocument,
|
||||
theme,
|
||||
index,
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
steps.push(whenStep)
|
||||
} else {
|
||||
steps.push(
|
||||
getStep({
|
||||
document,
|
||||
theme,
|
||||
index,
|
||||
})
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
return (
|
||||
`${Handlebars.helpers.titleLevel()} Diagram\n\n` +
|
||||
formatWorkflowDiagramComponent({
|
||||
component: workflowDiagramComponent,
|
||||
componentItem: {
|
||||
name: this.name,
|
||||
steps,
|
||||
},
|
||||
})
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
function getStep({
|
||||
document,
|
||||
theme,
|
||||
index,
|
||||
}: {
|
||||
document: DocumentReflection
|
||||
theme: MarkdownTheme
|
||||
index: number
|
||||
}) {
|
||||
const type = document.comment?.modifierTags.has("@workflowStep")
|
||||
? "workflow"
|
||||
: document.comment?.modifierTags.has("@hook")
|
||||
? "hook"
|
||||
: "step"
|
||||
|
||||
const namespaceRefl = theme.project
|
||||
? getWorkflowReflectionFromNamespace(theme.project, document.name)
|
||||
: undefined
|
||||
|
||||
const associatedReflection =
|
||||
namespaceRefl ||
|
||||
(theme.project ? getProjectChild(theme.project, document.name) : undefined)
|
||||
const depth = getDocumentTagValue(document, `@workflowDepth`) || `${index}`
|
||||
|
||||
return {
|
||||
type,
|
||||
name: document.name,
|
||||
description: associatedReflection?.comment
|
||||
? Handlebars.helpers.comments(associatedReflection.comment, true)
|
||||
: "",
|
||||
link:
|
||||
type === "hook" || !associatedReflection?.url
|
||||
? `#${document.name}`
|
||||
: Handlebars.helpers.relativeURL(associatedReflection.url),
|
||||
depth: parseInt(depth),
|
||||
}
|
||||
}
|
||||
|
||||
function getDocumentTagValue(
|
||||
document: DocumentReflection,
|
||||
tag: `@${string}`
|
||||
): string | undefined {
|
||||
return document.comment
|
||||
?.getTag(tag)
|
||||
?.content.find((tagContent) => tagContent.kind === "text")?.text
|
||||
}
|
||||
+72
@@ -0,0 +1,72 @@
|
||||
import { MarkdownTheme } from "../../theme"
|
||||
import * as Handlebars from "handlebars"
|
||||
import { SignatureReflection } from "typedoc"
|
||||
import { cleanUpHookInput, getProjectChild } from "utils"
|
||||
|
||||
export default function (theme: MarkdownTheme) {
|
||||
Handlebars.registerHelper(
|
||||
"workflowHooks",
|
||||
function (this: SignatureReflection): string {
|
||||
if (!this.parent?.documents || !theme.project) {
|
||||
return ""
|
||||
}
|
||||
|
||||
const hooks = this.parent.documents.filter(
|
||||
(document) => document.comment?.modifierTags.has("@hook")
|
||||
)
|
||||
|
||||
if (!hooks.length) {
|
||||
return ""
|
||||
}
|
||||
|
||||
let str = `${Handlebars.helpers.titleLevel()} Hooks`
|
||||
|
||||
Handlebars.helpers.incrementCurrentTitleLevel()
|
||||
|
||||
const hooksTitleLevel = Handlebars.helpers.titleLevel()
|
||||
|
||||
hooks.forEach((hook) => {
|
||||
// show the hook's input
|
||||
const hookReflection = getProjectChild(theme.project!, hook.name)
|
||||
|
||||
if (
|
||||
!hookReflection ||
|
||||
!hookReflection.signatures?.length ||
|
||||
!hookReflection.signatures[0].parameters?.length
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
str += `\n\n${hooksTitleLevel} ${hook.name}\n\n`
|
||||
|
||||
const hookExample = hookReflection.comment?.getTag(`@example`)
|
||||
|
||||
if (hookExample) {
|
||||
Handlebars.helpers.incrementCurrentTitleLevel()
|
||||
const innerTitleLevel = Handlebars.helpers.titleLevel()
|
||||
|
||||
str += `${innerTitleLevel} Example\n\n\`\`\`ts\n${Handlebars.helpers.comment(
|
||||
hookExample.content
|
||||
)}\n\`\`\`\n\n${innerTitleLevel} Input\n\n`
|
||||
|
||||
Handlebars.helpers.decrementCurrentTitleLevel()
|
||||
}
|
||||
|
||||
str += `Handlers consuming this hook accept the following input.\n\n`
|
||||
|
||||
str += Handlebars.helpers.parameterComponent.call(
|
||||
cleanUpHookInput(hookReflection.signatures[0].parameters),
|
||||
{
|
||||
hash: {
|
||||
sectionTitle: hook.name,
|
||||
},
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
Handlebars.helpers.decrementCurrentTitleLevel()
|
||||
|
||||
return str
|
||||
}
|
||||
)
|
||||
}
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
import { MarkdownTheme } from "../../theme"
|
||||
import * as Handlebars from "handlebars"
|
||||
import { SignatureReflection } from "typedoc"
|
||||
import { getWorkflowInputType } from "utils"
|
||||
import { formatParameterComponent } from "../../utils/format-parameter-component"
|
||||
import { getReflectionTypeParameters } from "../../utils/reflection-type-parameters"
|
||||
|
||||
export default function (theme: MarkdownTheme) {
|
||||
Handlebars.registerHelper(
|
||||
"workflowInput",
|
||||
function (
|
||||
this: SignatureReflection,
|
||||
options: Handlebars.HelperOptions
|
||||
): string {
|
||||
const { parameterComponent, maxLevel, parameterComponentExtraProps } =
|
||||
theme.getFormattingOptionsForLocation()
|
||||
|
||||
const inputType = getWorkflowInputType(this)
|
||||
if (!inputType) {
|
||||
return ""
|
||||
}
|
||||
|
||||
const input = getReflectionTypeParameters({
|
||||
reflectionType: inputType,
|
||||
project: this.project || options.data.theme.project,
|
||||
maxLevel,
|
||||
wrapObject: true,
|
||||
})
|
||||
|
||||
if (!input.length) {
|
||||
return ""
|
||||
}
|
||||
|
||||
const formattedComponent = formatParameterComponent({
|
||||
parameterComponent,
|
||||
componentItems: input,
|
||||
extraProps: parameterComponentExtraProps,
|
||||
sectionTitle: options.hash.sectionTitle,
|
||||
})
|
||||
|
||||
return `${Handlebars.helpers.titleLevel()} Input\n\n${formattedComponent}`
|
||||
}
|
||||
)
|
||||
}
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
import { MarkdownTheme } from "../../theme"
|
||||
import * as Handlebars from "handlebars"
|
||||
import { SignatureReflection } from "typedoc"
|
||||
import { getWorkflowOutputType } from "utils"
|
||||
import { formatParameterComponent } from "../../utils/format-parameter-component"
|
||||
import { getReflectionTypeParameters } from "../../utils/reflection-type-parameters"
|
||||
|
||||
export default function (theme: MarkdownTheme) {
|
||||
Handlebars.registerHelper(
|
||||
"workflowOutput",
|
||||
function (
|
||||
this: SignatureReflection,
|
||||
options: Handlebars.HelperOptions
|
||||
): string {
|
||||
const { parameterComponent, maxLevel, parameterComponentExtraProps } =
|
||||
theme.getFormattingOptionsForLocation()
|
||||
|
||||
const outputType = getWorkflowOutputType(this)
|
||||
if (!outputType) {
|
||||
return ""
|
||||
}
|
||||
|
||||
const output = getReflectionTypeParameters({
|
||||
reflectionType: outputType,
|
||||
project: this.project || options.data.theme.project,
|
||||
maxLevel,
|
||||
wrapObject: true,
|
||||
})
|
||||
|
||||
if (!output.length) {
|
||||
return ""
|
||||
}
|
||||
|
||||
const formattedComponent = formatParameterComponent({
|
||||
parameterComponent,
|
||||
componentItems: output,
|
||||
extraProps: parameterComponentExtraProps,
|
||||
sectionTitle: options.hash.sectionTitle,
|
||||
})
|
||||
|
||||
return `${Handlebars.helpers.titleLevel()} Output\n\n${formattedComponent}`
|
||||
}
|
||||
)
|
||||
}
|
||||
+8
@@ -4,6 +4,12 @@
|
||||
|
||||
{{else}}
|
||||
|
||||
{{#ifWorkflow}}
|
||||
|
||||
{{> member.workflow}}
|
||||
|
||||
{{else}}
|
||||
|
||||
{{#ifWorkflowStep}}
|
||||
|
||||
{{> member.step}}
|
||||
@@ -14,4 +20,6 @@
|
||||
|
||||
{{/ifWorkflowStep}}
|
||||
|
||||
{{/ifWorkflow}}
|
||||
|
||||
{{/ifReactQueryType}}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
{{{signatureTitle accessor parent}}}
|
||||
|
||||
{{#if (sectionEnabled "member_signature_comment")}}
|
||||
|
||||
{{> comment}}
|
||||
|
||||
{{/if}}
|
||||
|
||||
{{#if (sectionEnabled "member_signature_example")}}
|
||||
|
||||
{{{example this}}}
|
||||
|
||||
{{/if}}
|
||||
|
||||
{{{workflowDiagram}}}
|
||||
|
||||
{{{workflowInput sectionTitle=name}}}
|
||||
|
||||
{{{workflowOutput sectionTitle=name}}}
|
||||
|
||||
{{{workflowHooks}}}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
export function formatWorkflowDiagramComponent({
|
||||
component,
|
||||
componentItem,
|
||||
}: {
|
||||
component: string | undefined
|
||||
componentItem: Record<string, unknown>
|
||||
}): string {
|
||||
return `<${component} workflow={${JSON.stringify(componentItem)}} />`
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
import {
|
||||
DeclarationReflection,
|
||||
ProjectReflection,
|
||||
ReflectionKind,
|
||||
} from "typedoc"
|
||||
|
||||
export function getWorkflowReflectionFromNamespace(
|
||||
project: ProjectReflection,
|
||||
reflName: string
|
||||
): DeclarationReflection | undefined {
|
||||
let found: DeclarationReflection | undefined
|
||||
project
|
||||
.getChildrenByKind(ReflectionKind.Module)
|
||||
.find((moduleRef) => moduleRef.name === "core-flows")
|
||||
?.getChildrenByKind(ReflectionKind.Namespace)
|
||||
.some((namespace) => {
|
||||
found = namespace.getChildByName(reflName) as DeclarationReflection
|
||||
|
||||
return found !== undefined
|
||||
})
|
||||
|
||||
return found
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
import {
|
||||
Application,
|
||||
Comment,
|
||||
CommentTag,
|
||||
Context,
|
||||
Converter,
|
||||
DeclarationReflection,
|
||||
@@ -14,7 +15,13 @@ import {
|
||||
import ts, { SyntaxKind, VariableStatement } from "typescript"
|
||||
import { WorkflowManager, WorkflowDefinition } from "@medusajs/orchestration"
|
||||
import Helper from "./utils/helper"
|
||||
import { isWorkflow } from "utils"
|
||||
import { isWorkflow, isWorkflowStep } from "utils"
|
||||
import { StepType } from "./types"
|
||||
|
||||
type ParsedStep = {
|
||||
stepReflection: DeclarationReflection
|
||||
stepType: StepType
|
||||
}
|
||||
|
||||
/**
|
||||
* A plugin that extracts a workflow's steps, hooks, their types, and attaches them as
|
||||
@@ -92,12 +99,20 @@ class WorkflowsPlugin {
|
||||
continue
|
||||
}
|
||||
|
||||
this.parseSteps({
|
||||
this.parseWorkflow({
|
||||
workflowId,
|
||||
constructorFn: initializer.arguments[1],
|
||||
context,
|
||||
parentReflection: reflection.parent,
|
||||
})
|
||||
|
||||
if (!reflection.comment && reflection.parent.comment) {
|
||||
reflection.comment = reflection.parent.comment
|
||||
}
|
||||
} else if (isWorkflowStep(reflection)) {
|
||||
if (!reflection.comment && reflection.parent.comment) {
|
||||
reflection.comment = reflection.parent.comment
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -107,7 +122,7 @@ class WorkflowsPlugin {
|
||||
*
|
||||
* @param param0 - The workflow's details.
|
||||
*/
|
||||
parseSteps({
|
||||
parseWorkflow({
|
||||
workflowId,
|
||||
constructorFn,
|
||||
context,
|
||||
@@ -130,138 +145,241 @@ class WorkflowsPlugin {
|
||||
parentReflection.documents = []
|
||||
}
|
||||
|
||||
let stepDepth = 1
|
||||
|
||||
constructorFn.body.statements.forEach((statement) => {
|
||||
let initializer: ts.CallExpression | undefined
|
||||
switch (statement.kind) {
|
||||
case SyntaxKind.VariableStatement:
|
||||
const variableInitializer = (statement as VariableStatement)
|
||||
.declarationList.declarations[0].initializer
|
||||
|
||||
if (
|
||||
!variableInitializer ||
|
||||
!ts.isCallExpression(variableInitializer)
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
initializer = variableInitializer
|
||||
break
|
||||
case SyntaxKind.ExpressionStatement:
|
||||
const statementInitializer = (statement as ts.ExpressionStatement)
|
||||
.expression
|
||||
if (!ts.isCallExpression(statementInitializer)) {
|
||||
return
|
||||
}
|
||||
|
||||
initializer = statementInitializer
|
||||
}
|
||||
const initializer = this.getInitializerOfNode(statement)
|
||||
|
||||
if (!initializer) {
|
||||
return
|
||||
}
|
||||
|
||||
const { stepId, stepReflection } =
|
||||
this.parseStep({
|
||||
const initializerName = this.helper.normalizeName(
|
||||
initializer.expression.getText()
|
||||
)
|
||||
|
||||
if (initializerName === "when") {
|
||||
this.parseWhenStep({
|
||||
initializer,
|
||||
parentReflection,
|
||||
context,
|
||||
workflow,
|
||||
stepDepth,
|
||||
})
|
||||
} else {
|
||||
const steps = this.parseSteps({
|
||||
initializer,
|
||||
context,
|
||||
workflow,
|
||||
}) || {}
|
||||
workflowVarName: parentReflection.name,
|
||||
})
|
||||
|
||||
if (!stepId || !stepReflection) {
|
||||
return
|
||||
if (!steps.length) {
|
||||
return
|
||||
}
|
||||
|
||||
steps.forEach((step) => {
|
||||
this.createStepDocumentReflection({
|
||||
...step,
|
||||
depth: stepDepth,
|
||||
parentReflection,
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
const stepModifier = this.helper.getModifier(initializer)
|
||||
|
||||
const documentReflection = new DocumentReflection(
|
||||
stepReflection.name,
|
||||
stepReflection,
|
||||
[],
|
||||
{}
|
||||
)
|
||||
|
||||
documentReflection.comment = new Comment()
|
||||
documentReflection.comment.modifierTags.add(stepModifier)
|
||||
|
||||
parentReflection.documents?.push(documentReflection)
|
||||
stepDepth++
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a step to retrieve its ID and reflection.
|
||||
* Parses steps in an initializer, retrieving each of their ID and reflection.
|
||||
*
|
||||
* @param param0 - The step's details.
|
||||
* @returns The step's ID and reflection, if found.
|
||||
*/
|
||||
parseStep({
|
||||
parseSteps({
|
||||
initializer,
|
||||
context,
|
||||
workflow,
|
||||
workflowVarName,
|
||||
}: {
|
||||
initializer: ts.CallExpression
|
||||
context: Context
|
||||
workflow?: WorkflowDefinition
|
||||
}):
|
||||
| {
|
||||
stepId: string
|
||||
stepReflection: DeclarationReflection
|
||||
}
|
||||
| undefined {
|
||||
workflowVarName: string
|
||||
}): ParsedStep[] {
|
||||
const steps: ParsedStep[] = []
|
||||
const initializerName = this.helper.normalizeName(
|
||||
initializer.expression.getText()
|
||||
)
|
||||
|
||||
let stepId: string | undefined
|
||||
let stepReflection: DeclarationReflection | undefined
|
||||
|
||||
if (
|
||||
this.helper.getStepType(initializer) === "hook" &&
|
||||
"symbol" in initializer.arguments[1]
|
||||
) {
|
||||
// get the hook's name from the first argument
|
||||
stepId = this.helper.normalizeName(initializer.arguments[0].getText())
|
||||
stepReflection = this.assembleHookReflection({
|
||||
stepId,
|
||||
context,
|
||||
inputSymbol: initializer.arguments[1].symbol as ts.Symbol,
|
||||
})
|
||||
} else {
|
||||
const initializerReflection =
|
||||
context.project.getChildByName(initializerName)
|
||||
|
||||
if (
|
||||
!initializerReflection ||
|
||||
!(initializerReflection instanceof DeclarationReflection)
|
||||
) {
|
||||
return
|
||||
if (initializerName === "parallelize") {
|
||||
if (!initializer.arguments.length) {
|
||||
return steps
|
||||
}
|
||||
|
||||
const { initializer } =
|
||||
this.helper.getReflectionSymbolAndInitializer({
|
||||
project: context.project,
|
||||
reflection: initializerReflection,
|
||||
}) || {}
|
||||
initializer.arguments.forEach((argument) => {
|
||||
if (!ts.isCallExpression(argument)) {
|
||||
return
|
||||
}
|
||||
|
||||
steps.push(
|
||||
...this.parseSteps({
|
||||
initializer: argument,
|
||||
context,
|
||||
workflow,
|
||||
workflowVarName,
|
||||
})
|
||||
)
|
||||
})
|
||||
} else {
|
||||
let stepId: string | undefined
|
||||
let stepReflection: DeclarationReflection | undefined
|
||||
let stepType = this.helper.getStepType(initializer)
|
||||
|
||||
if (stepType === "hook" && "symbol" in initializer.arguments[1]) {
|
||||
// get the hook's name from the first argument
|
||||
stepId = this.helper.normalizeName(initializer.arguments[0].getText())
|
||||
stepReflection = this.assembleHookReflection({
|
||||
stepId,
|
||||
context,
|
||||
inputSymbol: initializer.arguments[1].symbol as ts.Symbol,
|
||||
workflowName: workflowVarName,
|
||||
})
|
||||
} else {
|
||||
const initializerReflection =
|
||||
context.project.getChildByName(initializerName)
|
||||
|
||||
if (
|
||||
!initializerReflection ||
|
||||
!(initializerReflection instanceof DeclarationReflection)
|
||||
) {
|
||||
return steps
|
||||
}
|
||||
|
||||
const { initializer: originalInitializer } =
|
||||
this.helper.getReflectionSymbolAndInitializer({
|
||||
project: context.project,
|
||||
reflection: initializerReflection,
|
||||
}) || {}
|
||||
|
||||
if (!originalInitializer) {
|
||||
return steps
|
||||
}
|
||||
|
||||
stepId = this.helper.getStepOrWorkflowId(
|
||||
originalInitializer,
|
||||
context.project,
|
||||
true
|
||||
)
|
||||
stepType = this.helper.getStepType(originalInitializer)
|
||||
stepReflection = initializerReflection
|
||||
}
|
||||
|
||||
// check if is a step in the workflow
|
||||
if (
|
||||
stepId &&
|
||||
stepType &&
|
||||
stepReflection &&
|
||||
workflow?.handlers_.get(stepId)
|
||||
) {
|
||||
steps.push({
|
||||
stepReflection,
|
||||
stepType,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return steps
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the step in a `when` condition, and creates a `when` document with the steps as child documents.
|
||||
*
|
||||
* @param param0 - The when stp's details.
|
||||
*/
|
||||
parseWhenStep({
|
||||
initializer,
|
||||
parentReflection,
|
||||
context,
|
||||
workflow,
|
||||
stepDepth,
|
||||
}: {
|
||||
initializer: ts.CallExpression
|
||||
parentReflection: DeclarationReflection
|
||||
context: Context
|
||||
workflow?: WorkflowDefinition
|
||||
stepDepth: number
|
||||
}) {
|
||||
const whenInitializer = (initializer.expression as ts.CallExpression)
|
||||
.expression as ts.CallExpression
|
||||
const thenInitializer = initializer
|
||||
|
||||
if (
|
||||
whenInitializer.arguments.length < 2 ||
|
||||
(!ts.isFunctionExpression(whenInitializer.arguments[1]) &&
|
||||
!ts.isArrowFunction(whenInitializer.arguments[1])) ||
|
||||
thenInitializer.arguments.length < 1 ||
|
||||
(!ts.isFunctionExpression(thenInitializer.arguments[0]) &&
|
||||
!ts.isArrowFunction(thenInitializer.arguments[0]))
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
const whenCondition = whenInitializer.arguments[1].body.getText()
|
||||
|
||||
const thenStatements = (thenInitializer.arguments[0].body as ts.Block)
|
||||
.statements
|
||||
|
||||
const documentReflection = new DocumentReflection(
|
||||
"when",
|
||||
parentReflection,
|
||||
[],
|
||||
{}
|
||||
)
|
||||
|
||||
documentReflection.comment = new Comment()
|
||||
documentReflection.comment.modifierTags.add(this.helper.getModifier(`when`))
|
||||
documentReflection.comment.blockTags.push(
|
||||
new CommentTag(`@workflowDepth`, [
|
||||
{
|
||||
kind: "text",
|
||||
text: `${stepDepth}`,
|
||||
},
|
||||
])
|
||||
)
|
||||
documentReflection.comment.blockTags.push(
|
||||
new CommentTag(`@whenCondition`, [
|
||||
{
|
||||
kind: "text",
|
||||
text: whenCondition,
|
||||
},
|
||||
])
|
||||
)
|
||||
|
||||
thenStatements.forEach((statement) => {
|
||||
const initializer = this.getInitializerOfNode(statement)
|
||||
|
||||
if (!initializer) {
|
||||
return
|
||||
}
|
||||
|
||||
stepId = this.helper.getStepOrWorkflowId(
|
||||
this.parseSteps({
|
||||
initializer,
|
||||
context.project,
|
||||
true
|
||||
)
|
||||
stepReflection = initializerReflection
|
||||
}
|
||||
context,
|
||||
workflow,
|
||||
workflowVarName: parentReflection.name,
|
||||
}).forEach((step) => {
|
||||
this.createStepDocumentReflection({
|
||||
...step,
|
||||
depth: stepDepth,
|
||||
parentReflection: documentReflection,
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
// check if is a step in the workflow
|
||||
if (!stepId || !stepReflection || !workflow?.handlers_.get(stepId)) {
|
||||
return
|
||||
}
|
||||
|
||||
return {
|
||||
stepId,
|
||||
stepReflection,
|
||||
if (documentReflection.children?.length) {
|
||||
parentReflection.documents?.push(documentReflection)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -275,10 +393,12 @@ class WorkflowsPlugin {
|
||||
stepId,
|
||||
context,
|
||||
inputSymbol,
|
||||
workflowName,
|
||||
}: {
|
||||
stepId: string
|
||||
context: Context
|
||||
inputSymbol: ts.Symbol
|
||||
workflowName: string
|
||||
}): DeclarationReflection {
|
||||
const declarationReflection = context.createDeclarationReflection(
|
||||
ReflectionKind.Function,
|
||||
@@ -286,6 +406,14 @@ class WorkflowsPlugin {
|
||||
undefined,
|
||||
stepId
|
||||
)
|
||||
|
||||
declarationReflection.comment = new Comment()
|
||||
declarationReflection.comment.summary = [
|
||||
{
|
||||
kind: "text",
|
||||
text: "This step is a hook that you can inject custom functionality into.",
|
||||
},
|
||||
]
|
||||
const signatureReflection = new SignatureReflection(
|
||||
stepId,
|
||||
ReflectionKind.SomeSignature,
|
||||
@@ -300,6 +428,10 @@ class WorkflowsPlugin {
|
||||
|
||||
parameter.type = ReferenceType.createSymbolReference(inputSymbol, context)
|
||||
|
||||
if (parameter.type.name === "__object") {
|
||||
parameter.type.name = "object"
|
||||
}
|
||||
|
||||
signatureReflection.parameters = []
|
||||
|
||||
signatureReflection.parameters.push(parameter)
|
||||
@@ -308,8 +440,162 @@ class WorkflowsPlugin {
|
||||
|
||||
declarationReflection.signatures.push(signatureReflection)
|
||||
|
||||
declarationReflection.comment.blockTags.push(
|
||||
new CommentTag(`@example`, [
|
||||
{
|
||||
kind: "code",
|
||||
text: this.helper.generateHookExample({
|
||||
hookName: stepId,
|
||||
workflowName,
|
||||
parameter,
|
||||
}),
|
||||
},
|
||||
])
|
||||
)
|
||||
|
||||
return declarationReflection
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a document reflection for a step.
|
||||
*
|
||||
* @param param0 - The step's details.
|
||||
*/
|
||||
createStepDocumentReflection({
|
||||
stepType,
|
||||
stepReflection,
|
||||
depth,
|
||||
parentReflection,
|
||||
}: ParsedStep & {
|
||||
depth: number
|
||||
parentReflection: DeclarationReflection | DocumentReflection
|
||||
}) {
|
||||
const stepModifier = this.helper.getModifier(stepType)
|
||||
|
||||
const documentReflection = new DocumentReflection(
|
||||
stepReflection.name,
|
||||
stepReflection,
|
||||
[],
|
||||
{}
|
||||
)
|
||||
|
||||
documentReflection.comment = new Comment()
|
||||
documentReflection.comment.modifierTags.add(stepModifier)
|
||||
documentReflection.comment.blockTags.push(
|
||||
new CommentTag(`@workflowDepth`, [
|
||||
{
|
||||
kind: "text",
|
||||
text: `${depth}`,
|
||||
},
|
||||
])
|
||||
)
|
||||
|
||||
if (parentReflection.isDocument()) {
|
||||
parentReflection.addChild(documentReflection)
|
||||
} else {
|
||||
parentReflection.documents?.push(documentReflection)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the initializer in a node, if available.
|
||||
*
|
||||
* @param node - The node to search for an initializer in.
|
||||
* @returns The initializer, if found.
|
||||
*/
|
||||
getInitializerOfNode(node: ts.Node): ts.CallExpression | undefined {
|
||||
let initializer: ts.CallExpression | undefined
|
||||
switch (node.kind) {
|
||||
case SyntaxKind.CallExpression:
|
||||
initializer = node as ts.CallExpression
|
||||
break
|
||||
case SyntaxKind.VariableStatement:
|
||||
const variableInitializer = (node as VariableStatement).declarationList
|
||||
.declarations[0].initializer
|
||||
|
||||
if (!variableInitializer) {
|
||||
return
|
||||
}
|
||||
|
||||
initializer = this.findCallExpression(variableInitializer)
|
||||
break
|
||||
case SyntaxKind.ExpressionStatement:
|
||||
const statementInitializer = (node as ts.ExpressionStatement).expression
|
||||
|
||||
initializer = this.findCallExpression(statementInitializer)
|
||||
break
|
||||
case SyntaxKind.ReturnStatement:
|
||||
let returnInitializer = (node as ts.ReturnStatement).expression
|
||||
|
||||
if (
|
||||
returnInitializer &&
|
||||
ts.isNewExpression(returnInitializer) &&
|
||||
returnInitializer.expression.getText().includes("WorkflowResponse") &&
|
||||
returnInitializer.arguments?.length
|
||||
) {
|
||||
returnInitializer = this.getInitializerOfNode(
|
||||
returnInitializer.arguments[0]
|
||||
)
|
||||
}
|
||||
|
||||
if (!returnInitializer) {
|
||||
return
|
||||
}
|
||||
|
||||
initializer = this.findCallExpression(returnInitializer)
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
return initializer ? this.cleanUpInitializer(initializer) : undefined
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds a `CallExpression` in an expression and returns it, if available.
|
||||
*
|
||||
* @param expression - The expression to search in.
|
||||
* @param skipCallCheck - Whether to skip the `CallExpression` check the first time. Useful for the {@link cleanUpInitializer} method.
|
||||
* @returns The `CallExpression` if found.
|
||||
*/
|
||||
findCallExpression(
|
||||
expression: ts.Expression,
|
||||
skipCallCheck = false
|
||||
): ts.CallExpression | undefined {
|
||||
let initializer = expression
|
||||
while (
|
||||
(skipCallCheck || !ts.isCallExpression(initializer)) &&
|
||||
"expression" in initializer
|
||||
) {
|
||||
initializer = initializer.expression as ts.Expression
|
||||
skipCallCheck = false
|
||||
}
|
||||
|
||||
return initializer && ts.isCallExpression(initializer)
|
||||
? initializer
|
||||
: undefined
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds an inner call expression of a call expression, if the provided one is not allowed.
|
||||
* This is useful for steps that chain a `.config` method, for example.
|
||||
*
|
||||
* @param initializer - The call expression to search in.
|
||||
* @returns The call expression to be used.
|
||||
*/
|
||||
cleanUpInitializer(initializer: ts.CallExpression): ts.CallExpression {
|
||||
if (!("name" in initializer.expression)) {
|
||||
return initializer
|
||||
}
|
||||
|
||||
const initializerName = (initializer.expression.name as ts.Identifier)
|
||||
.escapedText
|
||||
|
||||
if (initializerName === "config") {
|
||||
return this.findCallExpression(initializer, true) || initializer
|
||||
}
|
||||
|
||||
return initializer
|
||||
}
|
||||
}
|
||||
|
||||
export default WorkflowsPlugin
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
export type StepType = "step" | "workflowStep" | "hook"
|
||||
export type StepType = "step" | "workflowStep" | "hook" | "when"
|
||||
|
||||
export type StepModifier = "@step" | "@workflowStep" | "@hook"
|
||||
export type StepModifier = "@step" | "@workflowStep" | "@hook" | "@when"
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
import { DeclarationReflection, ProjectReflection } from "typedoc"
|
||||
import ts from "typescript"
|
||||
import {
|
||||
DeclarationReflection,
|
||||
ParameterReflection,
|
||||
ProjectReflection,
|
||||
} from "typedoc"
|
||||
import ts, { isStringLiteral } from "typescript"
|
||||
import { StepModifier, StepType } from "../types"
|
||||
|
||||
/**
|
||||
@@ -13,7 +17,27 @@ export default class Helper {
|
||||
* @returns The normalized name.
|
||||
*/
|
||||
normalizeName(name: string) {
|
||||
return name.replace(".runAsStep", "").replace(/^"/, "").replace(/"$/, "")
|
||||
const nameWithoutQuotes = name.replace(/^"/, "").replace(/"$/, "")
|
||||
|
||||
const dotPos = nameWithoutQuotes.indexOf(".")
|
||||
const parenPos = nameWithoutQuotes.indexOf("(")
|
||||
|
||||
// If both indices of dot and parenthesis are -1, set endIndex to -1
|
||||
// if one of them is -1, use the other's value
|
||||
// if both aren't -1, use the minimum
|
||||
const endIndex =
|
||||
dotPos === -1 && parenPos === -1
|
||||
? -1
|
||||
: dotPos === -1
|
||||
? parenPos
|
||||
: parenPos === -1
|
||||
? dotPos
|
||||
: Math.min(dotPos, parenPos)
|
||||
|
||||
return nameWithoutQuotes.substring(
|
||||
0,
|
||||
endIndex === -1 ? nameWithoutQuotes.length : endIndex
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -64,13 +88,39 @@ export default class Helper {
|
||||
project: ProjectReflection,
|
||||
checkWorkflowStep = false
|
||||
): string | undefined {
|
||||
const idVar = initializer.arguments[0]
|
||||
const idArg = initializer.arguments[0]
|
||||
const isWorkflowStep =
|
||||
checkWorkflowStep && this.getStepType(initializer) === "workflowStep"
|
||||
const idVarName = this.normalizeName(idVar.getText())
|
||||
const idArgValue = this.normalizeName(idArg.getText())
|
||||
|
||||
let stepId: string | undefined
|
||||
|
||||
if (ts.isObjectLiteralExpression(idArg)) {
|
||||
const nameProperty = idArg.properties.find(
|
||||
(property) => property.name?.getText() === "name"
|
||||
)
|
||||
|
||||
if (nameProperty && ts.isPropertyAssignment(nameProperty)) {
|
||||
const nameValue = this.normalizeName(nameProperty.initializer.getText())
|
||||
stepId = ts.isStringLiteral(nameProperty.initializer)
|
||||
? nameValue
|
||||
: this.getValueFromReflection(nameValue, project)
|
||||
}
|
||||
} else if (!isStringLiteral(idArg)) {
|
||||
stepId = this.getValueFromReflection(idArgValue, project)
|
||||
} else {
|
||||
stepId = idArgValue
|
||||
}
|
||||
|
||||
return isWorkflowStep ? `${stepId}-as-step` : stepId
|
||||
}
|
||||
|
||||
private getValueFromReflection(
|
||||
refName: string,
|
||||
project: ProjectReflection
|
||||
): string | undefined {
|
||||
// load it from the project
|
||||
const idVarReflection = project.getChildByName(idVarName)
|
||||
const idVarReflection = project.getChildByName(refName)
|
||||
|
||||
if (
|
||||
!idVarReflection ||
|
||||
@@ -80,9 +130,7 @@ export default class Helper {
|
||||
return
|
||||
}
|
||||
|
||||
const stepId = idVarReflection.type.value as string
|
||||
|
||||
return isWorkflowStep ? `${stepId}-as-step` : stepId
|
||||
return idVarReflection.type.value as string
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -97,6 +145,8 @@ export default class Helper {
|
||||
return "workflowStep"
|
||||
case "createHook":
|
||||
return "hook"
|
||||
case "when":
|
||||
return "when"
|
||||
default:
|
||||
return "step"
|
||||
}
|
||||
@@ -108,9 +158,39 @@ export default class Helper {
|
||||
* @param initializer - The step's initializer.
|
||||
* @returns The step's modifier.
|
||||
*/
|
||||
getModifier(initializer: ts.CallExpression): StepModifier {
|
||||
const stepType = this.getStepType(initializer)
|
||||
|
||||
getModifier(stepType: StepType): StepModifier {
|
||||
return `@${stepType}`
|
||||
}
|
||||
|
||||
generateHookExample({
|
||||
hookName,
|
||||
workflowName,
|
||||
parameter,
|
||||
}: {
|
||||
hookName: string
|
||||
workflowName: string
|
||||
parameter: ParameterReflection
|
||||
}): string {
|
||||
let str = `import { ${workflowName} } from "@medusajs/core-flows"\n\n`
|
||||
|
||||
str += `${workflowName}.hooks.${hookName}(\n\tasync (({`
|
||||
|
||||
if (
|
||||
parameter.type?.type === "reference" &&
|
||||
parameter.type.reflection instanceof DeclarationReflection &&
|
||||
parameter.type.reflection.children
|
||||
) {
|
||||
parameter.type.reflection.children.forEach((childParam, index) => {
|
||||
if (index > 0) {
|
||||
str += `,`
|
||||
}
|
||||
|
||||
str += ` ${childParam.name}`
|
||||
})
|
||||
}
|
||||
|
||||
str += ` }, { container }) => {\n\t\t//TODO\n\t})\n)`
|
||||
|
||||
return str
|
||||
}
|
||||
}
|
||||
|
||||
+5
@@ -83,6 +83,7 @@ export type FormattingOptionType = {
|
||||
endSections?: string[]
|
||||
shouldIncrementAfterStartSections?: boolean
|
||||
hideTocHeaders?: boolean
|
||||
workflowDiagramComponent?: string
|
||||
}
|
||||
|
||||
export declare module "typedoc" {
|
||||
@@ -286,4 +287,8 @@ export declare type NamespaceGenerateDetails = {
|
||||
* namespace
|
||||
*/
|
||||
pathPattern: string
|
||||
/**
|
||||
* The namespace's children
|
||||
*/
|
||||
children?: NamespaceGenerateDetails[]
|
||||
}
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
import {
|
||||
DeclarationReflection,
|
||||
IntrinsicType,
|
||||
ParameterReflection,
|
||||
Reflection,
|
||||
} from "typedoc"
|
||||
|
||||
export function cleanUpHookInput(
|
||||
parameters: ParameterReflection[]
|
||||
): ParameterReflection[] {
|
||||
return parameters.map((parameter) => {
|
||||
if (parameter.type?.type !== "reference" || !parameter.type.reflection) {
|
||||
return parameter
|
||||
}
|
||||
|
||||
cleanUpReflectionType(parameter.type.reflection)
|
||||
|
||||
if (
|
||||
parameter.type.reflection &&
|
||||
parameter.type.reflection instanceof DeclarationReflection &&
|
||||
parameter.type.reflection.children
|
||||
) {
|
||||
parameter.type.reflection.children.forEach(cleanUpReflectionType)
|
||||
}
|
||||
|
||||
return parameter
|
||||
})
|
||||
}
|
||||
|
||||
function cleanUpReflectionType(reflection: Reflection): Reflection {
|
||||
if (
|
||||
!(reflection instanceof DeclarationReflection) &&
|
||||
!(reflection instanceof ParameterReflection)
|
||||
) {
|
||||
return reflection
|
||||
}
|
||||
if (
|
||||
reflection.type?.type === "reference" &&
|
||||
reflection.type.name === "WorkflowData" &&
|
||||
reflection.type.typeArguments?.length
|
||||
) {
|
||||
reflection.type = reflection.type.typeArguments[0]
|
||||
}
|
||||
|
||||
if (reflection.defaultValue) {
|
||||
delete reflection.defaultValue
|
||||
}
|
||||
|
||||
if (reflection.name === "additional_data") {
|
||||
reflection.type = new IntrinsicType("Record<string, unknown> | undefined")
|
||||
} else if (
|
||||
reflection.type?.type === "intersection" &&
|
||||
reflection.type.types.length >= 2
|
||||
) {
|
||||
reflection.type = reflection.type.types[1]
|
||||
}
|
||||
|
||||
if (reflection instanceof DeclarationReflection && reflection.children) {
|
||||
reflection.children.forEach(cleanUpReflectionType)
|
||||
}
|
||||
|
||||
return reflection
|
||||
}
|
||||
@@ -2,6 +2,7 @@ export * from "./dml-utils"
|
||||
export * from "./get-type-children"
|
||||
export * from "./get-project-child"
|
||||
export * from "./get-type-str"
|
||||
export * from "./hooks-util"
|
||||
export * from "./step-utils"
|
||||
export * from "./str-formatting"
|
||||
export * from "./str-utils"
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { ArrayType, SignatureReflection, SomeType, UnionType } from "typedoc"
|
||||
|
||||
const disallowedIntrinsicTypeNames = ["unknown", "void", "any", "never"]
|
||||
|
||||
export function isWorkflowStep(reflection: SignatureReflection): boolean {
|
||||
return (
|
||||
reflection.parent?.children?.some((child) => child.name === "__step__") ||
|
||||
@@ -10,11 +12,7 @@ export function isWorkflowStep(reflection: SignatureReflection): boolean {
|
||||
export function getStepInputType(
|
||||
reflection: SignatureReflection
|
||||
): SomeType | undefined {
|
||||
if (!isWorkflowStep(reflection)) {
|
||||
return
|
||||
}
|
||||
|
||||
if (!reflection.parameters?.length) {
|
||||
if (!isWorkflowStep(reflection) || !reflection.parameters?.length) {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -28,6 +26,13 @@ export function getStepOutputType(
|
||||
return
|
||||
}
|
||||
|
||||
if (
|
||||
reflection.type?.type === "intrinsic" &&
|
||||
disallowedIntrinsicTypeNames.includes(reflection.type.name)
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
if (reflection.type?.type !== "intersection") {
|
||||
return reflection.type
|
||||
}
|
||||
@@ -52,6 +57,12 @@ function cleanUpType(itemType: SomeType | undefined): SomeType | undefined {
|
||||
return cleanUpUnionType(itemType)
|
||||
case "array":
|
||||
return cleanUpArrayType(itemType)
|
||||
case "intrinsic":
|
||||
if (disallowedIntrinsicTypeNames.includes(itemType.name)) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
return itemType
|
||||
default:
|
||||
return itemType
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { SignatureReflection } from "typedoc"
|
||||
import { ReferenceType, SignatureReflection, SomeType } from "typedoc"
|
||||
|
||||
export function isWorkflow(reflection: SignatureReflection): boolean {
|
||||
return (
|
||||
@@ -6,3 +6,57 @@ export function isWorkflow(reflection: SignatureReflection): boolean {
|
||||
false
|
||||
)
|
||||
}
|
||||
|
||||
export function getWorkflowInputType(
|
||||
reflection: SignatureReflection
|
||||
): SomeType | undefined {
|
||||
const exportedWorkflowType = getExportedWorkflowType(reflection)
|
||||
|
||||
const inputType = exportedWorkflowType?.typeArguments![0]
|
||||
|
||||
return isAllowedType(inputType) ? inputType : undefined
|
||||
}
|
||||
|
||||
export function getWorkflowOutputType(
|
||||
reflection: SignatureReflection
|
||||
): SomeType | undefined {
|
||||
const exportedWorkflowType = getExportedWorkflowType(reflection)
|
||||
|
||||
const outputType = exportedWorkflowType?.typeArguments![1]
|
||||
|
||||
return isAllowedType(outputType) ? outputType : undefined
|
||||
}
|
||||
|
||||
function getExportedWorkflowType(
|
||||
reflection: SignatureReflection
|
||||
): ReferenceType | undefined {
|
||||
if (
|
||||
!isWorkflow(reflection) ||
|
||||
reflection.type?.type !== "intersection" ||
|
||||
reflection.type.types.length < 2
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
const exportedWorkflowType = reflection.type.types[1]
|
||||
|
||||
if (
|
||||
exportedWorkflowType.type !== "reference" ||
|
||||
exportedWorkflowType.name !== "ExportedWorkflow" ||
|
||||
(exportedWorkflowType.typeArguments?.length || 0) < 1
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
return exportedWorkflowType
|
||||
}
|
||||
|
||||
const disallowedIntrinsicTypeNames = ["unknown", "void", "any", "never"]
|
||||
|
||||
function isAllowedType(type: SomeType | undefined): boolean {
|
||||
return (
|
||||
type !== undefined &&
|
||||
(type.type !== "intrinsic" ||
|
||||
!disallowedIntrinsicTypeNames.includes(type.name))
|
||||
)
|
||||
}
|
||||
|
||||
@@ -7250,6 +7250,7 @@ __metadata:
|
||||
cpy-cli: ^5.0.0
|
||||
eslint: ^8
|
||||
eslint-config-docs: "*"
|
||||
framer-motion: ^11.3.21
|
||||
mermaid: ^10.9.0
|
||||
next: 14.1.0
|
||||
npm-to-yarn: ^2.1.0
|
||||
@@ -8606,6 +8607,26 @@ eslint-config-next@latest:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"framer-motion@npm:^11.3.21":
|
||||
version: 11.3.21
|
||||
resolution: "framer-motion@npm:11.3.21"
|
||||
dependencies:
|
||||
tslib: ^2.4.0
|
||||
peerDependencies:
|
||||
"@emotion/is-prop-valid": "*"
|
||||
react: ^18.0.0
|
||||
react-dom: ^18.0.0
|
||||
peerDependenciesMeta:
|
||||
"@emotion/is-prop-valid":
|
||||
optional: true
|
||||
react:
|
||||
optional: true
|
||||
react-dom:
|
||||
optional: true
|
||||
checksum: 47ea88207920800294159f20d89d217c0118cbe2f5b7c4f1204490a04ac379a60e4ba323bfb101f334155014ebf07b51ad70a4d67b0363043f9d1d00347b1b3b
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"fs-minipass@npm:^2.0.0":
|
||||
version: 2.1.0
|
||||
resolution: "fs-minipass@npm:2.1.0"
|
||||
|
||||
Reference in New Issue
Block a user