diff --git a/www/utils/generated/events-output.json b/www/utils/generated/events-output.json new file mode 100644 index 0000000000..407ac9e833 --- /dev/null +++ b/www/utils/generated/events-output.json @@ -0,0 +1,675 @@ +[ + { + "name": "cart.created", + "parentName": "CartWorkflowEvents", + "propertyName": "CREATED", + "payload": "{\n id, // The ID of the cart\n}", + "description": "Emitted when a cart is created.", + "workflows": [ + "createCartWorkflow" + ], + "deprecated": false + }, + { + "name": "cart.updated", + "parentName": "CartWorkflowEvents", + "propertyName": "UPDATED", + "payload": "{\n id, // The ID of the cart\n}", + "description": "Emitted when a cart's details are updated.", + "workflows": [ + "updateLineItemInCartWorkflow", + "updateCartWorkflow", + "addToCartWorkflow", + "addShippingMethodToCartWorkflow" + ], + "deprecated": false + }, + { + "name": "cart.customer_updated", + "parentName": "CartWorkflowEvents", + "propertyName": "CUSTOMER_UPDATED", + "payload": "{\n id, // The ID of the cart\n}", + "description": "Emitted when the customer in the cart is updated.", + "workflows": [ + "updateCartWorkflow" + ], + "deprecated": false + }, + { + "name": "cart.region_updated", + "parentName": "CartWorkflowEvents", + "propertyName": "REGION_UPDATED", + "payload": "{\n id, // The ID of the cart\n}", + "workflows": [ + "updateCartWorkflow" + ], + "deprecated": false + }, + { + "name": "customer.created", + "parentName": "CustomerWorkflowEvents", + "propertyName": "CREATED", + "payload": "[{\n id, // The ID of the customer\n}]", + "description": "Emitted when a customer is created.", + "workflows": [ + "createCustomersWorkflow", + "createCustomerAccountWorkflow" + ], + "deprecated": false + }, + { + "name": "customer.updated", + "parentName": "CustomerWorkflowEvents", + "propertyName": "UPDATED", + "payload": "[{\n id, // The ID of the customer\n}]", + "description": "Emitted when a customer is updated.", + "workflows": [ + "updateCustomersWorkflow" + ], + "deprecated": false + }, + { + "name": "customer.deleted", + "parentName": "CustomerWorkflowEvents", + "propertyName": "DELETED", + "payload": "[{\n id, // The ID of the customer\n}]", + "description": "Emitted when a customer is deleted.", + "workflows": [ + "deleteCustomersWorkflow", + "removeCustomerAccountWorkflow" + ], + "deprecated": false + }, + { + "name": "order.updated", + "parentName": "OrderWorkflowEvents", + "propertyName": "UPDATED", + "payload": "{\n id, // The ID of the order\n}", + "description": "Emitted when the details of an order or draft order is updated. This\ndoesn't include updates made by an edit.", + "workflows": [ + "updateOrderWorkflow", + "updateDraftOrderWorkflow" + ], + "deprecated": false + }, + { + "name": "order.placed", + "parentName": "OrderWorkflowEvents", + "propertyName": "PLACED", + "payload": "{\n id, // The ID of the order\n}", + "description": "Emitted when an order is placed, or when a draft order is converted to an\norder.", + "workflows": [ + "completeCartWorkflow", + "convertDraftOrderWorkflow", + "processPaymentWorkflow" + ], + "deprecated": false + }, + { + "name": "order.canceled", + "parentName": "OrderWorkflowEvents", + "propertyName": "CANCELED", + "payload": "{\n id, // The ID of the order\n}", + "description": "Emitted when an order is canceld.", + "workflows": [ + "cancelOrderWorkflow" + ], + "deprecated": false + }, + { + "name": "order.completed", + "parentName": "OrderWorkflowEvents", + "propertyName": "COMPLETED", + "payload": "[{\n id, // The ID of the order\n}]", + "description": "Emitted when orders are completed.", + "workflows": [ + "completeOrderWorkflow" + ], + "deprecated": false + }, + { + "name": "order.archived", + "parentName": "OrderWorkflowEvents", + "propertyName": "ARCHIVED", + "payload": "[{\n id, // The ID of the order\n}]", + "description": "Emitted when an order is archived.", + "workflows": [ + "archiveOrderWorkflow" + ], + "deprecated": false + }, + { + "name": "order.fulfillment_created", + "parentName": "OrderWorkflowEvents", + "propertyName": "FULFILLMENT_CREATED", + "payload": "{\n order_id, // The ID of the order\n fulfillment_id, // The ID of the fulfillment\n no_notification, // Whether to notify the customer\n}", + "description": "Emitted when a fulfillment is created for an order.", + "workflows": [ + "createOrderFulfillmentWorkflow" + ], + "deprecated": false + }, + { + "name": "order.fulfillment_canceled", + "parentName": "OrderWorkflowEvents", + "propertyName": "FULFILLMENT_CANCELED", + "payload": "{\n order_id, // The ID of the order\n fulfillment_id, // The ID of the fulfillment\n no_notification, // Whether to notify the customer\n}", + "description": "Emitted when an order's fulfillment is canceled.", + "workflows": [ + "cancelOrderFulfillmentWorkflow" + ], + "deprecated": false + }, + { + "name": "order.return_requested", + "parentName": "OrderWorkflowEvents", + "propertyName": "RETURN_REQUESTED", + "payload": "{\n order_id, // The ID of the order\n return_id, // The ID of the return\n}", + "description": "Emitted when a return request is confirmed.", + "workflows": [ + "createAndCompleteReturnOrderWorkflow", + "confirmReturnRequestWorkflow" + ], + "deprecated": false + }, + { + "name": "order.return_received", + "parentName": "OrderWorkflowEvents", + "propertyName": "RETURN_RECEIVED", + "payload": "{\n order_id, // The ID of the order\n return_id, // The ID of the return\n}", + "description": "Emitted when a return is marked as received.", + "workflows": [ + "createAndCompleteReturnOrderWorkflow", + "confirmReturnReceiveWorkflow" + ], + "deprecated": false + }, + { + "name": "order.claim_created", + "parentName": "OrderWorkflowEvents", + "propertyName": "CLAIM_CREATED", + "payload": "{\n order_id, // The ID of the order\n claim_id, // The ID of the claim\n}", + "description": "Emitted when a claim is created for an order.", + "workflows": [ + "confirmClaimRequestWorkflow" + ], + "deprecated": false + }, + { + "name": "order.exchange_created", + "parentName": "OrderWorkflowEvents", + "propertyName": "EXCHANGE_CREATED", + "payload": "{\n order_id, // The ID of the order\n exchange_id, // The ID of the exchange\n}", + "description": "Emitted when an exchange is created for an order.", + "workflows": [ + "confirmExchangeRequestWorkflow" + ], + "deprecated": false + }, + { + "name": "order.transfer_requested", + "parentName": "OrderWorkflowEvents", + "propertyName": "TRANSFER_REQUESTED", + "payload": "{\n id, // The ID of the order\n order_change_id, // The ID of the order change created for the transfer\n}", + "description": "Emitted when an order is requested to be transferred to\nanother customer.", + "workflows": [ + "requestOrderTransferWorkflow" + ], + "deprecated": false + }, + { + "name": "order-edit.requested", + "parentName": "OrderEditWorkflowEvents", + "propertyName": "REQUESTED", + "payload": "{\n order_id, // The ID of the order\n actions, // The actions to edit the order\n}", + "description": "Emitted when an order edit is requested.", + "workflows": [ + "requestOrderEditRequestWorkflow" + ], + "deprecated": false + }, + { + "name": "order-edit.confirmed", + "parentName": "OrderEditWorkflowEvents", + "propertyName": "CONFIRMED", + "payload": "{\n order_id, // The ID of the order\n actions, // The actions to edit the order\n}", + "description": "Emitted when an order edit request is confirmed.", + "workflows": [ + "confirmOrderEditRequestWorkflow" + ], + "deprecated": false + }, + { + "name": "order-edit.canceled", + "parentName": "OrderEditWorkflowEvents", + "propertyName": "CANCELED", + "payload": "{\n order_id, // The ID of the order\n actions, // The actions to edit the order\n}", + "description": "Emitted when an order edit request is canceled.", + "workflows": [ + "cancelBeginOrderEditWorkflow" + ], + "deprecated": false + }, + { + "name": "user.created", + "parentName": "UserWorkflowEvents", + "propertyName": "CREATED", + "payload": "[{\n id, // The ID of the user\n}]", + "description": "Emitted when users are created.", + "workflows": [ + "createUsersWorkflow", + "createUserAccountWorkflow", + "acceptInviteWorkflow" + ], + "deprecated": false + }, + { + "name": "user.updated", + "parentName": "UserWorkflowEvents", + "propertyName": "UPDATED", + "payload": "[{\n id, // The ID of the user\n}]", + "description": "Emitted when users are updated.", + "workflows": [ + "updateUsersWorkflow" + ], + "deprecated": false + }, + { + "name": "user.deleted", + "parentName": "UserWorkflowEvents", + "propertyName": "DELETED", + "payload": "[{\n id, // The ID of the user\n}]", + "description": "Emitted when users are deleted.", + "workflows": [ + "deleteUsersWorkflow", + "removeUserAccountWorkflow" + ], + "deprecated": false + }, + { + "name": "auth.password_reset", + "parentName": "AuthWorkflowEvents", + "propertyName": "PASSWORD_RESET", + "payload": "{\n entity_id, // The identifier of the user or customer. For example, an email address.\n actor_type, // The type of actor. For example, \"customer\", \"user\", or custom.\n token, // The generated token.\n}", + "description": "Emitted when a reset password token is generated. You can listen to this event\nto send a reset password email to the user or customer, for example.", + "workflows": [ + "generateResetPasswordTokenWorkflow" + ], + "deprecated": false + }, + { + "name": "sales-channel.created", + "parentName": "SalesChannelWorkflowEvents", + "propertyName": "CREATED", + "payload": "[{\n id, // The ID of the sales channel\n}]", + "description": "Emitted when sales channels are created.", + "workflows": [ + "createSalesChannelsWorkflow" + ], + "deprecated": false + }, + { + "name": "sales-channel.updated", + "parentName": "SalesChannelWorkflowEvents", + "propertyName": "UPDATED", + "payload": "[{\n id, // The ID of the sales channel\n}]", + "description": "Emitted when sales channels are updated.", + "workflows": [ + "updateSalesChannelsWorkflow" + ], + "deprecated": false + }, + { + "name": "sales-channel.deleted", + "parentName": "SalesChannelWorkflowEvents", + "propertyName": "DELETED", + "payload": "[{\n id, // The ID of the sales channel\n}]", + "description": "Emitted when sales channels are deleted.", + "workflows": [ + "deleteSalesChannelsWorkflow" + ], + "deprecated": false + }, + { + "name": "product-category.created", + "parentName": "ProductCategoryWorkflowEvents", + "propertyName": "CREATED", + "payload": "[{\n id, // The ID of the product category\n}]", + "description": "Emitted when product categories are created.", + "workflows": [ + "createProductCategoriesWorkflow" + ], + "deprecated": false + }, + { + "name": "product-category.updated", + "parentName": "ProductCategoryWorkflowEvents", + "propertyName": "UPDATED", + "payload": "[{\n id, // The ID of the product category\n}]", + "description": "Emitted when product categories are updated.", + "workflows": [ + "updateProductCategoriesWorkflow" + ], + "deprecated": false + }, + { + "name": "product-category.deleted", + "parentName": "ProductCategoryWorkflowEvents", + "propertyName": "DELETED", + "payload": "[{\n id, // The ID of the product category\n}]", + "description": "Emitted when product categories are deleted.", + "workflows": [ + "deleteProductCategoriesWorkflow" + ], + "deprecated": false + }, + { + "name": "product-collection.created", + "parentName": "ProductCollectionWorkflowEvents", + "propertyName": "CREATED", + "payload": "[{\n id, // The ID of the product collection\n}]", + "description": "Emitted when product collections are created.", + "workflows": [ + "createCollectionsWorkflow" + ], + "deprecated": false + }, + { + "name": "product-collection.updated", + "parentName": "ProductCollectionWorkflowEvents", + "propertyName": "UPDATED", + "payload": "[{\n id, // The ID of the product collection\n}]", + "description": "Emitted when product collections are updated.", + "workflows": [ + "updateCollectionsWorkflow" + ], + "deprecated": false + }, + { + "name": "product-collection.deleted", + "parentName": "ProductCollectionWorkflowEvents", + "propertyName": "DELETED", + "payload": "[{\n id, // The ID of the product collection\n}]", + "description": "Emitted when product collections are deleted.", + "workflows": [ + "deleteCollectionsWorkflow" + ], + "deprecated": false + }, + { + "name": "product-variant.updated", + "parentName": "ProductVariantWorkflowEvents", + "propertyName": "UPDATED", + "payload": "[{\n id, // The ID of the product variant\n}]", + "description": "Emitted when product variants are updated.", + "workflows": [ + "updateProductVariantsWorkflow", + "batchProductVariantsWorkflow" + ], + "deprecated": false + }, + { + "name": "product-variant.created", + "parentName": "ProductVariantWorkflowEvents", + "propertyName": "CREATED", + "payload": "[{\n id, // The ID of the product variant\n}]", + "description": "Emitted when product variants are created.", + "workflows": [ + "createProductVariantsWorkflow", + "createProductsWorkflow", + "batchProductVariantsWorkflow", + "batchProductsWorkflow", + "importProductsWorkflow" + ], + "deprecated": false + }, + { + "name": "product-variant.deleted", + "parentName": "ProductVariantWorkflowEvents", + "propertyName": "DELETED", + "payload": "[{\n id, // The ID of the product variant\n}]", + "description": "Emitted when product variants are deleted.", + "workflows": [ + "deleteProductVariantsWorkflow", + "batchProductVariantsWorkflow" + ], + "deprecated": false + }, + { + "name": "product.updated", + "parentName": "ProductWorkflowEvents", + "propertyName": "UPDATED", + "payload": "[{\n id, // The ID of the product\n}]", + "description": "Emitted when products are updated.", + "workflows": [ + "updateProductsWorkflow", + "batchProductsWorkflow", + "importProductsWorkflow" + ], + "deprecated": false + }, + { + "name": "product.created", + "parentName": "ProductWorkflowEvents", + "propertyName": "CREATED", + "payload": "[{\n id, // The ID of the product\n}]", + "description": "Emitted when products are created.", + "workflows": [ + "createProductsWorkflow", + "batchProductsWorkflow", + "importProductsWorkflow" + ], + "deprecated": false + }, + { + "name": "product.deleted", + "parentName": "ProductWorkflowEvents", + "propertyName": "DELETED", + "payload": "[{\n id, // The ID of the product\n}]", + "description": "Emitted when products are deleted.", + "workflows": [ + "deleteProductsWorkflow", + "batchProductsWorkflow", + "importProductsWorkflow" + ], + "deprecated": false + }, + { + "name": "product-type.updated", + "parentName": "ProductTypeWorkflowEvents", + "propertyName": "UPDATED", + "payload": "[{\n id, // The ID of the product type\n}]", + "description": "Emitted when product types are updated.", + "workflows": [ + "updateProductTypesWorkflow" + ], + "deprecated": false + }, + { + "name": "product-type.created", + "parentName": "ProductTypeWorkflowEvents", + "propertyName": "CREATED", + "payload": "[{\n id, // The ID of the product type\n}]", + "description": "Emitted when product types are created.", + "workflows": [ + "createProductTypesWorkflow" + ], + "deprecated": false + }, + { + "name": "product-type.deleted", + "parentName": "ProductTypeWorkflowEvents", + "propertyName": "DELETED", + "payload": "[{\n id, // The ID of the product type\n}]", + "description": "Emitted when product types are deleted.", + "workflows": [ + "deleteProductTypesWorkflow" + ], + "deprecated": false + }, + { + "name": "product-tag.updated", + "parentName": "ProductTagWorkflowEvents", + "propertyName": "UPDATED", + "payload": "[{\n id, // The ID of the product tag\n}]", + "description": "Emitted when product tags are updated.", + "workflows": [ + "updateProductTagsWorkflow" + ], + "deprecated": false + }, + { + "name": "product-tag.created", + "parentName": "ProductTagWorkflowEvents", + "propertyName": "CREATED", + "payload": "[{\n id, // The ID of the product tag\n}]", + "description": "Emitted when product tags are created.", + "workflows": [ + "createProductTagsWorkflow" + ], + "deprecated": false + }, + { + "name": "product-tag.deleted", + "parentName": "ProductTagWorkflowEvents", + "propertyName": "DELETED", + "payload": "[{\n id, // The ID of the product tag\n}]", + "description": "Emitted when product tags are deleted.", + "workflows": [ + "deleteProductTagsWorkflow" + ], + "deprecated": false + }, + { + "name": "product-option.updated", + "parentName": "ProductOptionWorkflowEvents", + "propertyName": "UPDATED", + "payload": "[{\n id, // The ID of the product option\n}]", + "description": "Emitted when product options are updated.", + "workflows": [ + "updateProductOptionsWorkflow" + ], + "deprecated": false + }, + { + "name": "product-option.created", + "parentName": "ProductOptionWorkflowEvents", + "propertyName": "CREATED", + "payload": "[{\n id, // The ID of the product option\n}]", + "description": "Emitted when product options are created.", + "workflows": [ + "createProductOptionsWorkflow" + ], + "deprecated": false + }, + { + "name": "product-option.deleted", + "parentName": "ProductOptionWorkflowEvents", + "propertyName": "DELETED", + "payload": "[{\n id, // The ID of the product option\n}]", + "description": "Emitted when product options are deleted.", + "workflows": [ + "deleteProductOptionsWorkflow" + ], + "deprecated": false + }, + { + "name": "invite.accepted", + "parentName": "InviteWorkflowEvents", + "propertyName": "ACCEPTED", + "payload": "{\n id, // The ID of the invite\n}", + "description": "Emitted when an invite is accepted.", + "workflows": [ + "acceptInviteWorkflow" + ], + "deprecated": false + }, + { + "name": "invite.created", + "parentName": "InviteWorkflowEvents", + "propertyName": "CREATED", + "payload": "[{\n id, // The ID of the invite\n}]", + "description": "Emitted when invites are created. You can listen to this event\nto send an email to the invited users, for example.", + "workflows": [ + "createInvitesWorkflow" + ], + "deprecated": false + }, + { + "name": "invite.deleted", + "parentName": "InviteWorkflowEvents", + "propertyName": "DELETED", + "payload": "[{\n id, // The ID of the invite\n}]", + "description": "Emitted when invites are deleted.", + "workflows": [ + "deleteInvitesWorkflow" + ], + "deprecated": false + }, + { + "name": "invite.resent", + "parentName": "InviteWorkflowEvents", + "propertyName": "RESENT", + "payload": "[{\n id, // The ID of the invite\n}]", + "description": "Emitted when invites should be resent because their token was\nrefreshed. You can listen to this event to send an email to the invited users,\nfor example.", + "workflows": [ + "refreshInviteTokensWorkflow" + ], + "deprecated": false + }, + { + "name": "region.updated", + "parentName": "RegionWorkflowEvents", + "propertyName": "UPDATED", + "payload": "[{\n id, // The ID of the region\n}]", + "description": "Emitted when regions are updated.", + "workflows": [ + "updateRegionsWorkflow" + ], + "deprecated": false + }, + { + "name": "region.created", + "parentName": "RegionWorkflowEvents", + "propertyName": "CREATED", + "payload": "[{\n id, // The ID of the region\n}]", + "description": "Emitted when regions are created.", + "workflows": [ + "createRegionsWorkflow" + ], + "deprecated": false + }, + { + "name": "region.deleted", + "parentName": "RegionWorkflowEvents", + "propertyName": "DELETED", + "payload": "[{\n id, // The ID of the region\n}]", + "description": "Emitted when regions are deleted.", + "workflows": [ + "deleteRegionsWorkflow" + ], + "deprecated": false + }, + { + "name": "shipment.created", + "parentName": "FulfillmentWorkflowEvents", + "propertyName": "SHIPMENT_CREATED", + "payload": "{\n id, // the ID of the shipment\n no_notification, // whether to notify the customer\n}", + "description": "Emitted when a shipment is created for an order.", + "workflows": [ + "createOrderShipmentWorkflow" + ], + "deprecated": false + }, + { + "name": "delivery.created", + "parentName": "FulfillmentWorkflowEvents", + "propertyName": "DELIVERY_CREATED", + "payload": "{\n id, // the ID of the fulfillment\n}", + "description": "Emitted when a fulfillment is marked as delivered.", + "workflows": [ + "markOrderFulfillmentAsDeliveredWorkflow" + ], + "deprecated": false + } +] \ No newline at end of file diff --git a/www/utils/packages/docs-generator/package.json b/www/utils/packages/docs-generator/package.json index 6c2975858e..4b11756067 100644 --- a/www/utils/packages/docs-generator/package.json +++ b/www/utils/packages/docs-generator/package.json @@ -7,9 +7,10 @@ "build": "tsc", "watch": "tsc --watch", "prepublishOnly": "cross-env NODE_ENV=production tsc --build", - "generate:oas": "yarn generate:route-examples && yarn start run ../../../../packages/medusa/src/api --type oas && yarn start clean:oas", + "generate:oas": "yarn generate:route-examples && yarn generate:events && yarn start run ../../../../packages/medusa/src/api --type oas && yarn start clean:oas", "generate:dml": "yarn start run ../../../../packages/modules --type dml && yarn start clean:dml", - "generate:route-examples": "yarn start run ../../../../packages/core/js-sdk/src --type route-examples" + "generate:route-examples": "yarn start run ../../../../packages/core/js-sdk/src --type route-examples", + "generate:events": "yarn start run ../../../../packages/core/utils/src/core-flows/events.ts --type events" }, "publishConfig": { "access": "public" @@ -23,6 +24,7 @@ "commander": "^11.1.0", "dotenv": "^16.3.1", "eslint": "8.56.0", + "glob": "^11.0.2", "minimatch": "^9.0.3", "openai": "^4.29.1", "openapi-types": "^12.1.3", diff --git a/www/utils/packages/docs-generator/src/classes/generators/events.ts b/www/utils/packages/docs-generator/src/classes/generators/events.ts new file mode 100644 index 0000000000..b623c9e239 --- /dev/null +++ b/www/utils/packages/docs-generator/src/classes/generators/events.ts @@ -0,0 +1,103 @@ +import ts from "typescript" +import EventsKindGenerator from "../kinds/events.js" +import AbstractGenerator from "./index.js" +import { GeneratorEvent } from "../helpers/generator-event-manager.js" +import { minimatch } from "minimatch" +import getBasePath from "../../utils/get-base-path.js" +import { getEventsOutputBasePath } from "../../utils/get-output-base-paths.js" + +class EventsGenerator extends AbstractGenerator { + protected eventsKindGenerator?: EventsKindGenerator + + async run() { + this.init() + + this.eventsKindGenerator = new EventsKindGenerator({ + checker: this.checker!, + generatorEventManager: this.generatorEventManager, + }) + + await Promise.all( + this.program!.getSourceFiles().map(async (file) => { + // Ignore .d.ts files + if (file.isDeclarationFile || !this.isFileIncluded(file.fileName)) { + return + } + + const fileNodes: ts.Node[] = [file] + + console.log(`[EVENTS] Generating for ${file.fileName}...`) + + // since typescript's compiler API doesn't support + // async processes, we have to retrieve the nodes first then + // traverse them separately. + const pushNodesToArr = (node: ts.Node) => { + fileNodes.push(node) + + ts.forEachChild(node, pushNodesToArr) + } + ts.forEachChild(file, pushNodesToArr) + + const events: Record[] = [] + await this.eventsKindGenerator!.populateWorkflows() + + const documentChild = async (node: ts.Node) => { + if ( + this.eventsKindGenerator!.isAllowed(node) && + this.eventsKindGenerator!.canDocumentNode(node) + ) { + const eventsJson = await this.eventsKindGenerator!.getDocBlock(node) + events.push(...JSON.parse(eventsJson)) + } + } + + await Promise.all( + fileNodes.map(async (node) => await documentChild(node)) + ) + + if (!this.options.dryRun) { + this.writeJson(events) + } + + this.generatorEventManager.emit(GeneratorEvent.FINISHED_GENERATE_EVENT) + console.log(`[EVENTS] Finished generating OAS for ${file.fileName}.`) + }) + ) + } + + /** + * Checks whether the specified file path is included in the program + * and is an API file. + * + * @param fileName - The file path to check + * @returns Whether the OAS generator can run on this file. + */ + isFileIncluded(fileName: string): boolean { + return ( + super.isFileIncluded(fileName) && + minimatch( + getBasePath(fileName), + "packages/core/utils/src/core-flows/events.ts", + { + matchBase: true, + } + ) + ) + } + + /** + * This method writes the DML JSON file. If the file already exists, it only updates + * the data model's object in the JSON file. + * + * @param filePath - The path of the file to write the DML JSON to. + * @param dataModelJson - The DML JSON. + */ + writeJson(events: Record[]) { + const filePath = getEventsOutputBasePath() + const eventsJson = JSON.stringify(events, null, 2) + + ts.sys.writeFile(filePath, eventsJson) + } +} + +export default EventsGenerator diff --git a/www/utils/packages/docs-generator/src/classes/kinds/events.ts b/www/utils/packages/docs-generator/src/classes/kinds/events.ts new file mode 100644 index 0000000000..0795e88543 --- /dev/null +++ b/www/utils/packages/docs-generator/src/classes/kinds/events.ts @@ -0,0 +1,180 @@ +import ts from "typescript" +import DefaultKindGenerator, { GetDocBlockOptions } from "./default.js" +import { glob } from "glob" +import getMonorepoRoot from "../../utils/get-monorepo-root.js" +import { readFile } from "fs/promises" + +class EventsKindGenerator extends DefaultKindGenerator { + protected allowedKinds: ts.SyntaxKind[] = [ts.SyntaxKind.VariableDeclaration] + public name = "events" + protected workflows: Record = {} + protected workflowsEmittingEvents: Record = {} + + isAllowed(node: ts.Node): node is ts.VariableDeclaration { + if ( + !super.isAllowed(node) || + !node.initializer || + !ts.isObjectLiteralExpression(node.initializer) + ) { + return false + } + + return node.initializer.properties.length > 0 + } + + async getDocBlock( + node: ts.VariableDeclaration | ts.Node, + options?: GetDocBlockOptions + ): Promise { + if (!this.isAllowed(node)) { + return await super.getDocBlock(node, options) + } + + const properties = (node.initializer as ts.ObjectLiteralExpression) + .properties + + const events: { + name: string + parentName: string + propertyName: string + payload: string + description?: string + workflows: string[] + version?: string + deprecated?: boolean + deprecated_message?: string + }[] = properties + .filter((property) => ts.isPropertyAssignment(property)) + .map((property) => { + const propertyAssignment = property as ts.PropertyAssignment + const eventVariableName = node.name.getText() + const eventPropertyName = propertyAssignment.name.getText() + const workflows = this.getWorkflowsUsingEvent({ + eventVariableName, + eventPropertyName, + }) + if (!workflows.length) { + return null + } + + const commentsAndTags = ts.getJSDocCommentsAndTags(propertyAssignment) + let payloadTag: ts.JSDocTag | undefined + let versionTag: ts.JSDocTag | undefined + let deprecatedTag: ts.JSDocTag | undefined + let description: string | undefined + commentsAndTags.forEach((comment) => { + if (!("tags" in comment)) { + return + } + + if (typeof comment.comment === "string") { + description = comment.comment + } + + comment.tags?.forEach((tag) => { + if (tag.tagName.getText() === "eventPayload") { + payloadTag = tag + } + + if (tag.tagName.getText() === "version") { + versionTag = tag + } + + if (tag.tagName.getText() === "deprecated") { + deprecatedTag = tag + } + }) + }) + + return { + name: propertyAssignment.initializer.getText().replaceAll(`"`, ""), + parentName: eventVariableName, + propertyName: eventPropertyName, + payload: (payloadTag?.comment as string) ?? "", + description, + workflows, + version: versionTag?.comment as string, + deprecated: deprecatedTag !== undefined, + deprecated_message: deprecatedTag?.comment as string, + } + }) + .filter((event) => event !== null) + + return JSON.stringify(events) + } + + getWorkflowsUsingEvent({ + eventVariableName, + eventPropertyName, + }: { + eventVariableName: string + eventPropertyName: string + }): string[] { + const eventName = `${eventVariableName}.${eventPropertyName}` + + const workflows = this.findWorkflowsUsingEvent(eventName) + return workflows + } + + async populateWorkflows() { + if (Object.keys(this.workflows).length > 0) { + return + } + + const files = await glob( + `${getMonorepoRoot()}/packages/core/core-flows/src/**/workflows/**/*.ts` + ) + + for (const file of files) { + const workflowFile = await readFile(file, "utf-8") + const workflowName = this.getWorkflowNameFromWorkflowFile(workflowFile) + if (!workflowName) { + continue + } + + this.workflows[workflowName] = workflowFile + if (workflowFile.includes("emitEventStep")) { + this.workflowsEmittingEvents[workflowName] = workflowFile + } + } + } + + getWorkflowNameFromWorkflowFile(workflowFile: string) { + const workflowNameMatch = workflowFile.match( + /export const\s+(\w+)\s*=\s*createWorkflow\(/ + ) + return workflowNameMatch ? workflowNameMatch[1] : null + } + + findWorkflowsUsingEvent(eventName: string) { + const workflows = Object.keys(this.workflowsEmittingEvents).filter( + (workflowName) => + this.workflowsEmittingEvents[workflowName].includes(eventName) + ) + + // find workflows using the extracted workflows + let newWorkflows: string[] = [...workflows] + while (newWorkflows.length > 0) { + // loop over the workflows and find new workflows that use the extracted workflows + const foundWorkflows: string[] = [] + for (const workflowName of newWorkflows) { + foundWorkflows.push( + ...Object.keys(this.workflows).filter( + (workflowKey) => + workflowKey !== workflowName && + this.workflows[workflowKey].match( + new RegExp(`${workflowName}[\n\\s]*\\.run`) + ) + ) + ) + } + + workflows.push(...foundWorkflows) + newWorkflows = foundWorkflows + } + + return workflows + } +} + +export default EventsKindGenerator diff --git a/www/utils/packages/docs-generator/src/commands/run-git-changes.ts b/www/utils/packages/docs-generator/src/commands/run-git-changes.ts index 0bd301441b..33e73c4552 100644 --- a/www/utils/packages/docs-generator/src/commands/run-git-changes.ts +++ b/www/utils/packages/docs-generator/src/commands/run-git-changes.ts @@ -6,6 +6,7 @@ import { CommonCliOptions } from "../types/index.js" import OasGenerator from "../classes/generators/oas.js" import DmlGenerator from "../classes/generators/dml.js" import RouteExamplesGenerator from "../classes/generators/route-examples.js" +import EventsGenerator from "../classes/generators/events.js" export default async function runGitChanges({ type, @@ -63,5 +64,14 @@ export default async function runGitChanges({ await routeExamplesGenerator.run() } + if (type === "all" || type === "events") { + const eventsGenerator = new EventsGenerator({ + paths: files, + ...options, + }) + + await eventsGenerator.run() + } + console.log(`Finished generating docs for ${files.length} files.`) } diff --git a/www/utils/packages/docs-generator/src/commands/run-git-commit.ts b/www/utils/packages/docs-generator/src/commands/run-git-commit.ts index d0c364144a..3b8f1ea914 100644 --- a/www/utils/packages/docs-generator/src/commands/run-git-commit.ts +++ b/www/utils/packages/docs-generator/src/commands/run-git-commit.ts @@ -7,6 +7,7 @@ import { CommonCliOptions } from "../types/index.js" import { GitManager } from "../classes/helpers/git-manager.js" import DmlGenerator from "../classes/generators/dml.js" import RouteExamplesGenerator from "../classes/generators/route-examples.js" +import EventsGenerator from "../classes/generators/events.js" export default async function ( commitSha: string, @@ -71,5 +72,14 @@ export default async function ( await routeExamplesGenerator.run() } + if (type === "all" || type === "events") { + const eventsGenerator = new EventsGenerator({ + paths: filteredFiles, + ...options, + }) + + await eventsGenerator.run() + } + console.log(`Finished generating docs for ${filteredFiles.length} files.`) } diff --git a/www/utils/packages/docs-generator/src/commands/run-release.ts b/www/utils/packages/docs-generator/src/commands/run-release.ts index d31aa85dad..e781eeb9e5 100644 --- a/www/utils/packages/docs-generator/src/commands/run-release.ts +++ b/www/utils/packages/docs-generator/src/commands/run-release.ts @@ -7,6 +7,7 @@ import OasGenerator from "../classes/generators/oas.js" import { CommonCliOptions } from "../types/index.js" import DmlGenerator from "../classes/generators/dml.js" import RouteExamplesGenerator from "../classes/generators/route-examples.js" +import EventsGenerator from "../classes/generators/events.js" export default async function ({ type, tag, ...options }: CommonCliOptions) { const gitManager = new GitManager() @@ -69,5 +70,14 @@ export default async function ({ type, tag, ...options }: CommonCliOptions) { await routeExamplesGenerator.run() } + if (type === "all" || type === "events") { + const eventsGenerator = new EventsGenerator({ + paths: filteredFiles, + ...options, + }) + + await eventsGenerator.run() + } + console.log(`Finished generating docs for ${filteredFiles.length} files.`) } diff --git a/www/utils/packages/docs-generator/src/commands/run.ts b/www/utils/packages/docs-generator/src/commands/run.ts index 028c2b1d8a..7bbdb26e32 100644 --- a/www/utils/packages/docs-generator/src/commands/run.ts +++ b/www/utils/packages/docs-generator/src/commands/run.ts @@ -1,5 +1,6 @@ import DmlGenerator from "../classes/generators/dml.js" import DocblockGenerator from "../classes/generators/docblock.js" +import EventsGenerator from "../classes/generators/events.js" import { Options } from "../classes/generators/index.js" import OasGenerator from "../classes/generators/oas.js" import RouteExamplesGenerator from "../classes/generators/route-examples.js" @@ -47,5 +48,14 @@ export default async function run( await routeExamplesGenerator.run() } + if (type === "all" || type === "events") { + const eventsGenerator = new EventsGenerator({ + paths, + ...options, + }) + + await eventsGenerator.run() + } + console.log(`Finished running.`) } diff --git a/www/utils/packages/docs-generator/src/index.ts b/www/utils/packages/docs-generator/src/index.ts index 911e92e351..56120700cd 100644 --- a/www/utils/packages/docs-generator/src/index.ts +++ b/www/utils/packages/docs-generator/src/index.ts @@ -14,7 +14,7 @@ program.name("docs-generator").description("Generate TSDoc doc-blocks") // define common options const typeOption = new Option("--type ", "The type of docs to generate.") - .choices(["all", "docs", "oas", "dml", "route-examples"]) + .choices(["all", "docs", "oas", "dml", "route-examples", "events"]) .default("all") const generateExamplesOption = new Option( diff --git a/www/utils/packages/docs-generator/src/types/index.d.ts b/www/utils/packages/docs-generator/src/types/index.d.ts index 39faaee6ee..b17fa5aea6 100644 --- a/www/utils/packages/docs-generator/src/types/index.d.ts +++ b/www/utils/packages/docs-generator/src/types/index.d.ts @@ -13,7 +13,7 @@ export declare type OpenApiOperation = Partial & { } export declare type CommonCliOptions = { - type: "all" | "oas" | "docs" | "dml" | "route-examples" + type: "all" | "oas" | "docs" | "dml" | "route-examples" | "events" generateExamples?: boolean tag?: string } diff --git a/www/utils/packages/docs-generator/src/utils/get-output-base-paths.ts b/www/utils/packages/docs-generator/src/utils/get-output-base-paths.ts index 89cd2f5a97..7d5ec839fe 100644 --- a/www/utils/packages/docs-generator/src/utils/get-output-base-paths.ts +++ b/www/utils/packages/docs-generator/src/utils/get-output-base-paths.ts @@ -15,6 +15,19 @@ export function getDmlOutputBasePath() { return path.join(getMonorepoRoot(), "www", "utils", "generated", "dml-output") } +/** + * Retrieves the base path to the `events-output` directory. + */ +export function getEventsOutputBasePath() { + return path.join( + getMonorepoRoot(), + "www", + "utils", + "generated", + "events-output.json" + ) +} + /** * Retrieves the base path to the `route-examples-output` directory. */ diff --git a/www/utils/yarn.lock b/www/utils/yarn.lock index 6c477c0049..9c18212f34 100644 --- a/www/utils/yarn.lock +++ b/www/utils/yarn.lock @@ -2646,6 +2646,7 @@ __metadata: commander: ^11.1.0 dotenv: ^16.3.1 eslint: 8.56.0 + glob: ^11.0.2 minimatch: ^9.0.3 openai: ^4.29.1 openapi-types: ^12.1.3 @@ -3516,6 +3517,22 @@ __metadata: languageName: node linkType: hard +"glob@npm:^11.0.2": + version: 11.0.2 + resolution: "glob@npm:11.0.2" + dependencies: + foreground-child: ^3.1.0 + jackspeak: ^4.0.1 + minimatch: ^10.0.0 + minipass: ^7.1.2 + package-json-from-dist: ^1.0.0 + path-scurry: ^2.0.0 + bin: + glob: dist/esm/bin.mjs + checksum: 49f91c64ca882d5e3a72397bd45a146ca91fd3ca53dafb5254daf6c0e83fc510d39ea66f136f9ac7ca075cdd11fbe9aaa235b28f743bd477622e472f4fdc0240 + languageName: node + linkType: hard + "glob@npm:^7.0.5, glob@npm:^7.1.3": version: 7.2.3 resolution: "glob@npm:7.2.3" @@ -3934,6 +3951,15 @@ __metadata: languageName: node linkType: hard +"jackspeak@npm:^4.0.1": + version: 4.1.0 + resolution: "jackspeak@npm:4.1.0" + dependencies: + "@isaacs/cliui": ^8.0.2 + checksum: 08a6a24a366c90b83aef3ad6ec41dcaaa65428ffab8d80bc7172add0fbb8b134a34f415ad288b2a6fbd406526e9a62abdb40ed4f399fbe00cb45c44056d4dce0 + languageName: node + linkType: hard + "js-beautify@npm:^1.15.1": version: 1.15.1 resolution: "js-beautify@npm:1.15.1" @@ -4289,6 +4315,13 @@ __metadata: languageName: node linkType: hard +"lru-cache@npm:^11.0.0": + version: 11.1.0 + resolution: "lru-cache@npm:11.1.0" + checksum: 85c312f7113f65fae6a62de7985348649937eb34fb3d212811acbf6704dc322a421788aca253b62838f1f07049a84cc513d88f494e373d3756514ad263670a64 + languageName: node + linkType: hard + "lru-cache@npm:^5.1.1": version: 5.1.1 resolution: "lru-cache@npm:5.1.1" @@ -4439,7 +4472,7 @@ __metadata: languageName: node linkType: hard -"minimatch@npm:^10.0.1": +"minimatch@npm:^10.0.0, minimatch@npm:^10.0.1": version: 10.0.1 resolution: "minimatch@npm:10.0.1" dependencies: @@ -4876,6 +4909,16 @@ __metadata: languageName: node linkType: hard +"path-scurry@npm:^2.0.0": + version: 2.0.0 + resolution: "path-scurry@npm:2.0.0" + dependencies: + lru-cache: ^11.0.0 + minipass: ^7.1.2 + checksum: 3da4adedaa8e7ef8d6dc4f35a0ff8f05a9b4d8365f2b28047752b62d4c1ad73eec21e37b1579ef2d075920157856a3b52ae8309c480a6f1a8bbe06ff8e52b33c + languageName: node + linkType: hard + "path-type@npm:^4.0.0": version: 4.0.0 resolution: "path-type@npm:4.0.0"