diff --git a/packages/framework/framework/package.json b/packages/framework/framework/package.json index fc005988fc9fb..5e56383078c02 100644 --- a/packages/framework/framework/package.json +++ b/packages/framework/framework/package.json @@ -13,6 +13,7 @@ "./logger": "./dist/logger/index.js", "./database": "./dist/database/index.js", "./subscribers": "./dist/subscribers/index.js", + "./workflows": "./dist/workflows/index.js", "./links": "./dist/links/index.js", "./jobs": "./dist/jobs/index.js" }, @@ -49,6 +50,7 @@ "dependencies": { "@medusajs/medusa-cli": "^1.3.22", "@medusajs/modules-sdk": "^1.12.11", + "@medusajs/orchestration": "^0.5.7", "@medusajs/utils": "^1.11.9", "@medusajs/workflows-sdk": "^0.1.6", "awilix": "^8.0.0", diff --git a/packages/framework/framework/src/config/__fixtures__/medusa-config-2.js b/packages/framework/framework/src/config/__fixtures__/medusa-config-2.js new file mode 100644 index 0000000000000..49a19698e75c0 --- /dev/null +++ b/packages/framework/framework/src/config/__fixtures__/medusa-config-2.js @@ -0,0 +1,7 @@ +import { defineConfig } from "@medusajs/utils" + +export default defineConfig({ + projectConfig: { + databaseName: "foo", + }, +}) diff --git a/packages/framework/framework/src/config/__fixtures__/medusa-config.js b/packages/framework/framework/src/config/__fixtures__/medusa-config.js new file mode 100644 index 0000000000000..db0232e6e4c09 --- /dev/null +++ b/packages/framework/framework/src/config/__fixtures__/medusa-config.js @@ -0,0 +1,3 @@ +import { defineConfig } from "@medusajs/utils" + +export default defineConfig() diff --git a/packages/framework/framework/src/config/__tests__/index.spec.ts b/packages/framework/framework/src/config/__tests__/index.spec.ts new file mode 100644 index 0000000000000..542533eec8db5 --- /dev/null +++ b/packages/framework/framework/src/config/__tests__/index.spec.ts @@ -0,0 +1,30 @@ +import { configLoader } from "../loader" +import { join } from "path" +import { container } from "../../container" +import { ContainerRegistrationKeys } from "@medusajs/utils" + +describe("configLoader", () => { + const entryDirectory = join(__dirname, "../__fixtures__") + + it("should load the config properly", async () => { + let configModule = container.resolve( + ContainerRegistrationKeys.CONFIG_MODULE + ) + + expect(configModule).toBeUndefined() + + configLoader(entryDirectory, "medusa-config.js") + + configModule = container.resolve(ContainerRegistrationKeys.CONFIG_MODULE) + + expect(configModule).toBeDefined() + expect(configModule.projectConfig.databaseName).toBeUndefined() + + configLoader(entryDirectory, "medusa-config-2.js") + + configModule = container.resolve(ContainerRegistrationKeys.CONFIG_MODULE) + + expect(configModule).toBeDefined() + expect(configModule.projectConfig.databaseName).toBe("foo") + }) +}) diff --git a/packages/framework/framework/src/config/loader.ts b/packages/framework/framework/src/config/loader.ts index c1c22c066bf2e..9b8a438e27b6b 100644 --- a/packages/framework/framework/src/config/loader.ts +++ b/packages/framework/framework/src/config/loader.ts @@ -13,12 +13,11 @@ const handleConfigError = (error: Error): void => { process.exit(1) } -// TODO: Later on we could store the config manager into the unique container export const configManager = new ConfigManager() container.register( ContainerRegistrationKeys.CONFIG_MODULE, - asFunction(() => configManager) + asFunction(() => configManager.config) ) /** diff --git a/packages/framework/framework/src/index.ts b/packages/framework/framework/src/index.ts index fc71a46e4a858..69978558668bd 100644 --- a/packages/framework/framework/src/index.ts +++ b/packages/framework/framework/src/index.ts @@ -7,3 +7,4 @@ export * from "./subscribers" export * from "./links" export * from "./jobs" export * from "./feature-flags" +export * from "./workflows" diff --git a/packages/framework/framework/src/subscribers/subscriber-loader.ts b/packages/framework/framework/src/subscribers/subscriber-loader.ts index be895c26b60c0..322b0e8ba4270 100644 --- a/packages/framework/framework/src/subscribers/subscriber-loader.ts +++ b/packages/framework/framework/src/subscribers/subscriber-loader.ts @@ -33,6 +33,8 @@ export class SubscriberLoader { * @private */ #excludes: RegExp[] = [ + /index\.js/, + /index\.ts/, /\.DS_Store/, /(\.ts\.map|\.js\.map|\.d\.ts|\.md)/, /^_[^/\\]*(\.[^/\\]+)?$/, @@ -129,17 +131,17 @@ export class SubscriberLoader { recursive: true, withFileTypes: true, }).then(async (entries) => { - return entries.flatMap(async (entry) => { - if (this.#excludes.some((exclude) => exclude.test(entry.name))) { - return - } - - const fullPath = join(dirPath, entry.name) + const fileEntries = entries.filter((entry) => { + return ( + !entry.isDirectory() && + !this.#excludes.some((exclude) => exclude.test(entry.name)) + ) + }) - if (entry.isDirectory()) { - return await this.createMap(fullPath) - } + logger.debug(`Registering subscribers from ${dirPath}.`) + return fileEntries.flatMap(async (entry) => { + const fullPath = join(entry.path, entry.name) return await this.createDescriptor(fullPath) }) }) @@ -237,6 +239,8 @@ export class SubscriberLoader { }) } + logger.debug(`Subscribers registered.`) + /** * Return the file paths of the registered subscribers, to prevent the * backwards compatible loader from trying to register them. diff --git a/packages/framework/framework/src/workflows/__fixtures__/workflows/deep-workflows/product-updater.ts b/packages/framework/framework/src/workflows/__fixtures__/workflows/deep-workflows/product-updater.ts new file mode 100644 index 0000000000000..4eab47bebea04 --- /dev/null +++ b/packages/framework/framework/src/workflows/__fixtures__/workflows/deep-workflows/product-updater.ts @@ -0,0 +1,16 @@ +import { + createStep, + createWorkflow, + WorkflowResponse, +} from "@medusajs/workflows-sdk" + +export const productWorkflowId = "product-notifier-workflow" + +const step = createStep("product-step", () => { + return {} as any +}) + +export const productUpdatedWorkflow = createWorkflow(productWorkflowId, () => { + step() + return new WorkflowResponse(void 0) +}) diff --git a/packages/framework/framework/src/workflows/__fixtures__/workflows/order-notifier.ts b/packages/framework/framework/src/workflows/__fixtures__/workflows/order-notifier.ts new file mode 100644 index 0000000000000..b05b38563c07e --- /dev/null +++ b/packages/framework/framework/src/workflows/__fixtures__/workflows/order-notifier.ts @@ -0,0 +1,16 @@ +import { + createStep, + createWorkflow, + WorkflowResponse, +} from "@medusajs/workflows-sdk" + +export const orderWorkflowId = "order-notifier-workflow" + +const step = createStep("order-step", () => { + return {} as any +}) + +export const orderNotifierWorkflow = createWorkflow(orderWorkflowId, () => { + step() + return new WorkflowResponse(void 0) +}) diff --git a/packages/framework/framework/src/workflows/__tests__/index.spec.ts b/packages/framework/framework/src/workflows/__tests__/index.spec.ts new file mode 100644 index 0000000000000..78c967154c7e1 --- /dev/null +++ b/packages/framework/framework/src/workflows/__tests__/index.spec.ts @@ -0,0 +1,21 @@ +import { join } from "path" +import { WorkflowLoader } from "../workflow-loader" +import { WorkflowManager } from "@medusajs/orchestration" +import { orderWorkflowId } from "../__fixtures__/workflows/order-notifier" +import { productWorkflowId } from "../__fixtures__/workflows/deep-workflows/product-updater" + +describe("WorkflowLoader", () => { + const rootDir = join(__dirname, "../__fixtures__", "workflows") + + beforeAll(async () => { + await new WorkflowLoader(rootDir).load() + }) + + it("should register each workflow in the '/workflows' folder and sub folder", async () => { + const registeredWorkflows = WorkflowManager.getWorkflows() + + expect(registeredWorkflows.size).toBe(2) + expect(registeredWorkflows.has(orderWorkflowId)).toBe(true) + expect(registeredWorkflows.has(productWorkflowId)).toBe(true) + }) +}) diff --git a/packages/framework/framework/src/workflows/index.ts b/packages/framework/framework/src/workflows/index.ts new file mode 100644 index 0000000000000..17ce03811f2e7 --- /dev/null +++ b/packages/framework/framework/src/workflows/index.ts @@ -0,0 +1 @@ +export * from "./workflow-loader" diff --git a/packages/framework/framework/src/workflows/workflow-loader.ts b/packages/framework/framework/src/workflows/workflow-loader.ts new file mode 100644 index 0000000000000..cafb9711757e0 --- /dev/null +++ b/packages/framework/framework/src/workflows/workflow-loader.ts @@ -0,0 +1,71 @@ +import { promiseAll } from "@medusajs/utils" +import { logger } from "../logger" +import { access, readdir } from "fs/promises" +import { join } from "path" + +export class WorkflowLoader { + /** + * The directory from which to load the workflows + * @private + */ + #sourceDir: string | string[] + + /** + * The list of file names to exclude from the subscriber scan + * @private + */ + #excludes: RegExp[] = [ + /index\.js/, + /index\.ts/, + /\.DS_Store/, + /(\.ts\.map|\.js\.map|\.d\.ts|\.md)/, + /^_[^/\\]*(\.[^/\\]+)?$/, + ] + + constructor(sourceDir: string | string[]) { + this.#sourceDir = sourceDir + } + + /** + * Load workflows from the source paths, workflows are registering themselves, + * therefore we only need to import them + */ + async load() { + const normalizedSourcePath = Array.isArray(this.#sourceDir) + ? this.#sourceDir + : [this.#sourceDir] + + const promises = normalizedSourcePath.map(async (sourcePath) => { + try { + await access(sourcePath) + } catch { + return + } + + return await readdir(sourcePath, { + recursive: true, + withFileTypes: true, + }).then(async (entries) => { + const fileEntries = entries.filter((entry) => { + return ( + !entry.isDirectory() && + !this.#excludes.some((exclude) => exclude.test(entry.name)) + ) + }) + + logger.debug(`Registering workflows from ${sourcePath}.`) + + return await promiseAll( + fileEntries.map(async (entry) => { + const fullPath = join(entry.path, entry.name) + return await import(fullPath) + }) + ) + }) + }) + + await promiseAll(promises) + + logger.debug(`Workflows registered.`) + } +} diff --git a/packages/medusa/src/loaders/helpers/register-workflows.ts b/packages/medusa/src/loaders/helpers/register-workflows.ts deleted file mode 100644 index e939c30b29a8c..0000000000000 --- a/packages/medusa/src/loaders/helpers/register-workflows.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { glob } from "glob" -import { PluginDetails } from "@medusajs/types" - -/** - * import files from the workflows directory to run the registration of the wofklows - * @param pluginDetails - */ -export async function registerWorkflows( - plugins: PluginDetails[] -): Promise { - await Promise.all( - plugins.map(async (pluginDetails) => { - const files = glob.sync( - `${pluginDetails.resolve}/workflows/*.{ts,js,mjs,mts}`, - { - ignore: ["**/*.d.ts", "**/*.map"], - } - ) - return Promise.all(files.map(async (file) => import(file))) - }) - ) -} diff --git a/packages/medusa/src/loaders/index.ts b/packages/medusa/src/loaders/index.ts index d514c9a094faf..d63e8a98f2748 100644 --- a/packages/medusa/src/loaders/index.ts +++ b/packages/medusa/src/loaders/index.ts @@ -13,13 +13,13 @@ import { container, expressLoader, featureFlagsLoader, - LinkLoader, JobLoader, + LinkLoader, logger, pgConnectionLoader, SubscriberLoader, + WorkflowLoader, } from "@medusajs/framework" -import { registerWorkflows } from "./helpers/register-workflows" import { getResolvedPlugins } from "./helpers/resolve-plugins" import loadMedusaApp from "./medusa-app" @@ -45,6 +45,7 @@ async function subscribersLoader(plugins: PluginDetails[]) { */ await new SubscriberLoader(join(__dirname, "../subscribers")).load() + // TODO: make it the same as the other loaders, taking an array of paths to load from /** * Load subscribers from all the plugins. */ @@ -121,15 +122,11 @@ async function loadEntrypoints( export async function initializeContainer( rootDirectory: string ): Promise { - const configModule = configLoader(rootDirectory, "medusa-config.js") - const featureFlagRouter = await featureFlagsLoader( - join(__dirname, "feature-flags") - ) + configLoader(rootDirectory, "medusa-config.js") + await featureFlagsLoader(join(__dirname, "feature-flags")) container.register({ [ContainerRegistrationKeys.LOGGER]: asValue(logger), - [ContainerRegistrationKeys.FEATURE_FLAG_ROUTER]: asValue(featureFlagRouter), - [ContainerRegistrationKeys.CONFIG_MODULE]: asValue(configModule), [ContainerRegistrationKeys.REMOTE_QUERY]: asValue(null), }) @@ -164,7 +161,10 @@ export default async ({ container, }) - await registerWorkflows(plugins) + const workflowsSourcePaths = plugins.map((p) => join(p.resolve, "workflows")) + const workflowLoader = new WorkflowLoader(workflowsSourcePaths) + await workflowLoader.load() + const entrypointsShutdown = await loadEntrypoints( plugins, container, diff --git a/yarn.lock b/yarn.lock index c2a8b53385e16..b39027d39dfb3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4611,6 +4611,7 @@ __metadata: dependencies: "@medusajs/medusa-cli": ^1.3.22 "@medusajs/modules-sdk": ^1.12.11 + "@medusajs/orchestration": ^0.5.7 "@medusajs/types": ^1.11.16 "@medusajs/utils": ^1.11.9 "@medusajs/workflows-sdk": ^0.1.6