diff --git a/.eslintignore b/.eslintignore index 2af245f62d..f7f026ec94 100644 --- a/.eslintignore +++ b/.eslintignore @@ -24,11 +24,12 @@ packages/* !packages/orchestration !packages/workflows-sdk !packages/core-flows +!packages/types +!packages/medusa-react !packages/workflow-engine-redis !packages/workflow-engine-inmemory - **/models/* **/scripts/* **/dist/* diff --git a/.eslintrc.js b/.eslintrc.js index caafd0d9f6..c34c47f13a 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -99,6 +99,7 @@ module.exports = { "./packages/orchestration/tsconfig.json", "./packages/workflows-sdk/tsconfig.spec.json", "./packages/core-flows/tsconfig.spec.json", + "./packages/types/tsconfig.json", "./packages/workflow-engine-redis/tsconfig.spec.json", "./packages/workflow-engine-inmemory/tsconfig.spec.json", ], diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 75df0f9354..7585200f4e 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -38,3 +38,43 @@ jobs: uses: changesets/action@v1 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + tsdoc-pr: + name: Generated TSDoc PRs + runs-on: ubuntu-latest + steps: + - name: Checkout Repo + uses: actions/checkout@v3 + + - name: Setup Node.js 16 + uses: actions/setup-node@v3 + with: + node-version: 16 + + - name: Install Dependencies + run: yarn + + - name: Build packages + run: yarn build + + - name: Install docs-util Dependencies + run: yarn + working-directory: docs-util + + - name: Build packages + run: yarn build + working-directory: docs-util + + - name: Run docblock generator + run: "yarn start run:commit ${{ github.sha }}" + working-directory: docs-util/packages/docblock-generator + + - name: Create Pull Request + uses: peter-evans/create-pull-request@v5 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + title: "Generated TSDocs" + body: "This PR holds all generated TSDocs for the upcoming release." + branch: "chore/generate-tsdocs" + team-reviewers: "@medusajs/docs" + add-paths: packages/** \ No newline at end of file diff --git a/docs-util/packages/docblock-generator/.env.sample b/docs-util/packages/docblock-generator/.env.sample new file mode 100644 index 0000000000..dcbe497377 --- /dev/null +++ b/docs-util/packages/docblock-generator/.env.sample @@ -0,0 +1 @@ +MONOREPO_ROOT_PATH=/Users/medusa/medusa \ No newline at end of file diff --git a/docs-util/packages/docblock-generator/README.md b/docs-util/packages/docblock-generator/README.md new file mode 100644 index 0000000000..b2eb9d9e5c --- /dev/null +++ b/docs-util/packages/docblock-generator/README.md @@ -0,0 +1,38 @@ +# docblock-generator + +A CLI tool that can be used to generate TSDoc docblocks for TypeScript/JavaScript files under the `packages` directory of the main monorepo. + +## Prerequisites + +1. Run the `yarn` command to install dependencies. +2. Copy the `.env.sample` to `.env` and change the `MONOREPO_ROOT_PATH` variable to the absolute path to the monorepo root. + +--- + +## Usage + +### Generate for a specific file + +Run the following command to run the tool for a specific file: + +```bash +yarn start run /absolute/path/to/file.ts +``` + +### Generate for git-changed files + +Run the following command to run the tool for applicable git file changes: + +```bash +yarn start run:changes +``` + +### Generate for a specific commit + +Run the following command to run the tool for a commit SHA hash: + +```bash +yarn start run:commit +``` + +Where `` is the SHA of the commit. For example, `e28fa7fbdf45c5b1fa19848db731132a0bf1757d`. diff --git a/docs-util/packages/docblock-generator/package.json b/docs-util/packages/docblock-generator/package.json new file mode 100644 index 0000000000..2bec3ace9e --- /dev/null +++ b/docs-util/packages/docblock-generator/package.json @@ -0,0 +1,31 @@ +{ + "name": "docblock-generator", + "license": "MIT", + "scripts": { + "start": "ts-node src/index.ts", + "build": "tsc", + "watch": "tsc --watch", + "prepublishOnly": "cross-env NODE_ENV=production tsc --build" + }, + "publishConfig": { + "access": "public" + }, + "version": "0.0.0", + "type": "module", + "exports": "./dist/index.js", + "bin": { + "workflow-diagrams-generator": "dist/index.js" + }, + "dependencies": { + "@octokit/core": "^5.0.2", + "commander": "^11.1.0", + "dotenv": "^16.3.1", + "eslint": "^8.56.0", + "minimatch": "^9.0.3", + "ts-node": "^10.9.1", + "typescript": "5.2" + }, + "devDependencies": { + "@types/node": "^20.9.4" + } +} diff --git a/docs-util/packages/docblock-generator/src/classes/docblock-generator.ts b/docs-util/packages/docblock-generator/src/classes/docblock-generator.ts new file mode 100644 index 0000000000..4214267c82 --- /dev/null +++ b/docs-util/packages/docblock-generator/src/classes/docblock-generator.ts @@ -0,0 +1,145 @@ +/* eslint-disable no-case-declarations */ +import ts from "typescript" +import Formatter from "./formatter.js" +import KindsRegistry from "./kinds/registry.js" +import nodeHasComments from "../utils/node-has-comments.js" + +export type Options = { + paths: string[] + dryRun?: boolean +} + +/** + * A class used to generate docblock for one or multiple file paths. + */ +class DocblockGenerator { + protected options: Options + protected program?: ts.Program + protected checker?: ts.TypeChecker + protected formatter: Formatter + protected kindsRegistry?: KindsRegistry + + constructor(options: Options) { + this.options = options + this.formatter = new Formatter() + } + + /** + * Generate the docblock for the paths specified in the {@link options} class property. + */ + async run() { + this.program = ts.createProgram(this.options.paths, {}) + + this.checker = this.program.getTypeChecker() + + this.kindsRegistry = new KindsRegistry(this.checker) + + const printer = ts.createPrinter({ + removeComments: false, + }) + + await Promise.all( + this.program.getSourceFiles().map(async (file) => { + // Ignore .d.ts files + if (file.isDeclarationFile || !this.isFileIncluded(file.fileName)) { + return + } + + console.log(`Generating for ${file.fileName}...`) + + let fileContent = file.getFullText() + let fileComments: string = "" + + const documentChild = (node: ts.Node, topLevel = false) => { + const isSourceFile = ts.isSourceFile(node) + const origNodeText = node.getFullText().trim() + const nodeKindGenerator = this.kindsRegistry?.getKindGenerator(node) + let docComment: string | undefined + + if (nodeKindGenerator && this.canDocumentNode(node)) { + docComment = nodeKindGenerator.getDocBlock(node) + if (docComment.length) { + if (isSourceFile) { + fileComments = docComment + } else { + ts.addSyntheticLeadingComment( + node, + ts.SyntaxKind.MultiLineCommentTrivia, + docComment, + true + ) + } + } + } + + ts.forEachChild(node, (childNode) => + documentChild(childNode, isSourceFile) + ) + + if (!isSourceFile && topLevel) { + const newNodeText = printer.printNode( + ts.EmitHint.Unspecified, + node, + file + ) + + if (newNodeText !== origNodeText) { + fileContent = fileContent.replace(origNodeText, newNodeText) + } + } + } + + documentChild(file, true) + + if (!this.options.dryRun) { + ts.sys.writeFile( + file.fileName, + this.formatter.addCommentsToSourceFile( + fileComments, + await this.formatter.formatStr(fileContent, file.fileName) + ) + ) + } + + console.log(`Finished generating docblock for ${file.fileName}.`) + }) + ) + + this.reset() + } + + /** + * Checks whether a file is included in the specified files. + * + * @param {string} fileName - The file to check for. + * @returns {boolean} Whether the file can have docblocks generated for it. + */ + isFileIncluded(fileName: string): boolean { + return this.options.paths.some((path) => path.includes(fileName)) + } + + /** + * Checks whether a node can be documented. + * + * @privateRemark + * I'm leaving this method in case other conditions arise for a node to be documented. + * Otherwise, we can directly use the {@link nodeHasComments} function. + * + * @param {ts.Node} node - The node to check for. + * @returns {boolean} Whether the node can be documented. + */ + canDocumentNode(node: ts.Node): boolean { + // check if node already has docblock + return !nodeHasComments(node) + } + + /** + * Reset the generator's properties for new usage. + */ + reset() { + this.program = undefined + this.checker = undefined + } +} + +export default DocblockGenerator diff --git a/docs-util/packages/docblock-generator/src/classes/formatter.ts b/docs-util/packages/docblock-generator/src/classes/formatter.ts new file mode 100644 index 0000000000..eededa70c2 --- /dev/null +++ b/docs-util/packages/docblock-generator/src/classes/formatter.ts @@ -0,0 +1,238 @@ +import getMonorepoRoot from "../utils/get-monorepo-root.js" +import { ESLint, Linter } from "eslint" +import path from "path" +import dirname from "../utils/dirname.js" +import { minimatch } from "minimatch" +import { existsSync } from "fs" +import getRelativePaths from "../utils/get-relative-paths.js" + +/** + * A class used to apply formatting to files using ESLint and other formatting options. + */ +class Formatter { + protected cwd: string + protected eslintConfig?: Linter.Config + protected generalESLintConfig?: Linter.ConfigOverride + protected configForFile: Map< + string, + Linter.ConfigOverride + > + + constructor() { + this.cwd = getMonorepoRoot() + this.configForFile = new Map() + } + + /** + * Adds new lines before and after a comment if it's preceeded/followed immediately by a word (not by an empty line). + * + * @param {string} content - The content to format. + * @returns {string} The returned formatted content. + */ + normalizeCommentNewLine(content: string): string { + return content + .replaceAll(/(.)\n(\s*)\/\*\*/g, "$1\n\n$2/**") + .replaceAll(/\*\/\s*(.)/g, "*/\n$1") + } + + /** + * Normalizes an ESLint overrides configuration object. If a file name is specified, the configuration are normalized to + * include the `tsconfig` related to the file. If a file name isn't specified, the tsconfig file path names + * in the `parserConfig.project` array are normalized to have a full relative path (as that is required by ESLint). + * + * @param {Linter.ConfigOverride} config - The original configuration object. + * @param {string} fileName - The file name that + * @returns {Linter.ConfigOverride} The normalized and cloned configuration object. + */ + normalizeOverridesConfigObject( + config: Linter.ConfigOverride, + fileName?: string + ): Linter.ConfigOverride { + // clone config + const newConfig = structuredClone(config) + if (!newConfig.parserOptions) { + return newConfig + } + + if (fileName) { + const packagePattern = /^(?.*\/packages\/[^/]*).*$/ + // try to manually set the project of the parser options + const matchFilePackage = packagePattern.exec(fileName) + + if (matchFilePackage?.groups?.packagePath) { + const tsConfigPath = path.join( + matchFilePackage.groups.packagePath, + "tsconfig.json" + ) + const tsConfigSpecPath = path.join( + matchFilePackage.groups.packagePath, + "tsconfig.spec.json" + ) + + newConfig.parserOptions.project = [ + existsSync(tsConfigSpecPath) + ? tsConfigSpecPath + : existsSync(tsConfigPath) + ? tsConfigPath + : [ + ...getRelativePaths( + newConfig.parserOptions.project || [], + this.cwd + ), + ], + ] + } + } else if (newConfig.parserOptions.project?.length) { + // fix parser projects paths to be relative to this script + newConfig.parserOptions.project = getRelativePaths( + newConfig.parserOptions.project as string[], + this.cwd + ) + } + + return newConfig + } + + /** + * Retrieves the general ESLint configuration and sets it to the `eslintConfig` class property, if it's not already set. + * It also tries to set the `generalESLintConfig` class property to the override configuration in the `eslintConfig` + * whose `files` array includes `*.ts`. + */ + async getESLintConfig() { + if (this.eslintConfig) { + return + } + + this.eslintConfig = ( + await import( + path.relative(dirname(), path.join(this.cwd, ".eslintrc.js")) + ) + ).default as Linter.Config + + this.generalESLintConfig = this.eslintConfig!.overrides?.find((item) => + item.files.includes("*.ts") + ) + + if (this.generalESLintConfig) { + this.generalESLintConfig = this.normalizeOverridesConfigObject( + this.generalESLintConfig + ) + } + } + + /** + * Retrieves the normalized ESLint overrides configuration for a specific file. + * + * @param {string} filePath - The file's path. + * @returns {Promise | undefined>} The normalized configuration object or `undefined` if not found. + */ + async getESLintOverridesConfigForFile( + filePath: string + ): Promise | undefined> { + await this.getESLintConfig() + + if (this.configForFile.has(filePath)) { + return this.configForFile.get(filePath)! + } + + let relevantConfig = this.eslintConfig!.overrides?.find((item) => { + if (typeof item.files === "string") { + return minimatch(filePath, item.files) + } + + return item.files.some((file) => minimatch(filePath, file)) + }) + + if (!relevantConfig && !this.generalESLintConfig) { + return undefined + } + + relevantConfig = this.normalizeOverridesConfigObject( + structuredClone(relevantConfig || this.generalESLintConfig!), + filePath + ) + + relevantConfig!.files = [path.relative(this.cwd, filePath)] + + this.configForFile.set(filePath, relevantConfig) + + return relevantConfig + } + + /** + * Formats a string with ESLint. + * + * @param {string} content - The content to format. + * @param {string} fileName - The path to the file that the content belongs to. + * @returns {Promise} The formatted content. + */ + async formatStrWithEslint( + content: string, + fileName: string + ): Promise { + const relevantConfig = await this.getESLintOverridesConfigForFile(fileName) + + const eslint = new ESLint({ + overrideConfig: { + ...this.eslintConfig, + overrides: relevantConfig ? [relevantConfig] : undefined, + }, + cwd: this.cwd, + resolvePluginsRelativeTo: this.cwd, + fix: true, + ignore: false, + }) + + let newContent = content + const result = await eslint.lintText(content, { + filePath: fileName, + }) + + if (result.length) { + newContent = result[0].output || newContent + } + + return newContent + } + + /** + * Applies all formatting types to a string. + * + * @param {string} content - The content to format. + * @param {string} fileName - The path to the file that holds the content. + * @returns {Promise} The formatted content. + */ + async formatStr(content: string, fileName: string): Promise { + const newContent = await this.formatStrWithEslint(content, fileName) + + let normalizedContent = this.normalizeCommentNewLine(newContent) + + if (normalizedContent !== newContent) { + /** + * Since adding the new lines after comments as done in {@link normalizeCommentNewLine} method may lead to linting errors, + * we have to rerun the {@link formatStrWithEslint}. It's not possible to run {@link normalizeCommentNewLine} the first time + * and provide the expected result. + */ + normalizedContent = await this.formatStrWithEslint( + normalizedContent, + fileName + ) + } + + return normalizedContent + } + + /** + * Adds comments of a source file to the top of the file's content. It should have additional extra line after the comment. + * If the comment's length is 0, the `content` is returned as is. + * + * @param {string} comment - The comments of the source file. + * @param {string} content - The source file's comments. + * @returns {string} The full content with the comments. + */ + addCommentsToSourceFile(comment: string, content: string): string { + return comment.length ? `/**\n ${comment}*/\n\n${content}` : content + } +} + +export default Formatter diff --git a/docs-util/packages/docblock-generator/src/classes/kinds/default.ts b/docs-util/packages/docblock-generator/src/classes/kinds/default.ts new file mode 100644 index 0000000000..7cd16cbf40 --- /dev/null +++ b/docs-util/packages/docblock-generator/src/classes/kinds/default.ts @@ -0,0 +1,503 @@ +import ts from "typescript" +import { + DOCBLOCK_START, + DOCBLOCK_END_LINE, + DOCBLOCK_DOUBLE_LINES, + DOCBLOCK_NEW_LINE, +} from "../../constants.js" +import getSymbol from "../../utils/get-symbol.js" +import KnowledgeBaseFactory, { + RetrieveOptions, +} from "../knowledge-base-factory.js" +import { + getCustomNamespaceTag, + shouldHaveCustomNamespace, +} from "../../utils/medusa-react-utils.js" +import { + camelToWords, + capitalize, + normalizeName, +} from "../../utils/str-formatting.js" + +export type GeneratorOptions = { + checker: ts.TypeChecker + kinds?: ts.SyntaxKind[] +} + +export type GetDocBlockOptions = { + addEnd?: boolean + summaryPrefix?: string +} + +type CommonDocsOptions = { + addDefaultSummary?: boolean + prefixWithLineBreaks?: boolean +} + +/** + * Class used to generate docblocks for basic kinds. It can be + * extended for kinds requiring more elaborate TSDocs. + */ +class DefaultKindGenerator { + static DEFAULT_ALLOWED_NODE_KINDS = [ + ts.SyntaxKind.SourceFile, + ts.SyntaxKind.ClassDeclaration, + ts.SyntaxKind.EnumDeclaration, + ts.SyntaxKind.EnumMember, + ts.SyntaxKind.ModuleDeclaration, + ts.SyntaxKind.PropertyDeclaration, + ts.SyntaxKind.InterfaceDeclaration, + ts.SyntaxKind.TypeAliasDeclaration, + ts.SyntaxKind.PropertySignature, + ] + protected allowedKinds: ts.SyntaxKind[] + protected checker: ts.TypeChecker + protected defaultSummary = "{summary}" + protected knowledgeBaseFactory: KnowledgeBaseFactory + + constructor({ checker, kinds }: GeneratorOptions) { + this.allowedKinds = kinds || DefaultKindGenerator.DEFAULT_ALLOWED_NODE_KINDS + this.checker = checker + this.knowledgeBaseFactory = new KnowledgeBaseFactory() + } + + /** + * @returns the kinds that are handled by this generator. + */ + getAllowedKinds(): ts.SyntaxKind[] { + return this.allowedKinds + } + + /** + * Check whether this generator can be used for a node based on the node's kind. + * + * @param {ts.Node} node - The node to check for. + * @returns {boolean} Whether this generator can be used with the specified node. + */ + isAllowed(node: ts.Node): node is T { + return this.allowedKinds.includes(node.kind) + } + + /** + * Retrieve the doc block for the passed node. + * + * @param {T | ts.Node} node - The node to retrieve the docblock for. + * @param {GetDocBlockOptions} options - Options useful for children classes of this class to specify the formatting of the docblock. + * @returns {string} The node's docblock. + */ + // eslint-disable-next-line @typescript-eslint/no-unused-vars + getDocBlock( + node: T | ts.Node, + options: GetDocBlockOptions = { addEnd: true } + ): string { + let str = DOCBLOCK_START + const summary = this.getNodeSummary({ node }) + + switch (node.kind) { + case ts.SyntaxKind.EnumDeclaration: + str += `@enum${DOCBLOCK_DOUBLE_LINES}${summary}` + break + case ts.SyntaxKind.TypeAliasDeclaration: + str += `@interface${DOCBLOCK_DOUBLE_LINES}${summary}` + break + default: + str += summary + } + + str += this.getCommonDocs(node, { + prefixWithLineBreaks: true, + }) + + return `${str}${options.addEnd ? DOCBLOCK_END_LINE : ""}` + } + + /** + * Retrieves the summary comment of a node. It gives precedense to the node's symbol if it's provided/retrieved and if it's available using the {@link getSymbolDocBlock}. + * Otherwise, it retrieves the comments of the type using the {@link getTypeDocBlock} + * @returns {string} The summary comment. + */ + getNodeSummary({ + node, + symbol, + nodeType, + }: { + /** + * The node to retrieve the summary comment for. + */ + node: T | ts.Node + /** + * Optionally provide the node's symbol. If not provided, the + * method will try to retrieve it. + */ + symbol?: ts.Symbol + /** + * Optionally provide the node's type. If not provided, the method + * will try to retrieve it. + */ + nodeType?: ts.Type + }): string { + const knowledgeBaseOptions = this.getKnowledgeOptions(node) + if (!nodeType) { + nodeType = + "type" in node && node.type && ts.isTypeNode(node.type as ts.Node) + ? this.checker.getTypeFromTypeNode(node.type as ts.TypeNode) + : symbol + ? this.checker.getTypeOfSymbolAtLocation(symbol, node) + : this.checker.getTypeAtLocation(node) + } + + if (!symbol) { + symbol = getSymbol(node, this.checker) + } + + let summary = "" + + if (symbol) { + summary = this.getSymbolDocBlock(symbol, knowledgeBaseOptions) + } + + if (!summary.length) { + summary = this.getTypeDocBlock(nodeType, knowledgeBaseOptions) + } + + return summary.length > 0 ? summary : this.defaultSummary + } + + /** + * Retrieves the summary comment of a type. It tries to retrieve from the alias symbol, type arguments, or {@link KnowledgeBaseFactory}. + * If no summary comments are found, the {@link defaultSummary} is used. + * + * @param {ts.Type} nodeType - The type of a node. + * @returns {string} The summary comment. + */ + private getTypeDocBlock( + nodeType: ts.Type, + knowledgeBaseOptions?: Partial + ): string { + if (nodeType.aliasSymbol || nodeType.symbol) { + const symbolDoc = this.getSymbolDocBlock( + nodeType.aliasSymbol || nodeType.symbol + ) + + if (symbolDoc.length) { + return symbolDoc + } + } + + const typeArguments = this.checker.getTypeArguments( + nodeType as ts.TypeReference + ) + + if (typeArguments.length) { + // take only the first type argument to account + const typeArgumentDoc = this.getTypeDocBlock(typeArguments[0]) + + if (!typeArgumentDoc.length) { + const tryKnowledgeSummary = this.knowledgeBaseFactory.tryToGetSummary({ + ...knowledgeBaseOptions, + str: this.checker.typeToString(nodeType), + }) + + if (tryKnowledgeSummary?.length) { + return tryKnowledgeSummary + } + } + + if (!this.checker.isArrayType(nodeType)) { + return typeArgumentDoc + } + + // do some formatting if the encapsulating type is an array + return `The list of ${capitalize(typeArgumentDoc) || this.defaultSummary}` + } + + return ( + this.knowledgeBaseFactory.tryToGetSummary({ + ...knowledgeBaseOptions, + str: this.checker.typeToString(nodeType), + }) || "" + ) + } + + /** + * Retrieves the docblock of a symbol. It tries to retrieve it using the symbol's `getDocumentationComment` and `getJsDocTags` + * methods. If both methods don't return any comments, it tries to get the comments from the {@link KnowledgeBaseFactory}. + * + * @param {ts.Symbol} symbol - The symbol to retrieve its docblock. + * @returns {string} The symbol's docblock. + */ + private getSymbolDocBlock( + symbol: ts.Symbol, + knowledgeBaseOptions?: Partial + ): string { + const commentDisplayParts = symbol.getDocumentationComment(this.checker) + if (!commentDisplayParts.length) { + // try to get description from the first JSDoc comment + const jsdocComments = symbol.getJsDocTags(this.checker) + + if (jsdocComments.length) { + jsdocComments + .find((tag) => (tag.text?.length || 0) > 0) + ?.text!.forEach((tagText) => { + commentDisplayParts.push(tagText) + }) + } + } + + if (!commentDisplayParts.length) { + return ( + this.knowledgeBaseFactory.tryToGetSummary({ + ...knowledgeBaseOptions, + str: this.checker.typeToString(this.checker.getTypeOfSymbol(symbol)), + }) || + this.knowledgeBaseFactory.tryToGetSummary({ + ...knowledgeBaseOptions, + str: symbol.name, + }) || + "" + ) + } + + return ts + .displayPartsToString(commentDisplayParts) + .replaceAll("\n", DOCBLOCK_NEW_LINE) + } + + /** + * Retrieves docblocks based on decorators used on a symbol. + * + * @param {ts.Symbol} symbol - The symbol to retrieve its decorators docblock. + * @returns {string} The symbol's decorators docblock. + */ + getDecoratorDocs(symbol: ts.Symbol): string { + let str = "" + + symbol.declarations?.forEach((declaration) => { + const modifiers = + "modifiers" in declaration && declaration.modifiers + ? (declaration.modifiers as ts.NodeArray) + : [] + + modifiers.forEach((modifier) => { + if (!ts.isDecorator(modifier)) { + return + } + + // check for decorator text + ;(modifier as ts.Decorator).forEachChild((childNode) => { + if (ts.isCallExpression(childNode)) { + const childNodeExpression = (childNode as ts.CallExpression) + .expression + if (ts.isIdentifier(childNodeExpression)) { + switch (childNodeExpression.escapedText) { + case "FeatureFlagEntity": + // add the `@featureFlag` tag. + str += `${DOCBLOCK_DOUBLE_LINES}@featureFlag [flag_name]` + break + case "BeforeInsert": + case "BeforeLoad": + case "AfterLoad": + // add `@apiIgnore` tag + str += `${DOCBLOCK_DOUBLE_LINES}@apiIgnore` + } + } + } + }) + }) + }) + + return str + } + + /** + * Retrieve docblocks that are common to all nodes, despite their kind. + * + * @param {T | ts.Node} node - The node to retrieve its common doc blocks. + * @param {CommonDocsOptions} options - Formatting options. + * @returns {string} The common docblocks. + */ + getCommonDocs( + node: T | ts.Node, + options: CommonDocsOptions = { addDefaultSummary: false } + ): string { + const tags = new Set() + + const symbol = getSymbol(node, this.checker) + + if (!symbol) { + return "" + } + + if (ts.isSourceFile(node)) { + // comments for source files must start with this tag + tags.add(`@packageDocumentation`) + } + + if (options.addDefaultSummary) { + tags.add(this.defaultSummary) + } + + // check for private or protected modifiers + // and if found, add the `@ignore` tag. + symbol.declarations?.some((declaration) => { + if (!("modifiers" in declaration) || !declaration.modifiers) { + return false + } + + const hasPrivateOrProtected = ( + declaration.modifiers as ts.NodeArray + ).find((modifier) => { + modifier.kind === ts.SyntaxKind.PrivateKeyword || + modifier.kind === ts.SyntaxKind.ProtectedKeyword + }) + + if (!hasPrivateOrProtected) { + return false + } + + tags.add("@ignore") + return true + }) + + // if a symbol's name starts with `_` then we + // should add the `@ignore` tag + if (symbol.getName().startsWith("_")) { + tags.add("@ignore") + } + + // check if any docs can be added for the symbol's + // decorators + this.getDecoratorDocs(symbol) + .split(`${DOCBLOCK_DOUBLE_LINES}`) + .filter((docItem) => docItem.length > 0) + .forEach((docItem) => tags.add(docItem)) + + // add `@expandable` tag if the resource is + if (ts.isPropertyDeclaration(node)) { + const symbolType = this.checker.getTypeOfSymbol(symbol) + if ( + symbolType.symbol?.declarations?.length && + ts.isClassDeclaration(symbolType.symbol?.declarations[0]) && + this.isEntity({ + heritageClauses: ( + symbolType.symbol?.declarations[0] as ts.ClassDeclaration + ).heritageClauses, + node: symbolType.symbol?.declarations[0], + }) + ) { + tags.add(`@expandable`) + } + } + + // check if custom namespace should be added + if (shouldHaveCustomNamespace(node)) { + tags.add(getCustomNamespaceTag(node)) + } + + // check for default value + if ( + "initializer" in node && + node.initializer && + ts.isExpression(node.initializer as ts.Node) + ) { + const initializer = node.initializer as ts.Expression + + // retrieve default value only if the value is numeric, string, or boolean + const defaultValue = + ts.isNumericLiteral(initializer) || ts.isStringLiteral(initializer) + ? initializer.getText() + : initializer.kind === ts.SyntaxKind.FalseKeyword + ? "false" + : initializer.kind === ts.SyntaxKind.TrueKeyword + ? "true" + : "" + + if (defaultValue.length) { + tags.add(`@defaultValue ${defaultValue}`) + } + } + + let str = "" + tags.forEach((tag) => { + if (str.length > 0) { + str += `${DOCBLOCK_DOUBLE_LINES}` + } + str += `${tag}` + }) + + if (str.length && options.prefixWithLineBreaks) { + str = `${DOCBLOCK_DOUBLE_LINES}${str}` + } + + return str + } + + /** + * Check if a node is a Medusa entity. + * @returns {boolean} Whether the node is a Medusa entity. + */ + isEntity({ + /** + * The inherit/extend keywords of the node. + */ + heritageClauses, + /** + * Optionally provide the node to accurately retrieve its type name. + */ + node, + }: { + heritageClauses?: ts.NodeArray + node?: ts.Node + }): boolean { + return ( + heritageClauses?.some((heritageClause) => { + return heritageClause.types.some((heritageClauseType) => { + const symbolType = this.checker.getTypeAtLocation( + heritageClauseType.expression + ) + + if ( + this.checker + .typeToString(symbolType, node, undefined) + .includes("BaseEntity") + ) { + return true + } + + if ( + symbolType.symbol.valueDeclaration && + "heritageClauses" in symbolType.symbol.valueDeclaration + ) { + return this.isEntity({ + heritageClauses: symbolType.symbol.valueDeclaration + .heritageClauses as ts.NodeArray, + node, + }) + } + + return false + }) + }) || false + ) + } + + getKnowledgeOptions(node: ts.Node): Partial { + const rawParentName = + "name" in node.parent && + node.parent.name && + ts.isIdentifier(node.parent.name as ts.Node) + ? (node.parent.name as ts.Identifier).getText() + : undefined + return { + kind: node.kind, + templateOptions: { + rawParentName, + parentName: rawParentName + ? camelToWords(normalizeName(rawParentName)) + : undefined, + }, + } + } +} + +export default DefaultKindGenerator diff --git a/docs-util/packages/docblock-generator/src/classes/kinds/dto-property.ts b/docs-util/packages/docblock-generator/src/classes/kinds/dto-property.ts new file mode 100644 index 0000000000..9083a74e9c --- /dev/null +++ b/docs-util/packages/docblock-generator/src/classes/kinds/dto-property.ts @@ -0,0 +1,104 @@ +import ts from "typescript" +import DefaultKindGenerator, { GetDocBlockOptions } from "./default.js" +import { DOCBLOCK_END_LINE, DOCBLOCK_START } from "../../constants.js" +import { + camelToWords, + normalizeName, + snakeToWords, +} from "../../utils/str-formatting.js" + +/** + * A class that generates doc blocks for properties in a DTO interface/type. + */ +class DTOPropertyGenerator extends DefaultKindGenerator { + protected allowedKinds: ts.SyntaxKind[] = [ts.SyntaxKind.PropertySignature] + + /** + * Check that the generator can handle generating for the node. + * + * @param {ts.Node} node - The node to check. + * @returns {boolean} Whether the generator can handle generating for the node. + */ + isAllowed(node: ts.Node): node is ts.PropertySignature { + if (!super.isAllowed(node)) { + return false + } + + return ( + this.getParentName((node as ts.PropertySignature).parent).endsWith( + "DTO" + ) || false + ) + } + + getDocBlock( + node: ts.PropertyDeclaration | ts.Node, + options?: GetDocBlockOptions + ): string { + if (!this.isAllowed(node)) { + return super.getDocBlock(node, options) + } + + let str = DOCBLOCK_START + const rawParentName = this.getParentName(node.parent) + const parentName = this.formatInterfaceName(rawParentName) + + // try first to retrieve the summary from the knowledge base if it exists. + const summary = this.knowledgeBaseFactory.tryToGetSummary({ + str: node.name.getText(), + kind: node.kind, + templateOptions: { + rawParentName, + parentName, + }, + }) + + if (summary) { + str += summary + } else { + // check if the property's type is interface/type/class + const propertyType = this.checker.getTypeAtLocation(node) + if (propertyType.isClassOrInterface()) { + str += `The associated ${this.formatInterfaceName( + this.checker.typeToString(propertyType) + )}.` + } else if ( + "intrinsicName" in propertyType && + propertyType.intrinsicName === "boolean" + ) { + str += `Whether the ${parentName} ${snakeToWords(node.name.getText())}.` + } else { + // format summary + str += `The ${snakeToWords(node.name.getText())} of the ${parentName}.` + } + } + + return `${str}${DOCBLOCK_END_LINE}` + } + + /** + * Format the name of the interface/type. + * + * @param {string} name - The name to format. + * @returns {string} The formatted name. + */ + formatInterfaceName(name: string): string { + return camelToWords(normalizeName(name)) + } + + /** + * Get the name of the parent interface/type. + * + * @param {ts.InterfaceDeclaration | ts.TypeLiteralNode} parent - The parent node. + * @returns {string} The name of the parent. + */ + getParentName(parent: ts.InterfaceDeclaration | ts.TypeLiteralNode): string { + if (ts.isInterfaceDeclaration(parent)) { + return parent.name.getText() + } + + return this.checker.typeToString(this.checker.getTypeFromTypeNode(parent)) + } +} + +export default DTOPropertyGenerator diff --git a/docs-util/packages/docblock-generator/src/classes/kinds/function.ts b/docs-util/packages/docblock-generator/src/classes/kinds/function.ts new file mode 100644 index 0000000000..fc20a60289 --- /dev/null +++ b/docs-util/packages/docblock-generator/src/classes/kinds/function.ts @@ -0,0 +1,261 @@ +import ts from "typescript" +import DefaultKindGenerator, { GetDocBlockOptions } from "./default.js" +import { + DOCBLOCK_NEW_LINE, + DOCBLOCK_END_LINE, + DOCBLOCK_START, + DOCBLOCK_DOUBLE_LINES, +} from "../../constants.js" +import getSymbol from "../../utils/get-symbol.js" + +export type FunctionNode = + | ts.MethodDeclaration + | ts.MethodSignature + | ts.FunctionDeclaration + | ts.ArrowFunction + +type VariableNode = ts.VariableDeclaration | ts.VariableStatement + +export type FunctionOrVariableNode = FunctionNode | ts.VariableStatement + +/** + * Docblock generator for functions. + */ +// eslint-disable-next-line max-len +class FunctionKindGenerator extends DefaultKindGenerator { + protected methodKinds: ts.SyntaxKind[] = [ + ts.SyntaxKind.MethodDeclaration, + ts.SyntaxKind.MethodSignature, + ] + protected functionKinds: ts.SyntaxKind[] = [ts.SyntaxKind.FunctionDeclaration] + protected allowedKinds: ts.SyntaxKind[] = [ + ...this.methodKinds, + ...this.functionKinds, + ] + + /** + * Checks whether a node is considered a function node. A node is considered a function node if: + * + * 1. It is a method declaration (typically in classes), a method signature (typically in interfaces), or a function declaration. + * 2. An arrow function. However, for better docblock placement and formatting, we detect the variable statement surrounding the arrow function + * rather than the arrow function itself. + * + * @param {ts.Node} node - The node to check. + * @returns {boolean} Whether the node is a function node and can be handled by this generator. + */ + isAllowed(node: ts.Node): node is FunctionOrVariableNode { + if (!super.isAllowed(node)) { + return ts.isVariableStatement(node) && this.isFunctionVariable(node) + } + + return true + } + + /** + * Checks whether a node is a variable statement/declaration with underlying node function + * using the {@link extractFunctionNode} method. + * + * @param {ts.Node} node - The node to check. + * @returns {boolean} Whether the node is a variable statement/declaration with underlying node function. + */ + isFunctionVariable(node: ts.Node): node is VariableNode { + if (ts.isVariableStatement(node)) { + return node.declarationList.declarations.some((declaration) => { + return this.isFunctionVariable(declaration) + }) + } else if (ts.isVariableDeclaration(node)) { + return this.extractFunctionNode(node) !== undefined + } + + return false + } + + /** + * Retrieves the underlying function/method/arrow function of a variable statement or declaration. + * + * @param {ts.Node} node - The variable statement/declaration to retrieve the function/method from. + * @returns The function/method if found. + */ + extractFunctionNode(node: VariableNode): FunctionNode | undefined { + if (ts.isVariableStatement(node)) { + const variableDeclaration = node.declarationList.declarations.find( + (declaration) => ts.isVariableDeclaration(declaration) + ) + + return variableDeclaration + ? this.extractFunctionNode(variableDeclaration) + : undefined + } else if ( + node.initializer && + (this.isAllowed(node.initializer) || ts.isArrowFunction(node.initializer)) + ) { + return node.initializer + } + } + + /** + * Check whether a node refers to a method. + * + * @param {FunctionNode} node - The node to check. + * @returns {boolean} Whether the node is a method. + */ + isMethod( + node: FunctionNode + ): node is ts.MethodDeclaration | ts.MethodSignature { + return this.methodKinds.includes(node.kind) + } + + /** + * Checks whether a type, typically the type of a function's signature, has return data. + * + * @param {string} typeStr - The type's string representation. + * @returns {boolean} Whether the type has return data. + */ + hasReturnData(typeStr: string): boolean { + return ( + typeStr !== "void" && + typeStr !== "never" && + typeStr !== "Promise" && + typeStr !== "Promise" + ) + } + + /** + * Retrieves the return type of a function. + * + * @param {FunctionNode} node - The function's node. + * @returns {ts.Type} The function's return type. + */ + getReturnType(node: FunctionNode): ts.Type { + return node.type + ? this.checker.getTypeFromTypeNode(node.type) + : this.checker.getTypeAtLocation(node) + } + + /** + * Retrieves the summary comment of a function. + * + * @param {FunctionNode} node - The node's function. + * @param {ts.Symbol} symbol - The node's symbol. If provided, the method will try to retrieve the summary from the {@link KnowledgeBaseFactory}. + * @returns {string} The function's summary comment. + */ + getFunctionSummary(node: FunctionNode, symbol?: ts.Symbol): string { + return symbol + ? this.knowledgeBaseFactory.tryToGetFunctionSummary({ + symbol: symbol, + kind: node.kind, + }) || this.getNodeSummary({ node, symbol }) + : this.getNodeSummary({ node, symbol }) + } + + /** + * Retrieve the function's example comment. + * + * @param {ts.Symbol} symbol - The function's symbol. If provided, the method will try to retrieve the example from the {@link KnowledgeBaseFactory}. + * @returns {string} The function's example comment. + */ + getFunctionExample(symbol?: ts.Symbol): string { + const str = `${DOCBLOCK_DOUBLE_LINES}@example${DOCBLOCK_NEW_LINE}` + return `${str}${ + symbol + ? this.knowledgeBaseFactory.tryToGetFunctionExamples({ + symbol: symbol, + }) || `{example-code}` + : `{example-code}` + }` + } + + /** + * Retrieve the full docblock of a function. + * + * @param {FunctionOrVariableNode | ts.Node} node - The function node. If a variable statement is provided, the underlying function is retrieved. + * If a different node type is provided, the parent generator is used to retrieve the docblock comment. + * @param {GetDocBlockOptions} options - Formatting options. + * @returns {string} The function's docblock. + */ + getDocBlock( + node: FunctionOrVariableNode | ts.Node, + options: GetDocBlockOptions = { addEnd: true } + ): string { + if (!this.isAllowed(node)) { + return super.getDocBlock(node, options) + } + + const actualNode = ts.isVariableStatement(node) + ? this.extractFunctionNode(node) + : node + + if (!actualNode) { + return super.getDocBlock(node, options) + } + + const nodeSymbol = getSymbol(node, this.checker) + + let str = DOCBLOCK_START + + // add summary + str += `${ + options.summaryPrefix || + (this.isMethod(actualNode) ? `This method` : `This function`) + } ${this.getFunctionSummary(actualNode, nodeSymbol)}${DOCBLOCK_NEW_LINE}` + + // add params + actualNode.forEachChild((childNode) => { + if (!ts.isParameter(childNode)) { + return + } + const symbol = getSymbol(childNode, this.checker) + if (!symbol) { + return + } + + const symbolType = this.checker.getTypeOfSymbolAtLocation( + symbol, + childNode + ) + + str += `${DOCBLOCK_NEW_LINE}@param {${this.checker.typeToString( + symbolType + )}} ${symbol.getName()} - ${this.getNodeSummary({ + node: childNode, + symbol, + nodeType: symbolType, + })}` + }) + + // add returns + const nodeType = this.getReturnType(actualNode) + const returnTypeStr = this.checker.typeToString(nodeType) + const possibleReturnSummary = !this.hasReturnData(returnTypeStr) + ? `Resolves when ${this.defaultSummary}` + : this.getNodeSummary({ + node: actualNode, + nodeType, + }) + + str += `${DOCBLOCK_NEW_LINE}@returns {${returnTypeStr}} ${ + nodeSymbol + ? this.knowledgeBaseFactory.tryToGetFunctionReturns({ + symbol: nodeSymbol, + kind: actualNode.kind, + }) || possibleReturnSummary + : possibleReturnSummary + }` + + // add example + str += this.getFunctionExample(nodeSymbol) + + // add common docs + str += this.getCommonDocs(node, { + prefixWithLineBreaks: true, + }) + + if (options.addEnd) { + str += DOCBLOCK_END_LINE + } + + return str + } +} + +export default FunctionKindGenerator diff --git a/docs-util/packages/docblock-generator/src/classes/kinds/medusa-react-hooks.ts b/docs-util/packages/docblock-generator/src/classes/kinds/medusa-react-hooks.ts new file mode 100644 index 0000000000..b2b4beaa05 --- /dev/null +++ b/docs-util/packages/docblock-generator/src/classes/kinds/medusa-react-hooks.ts @@ -0,0 +1,195 @@ +import ts from "typescript" +import FunctionKindGenerator, { + FunctionNode, + FunctionOrVariableNode, +} from "./function.js" +import { + DOCBLOCK_NEW_LINE, + DOCBLOCK_END_LINE, + DOCBLOCK_START, + DOCBLOCK_DOUBLE_LINES, +} from "../../constants.js" +import nodeHasComments from "../../utils/node-has-comments.js" +import { + CUSTOM_NAMESPACE_TAG, + getCustomNamespaceTag, +} from "../../utils/medusa-react-utils.js" + +/** + * Docblock generate for medusa-react hooks. Since hooks are essentially functions, + * it extends the {@link FunctionKindGenerator} class. + */ +class MedusaReactHooksKindGenerator extends FunctionKindGenerator { + /** + * Checks whether the generator can retrieve the docblock of the specified node. It uses the parent generator + * to check that the node is a function, then checks if the function is a mutation using the {@link isMutation} method, + * or a query using the {@link isQuery} method. + * + * @param {ts.Node} node - The node to check. + * @returns {boolean} Whether this generator can be used on this node. + */ + isAllowed(node: ts.Node): node is FunctionOrVariableNode { + if (!super.isAllowed(node)) { + return false + } + + const actualNode = ts.isVariableStatement(node) + ? this.extractFunctionNode(node) + : node + + return ( + actualNode !== undefined && + (this.isMutation(actualNode) || this.isQuery(actualNode)) + ) + } + + /** + * Checks whether a function node is a mutation. + * + * @param {FunctionNode} node - The function node to check. + * @returns {boolean} Whether the node is a mutation. + */ + isMutation(node: FunctionNode): boolean { + const nodeType = this.getReturnType(node) + + const callSignatures = nodeType.getCallSignatures() + + return ( + callSignatures.length > 0 && + this.checker + .typeToString(this.checker.getReturnTypeOfSignature(callSignatures[0])) + .startsWith("UseMutationResult") + ) + } + + /** + * Checks whether a function node is a query. + * + * @param {FunctionNode} node - The function node to check. + * @returns {boolean} Whether the node is a query. + */ + isQuery(node: FunctionNode): boolean { + return node.parameters.some( + (parameter) => + parameter.type?.getText().startsWith("UseQueryOptionsWrapper") + ) + } + + /** + * Retrieves the docblock of the medusa-react hook or mutation. + * + * @param {FunctionNode & ts.VariableDeclaration} node - The node to retrieve its docblock. + * @returns {string} The node's docblock. + */ + getDocBlock(node: FunctionNode & ts.VariableDeclaration): string { + if (!this.isAllowed(node)) { + return super.getDocBlock(node) + } + + const actualNode = ts.isVariableStatement(node) + ? this.extractFunctionNode(node) + : node + + if (!actualNode) { + return super.getDocBlock(node) + } + const isMutation = this.isMutation(actualNode) + + let str = `${DOCBLOCK_START}This hook ${this.getFunctionSummary(node)}` + + // add example + str += this.getFunctionExample() + + // loop over parameters that aren't query/mutation parameters + // and add docblock to them + this.getActualParameters(actualNode).forEach((parameter) => { + ts.addSyntheticLeadingComment( + parameter, + ts.SyntaxKind.MultiLineCommentTrivia, + super.getDocBlock(parameter), + true + ) + }) + + // check if mutation parameter is an intrinsic type and, if so, add the `@typeParamDefinition` + // tag to the hook + if (isMutation) { + const typeArg = this.getMutationRequestTypeArg(actualNode) + if (typeArg) { + str += `${DOCBLOCK_DOUBLE_LINES}@typeParamDefinition ${this.checker.typeToString( + typeArg + )} - {summary}` + } + } + + // add common docs + str += this.getCommonDocs(node, { + prefixWithLineBreaks: true, + }) + + // add namespace in case it's not added + if (!str.includes(CUSTOM_NAMESPACE_TAG)) { + str += `${DOCBLOCK_DOUBLE_LINES}${getCustomNamespaceTag(actualNode)}` + } + + // add the category + str += `${DOCBLOCK_NEW_LINE}@category ${ + isMutation ? "Mutations" : "Queries" + }` + + return `${str}${DOCBLOCK_END_LINE}` + } + + /** + * Retrieves the parameters of a function node that aren't query/mutation options. + * + * @param {FunctionNode} node - The function node to retrieve its parameters. + * @returns {ts.ParameterDeclaration[]} - The function's actual parameters. + */ + getActualParameters(node: FunctionNode): ts.ParameterDeclaration[] { + return node.parameters.filter((parameter) => { + const parameterTypeStr = parameter.type?.getText() + return ( + !parameterTypeStr?.startsWith("UseQueryOptionsWrapper") && + !parameterTypeStr?.startsWith("UseMutationOptions") && + !nodeHasComments(parameter) + ) + }) + } + + /** + * Retreives a mutation's intrinsic request type, if available, which is specified as the third type argument of `UseMutationOptions`. + * + * @param {FunctionNode} node - The function node to retrieve its request type. + * @returns {ts.Type | undefined} The mutation's request type, if available. + */ + getMutationRequestTypeArg(node: FunctionNode): ts.Type | undefined { + const parameter = node.parameters.find( + (parameter) => parameter.type?.getText().startsWith("UseMutationOptions") + ) + + if (!parameter) { + return + } + + const parameterType = this.checker.getTypeFromTypeNode(parameter.type!) + const typeArgs = + parameterType.aliasTypeArguments || + ("resolvedTypeArguments" in parameterType + ? (parameterType.resolvedTypeArguments as ts.Type[]) + : []) + if ( + !typeArgs || + typeArgs.length < 3 || + !("intrinsicName" in typeArgs[2]) || + ["void", "unknown"].includes(typeArgs[2].intrinsicName as string) + ) { + return + } + + // find request in third type argument + return typeArgs[2] + } +} + +export default MedusaReactHooksKindGenerator diff --git a/docs-util/packages/docblock-generator/src/classes/kinds/registry.ts b/docs-util/packages/docblock-generator/src/classes/kinds/registry.ts new file mode 100644 index 0000000000..e8d3f22172 --- /dev/null +++ b/docs-util/packages/docblock-generator/src/classes/kinds/registry.ts @@ -0,0 +1,51 @@ +import ts from "typescript" +import FunctionKindGenerator from "./function.js" +import DefaultKindGenerator from "./default.js" +import MedusaReactHooksKindGenerator from "./medusa-react-hooks.js" +import SourceFileKindGenerator from "./source-file.js" +import DTOPropertyGenerator from "./dto-property.js" + +/** + * A class that is used as a registry for the kind generators. + */ +class KindsRegistry { + protected kindInstances: DefaultKindGenerator[] + protected defaultKindGenerator: DefaultKindGenerator + + constructor(checker: ts.TypeChecker) { + this.kindInstances = [ + new MedusaReactHooksKindGenerator({ checker }), + new FunctionKindGenerator({ checker }), + new SourceFileKindGenerator({ checker }), + new DTOPropertyGenerator({ checker }), + ] + this.defaultKindGenerator = new DefaultKindGenerator({ checker }) + } + + /** + * Retrieve the generator for a node based on its kind, if any. + * + * @param {ts.Node} node - The node to retrieve its docblock generator. + * @returns {DefaultKindGenerator | undefined} The generator that can handle the node's kind, if any. + */ + getKindGenerator(node: ts.Node): DefaultKindGenerator | undefined { + return ( + this.kindInstances.find((generator) => generator.isAllowed(node)) || + (this.defaultKindGenerator.isAllowed(node) + ? this.defaultKindGenerator + : undefined) + ) + } + + /** + * Checks whether a node has a kind generator. + * + * @param {ts.Node} node - The node to check for. + * @returns {boolean} Whether the node has a kind generator. + */ + hasGenerator(node: ts.Node): boolean { + return this.getKindGenerator(node) !== undefined + } +} + +export default KindsRegistry diff --git a/docs-util/packages/docblock-generator/src/classes/kinds/source-file.ts b/docs-util/packages/docblock-generator/src/classes/kinds/source-file.ts new file mode 100644 index 0000000000..906fcfb2aa --- /dev/null +++ b/docs-util/packages/docblock-generator/src/classes/kinds/source-file.ts @@ -0,0 +1,37 @@ +import ts from "typescript" +import DefaultKindGenerator, { GetDocBlockOptions } from "./default.js" +import { DOCBLOCK_END_LINE, DOCBLOCK_START } from "../../constants.js" +import { shouldHaveCustomNamespace } from "../../utils/medusa-react-utils.js" + +/** + * A generator used to retrieve doc blocks for a source file. + */ +class SourceFileKindGenerator extends DefaultKindGenerator { + protected allowedKinds: ts.SyntaxKind[] = [ts.SyntaxKind.SourceFile] + + /** + * Retrieve the docblock of a source file. + * + * @param {ts.SourceFile | ts.Node} node - The node to retrieve its docblocks. + * @param {GetDocBlockOptions} options - The formatting options. + * @returns {string} The node's docblock. + */ + getDocBlock( + node: ts.SourceFile | ts.Node, + options?: GetDocBlockOptions + ): string { + if (!this.isAllowed(node)) { + return super.getDocBlock(node, options) + } + + if (shouldHaveCustomNamespace(node)) { + return `${DOCBLOCK_START}${this.getCommonDocs(node, { + addDefaultSummary: true, + })}${DOCBLOCK_END_LINE}` + } + + return "" + } +} + +export default SourceFileKindGenerator diff --git a/docs-util/packages/docblock-generator/src/classes/knowledge-base-factory.ts b/docs-util/packages/docblock-generator/src/classes/knowledge-base-factory.ts new file mode 100644 index 0000000000..78222e4b77 --- /dev/null +++ b/docs-util/packages/docblock-generator/src/classes/knowledge-base-factory.ts @@ -0,0 +1,325 @@ +import ts from "typescript" +import { DOCBLOCK_DOUBLE_LINES, DOCBLOCK_NEW_LINE } from "../constants.js" +import { + camelToTitle, + camelToWords, + normalizeName, +} from "../utils/str-formatting.js" + +type TemplateOptions = { + parentName?: string + rawParentName?: string + returnTypeName?: string +} + +type KnowledgeBase = { + startsWith?: string + endsWith?: string + exact?: string + template: string | ((str: string, options?: TemplateOptions) => string) + kind?: ts.SyntaxKind[] +} + +export type RetrieveOptions = { + /** + * A name that can be of a function, type, etc... + */ + str: string + /** + * Options to pass to the `template` function of a + * knowledge base item. + */ + templateOptions?: TemplateOptions + /** + * The kind of the associated node. + */ + kind?: ts.SyntaxKind +} + +type RetrieveSymbolOptions = Omit & { + /** + * The symbol to retrieve the item from the knowledge base. + */ + symbol: ts.Symbol +} + +/** + * A class that holds common Medusa patterns and acts as a knowledge base for possible summaries/examples/general templates. + */ +class KnowledgeBaseFactory { + private summaryKnowledgeBase: KnowledgeBase[] = [ + { + startsWith: "FindConfig", + template: (str) => { + const typeArgs = str + .replace("FindConfig<", "") + .replace(/>$/, "") + .split(",") + .map((part) => camelToWords(normalizeName(part.trim()))) + const typeName = + typeArgs.length > 0 && typeArgs[0].length > 0 + ? typeArgs[0] + : `{type name}` + return `The configurations determining how the ${typeName} is retrieved. Its properties, such as \`select\` or \`relations\`, accept the ${DOCBLOCK_NEW_LINE}attributes or relations associated with a ${typeName}.` + }, + }, + { + startsWith: "Filterable", + endsWith: "Props", + template: (str) => { + return `The filters to apply on the retrieved ${camelToTitle( + normalizeName(str) + )}.` + }, + }, + { + startsWith: "Create", + endsWith: "DTO", + template: (str) => { + return `The ${camelToTitle(normalizeName(str))} to be created.` + }, + }, + { + startsWith: "Update", + endsWith: "DTO", + template: (str) => { + return `The attributes to update in the ${camelToTitle( + normalizeName(str) + )}.` + }, + }, + { + startsWith: "RestoreReturn", + template: `Configurations determining which relations to restore along with each of the {type name}. You can pass to its \`returnLinkableKeys\` ${DOCBLOCK_NEW_LINE}property any of the {type name}'s relation attribute names, such as \`{type relation name}\`.`, + }, + { + endsWith: "DTO", + template: (str: string): string => { + return `The ${camelToTitle(normalizeName(str))} details.` + }, + }, + { + endsWith: "_id", + template: (str: string): string => { + const formatted = str.replace(/_id$/, "").split("_").join(" ") + + return `The associated ${formatted}'s ID.` + }, + kind: [ts.SyntaxKind.PropertySignature], + }, + { + endsWith: "Id", + template: (str: string): string => { + const formatted = camelToWords(str.replace(/Id$/, "")) + + return `The ${formatted}'s ID.` + }, + kind: [ + ts.SyntaxKind.PropertySignature, + ts.SyntaxKind.PropertyDeclaration, + ts.SyntaxKind.Parameter, + ], + }, + { + exact: "id", + template: (str, options) => { + if (options?.rawParentName?.startsWith("Filterable")) { + return `The IDs to filter the ${options?.parentName || `{name}`} by.` + } + return `The ID of the ${options?.parentName || `{name}`}.` + }, + kind: [ts.SyntaxKind.PropertySignature], + }, + { + exact: "metadata", + template: "Holds custom data in key-value pairs.", + kind: [ts.SyntaxKind.PropertySignature], + }, + { + exact: "customHeaders", + template: "Custom headers to attach to the request.", + }, + ] + private functionSummaryKnowledgeBase: KnowledgeBase[] = [ + { + startsWith: "listAndCount", + template: + "retrieves a paginated list of {return type} along with the total count of available {return type}(s) satisfying the provided filters.", + }, + { + startsWith: "list", + template: + "retrieves a paginated list of {return type}(s) based on optional filters and configuration.", + }, + { + startsWith: "retrieve", + template: "retrieves a {return type} by its ID.", + }, + { + startsWith: "create", + template: "creates {return type}(s)", + }, + { + startsWith: "delete", + template: "deletes {return type} by its ID.", + }, + { + startsWith: "update", + template: "updates existing {return type}(s).", + }, + { + startsWith: "softDelete", + template: "soft deletes {return type}(s) by their IDs.", + }, + { + startsWith: "restore", + template: "restores soft deleted {return type}(s) by their IDs.", + }, + ] + private exampleCodeBlockLine = `${DOCBLOCK_DOUBLE_LINES}\`\`\`ts${DOCBLOCK_NEW_LINE}{example-code}${DOCBLOCK_NEW_LINE}\`\`\`${DOCBLOCK_DOUBLE_LINES}` + private examplesKnowledgeBase: KnowledgeBase[] = [ + { + startsWith: "list", + template: `To retrieve a list of {type name} using their IDs: ${this.exampleCodeBlockLine}To specify relations that should be retrieved within the {type name}: ${this.exampleCodeBlockLine}By default, only the first \`{default limit}\` records are retrieved. You can control pagination by specifying the \`skip\` and \`take\` properties of the \`config\` parameter: ${this.exampleCodeBlockLine}`, + }, + { + startsWith: "retrieve", + template: `A simple example that retrieves a {type name} by its ID: ${this.exampleCodeBlockLine}To specify relations that should be retrieved: ${this.exampleCodeBlockLine}`, + }, + ] + private functionReturnKnowledgeBase: KnowledgeBase[] = [ + { + startsWith: "listAndCount", + template: "The list of {return type}(s) along with their total count.", + }, + { + startsWith: "list", + template: "The list of {return type}(s).", + }, + { + startsWith: "retrieve", + template: "The retrieved {return type}(s).", + }, + { + startsWith: "create", + template: "The created {return type}(s).", + }, + { + startsWith: "update", + template: "The updated {return type}(s).", + }, + { + startsWith: "restore", + template: `An object that includes the IDs of related records that were restored, such as the ID of associated {relation name}. ${DOCBLOCK_NEW_LINE}The object's keys are the ID attribute names of the {type name} entity's relations, such as \`{relation ID field name}\`, ${DOCBLOCK_NEW_LINE}and its value is an array of strings, each being the ID of the record associated with the money amount through this relation, ${DOCBLOCK_NEW_LINE}such as the IDs of associated {relation name}.`, + }, + ] + + /** + * Tries to find in a specified knowledge base a template relevant to the specified name. + * + * @returns {string | undefined} The matching knowledge base template, if found. + */ + private tryToFindInKnowledgeBase({ + str, + knowledgeBase, + templateOptions, + kind, + }: RetrieveOptions & { + /** + * A knowledge base to search in. + */ + knowledgeBase: KnowledgeBase[] + }): string | undefined { + const foundItem = knowledgeBase.find((item) => { + if (item.exact) { + return str === item.exact + } + + if (item.kind?.length && (!kind || !item.kind.includes(kind))) { + return false + } + + if (item.startsWith && item.endsWith) { + return str.startsWith(item.startsWith) && str.endsWith(item.endsWith) + } + + if (item.startsWith) { + return str.startsWith(item.startsWith) + } + + return item.endsWith ? str.endsWith(item.endsWith) : false + }) + + if (!foundItem) { + return + } + + return typeof foundItem.template === "string" + ? foundItem?.template + : foundItem?.template(str, templateOptions) + } + + /** + * Tries to retrieve the summary template of a specified type from the {@link summaryKnowledgeBase}. + * + * @returns {string | undefined} The matching knowledge base template, if found. + */ + tryToGetSummary({ str, ...options }: RetrieveOptions): string | undefined { + const normalizedTypeStr = str.replaceAll("[]", "") + return this.tryToFindInKnowledgeBase({ + ...options, + str: normalizedTypeStr, + knowledgeBase: this.summaryKnowledgeBase, + }) + } + + /** + * Tries to retrieve the summary template of a function's symbol from the {@link functionSummaryKnowledgeBase}. + * + * @returns {string | undefined} The matching knowledge base template, if found. + */ + tryToGetFunctionSummary({ + symbol, + ...options + }: RetrieveSymbolOptions): string | undefined { + return this.tryToFindInKnowledgeBase({ + ...options, + str: symbol.getName(), + knowledgeBase: this.functionSummaryKnowledgeBase, + }) + } + + /** + * Tries to retrieve the example template of a function's symbol from the {@link examplesKnowledgeBase}. + * + * @returns {string | undefined} The matching knowledge base template, if found. + */ + tryToGetFunctionExamples({ + symbol, + ...options + }: RetrieveSymbolOptions): string | undefined { + return this.tryToFindInKnowledgeBase({ + ...options, + str: symbol.getName(), + knowledgeBase: this.examplesKnowledgeBase, + }) + } + + /** + * Tries to retrieve the return template of a function's symbol from the {@link functionReturnKnowledgeBase}. + * + * @returns {string | undefined} The matching knowledge base template, if found. + */ + tryToGetFunctionReturns({ + symbol, + ...options + }: RetrieveSymbolOptions): string | undefined { + return this.tryToFindInKnowledgeBase({ + ...options, + str: symbol.getName(), + knowledgeBase: this.functionReturnKnowledgeBase, + }) + } +} + +export default KnowledgeBaseFactory diff --git a/docs-util/packages/docblock-generator/src/commands/run-git-changes.ts b/docs-util/packages/docblock-generator/src/commands/run-git-changes.ts new file mode 100644 index 0000000000..4a8bb31a0e --- /dev/null +++ b/docs-util/packages/docblock-generator/src/commands/run-git-changes.ts @@ -0,0 +1,40 @@ +import path from "path" +import DocblockGenerator from "../classes/docblock-generator.js" +import getMonorepoRoot from "../utils/get-monorepo-root.js" +import promiseExec from "../utils/promise-exec.js" +import filterFiles from "../utils/filter-files.js" + +export default async function runGitChanges() { + const monorepoPath = getMonorepoRoot() + // retrieve the changed files under `packages` in the monorepo root. + const childProcess = await promiseExec( + `git diff --name-only -- "packages/**/**.ts" "packages/**/*.js" "packages/**/*.tsx" "packages/**/*.jsx"`, + { + cwd: monorepoPath, + } + ) + + let files = filterFiles( + childProcess.stdout.toString().split("\n").filter(Boolean) + ) + + if (!files.length) { + console.log(`No file changes detected.`) + return + } + + console.log( + `${files.length} files have changed. Running generator on them...` + ) + + files = files.map((filePath) => path.resolve(monorepoPath, filePath)) + + // generate docblocks for each of the files. + const docblockGenerator = new DocblockGenerator({ + paths: files, + }) + + await docblockGenerator.run() + + console.log(`Finished generating docs for ${files.length} files.`) +} diff --git a/docs-util/packages/docblock-generator/src/commands/run-git-commit.ts b/docs-util/packages/docblock-generator/src/commands/run-git-commit.ts new file mode 100644 index 0000000000..d93ca04276 --- /dev/null +++ b/docs-util/packages/docblock-generator/src/commands/run-git-commit.ts @@ -0,0 +1,49 @@ +import { Octokit } from "@octokit/core" +import filterFiles from "../utils/filter-files.js" +import path from "path" +import getMonorepoRoot from "../utils/get-monorepo-root.js" +import DocblockGenerator from "../classes/docblock-generator.js" + +export default async function (commitSha: string) { + const monorepoPath = getMonorepoRoot() + // retrieve the files changed in the commit + const octokit = new Octokit({ + auth: process.env.GH_TOKEN, + }) + + const { + data: { files }, + } = await octokit.request("GET /repos/{owner}/{repo}/commits/{ref}", { + owner: "medusajs", + repo: "medusa", + ref: commitSha, + headers: { + "X-GitHub-Api-Version": "2022-11-28", + }, + }) + + // filter changed files + let filteredFiles = filterFiles(files?.map((file) => file.filename) || []) + + if (!filteredFiles.length) { + console.log("No applicable files changed. Canceling...") + return + } + + console.log( + `${filteredFiles.length} files have changed. Running generator on them...` + ) + + filteredFiles = filteredFiles.map((filePath) => + path.resolve(monorepoPath, filePath) + ) + + // generate docblocks for each of the files. + const docblockGenerator = new DocblockGenerator({ + paths: filteredFiles, + }) + + await docblockGenerator.run() + + console.log(`Finished generating docs for ${filteredFiles.length} files.`) +} diff --git a/docs-util/packages/docblock-generator/src/commands/run.ts b/docs-util/packages/docblock-generator/src/commands/run.ts new file mode 100644 index 0000000000..1349f4341a --- /dev/null +++ b/docs-util/packages/docblock-generator/src/commands/run.ts @@ -0,0 +1,17 @@ +import DocblockGenerator, { Options } from "../classes/docblock-generator.js" + +export default async function run( + paths: string[], + options: Omit +) { + console.log("Running...") + + const docblockGenerator = new DocblockGenerator({ + paths, + ...options, + }) + + await docblockGenerator.run() + + console.log(`Finished running.`) +} diff --git a/docs-util/packages/docblock-generator/src/constants.ts b/docs-util/packages/docblock-generator/src/constants.ts new file mode 100644 index 0000000000..17fa477d58 --- /dev/null +++ b/docs-util/packages/docblock-generator/src/constants.ts @@ -0,0 +1,4 @@ +export const DOCBLOCK_NEW_LINE = "\n * " +export const DOCBLOCK_START = `*${DOCBLOCK_NEW_LINE}` +export const DOCBLOCK_END_LINE = "\n" +export const DOCBLOCK_DOUBLE_LINES = `${DOCBLOCK_NEW_LINE}${DOCBLOCK_NEW_LINE}` diff --git a/docs-util/packages/docblock-generator/src/index.ts b/docs-util/packages/docblock-generator/src/index.ts new file mode 100644 index 0000000000..407c564e6d --- /dev/null +++ b/docs-util/packages/docblock-generator/src/index.ts @@ -0,0 +1,33 @@ +#!/usr/bin/env node +import "dotenv/config" +import { Command } from "commander" +import run from "./commands/run.js" +import runGitChanges from "./commands/run-git-changes.js" +import runGitCommit from "./commands/run-git-commit.js" + +const program = new Command() + +program.name("docblock-generator").description("Generate TSDoc doc-blocks") + +program + .command("run") + .description("Generate TSDoc doc-blocks for specified files.") + .argument("", "One or more TypeScript file or directory paths.") + .option( + "--dry-run", + "Whether to run the command without writing the changes." + ) + .action(run) + +program + .command("run:changes") + .description("Generate TSDoc doc-blocks for changed files in git.") + .action(runGitChanges) + +program + .command("run:commit") + .description("Generate TSDoc doc-blocks for changed files in a commit.") + .argument("", "The SHA of a commit.") + .action(runGitCommit) + +program.parse() diff --git a/docs-util/packages/docblock-generator/src/utils/dirname.ts b/docs-util/packages/docblock-generator/src/utils/dirname.ts new file mode 100644 index 0000000000..fbdb8f6a90 --- /dev/null +++ b/docs-util/packages/docblock-generator/src/utils/dirname.ts @@ -0,0 +1,8 @@ +import path from "path" +import { fileURLToPath } from "url" + +export default function dirname() { + const __filename = fileURLToPath(import.meta.url) + + return path.dirname(__filename) +} diff --git a/docs-util/packages/docblock-generator/src/utils/filter-files.ts b/docs-util/packages/docblock-generator/src/utils/filter-files.ts new file mode 100644 index 0000000000..70e4946f41 --- /dev/null +++ b/docs-util/packages/docblock-generator/src/utils/filter-files.ts @@ -0,0 +1,13 @@ +import { minimatch } from "minimatch" + +export default function (files: string[]): string[] { + return files.filter((file) => + minimatch( + file, + "**/packages/@(medusa|types|medusa-js|medusa-react)/src/**/*.@(ts|tsx|js|jsx)", + { + matchBase: true, + } + ) + ) +} diff --git a/docs-util/packages/docblock-generator/src/utils/get-monorepo-root.ts b/docs-util/packages/docblock-generator/src/utils/get-monorepo-root.ts new file mode 100644 index 0000000000..7199a0e442 --- /dev/null +++ b/docs-util/packages/docblock-generator/src/utils/get-monorepo-root.ts @@ -0,0 +1,15 @@ +import path from "path" +import dirname from "./dirname.js" + +/** + * Retrieves the monorepo root either from the `MONOREPO_ROOT_PATH` environment + * variable, or inferring it from the path. + * + * @returns {string} The absolute path to the monorepository. + */ +export default function getMonorepoRoot() { + return ( + process.env.MONOREPO_ROOT_PATH || + path.join(dirname(), "..", "..", "..", "..", "..") + ) +} diff --git a/docs-util/packages/docblock-generator/src/utils/get-relative-paths.ts b/docs-util/packages/docblock-generator/src/utils/get-relative-paths.ts new file mode 100644 index 0000000000..f079815c5d --- /dev/null +++ b/docs-util/packages/docblock-generator/src/utils/get-relative-paths.ts @@ -0,0 +1,15 @@ +import path from "path" + +/** + * Get relative path of multiple file paths to a specified path. + * + * @param {string[]} filePaths - The file paths to retrieve their relative path. + * @param {string} pathPrefix - The path to retrieve paths relative to. + * @returns {string[]} The relative file paths. + */ +export default function getRelativePaths( + filePaths: string[], + pathPrefix: string +): string[] { + return filePaths.map((filePath) => path.resolve(pathPrefix, filePath)) +} diff --git a/docs-util/packages/docblock-generator/src/utils/get-symbol.ts b/docs-util/packages/docblock-generator/src/utils/get-symbol.ts new file mode 100644 index 0000000000..7ff027d4cc --- /dev/null +++ b/docs-util/packages/docblock-generator/src/utils/get-symbol.ts @@ -0,0 +1,24 @@ +import ts from "typescript" + +/** + * Retrieves the symbol of a node. + * + * @param {ts.Node} node - The node to retrieve its symbol. + * @param {ts.TypeChecker} checker - The type checker of the TypeScript program the symbol is in. + * @returns {ts.Symbol | undefined} The symbol if found. + */ +export default function getSymbol( + node: ts.Node, + checker: ts.TypeChecker +): ts.Symbol | undefined { + if ( + ts.isVariableStatement(node) && + node.declarationList.declarations.length + ) { + return getSymbol(node.declarationList.declarations[0], checker) + } + + return "symbol" in node && node.symbol + ? (node.symbol as ts.Symbol) + : undefined +} diff --git a/docs-util/packages/docblock-generator/src/utils/medusa-react-utils.ts b/docs-util/packages/docblock-generator/src/utils/medusa-react-utils.ts new file mode 100644 index 0000000000..7b277ada61 --- /dev/null +++ b/docs-util/packages/docblock-generator/src/utils/medusa-react-utils.ts @@ -0,0 +1,101 @@ +import path from "path" +import getMonorepoRoot from "./get-monorepo-root.js" +import ts from "typescript" +import { minimatch } from "minimatch" +import { capitalize } from "./str-formatting.js" + +export const kindsCanHaveNamespace = [ + ts.SyntaxKind.SourceFile, + ts.SyntaxKind.ClassDeclaration, + ts.SyntaxKind.EnumDeclaration, + ts.SyntaxKind.ModuleDeclaration, + ts.SyntaxKind.InterfaceDeclaration, + ts.SyntaxKind.TypeAliasDeclaration, + ts.SyntaxKind.MethodDeclaration, + ts.SyntaxKind.MethodSignature, + ts.SyntaxKind.FunctionDeclaration, + ts.SyntaxKind.ArrowFunction, + ts.SyntaxKind.VariableStatement, +] + +export const pathsHavingCustomNamespace = [ + "**/packages/medusa\\-react/src/hooks/**/index.ts", + "**/packages/medusa\\-react/src/@(helpers|contexts)/**/*.@(tsx|ts)", +] + +export const CUSTOM_NAMESPACE_TAG = "@customNamespace" + +/** + * Get the path used with the {@link CUSTOM_NAMESPACE_TAG}. + * + * @param {ts.Node} node - The node to retrieve its custom namespace path. + * @returns {string} The namespace path. + */ +export function getNamespacePath(node: ts.Node): string { + const packagePathPrefix = `${path.resolve( + getMonorepoRoot(), + "packages/medusa-react/src" + )}/` + + const sourceFile = node.getSourceFile() + + let hookPath = path + .dirname(sourceFile.fileName) + .replace(packagePathPrefix, "") + + const fileName = path.basename(sourceFile.fileName) + + if ( + !fileName.startsWith("index") && + !fileName.startsWith("mutations") && + !fileName.startsWith("queries") + ) { + hookPath += `/${fileName.replace(path.extname(fileName), "")}` + } + + return hookPath + .split("/") + .map((pathItem, index) => { + if (index === 0) { + pathItem = pathItem + .replace("contexts", "providers") + .replace("helpers", "utilities") + } + + return pathItem + .split("-") + .map((item) => capitalize(item)) + .join(" ") + }) + .join(".") +} + +/** + * Retrieves the full tag of the custom namespace with its value. + * + * @param {ts.Node} node - The node to retrieve its custom namespace path. + * @returns {string} The custom namespace tag and value. + */ +export function getCustomNamespaceTag(node: ts.Node): string { + return `${CUSTOM_NAMESPACE_TAG} ${getNamespacePath(node)}` +} + +/** + * Checks whether a node should have a custom namespace path. + * + * @param {ts.Node} node - The node to check. + * @returns {boolean} Whether the node should have a custom namespace. + */ +export function shouldHaveCustomNamespace(node: ts.Node): boolean { + if (!kindsCanHaveNamespace.includes(node.kind)) { + return false + } + + const fileName = node.getSourceFile().fileName + + return pathsHavingCustomNamespace.some((pattern) => + minimatch(fileName, pattern, { + matchBase: true, + }) + ) +} diff --git a/docs-util/packages/docblock-generator/src/utils/node-has-comments.ts b/docs-util/packages/docblock-generator/src/utils/node-has-comments.ts new file mode 100644 index 0000000000..5376e9f0cb --- /dev/null +++ b/docs-util/packages/docblock-generator/src/utils/node-has-comments.ts @@ -0,0 +1,16 @@ +import ts from "typescript" + +/** + * Checks whether a node has comments. + * + * @param {ts.Node} node - The node to check. + * @returns {boolean} Whether the node has comments. + */ +export default function nodeHasComments(node: ts.Node): boolean { + return ( + ts.getLeadingCommentRanges( + node.getSourceFile().getFullText(), + node.getFullStart() + ) !== undefined + ) +} diff --git a/docs-util/packages/docblock-generator/src/utils/promise-exec.ts b/docs-util/packages/docblock-generator/src/utils/promise-exec.ts new file mode 100644 index 0000000000..da3707273d --- /dev/null +++ b/docs-util/packages/docblock-generator/src/utils/promise-exec.ts @@ -0,0 +1,4 @@ +import util from "node:util" +import { exec } from "child_process" + +export default util.promisify(exec) diff --git a/docs-util/packages/docblock-generator/src/utils/str-formatting.ts b/docs-util/packages/docblock-generator/src/utils/str-formatting.ts new file mode 100644 index 0000000000..1a96a52a93 --- /dev/null +++ b/docs-util/packages/docblock-generator/src/utils/str-formatting.ts @@ -0,0 +1,38 @@ +export function capitalize(str: string): string { + return `${str.charAt(0).toUpperCase()}${str.substring(1).toLowerCase()}` +} + +export function camelToWords(str: string): string { + return str + .replaceAll(/([A-Z])/g, " $1") + .trim() + .toLowerCase() +} + +export function camelToTitle(str: string): string { + return str + .replaceAll(/([A-Z])/g, " $1") + .split(" ") + .map((word) => capitalize(word)) + .join(" ") + .trim() + .toLowerCase() +} + +export function snakeToWords(str: string): string { + return str.replaceAll("_", " ").toLowerCase() +} + +/** + * Remove parts of the name such as DTO, Filterable, etc... + * + * @param {string} str - The name to format. + * @returns {string} The normalized name. + */ +export function normalizeName(str: string): string { + return str + .replace(/^(create|update|delete)/i, "") + .replace(/DTO$/, "") + .replace(/^Filterable/, "") + .replace(/Props$/, "") +} diff --git a/docs-util/packages/docblock-generator/tsconfig.json b/docs-util/packages/docblock-generator/tsconfig.json new file mode 100644 index 0000000000..aece32969e --- /dev/null +++ b/docs-util/packages/docblock-generator/tsconfig.json @@ -0,0 +1,20 @@ +{ + "extends": "../../tsconfig", + "compilerOptions": { + "target": "ESNext", + "module": "Node16", + "moduleResolution": "node16", + "outDir": "./dist", + "rootDir": "./src", + "esModuleInterop": true, + "strict": true, + "skipLibCheck": true, + "resolveJsonModule": true, + }, + "include": ["src"], + "ts-node": { + "esm": true, + "experimentalSpecifierResolution": "node", + "transpileOnly": true + } +} diff --git a/docs-util/yarn.lock b/docs-util/yarn.lock index a24c1f11b5..42584a8c95 100644 --- a/docs-util/yarn.lock +++ b/docs-util/yarn.lock @@ -419,6 +419,23 @@ __metadata: languageName: node linkType: hard +"@eslint/eslintrc@npm:^2.1.4": + version: 2.1.4 + resolution: "@eslint/eslintrc@npm:2.1.4" + dependencies: + ajv: ^6.12.4 + debug: ^4.3.2 + espree: ^9.6.0 + globals: ^13.19.0 + ignore: ^5.2.0 + import-fresh: ^3.2.1 + js-yaml: ^4.1.0 + minimatch: ^3.1.2 + strip-json-comments: ^3.1.1 + checksum: 32f67052b81768ae876c84569ffd562491ec5a5091b0c1e1ca1e0f3c24fb42f804952fdd0a137873bc64303ba368a71ba079a6f691cee25beee9722d94cc8573 + languageName: node + linkType: hard + "@eslint/js@npm:8.54.0": version: 8.54.0 resolution: "@eslint/js@npm:8.54.0" @@ -426,6 +443,13 @@ __metadata: languageName: node linkType: hard +"@eslint/js@npm:8.56.0": + version: 8.56.0 + resolution: "@eslint/js@npm:8.56.0" + checksum: 60b3a1cf240e2479cec9742424224465dc50e46d781da1b7f5ef240501b2d1202c225bd456207faac4b34a64f4765833345bc4ddffd00395e1db40fa8c426f5a + languageName: node + linkType: hard + "@fastify/busboy@npm:^2.0.0": version: 2.1.0 resolution: "@fastify/busboy@npm:2.1.0" @@ -825,6 +849,13 @@ __metadata: languageName: node linkType: hard +"@octokit/auth-token@npm:^4.0.0": + version: 4.0.0 + resolution: "@octokit/auth-token@npm:4.0.0" + checksum: 57acaa6c394c5abab2f74e8e1dcf4e7a16b236f713c77a54b8f08e2d14114de94b37946259e33ec2aab0566b26f724c2b71d2602352b59e541a9854897618f3c + languageName: node + linkType: hard + "@octokit/core@npm:^4.0.5": version: 4.2.4 resolution: "@octokit/core@npm:4.2.4" @@ -840,6 +871,21 @@ __metadata: languageName: node linkType: hard +"@octokit/core@npm:^5.0.2": + version: 5.0.2 + resolution: "@octokit/core@npm:5.0.2" + dependencies: + "@octokit/auth-token": ^4.0.0 + "@octokit/graphql": ^7.0.0 + "@octokit/request": ^8.0.2 + "@octokit/request-error": ^5.0.0 + "@octokit/types": ^12.0.0 + before-after-hook: ^2.2.0 + universal-user-agent: ^6.0.0 + checksum: f3b3cb72f8f374e763e60922eacad56cb08fc05ee0be26f2a7b61937f89a377a8fd1b54f3d621a2b9627a9402c595d4b7e24900602e401b8a8edaffd995fa98f + languageName: node + linkType: hard + "@octokit/endpoint@npm:^7.0.0": version: 7.0.6 resolution: "@octokit/endpoint@npm:7.0.6" @@ -851,6 +897,16 @@ __metadata: languageName: node linkType: hard +"@octokit/endpoint@npm:^9.0.0": + version: 9.0.4 + resolution: "@octokit/endpoint@npm:9.0.4" + dependencies: + "@octokit/types": ^12.0.0 + universal-user-agent: ^6.0.0 + checksum: f1c857c5d85afa9d7e8857f7f97dbec28d3b6ab1dc21fe35172f1bc9e5512c8a3a26edabf6b2d83bb60d700f7ad290c96be960496aa83606095630edfad06db4 + languageName: node + linkType: hard + "@octokit/graphql@npm:^5.0.0": version: 5.0.6 resolution: "@octokit/graphql@npm:5.0.6" @@ -862,6 +918,17 @@ __metadata: languageName: node linkType: hard +"@octokit/graphql@npm:^7.0.0": + version: 7.0.2 + resolution: "@octokit/graphql@npm:7.0.2" + dependencies: + "@octokit/request": ^8.0.1 + "@octokit/types": ^12.0.0 + universal-user-agent: ^6.0.0 + checksum: 96e5d6b970be60877134cc147b9249534f3a79d691b9932d731d453426fa1e1a0a36111a1b0a6ab43d61309c630903a65db5559b5c800300dc26cf588f50fea8 + languageName: node + linkType: hard + "@octokit/openapi-types@npm:^18.0.0": version: 18.1.1 resolution: "@octokit/openapi-types@npm:18.1.1" @@ -869,6 +936,13 @@ __metadata: languageName: node linkType: hard +"@octokit/openapi-types@npm:^19.1.0": + version: 19.1.0 + resolution: "@octokit/openapi-types@npm:19.1.0" + checksum: ae8081f52b797b91a12d4f6cddc475699c9d34b06645b337adc77d30b583d8fe8506597a45c42f8f1a96bfb2a9d092cee257d8a65d718bfeed23a0d153448eea + languageName: node + linkType: hard + "@octokit/request-error@npm:^3.0.0": version: 3.0.3 resolution: "@octokit/request-error@npm:3.0.3" @@ -880,6 +954,17 @@ __metadata: languageName: node linkType: hard +"@octokit/request-error@npm:^5.0.0": + version: 5.0.1 + resolution: "@octokit/request-error@npm:5.0.1" + dependencies: + "@octokit/types": ^12.0.0 + deprecation: ^2.0.0 + once: ^1.4.0 + checksum: e72a4627120de345b54876a1f007664095e5be9d624fce2e14fccf7668cd8f5e4929d444d8fc085d48e1fb5cd548538453974aab129a669101110d6679dce6c6 + languageName: node + linkType: hard + "@octokit/request@npm:^6.0.0": version: 6.2.8 resolution: "@octokit/request@npm:6.2.8" @@ -894,6 +979,27 @@ __metadata: languageName: node linkType: hard +"@octokit/request@npm:^8.0.1, @octokit/request@npm:^8.0.2": + version: 8.1.6 + resolution: "@octokit/request@npm:8.1.6" + dependencies: + "@octokit/endpoint": ^9.0.0 + "@octokit/request-error": ^5.0.0 + "@octokit/types": ^12.0.0 + universal-user-agent: ^6.0.0 + checksum: ef84418e0b1f28335c105bca2b1518b04797791761024d26f80f60a528cdcf468baf9897fd34f535c42af0643a598884f882bc832e68edbfe1ea530c2df563a4 + languageName: node + linkType: hard + +"@octokit/types@npm:^12.0.0": + version: 12.4.0 + resolution: "@octokit/types@npm:12.4.0" + dependencies: + "@octokit/openapi-types": ^19.1.0 + checksum: b52b3fd8af307a1868846991f8376548a790814b20639dee1110271a768c0489081970df893ca2230f6285066003230d22f5877eeac90418971a475c79808241 + languageName: node + linkType: hard + "@octokit/types@npm:^9.0.0": version: 9.3.2 resolution: "@octokit/types@npm:9.3.2" @@ -1826,6 +1932,23 @@ __metadata: languageName: node linkType: hard +"docblock-generator@workspace:packages/docblock-generator": + version: 0.0.0-use.local + resolution: "docblock-generator@workspace:packages/docblock-generator" + dependencies: + "@octokit/core": ^5.0.2 + "@types/node": ^20.9.4 + commander: ^11.1.0 + dotenv: ^16.3.1 + eslint: ^8.56.0 + minimatch: ^9.0.3 + ts-node: ^10.9.1 + typescript: 5.2 + bin: + workflow-diagrams-generator: dist/index.js + languageName: unknown + linkType: soft + "docs-util@workspace:.": version: 0.0.0-use.local resolution: "docs-util@workspace:." @@ -1858,6 +1981,13 @@ __metadata: languageName: node linkType: hard +"dotenv@npm:^16.3.1": + version: 16.3.1 + resolution: "dotenv@npm:16.3.1" + checksum: b95ff1bbe624ead85a3cd70dbd827e8e06d5f05f716f2d0cbc476532d54c7c9469c3bc4dd93ea519f6ad711cb522c00ac9a62b6eb340d5affae8008facc3fbd7 + languageName: node + linkType: hard + "dset@npm:^3.1.2": version: 3.1.3 resolution: "dset@npm:3.1.3" @@ -2051,6 +2181,54 @@ __metadata: languageName: node linkType: hard +"eslint@npm:^8.56.0": + version: 8.56.0 + resolution: "eslint@npm:8.56.0" + dependencies: + "@eslint-community/eslint-utils": ^4.2.0 + "@eslint-community/regexpp": ^4.6.1 + "@eslint/eslintrc": ^2.1.4 + "@eslint/js": 8.56.0 + "@humanwhocodes/config-array": ^0.11.13 + "@humanwhocodes/module-importer": ^1.0.1 + "@nodelib/fs.walk": ^1.2.8 + "@ungap/structured-clone": ^1.2.0 + ajv: ^6.12.4 + chalk: ^4.0.0 + cross-spawn: ^7.0.2 + debug: ^4.3.2 + doctrine: ^3.0.0 + escape-string-regexp: ^4.0.0 + eslint-scope: ^7.2.2 + eslint-visitor-keys: ^3.4.3 + espree: ^9.6.1 + esquery: ^1.4.2 + esutils: ^2.0.2 + fast-deep-equal: ^3.1.3 + file-entry-cache: ^6.0.1 + find-up: ^5.0.0 + glob-parent: ^6.0.2 + globals: ^13.19.0 + graphemer: ^1.4.0 + ignore: ^5.2.0 + imurmurhash: ^0.1.4 + is-glob: ^4.0.0 + is-path-inside: ^3.0.3 + js-yaml: ^4.1.0 + json-stable-stringify-without-jsonify: ^1.0.1 + levn: ^0.4.1 + lodash.merge: ^4.6.2 + minimatch: ^3.1.2 + natural-compare: ^1.4.0 + optionator: ^0.9.3 + strip-ansi: ^6.0.1 + text-table: ^0.2.0 + bin: + eslint: bin/eslint.js + checksum: 2be598f7da1339d045ad933ffd3d4742bee610515cd2b0d9a2b8b729395a01d4e913552fff555b559fccaefd89d7b37632825789d1b06470608737ae69ab43fb + languageName: node + linkType: hard + "esm@npm:^3.2.25": version: 3.2.25 resolution: "esm@npm:3.2.25" diff --git a/package.json b/package.json index b4a476e036..6313fa0d9b 100644 --- a/package.json +++ b/package.json @@ -26,8 +26,8 @@ "@babel/preset-react": "^7.18.6", "@babel/register": "^7.11.5", "@babel/runtime": "^7.11.2", - "@typescript-eslint/eslint-plugin": "^5.53.0", - "@typescript-eslint/parser": "^5.53.0", + "@typescript-eslint/eslint-plugin": "^6.19.0", + "@typescript-eslint/parser": "^6.19.0", "axios": "^0.21.4", "axios-mock-adapter": "^1.19.0", "babel-jest": "^26.6.3", @@ -67,6 +67,7 @@ "hooks:uninstall": "husky uninstall", "build": "turbo run build --concurrency=50% --no-daemon", "lint": "eslint --ignore-path .eslintignore --ext .js,.ts,.tsx .", + "lint:path": "eslint --ignore-path .eslintignore --ext .js,.ts,.tsx", "prettier": "prettier", "jest": "jest", "test": "turbo run test --concurrency=50% --no-daemon", diff --git a/packages/create-medusa-app/package.json b/packages/create-medusa-app/package.json index bb8538f14d..c088da5029 100644 --- a/packages/create-medusa-app/package.json +++ b/packages/create-medusa-app/package.json @@ -41,8 +41,8 @@ "@types/uuid": "^9.0.1", "@types/validator": "^13.7.17", "@types/wait-on": "^5.3.1", - "@typescript-eslint/eslint-plugin": "^5.59.5", - "@typescript-eslint/parser": "^5.59.5", + "@typescript-eslint/eslint-plugin": "^6.19.0", + "@typescript-eslint/parser": "^6.19.0", "configstore": "^6.0.0", "eslint": "^8.40.0", "eslint-config-google": "^0.14.0", diff --git a/packages/medusa-react/src/hooks/admin/auth/mutations.ts b/packages/medusa-react/src/hooks/admin/auth/mutations.ts index 4556ef5b08..68d985a644 100644 --- a/packages/medusa-react/src/hooks/admin/auth/mutations.ts +++ b/packages/medusa-react/src/hooks/admin/auth/mutations.ts @@ -41,9 +41,6 @@ import { adminAuthKeys } from "./queries" * @category Mutations */ export const useAdminLogin = ( - /** - * stuff again - */ options?: UseMutationOptions, Error, AdminPostAuthReq> ) => { const { client } = useMedusa() diff --git a/packages/medusa-react/src/hooks/admin/batch-jobs/mutations.ts b/packages/medusa-react/src/hooks/admin/batch-jobs/mutations.ts index 46c73d2a06..a5aff59c60 100644 --- a/packages/medusa-react/src/hooks/admin/batch-jobs/mutations.ts +++ b/packages/medusa-react/src/hooks/admin/batch-jobs/mutations.ts @@ -110,8 +110,6 @@ export const useAdminCancelBatchJob = ( } /** - * @reactMutationHook - * * When a batch job is created, it's not executed automatically if `dry_run` is set to `true`. This hook confirms that the batch job should be executed. * * @example diff --git a/packages/types/src/shared-context.ts b/packages/types/src/shared-context.ts index 0e414ec420..0779729e25 100644 --- a/packages/types/src/shared-context.ts +++ b/packages/types/src/shared-context.ts @@ -2,8 +2,8 @@ import { EntityManager } from "typeorm" /** * @interface - * - * A shared context object that is used to share resources between the application and the module. + * + * A context used to share resources, such as transaction manager, between the application and the module. */ export type SharedContext = { /** @@ -18,8 +18,8 @@ export type SharedContext = { /** * @interface - * - * A shared context object that is used to share resources between the application and the module. + * + * A context used to share resources, such as transaction manager, between the application and the module. */ export type Context = { /** diff --git a/yarn.lock b/yarn.lock index 8131536a76..115f392aff 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5579,7 +5579,7 @@ __metadata: languageName: node linkType: hard -"@eslint-community/eslint-utils@npm:^4.2.0": +"@eslint-community/eslint-utils@npm:^4.2.0, @eslint-community/eslint-utils@npm:^4.4.0": version: 4.4.0 resolution: "@eslint-community/eslint-utils@npm:4.4.0" dependencies: @@ -5597,6 +5597,13 @@ __metadata: languageName: node linkType: hard +"@eslint-community/regexpp@npm:^4.5.1": + version: 4.10.0 + resolution: "@eslint-community/regexpp@npm:4.10.0" + checksum: c5f60ef1f1ea7649fa7af0e80a5a79f64b55a8a8fa5086de4727eb4c86c652aedee407a9c143b8995d2c0b2d75c1222bec9ba5d73dbfc1f314550554f0979ef4 + languageName: node + linkType: hard + "@eslint/eslintrc@npm:^0.4.3": version: 0.4.3 resolution: "@eslint/eslintrc@npm:0.4.3" @@ -17422,6 +17429,13 @@ __metadata: languageName: node linkType: hard +"@types/json-schema@npm:^7.0.12": + version: 7.0.15 + resolution: "@types/json-schema@npm:7.0.15" + checksum: a996a745e6c5d60292f36731dd41341339d4eeed8180bb09226e5c8d23759067692b1d88e5d91d72ee83dfc00d3aca8e7bd43ea120516c17922cbcb7c3e252db + languageName: node + linkType: hard + "@types/json5@npm:^0.0.29": version: 0.0.29 resolution: "@types/json5@npm:0.0.29" @@ -18264,27 +18278,28 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/eslint-plugin@npm:^5.53.0, @typescript-eslint/eslint-plugin@npm:^5.59.5": - version: 5.60.1 - resolution: "@typescript-eslint/eslint-plugin@npm:5.60.1" +"@typescript-eslint/eslint-plugin@npm:^6.19.0": + version: 6.19.0 + resolution: "@typescript-eslint/eslint-plugin@npm:6.19.0" dependencies: - "@eslint-community/regexpp": ^4.4.0 - "@typescript-eslint/scope-manager": 5.60.1 - "@typescript-eslint/type-utils": 5.60.1 - "@typescript-eslint/utils": 5.60.1 + "@eslint-community/regexpp": ^4.5.1 + "@typescript-eslint/scope-manager": 6.19.0 + "@typescript-eslint/type-utils": 6.19.0 + "@typescript-eslint/utils": 6.19.0 + "@typescript-eslint/visitor-keys": 6.19.0 debug: ^4.3.4 - grapheme-splitter: ^1.0.4 - ignore: ^5.2.0 - natural-compare-lite: ^1.4.0 - semver: ^7.3.7 - tsutils: ^3.21.0 + graphemer: ^1.4.0 + ignore: ^5.2.4 + natural-compare: ^1.4.0 + semver: ^7.5.4 + ts-api-utils: ^1.0.1 peerDependencies: - "@typescript-eslint/parser": ^5.0.0 - eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 + "@typescript-eslint/parser": ^6.0.0 || ^6.0.0-alpha + eslint: ^7.0.0 || ^8.0.0 peerDependenciesMeta: typescript: optional: true - checksum: 1861e7fde48019ecae9acbc654d24f746e6a375f11f6f924e2ff3c9aa52528744a003da14592fafdbc407cf58b8cf7d0e42c624f3de06cb7bee5d8a31879ed79 + checksum: ab1a5ace6663b0c6d2418e321328fa28aa4bdc4b5fae257addec01346fb3a9c2d3a2960ade0f7114e6974c513a28632c9e8e602333cc0fab3135c445babdef59 languageName: node linkType: hard @@ -18321,20 +18336,21 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/parser@npm:^5.53.0, @typescript-eslint/parser@npm:^5.59.5": - version: 5.60.1 - resolution: "@typescript-eslint/parser@npm:5.60.1" +"@typescript-eslint/parser@npm:^6.19.0": + version: 6.19.0 + resolution: "@typescript-eslint/parser@npm:6.19.0" dependencies: - "@typescript-eslint/scope-manager": 5.60.1 - "@typescript-eslint/types": 5.60.1 - "@typescript-eslint/typescript-estree": 5.60.1 + "@typescript-eslint/scope-manager": 6.19.0 + "@typescript-eslint/types": 6.19.0 + "@typescript-eslint/typescript-estree": 6.19.0 + "@typescript-eslint/visitor-keys": 6.19.0 debug: ^4.3.4 peerDependencies: - eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 + eslint: ^7.0.0 || ^8.0.0 peerDependenciesMeta: typescript: optional: true - checksum: b44d041bb46908078df898ab1d8a0da0a58901caced0bb657af9d48c6bb54c090e565cc31d7526a27f25faeb25315fdec648873b2527feed043532a053914dd2 + checksum: d547bfb1aaed112cfc0f9f0be8506a280952ba3b61be42b749352139361bd94e4a47fa043d819e19c6a498cacbd8bb36a46e3628c436a7e2009e7ac27afc8861 languageName: node linkType: hard @@ -18348,16 +18364,6 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/scope-manager@npm:5.60.1": - version: 5.60.1 - resolution: "@typescript-eslint/scope-manager@npm:5.60.1" - dependencies: - "@typescript-eslint/types": 5.60.1 - "@typescript-eslint/visitor-keys": 5.60.1 - checksum: 50164675adb4850354a8e0297d498b59283eee961e10e0c00f9d664e03b464a9de4dc7a9d84c534c84df6ac3ea6f32238e2df9528374104bd66678b1ec9fbfec - languageName: node - linkType: hard - "@typescript-eslint/scope-manager@npm:5.62.0": version: 5.62.0 resolution: "@typescript-eslint/scope-manager@npm:5.62.0" @@ -18368,20 +18374,30 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/type-utils@npm:5.60.1": - version: 5.60.1 - resolution: "@typescript-eslint/type-utils@npm:5.60.1" +"@typescript-eslint/scope-manager@npm:6.19.0": + version: 6.19.0 + resolution: "@typescript-eslint/scope-manager@npm:6.19.0" dependencies: - "@typescript-eslint/typescript-estree": 5.60.1 - "@typescript-eslint/utils": 5.60.1 + "@typescript-eslint/types": 6.19.0 + "@typescript-eslint/visitor-keys": 6.19.0 + checksum: 1ec7b9dedca7975f0aa4543c1c382f7d6131411bd443a5f9b96f137acb6adb450888ed13c95f6d26546b682b2e0579ce8a1c883fdbe2255dc0b61052193b8243 + languageName: node + linkType: hard + +"@typescript-eslint/type-utils@npm:6.19.0": + version: 6.19.0 + resolution: "@typescript-eslint/type-utils@npm:6.19.0" + dependencies: + "@typescript-eslint/typescript-estree": 6.19.0 + "@typescript-eslint/utils": 6.19.0 debug: ^4.3.4 - tsutils: ^3.21.0 + ts-api-utils: ^1.0.1 peerDependencies: - eslint: "*" + eslint: ^7.0.0 || ^8.0.0 peerDependenciesMeta: typescript: optional: true - checksum: ef07f5c84636b8a9ff68dbc093ba6bce32d0e0f56831b214c2df3ff189502ae103c73bda7eb5e9f9ca21d3492dc851b7ac4b2cb83bdd6fbf5a9fe21068b404f4 + checksum: 5b146b985481e587122026c703ac9f537ad7e90eee1dca814971bca0d7e4a5d4ff9861fb4bf749014c28c6a4fbb4a01a4527355961315eb9501f3569f8e8dd38 languageName: node linkType: hard @@ -18392,13 +18408,6 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/types@npm:5.60.1": - version: 5.60.1 - resolution: "@typescript-eslint/types@npm:5.60.1" - checksum: 4be7654356f9b79fb942ae22196b518f30e20b07588355df22c85dfdcdaafe02f312b473de67af34fbb2283182d0d337aa65312f1117c6f8bf635cc427944c23 - languageName: node - linkType: hard - "@typescript-eslint/types@npm:5.62.0": version: 5.62.0 resolution: "@typescript-eslint/types@npm:5.62.0" @@ -18406,6 +18415,13 @@ __metadata: languageName: node linkType: hard +"@typescript-eslint/types@npm:6.19.0": + version: 6.19.0 + resolution: "@typescript-eslint/types@npm:6.19.0" + checksum: 6f81860a3c14df55232c2e6dec21fb166867b9f30b3c3369b325aef5ee1c7e41e827c0504654daa49c8ff1a3a9ca9d9bfe76786882b6212a7c1b58991a9c80b9 + languageName: node + linkType: hard + "@typescript-eslint/typescript-estree@npm:4.33.0": version: 4.33.0 resolution: "@typescript-eslint/typescript-estree@npm:4.33.0" @@ -18424,24 +18440,6 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/typescript-estree@npm:5.60.1": - version: 5.60.1 - resolution: "@typescript-eslint/typescript-estree@npm:5.60.1" - dependencies: - "@typescript-eslint/types": 5.60.1 - "@typescript-eslint/visitor-keys": 5.60.1 - debug: ^4.3.4 - globby: ^11.1.0 - is-glob: ^4.0.3 - semver: ^7.3.7 - tsutils: ^3.21.0 - peerDependenciesMeta: - typescript: - optional: true - checksum: 09933d3c1b1bacf7e043d33a7e134489650fca7b7b965b633a1c526453f20478b38c4aa9b7c6da269d2a43f26608110d4c129eec917c4561431149dae82c3b9d - languageName: node - linkType: hard - "@typescript-eslint/typescript-estree@npm:5.62.0": version: 5.62.0 resolution: "@typescript-eslint/typescript-estree@npm:5.62.0" @@ -18460,21 +18458,39 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/utils@npm:5.60.1": - version: 5.60.1 - resolution: "@typescript-eslint/utils@npm:5.60.1" +"@typescript-eslint/typescript-estree@npm:6.19.0": + version: 6.19.0 + resolution: "@typescript-eslint/typescript-estree@npm:6.19.0" dependencies: - "@eslint-community/eslint-utils": ^4.2.0 - "@types/json-schema": ^7.0.9 - "@types/semver": ^7.3.12 - "@typescript-eslint/scope-manager": 5.60.1 - "@typescript-eslint/types": 5.60.1 - "@typescript-eslint/typescript-estree": 5.60.1 - eslint-scope: ^5.1.1 - semver: ^7.3.7 + "@typescript-eslint/types": 6.19.0 + "@typescript-eslint/visitor-keys": 6.19.0 + debug: ^4.3.4 + globby: ^11.1.0 + is-glob: ^4.0.3 + minimatch: 9.0.3 + semver: ^7.5.4 + ts-api-utils: ^1.0.1 + peerDependenciesMeta: + typescript: + optional: true + checksum: 5b365f009e43c7beafdbb7d8ecad78ee1087b0a4338cd9ec695eed514b7b4c1089e56239761139ddae629ec0ce8d428840c6ebfeea3618d2efe00c84f8794da5 + languageName: node + linkType: hard + +"@typescript-eslint/utils@npm:6.19.0": + version: 6.19.0 + resolution: "@typescript-eslint/utils@npm:6.19.0" + dependencies: + "@eslint-community/eslint-utils": ^4.4.0 + "@types/json-schema": ^7.0.12 + "@types/semver": ^7.5.0 + "@typescript-eslint/scope-manager": 6.19.0 + "@typescript-eslint/types": 6.19.0 + "@typescript-eslint/typescript-estree": 6.19.0 + semver: ^7.5.4 peerDependencies: - eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 - checksum: cb408bd67dd5be3a3585b67ac19f60e9feb309a56782924b314077f7dc77cb10e690fdb4a7ac643784e8fab30bc04d6f23935c1aed6d08233f8dd8604782ac27 + eslint: ^7.0.0 || ^8.0.0 + checksum: 343ff4cd4f7e102df8c46b41254d017a33d95df76455531fda679fdb92aebb9c111df8ee9ab54972e73c1e8fad9dd7e421001233f0aee8115384462b0821852e languageName: node linkType: hard @@ -18506,16 +18522,6 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/visitor-keys@npm:5.60.1": - version: 5.60.1 - resolution: "@typescript-eslint/visitor-keys@npm:5.60.1" - dependencies: - "@typescript-eslint/types": 5.60.1 - eslint-visitor-keys: ^3.3.0 - checksum: e70bd584ff0eef1c8739e3457e7402485acc06aba468ff8f191f4546aaed93ec91f309bb567a3d8bbd9a4aec030f3b73c8e8df085bb82cbb8ea9a0209c7355f8 - languageName: node - linkType: hard - "@typescript-eslint/visitor-keys@npm:5.62.0": version: 5.62.0 resolution: "@typescript-eslint/visitor-keys@npm:5.62.0" @@ -18526,6 +18532,16 @@ __metadata: languageName: node linkType: hard +"@typescript-eslint/visitor-keys@npm:6.19.0": + version: 6.19.0 + resolution: "@typescript-eslint/visitor-keys@npm:6.19.0" + dependencies: + "@typescript-eslint/types": 6.19.0 + eslint-visitor-keys: ^3.4.1 + checksum: bb34e922e018aadf34866995ea5949d6623f184cc4f6470ab05767dd208ffabb003b7dc3872199714574b7f10afe89d49c6f89a4e8d086edea82be73e189f1bb + languageName: node + linkType: hard + "@uiw/react-json-view@npm:2.0.0-alpha.10": version: 2.0.0-alpha.10 resolution: "@uiw/react-json-view@npm:2.0.0-alpha.10" @@ -23822,8 +23838,8 @@ __metadata: "@types/uuid": ^9.0.1 "@types/validator": ^13.7.17 "@types/wait-on": ^5.3.1 - "@typescript-eslint/eslint-plugin": ^5.59.5 - "@typescript-eslint/parser": ^5.59.5 + "@typescript-eslint/eslint-plugin": ^6.19.0 + "@typescript-eslint/parser": ^6.19.0 boxen: ^5 chalk: ^5.2.0 commander: ^10.0.1 @@ -38265,6 +38281,15 @@ __metadata: languageName: node linkType: hard +"minimatch@npm:9.0.3": + version: 9.0.3 + resolution: "minimatch@npm:9.0.3" + dependencies: + brace-expansion: ^2.0.1 + checksum: 85f407dcd38ac3e180f425e86553911d101455ca3ad5544d6a7cec16286657e4f8a9aa6695803025c55e31e35a91a2252b5dc8e7d527211278b8b65b4dbd5eac + languageName: node + linkType: hard + "minimatch@npm:^5.0.1": version: 5.1.6 resolution: "minimatch@npm:5.1.6" @@ -38890,13 +38915,6 @@ __metadata: languageName: node linkType: hard -"natural-compare-lite@npm:^1.4.0": - version: 1.4.0 - resolution: "natural-compare-lite@npm:1.4.0" - checksum: f6cef26f5044515754802c0fc475d81426f3b90fe88c20fabe08771ce1f736ce46e0397c10acb569a4dd0acb84c7f1ee70676122f95d5bfdd747af3a6c6bbaa8 - languageName: node - linkType: hard - "natural-compare@npm:^1.4.0": version: 1.4.0 resolution: "natural-compare@npm:1.4.0" @@ -45272,8 +45290,8 @@ __metadata: "@babel/runtime": ^7.11.2 "@changesets/changelog-github": ^0.4.8 "@changesets/cli": ^2.26.0 - "@typescript-eslint/eslint-plugin": ^5.53.0 - "@typescript-eslint/parser": ^5.53.0 + "@typescript-eslint/eslint-plugin": ^6.19.0 + "@typescript-eslint/parser": ^6.19.0 axios: ^0.21.4 axios-mock-adapter: ^1.19.0 babel-jest: ^26.6.3 @@ -48723,6 +48741,15 @@ __metadata: languageName: node linkType: hard +"ts-api-utils@npm:^1.0.1": + version: 1.0.3 + resolution: "ts-api-utils@npm:1.0.3" + peerDependencies: + typescript: ">=4.2.0" + checksum: 9408338819c3aca2a709f0bc54e3f874227901506cacb1163612a6c8a43df224174feb965a5eafdae16f66fc68fd7bfee8d3275d0fa73fbb8699e03ed26520c9 + languageName: node + linkType: hard + "ts-clone-node@npm:^3.0.0": version: 3.0.0 resolution: "ts-clone-node@npm:3.0.0"