docs-util: infer resolved resources in workflow + steps (#10637)
This commit is contained in:
@@ -4,7 +4,7 @@ import { stringify } from "yaml"
|
||||
import { replaceTemplateVariables } from "../../utils/reflection-template-strings"
|
||||
import { Reflection } from "typedoc"
|
||||
import { FrontmatterData } from "types"
|
||||
import { getTagComments, getTagsAsArray } from "utils"
|
||||
import { getTagComments, getTagsAsArray, getUniqueStrArray } from "utils"
|
||||
|
||||
export default function (theme: MarkdownTheme) {
|
||||
Handlebars.registerHelper("frontmatter", function (this: Reflection) {
|
||||
@@ -29,6 +29,11 @@ export default function (theme: MarkdownTheme) {
|
||||
const tagContent = getTagsAsArray(tag)
|
||||
resolvedFrontmatter["tags"]?.push(...tagContent)
|
||||
})
|
||||
if (resolvedFrontmatter["tags"]?.length) {
|
||||
resolvedFrontmatter["tags"] = getUniqueStrArray(
|
||||
resolvedFrontmatter["tags"]
|
||||
)
|
||||
}
|
||||
|
||||
return `---\n${stringify(resolvedFrontmatter).trim()}\n---\n\n`
|
||||
})
|
||||
|
||||
@@ -14,13 +14,21 @@ import {
|
||||
} from "typedoc"
|
||||
import ts, { SyntaxKind, VariableStatement } from "typescript"
|
||||
import { WorkflowManager, WorkflowDefinition } from "@medusajs/orchestration"
|
||||
import Helper from "./utils/helper"
|
||||
import { findReflectionInNamespaces, isWorkflow, isWorkflowStep } from "utils"
|
||||
import Helper, { WORKFLOW_AS_STEP_SUFFIX } from "./utils/helper"
|
||||
import {
|
||||
findReflectionInNamespaces,
|
||||
isWorkflow,
|
||||
isWorkflowStep,
|
||||
addTagsToReflection,
|
||||
getResolvedResourcesOfStep,
|
||||
getUniqueStrArray,
|
||||
} from "utils"
|
||||
import { StepType } from "./types"
|
||||
|
||||
type ParsedStep = {
|
||||
stepReflection: DeclarationReflection
|
||||
stepType: StepType
|
||||
resources: string[]
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -30,10 +38,19 @@ type ParsedStep = {
|
||||
class WorkflowsPlugin {
|
||||
protected app: Application
|
||||
protected helper: Helper
|
||||
protected workflowsTagsMap: Map<string, string[]>
|
||||
protected addTagsAfterParsing: {
|
||||
[k: string]: {
|
||||
id: string
|
||||
workflowIds: string[]
|
||||
}
|
||||
}
|
||||
|
||||
constructor(app: Application) {
|
||||
this.app = app
|
||||
this.helper = new Helper()
|
||||
this.workflowsTagsMap = new Map()
|
||||
this.addTagsAfterParsing = {}
|
||||
|
||||
this.registerOptions()
|
||||
this.registerEventHandlers()
|
||||
@@ -110,6 +127,7 @@ class WorkflowsPlugin {
|
||||
constructorFn: initializer.arguments[1],
|
||||
context,
|
||||
parentReflection: reflection.parent,
|
||||
workflowReflection: reflection,
|
||||
})
|
||||
|
||||
if (!reflection.comment && reflection.parent.comment) {
|
||||
@@ -121,6 +139,8 @@ class WorkflowsPlugin {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.handleAddTagsAfterParsing(context)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -133,15 +153,18 @@ class WorkflowsPlugin {
|
||||
constructorFn,
|
||||
context,
|
||||
parentReflection,
|
||||
workflowReflection,
|
||||
}: {
|
||||
workflowId: string
|
||||
constructorFn: ts.ArrowFunction | ts.FunctionExpression
|
||||
context: Context
|
||||
parentReflection: DeclarationReflection
|
||||
workflowReflection: SignatureReflection
|
||||
}) {
|
||||
// use the workflow manager to check whether something in the constructor
|
||||
// body is a step/hook
|
||||
const workflow = WorkflowManager.getWorkflow(workflowId)
|
||||
const resources: string[] = []
|
||||
|
||||
if (!ts.isBlock(constructorFn.body)) {
|
||||
return
|
||||
@@ -165,19 +188,22 @@ class WorkflowsPlugin {
|
||||
)
|
||||
|
||||
if (initializerName === "when") {
|
||||
this.parseWhenStep({
|
||||
const { resources: whenResources } = this.parseWhenStep({
|
||||
initializer,
|
||||
parentReflection,
|
||||
context,
|
||||
workflow,
|
||||
stepDepth,
|
||||
workflowReflection,
|
||||
})
|
||||
resources.push(...whenResources)
|
||||
} else {
|
||||
const steps = this.parseSteps({
|
||||
initializer,
|
||||
context,
|
||||
workflow,
|
||||
workflowVarName: parentReflection.name,
|
||||
workflowReflection,
|
||||
})
|
||||
|
||||
if (!steps.length) {
|
||||
@@ -190,11 +216,15 @@ class WorkflowsPlugin {
|
||||
depth: stepDepth,
|
||||
parentReflection,
|
||||
})
|
||||
resources.push(...step.resources)
|
||||
})
|
||||
}
|
||||
|
||||
stepDepth++
|
||||
})
|
||||
|
||||
const uniqueResources = addTagsToReflection(parentReflection, resources)
|
||||
this.updateWorkflowsTagsMap(workflowId, uniqueResources)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -208,11 +238,13 @@ class WorkflowsPlugin {
|
||||
context,
|
||||
workflow,
|
||||
workflowVarName,
|
||||
workflowReflection,
|
||||
}: {
|
||||
initializer: ts.CallExpression
|
||||
context: Context
|
||||
workflow?: WorkflowDefinition
|
||||
workflowVarName: string
|
||||
workflowReflection: SignatureReflection
|
||||
}): ParsedStep[] {
|
||||
const steps: ParsedStep[] = []
|
||||
const initializerName = this.helper.normalizeName(
|
||||
@@ -235,6 +267,7 @@ class WorkflowsPlugin {
|
||||
context,
|
||||
workflow,
|
||||
workflowVarName,
|
||||
workflowReflection,
|
||||
})
|
||||
)
|
||||
})
|
||||
@@ -242,6 +275,7 @@ class WorkflowsPlugin {
|
||||
let stepId: string | undefined
|
||||
let stepReflection: DeclarationReflection | undefined
|
||||
let stepType = this.helper.getStepType(initializer)
|
||||
const resources: string[] = []
|
||||
|
||||
if (stepType === "hook" && "symbol" in initializer.arguments[1]) {
|
||||
// get the hook's name from the first argument
|
||||
@@ -281,6 +315,12 @@ class WorkflowsPlugin {
|
||||
"step",
|
||||
true
|
||||
)
|
||||
const stepResources = getResolvedResourcesOfStep(
|
||||
originalInitializer,
|
||||
stepId
|
||||
)
|
||||
|
||||
resources.push(...stepResources)
|
||||
stepType = this.helper.getStepType(originalInitializer)
|
||||
stepReflection = initializerReflection
|
||||
}
|
||||
@@ -295,7 +335,14 @@ class WorkflowsPlugin {
|
||||
steps.push({
|
||||
stepReflection,
|
||||
stepType,
|
||||
resources,
|
||||
})
|
||||
if (stepId?.endsWith(WORKFLOW_AS_STEP_SUFFIX)) {
|
||||
this.updateAddTagsAfterParsingMap(workflowReflection, {
|
||||
id: workflow.id,
|
||||
workflowId: stepId,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -313,13 +360,18 @@ class WorkflowsPlugin {
|
||||
context,
|
||||
workflow,
|
||||
stepDepth,
|
||||
workflowReflection,
|
||||
}: {
|
||||
initializer: ts.CallExpression
|
||||
parentReflection: DeclarationReflection
|
||||
context: Context
|
||||
workflow?: WorkflowDefinition
|
||||
stepDepth: number
|
||||
}) {
|
||||
workflowReflection: SignatureReflection
|
||||
}): {
|
||||
resources: string[]
|
||||
} {
|
||||
const resources: string[] = []
|
||||
const whenInitializer = (initializer.expression as ts.CallExpression)
|
||||
.expression as ts.CallExpression
|
||||
const thenInitializer = initializer
|
||||
@@ -332,7 +384,9 @@ class WorkflowsPlugin {
|
||||
(!ts.isFunctionExpression(thenInitializer.arguments[0]) &&
|
||||
!ts.isArrowFunction(thenInitializer.arguments[0]))
|
||||
) {
|
||||
return
|
||||
return {
|
||||
resources,
|
||||
}
|
||||
}
|
||||
|
||||
const whenCondition = whenInitializer.arguments[1].body.getText()
|
||||
@@ -378,18 +432,25 @@ class WorkflowsPlugin {
|
||||
context,
|
||||
workflow,
|
||||
workflowVarName: parentReflection.name,
|
||||
workflowReflection,
|
||||
}).forEach((step) => {
|
||||
this.createStepDocumentReflection({
|
||||
...step,
|
||||
depth: stepDepth,
|
||||
parentReflection: documentReflection,
|
||||
})
|
||||
|
||||
resources.push(...step.resources)
|
||||
})
|
||||
})
|
||||
|
||||
if (documentReflection.children?.length) {
|
||||
parentReflection.documents?.push(documentReflection)
|
||||
}
|
||||
|
||||
return {
|
||||
resources: getUniqueStrArray(resources),
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -473,6 +534,7 @@ class WorkflowsPlugin {
|
||||
createStepDocumentReflection({
|
||||
stepType,
|
||||
stepReflection,
|
||||
resources,
|
||||
depth,
|
||||
parentReflection,
|
||||
}: ParsedStep & {
|
||||
@@ -498,6 +560,7 @@ class WorkflowsPlugin {
|
||||
},
|
||||
])
|
||||
)
|
||||
addTagsToReflection(stepReflection, resources)
|
||||
|
||||
if (parentReflection.isDocument()) {
|
||||
parentReflection.addChild(documentReflection)
|
||||
@@ -605,6 +668,71 @@ class WorkflowsPlugin {
|
||||
|
||||
return initializer
|
||||
}
|
||||
|
||||
updateAddTagsAfterParsingMap(
|
||||
reflection: SignatureReflection,
|
||||
{
|
||||
id,
|
||||
workflowId,
|
||||
}: {
|
||||
id: string
|
||||
workflowId: string
|
||||
}
|
||||
) {
|
||||
const existingItem = this.addTagsAfterParsing[`${reflection.id}`] || {
|
||||
id,
|
||||
workflowIds: [],
|
||||
}
|
||||
existingItem.workflowIds.push(
|
||||
workflowId.replace(WORKFLOW_AS_STEP_SUFFIX, "")
|
||||
)
|
||||
this.addTagsAfterParsing[`${reflection.id}`] = existingItem
|
||||
}
|
||||
|
||||
updateWorkflowsTagsMap(workflowId: string, tags: string[]) {
|
||||
const existingItems = this.workflowsTagsMap.get(workflowId) || []
|
||||
existingItems.push(...tags)
|
||||
this.workflowsTagsMap.set(workflowId, existingItems)
|
||||
}
|
||||
|
||||
handleAddTagsAfterParsing(context: Context) {
|
||||
let keys = Object.keys(this.addTagsAfterParsing)
|
||||
|
||||
const handleForWorkflow = (
|
||||
key: string,
|
||||
{
|
||||
id,
|
||||
workflowIds,
|
||||
}: {
|
||||
id: string
|
||||
workflowIds: string[]
|
||||
}
|
||||
) => {
|
||||
const resources: string[] = []
|
||||
workflowIds.forEach((workflowId) => {
|
||||
// check if it exists in keys
|
||||
const existingKey = keys.find(
|
||||
(k) => this.addTagsAfterParsing[k].id === workflowId
|
||||
)
|
||||
if (existingKey) {
|
||||
handleForWorkflow(existingKey, this.addTagsAfterParsing[existingKey])
|
||||
}
|
||||
resources.push(...(this.workflowsTagsMap.get(workflowId) || []))
|
||||
})
|
||||
|
||||
const reflection = context.project.getReflectionById(parseInt(key))
|
||||
if (reflection) {
|
||||
const uniqueTags = addTagsToReflection(reflection, resources)
|
||||
this.updateWorkflowsTagsMap(id, uniqueTags)
|
||||
}
|
||||
delete this.addTagsAfterParsing[key]
|
||||
keys = Object.keys(this.addTagsAfterParsing)
|
||||
}
|
||||
|
||||
do {
|
||||
handleForWorkflow(keys[0], this.addTagsAfterParsing[keys[0]])
|
||||
} while (keys.length > 0)
|
||||
}
|
||||
}
|
||||
|
||||
export default WorkflowsPlugin
|
||||
|
||||
@@ -7,6 +7,8 @@ import ts from "typescript"
|
||||
import { StepModifier, StepType } from "../types"
|
||||
import { capitalize, findReflectionInNamespaces } from "utils"
|
||||
|
||||
export const WORKFLOW_AS_STEP_SUFFIX = `-as-step`
|
||||
|
||||
/**
|
||||
* A class of helper methods.
|
||||
*/
|
||||
@@ -126,7 +128,7 @@ export default class Helper {
|
||||
stepId = this._getStepOrWorkflowIdFromArrowFunction(initializer, type)
|
||||
}
|
||||
|
||||
return isWorkflowStep ? `${stepId}-as-step` : stepId
|
||||
return isWorkflowStep ? `${stepId}${WORKFLOW_AS_STEP_SUFFIX}` : stepId
|
||||
}
|
||||
|
||||
private _getStepOrWorkflowIdFromArrowFunction(
|
||||
|
||||
136
www/utils/packages/utils/src/get-resolved-resources.ts
Normal file
136
www/utils/packages/utils/src/get-resolved-resources.ts
Normal file
@@ -0,0 +1,136 @@
|
||||
import ts from "typescript"
|
||||
import { getUniqueStrArray } from "./str-utils"
|
||||
import { camelToWords } from "./str-formatting"
|
||||
|
||||
const RESOLVE_EXPRESSIONS = [`container.resolve`, `req.scope.resolve`]
|
||||
|
||||
export const getResolvedResources = (
|
||||
functionExpression: ts.ArrowFunction | ts.FunctionDeclaration
|
||||
): string[] => {
|
||||
const resources: string[] = []
|
||||
|
||||
if (!functionExpression.body) {
|
||||
return resources
|
||||
}
|
||||
|
||||
const body = ts.isBlock(functionExpression.body)
|
||||
? functionExpression.body
|
||||
: getBlockFromNode(functionExpression.body)
|
||||
|
||||
if (!body) {
|
||||
return resources
|
||||
}
|
||||
|
||||
body.statements.forEach((statement) => {
|
||||
if (!ts.isVariableStatement(statement)) {
|
||||
return
|
||||
}
|
||||
|
||||
statement.declarationList.declarations.forEach((declaration) => {
|
||||
if (
|
||||
!declaration.initializer ||
|
||||
!ts.isCallExpression(declaration.initializer) ||
|
||||
!declaration.initializer.arguments.length ||
|
||||
!("name" in declaration.initializer.arguments[0])
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
const initializerText = declaration.initializer.getText()
|
||||
const isContainerExpression = RESOLVE_EXPRESSIONS.some((exp) =>
|
||||
initializerText.startsWith(exp)
|
||||
)
|
||||
|
||||
if (!isContainerExpression) {
|
||||
return
|
||||
}
|
||||
|
||||
const resourceName = normalizeResolvedResourceName(
|
||||
declaration.initializer.arguments[0]
|
||||
)
|
||||
if (!resourceName.length) {
|
||||
return
|
||||
}
|
||||
|
||||
resources.push(resourceName)
|
||||
})
|
||||
})
|
||||
|
||||
return resources
|
||||
}
|
||||
|
||||
export const getResolvedResourcesOfStep = (
|
||||
expression: ts.CallExpression,
|
||||
stepId?: string
|
||||
): string[] => {
|
||||
if (
|
||||
!expression.arguments ||
|
||||
expression.arguments.length < 2 ||
|
||||
(!ts.isArrowFunction(expression.arguments[1]) &&
|
||||
!ts.isFunctionDeclaration(expression.arguments[1]))
|
||||
) {
|
||||
return stepId ? getResolvedResourcesByStepId(stepId) : []
|
||||
}
|
||||
const stepFunction: ts.ArrowFunction | ts.FunctionDeclaration =
|
||||
expression.arguments[1]
|
||||
|
||||
let resources = getResolvedResources(stepFunction)
|
||||
|
||||
if (
|
||||
expression.arguments.length === 3 &&
|
||||
(ts.isArrowFunction(expression.arguments[2]) ||
|
||||
ts.isFunctionDeclaration(expression.arguments[2]))
|
||||
) {
|
||||
// get resolved resources of compensation function
|
||||
resources.push(...getResolvedResources(expression.arguments[2]))
|
||||
|
||||
// make resources unique
|
||||
resources = getUniqueStrArray(resources)
|
||||
}
|
||||
|
||||
if (!resources.length && stepId) {
|
||||
return getResolvedResourcesByStepId(stepId)
|
||||
}
|
||||
|
||||
return resources
|
||||
}
|
||||
|
||||
const normalizeResolvedResourceName = (expression: ts.Expression): string => {
|
||||
let name = ""
|
||||
switch (true) {
|
||||
case ts.isPropertyAccessExpression(expression):
|
||||
name = expression.name.getText()
|
||||
break
|
||||
case ts.isStringLiteral(expression):
|
||||
name = camelToWords(expression.getText())
|
||||
}
|
||||
return name.toLowerCase().replaceAll("_", " ")
|
||||
}
|
||||
|
||||
const getBlockFromNode = (node: ts.Node): ts.Block | undefined => {
|
||||
if ("body" in node) {
|
||||
if (ts.isBlock(node.body as ts.Node)) {
|
||||
return node.body as ts.Block
|
||||
}
|
||||
return getBlockFromNode(node.body as ts.Node)
|
||||
}
|
||||
|
||||
if ("expression" in node) {
|
||||
return getBlockFromNode(node.expression as ts.Node)
|
||||
}
|
||||
|
||||
return undefined
|
||||
}
|
||||
|
||||
/**
|
||||
* Some steps like useQueryGraphStep are not possible
|
||||
* to detect due to their implementation. For those,
|
||||
* we have static resolutions
|
||||
*/
|
||||
const STEPS_RESOLVED_RESOURCES: Record<string, string[]> = {
|
||||
"use-query-graph-step": ["query"],
|
||||
}
|
||||
|
||||
export const getResolvedResourcesByStepId = (stepId: string): string[] => {
|
||||
return STEPS_RESOLVED_RESOURCES[stepId] || []
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
export * from "./dml-utils"
|
||||
export * from "./get-type-children"
|
||||
export * from "./get-project-child"
|
||||
export * from "./get-resolved-resources"
|
||||
export * from "./get-type-str"
|
||||
export * from "./hooks-util"
|
||||
export * from "./step-utils"
|
||||
|
||||
@@ -21,3 +21,7 @@ export function stripLineBreaks(str: string) {
|
||||
.trim()
|
||||
: ""
|
||||
}
|
||||
|
||||
export function getUniqueStrArray(str: string[]): string[] {
|
||||
return Array.from(new Set(str))
|
||||
}
|
||||
|
||||
@@ -1,11 +1,17 @@
|
||||
import { CommentTag, DeclarationReflection, Reflection } from "typedoc"
|
||||
import { Comment, CommentTag, DeclarationReflection, Reflection } from "typedoc"
|
||||
import { getUniqueStrArray } from "./str-utils"
|
||||
|
||||
export const getTagsAsArray = (tag: CommentTag): string[] => {
|
||||
return tag.content
|
||||
export const getTagsAsArray = (
|
||||
tag: CommentTag,
|
||||
makeUnique = true
|
||||
): string[] => {
|
||||
const tags = tag.content
|
||||
.map((content) => content.text)
|
||||
.join("")
|
||||
.split(",")
|
||||
.map((value) => value.trim())
|
||||
|
||||
return makeUnique ? getUniqueStrArray(tags) : tags
|
||||
}
|
||||
|
||||
export const getTagComments = (reflection: Reflection): CommentTag[] => {
|
||||
@@ -23,3 +29,42 @@ export const getTagComments = (reflection: Reflection): CommentTag[] => {
|
||||
|
||||
return tagComments
|
||||
}
|
||||
|
||||
export const getTagsAsValue = (tags: string[]): string => {
|
||||
return tags.join(",")
|
||||
}
|
||||
|
||||
export const addTagsToReflection = (
|
||||
reflection: Reflection,
|
||||
tags: string[]
|
||||
): string[] => {
|
||||
let tempTags = [...tags]
|
||||
// check if reflection has an existing tag
|
||||
const existingTag = reflection.comment?.blockTags.find(
|
||||
(tag) => tag.tag === `@tags`
|
||||
)
|
||||
if (existingTag) {
|
||||
tempTags.push(...getTagsAsArray(existingTag))
|
||||
}
|
||||
|
||||
if (!tags.length) {
|
||||
return tempTags
|
||||
}
|
||||
|
||||
// make tags unique
|
||||
tempTags = getUniqueStrArray(tempTags)
|
||||
|
||||
if (!reflection.comment) {
|
||||
reflection.comment = new Comment()
|
||||
}
|
||||
reflection.comment.blockTags.push(
|
||||
new CommentTag(`@tags`, [
|
||||
{
|
||||
kind: "text",
|
||||
text: getTagsAsValue(tempTags),
|
||||
},
|
||||
])
|
||||
)
|
||||
|
||||
return tempTags
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user