From 2dc12b8568fb05129d0ae154d426602f0ef37af1 Mon Sep 17 00:00:00 2001 From: olivermrbl Date: Tue, 30 Jan 2024 19:10:54 +0100 Subject: [PATCH 1/9] feat: Add payment provider support --- .../src/loaders/helpers/module-providers.ts | 173 ++++++++++++++++++ packages/payment/src/loaders/providers.ts | 54 ++++++ 2 files changed, 227 insertions(+) create mode 100644 packages/medusa/src/loaders/helpers/module-providers.ts create mode 100644 packages/payment/src/loaders/providers.ts diff --git a/packages/medusa/src/loaders/helpers/module-providers.ts b/packages/medusa/src/loaders/helpers/module-providers.ts new file mode 100644 index 0000000000000..9eed1cdfad7f6 --- /dev/null +++ b/packages/medusa/src/loaders/helpers/module-providers.ts @@ -0,0 +1,173 @@ +import { promiseAll } from "@medusajs/utils" +import { Lifetime, asFunction } from "awilix" +import fs from "fs" +import { sync as existsSync } from "fs-exists-cached" +import glob from "glob" +import { createRequireFromPath } from "medusa-core-utils" +import path from "path" +import { MedusaContainer } from "../../types/global" +import { formatRegistrationName } from "../../utils/format-registration-name" + +type PluginDetails = { + resolve: string + name: string + id: string + options: Record + version: string +} + +export async function moduleProviderLoader({ + container, + providers, + registerServiceFn, +}) { + const resolvedProviders = new Map>() + const installedPlugins = providers.providers.map((provider) => { + let resolvedProvider + if (resolvedProviders.has(provider.resolve)) { + resolvedProvider = resolvedProviders.get(provider.resolve) + } else { + const details = resolvePlugin(provider.resolve) + details.options = provider.options + + resolvedProviders.set(provider.resolve, details) + resolvedProvider = details + } + + return resolvedProvider + }) + + await promiseAll( + installedPlugins.map(async (pluginDetails) => { + await registerServices(pluginDetails, container, registerServiceFn) + }) + ) + + return installedPlugins +} + +export async function registerServices( + pluginDetails: PluginDetails, + container: MedusaContainer, + registerServiceFn?: (klass, container, pluginDetails) => Promise +): Promise { + const files = glob.sync(`${pluginDetails.resolve}/services/[!__]*.js`, {}) + const klasses: any[] = [] + + await promiseAll( + files.map(async (fn) => { + const loaded = require(fn).default + const name = formatRegistrationName(fn) + + if (typeof loaded !== "function") { + throw new Error( + `Cannot register ${name}. Make sure to default export a service class in ${fn}` + ) + } + + if (registerServiceFn) { + // Used to register the specific type of service in the provider + await registerServiceFn(loaded, container, pluginDetails.options) + } else { + container.register({ + [name]: asFunction( + (cradle) => new loaded(cradle, pluginDetails.options), + { + lifetime: loaded.LIFE_TIME || Lifetime.SCOPED, + } + ), + }) + } + + klasses.push(loaded) + }) + ) + + return klasses +} + +function resolvePlugin(pluginName: string): { + resolve: string + id: string + name: string + options: Record + version: string +} { + // Only find plugins when we're not given an absolute path + if (!existsSync(pluginName)) { + // Find the plugin in the local plugins folder + const resolvedPath = path.resolve(`./plugins/${pluginName}`) + + if (existsSync(resolvedPath)) { + if (existsSync(`${resolvedPath}/package.json`)) { + const packageJSON = JSON.parse( + fs.readFileSync(`${resolvedPath}/package.json`, `utf-8`) + ) + const name = packageJSON.name || pluginName + // warnOnIncompatiblePeerDependency(name, packageJSON) + + return { + resolve: resolvedPath, + name, + id: name, + options: {}, + version: + packageJSON.version || createFileContentHash(resolvedPath, `**`), + } + } else { + // Make package.json a requirement for local plugins too + throw new Error(`Plugin ${pluginName} requires a package.json file`) + } + } + } + + const rootDir = path.resolve(".") + + /** + * Here we have an absolute path to an internal plugin, or a name of a module + * which should be located in node_modules. + */ + try { + const requireSource = + rootDir !== null + ? createRequireFromPath(`${rootDir}/:internal:`) + : require + + // If the path is absolute, resolve the directory of the internal plugin, + // otherwise resolve the directory containing the package.json + const resolvedPath = path.dirname( + requireSource.resolve(`${pluginName}/package.json`) + ) + + const packageJSON = JSON.parse( + fs.readFileSync(`${resolvedPath}/package.json`, `utf-8`) + ) + // warnOnIncompatiblePeerDependency(packageJSON.name, packageJSON) + + const computedResolvedPath = + resolvedPath + (process.env.DEV_MODE ? "/src" : "") + + // Add support for a plugin to output the build into a dist directory + const resolvedPathToDist = resolvedPath + "/dist" + const isDistExist = + resolvedPathToDist && + !process.env.DEV_MODE && + existsSync(resolvedPath + "/dist") + + return { + resolve: isDistExist ? resolvedPathToDist : computedResolvedPath, + id: packageJSON.name, + name: packageJSON.name, + options: {}, + version: packageJSON.version, + } + } catch (err) { + throw new Error( + `Unable to find plugin "${pluginName}". Perhaps you need to install its package?` + ) + } +} + +function createFileContentHash(path, files): string { + return path + files +} diff --git a/packages/payment/src/loaders/providers.ts b/packages/payment/src/loaders/providers.ts new file mode 100644 index 0000000000000..e279e7fd2a182 --- /dev/null +++ b/packages/payment/src/loaders/providers.ts @@ -0,0 +1,54 @@ +import { + AbstractPaymentProcessor, + moduleProviderLoader, +} from "@medusajs/medusa" + +import { LoaderOptions, ModulesSdkTypes } from "@medusajs/types" +import { Lifetime, asFunction } from "awilix" + +type PaymentModuleProviders = { + providers: { + resolve: string + provider_id: string // e.g. stripe-usd + options: Record + }[] +} + +const loadAuthProviderService = async (klass, container, pluginOptions) => { + if (!AbstractPaymentProcessor.isPaymentProcessor(klass.prototype)) { + return + } + + container.register({ + [`payment_provider_${klass.prototype}`]: asFunction( + (cradle) => new klass(cradle, pluginOptions), + { + lifetime: klass.LIFE_TIME || Lifetime.SINGLETON, + } + ), + }) + + container.registerAdd( + "payment_providers", + asFunction((cradle) => new klass(cradle, pluginOptions), { + lifetime: klass.LIFE_TIME || Lifetime.SINGLETON, + }) + ) +} + +export default async ({ + container, + options, +}: LoaderOptions< + ( + | ModulesSdkTypes.ModuleServiceInitializeOptions + | ModulesSdkTypes.ModuleServiceInitializeCustomDataLayerOptions + ) & + PaymentModuleProviders +>): Promise => { + await moduleProviderLoader({ + container, + providers: options!.providers, + registerServiceFn: loadAuthProviderService, + }) +} From cf1818654b72add3026a892660ca968dab38a4b1 Mon Sep 17 00:00:00 2001 From: olivermrbl Date: Tue, 30 Jan 2024 19:46:25 +0100 Subject: [PATCH 2/9] move utils --- .../src/loaders/helpers/module-providers.ts | 2 - .../src/loaders/register-providers.ts | 170 ++++++++++++++++++ packages/payment/src/loaders/index.ts | 2 + packages/payment/src/loaders/providers.ts | 4 +- .../src/common/format-registration-name.ts | 47 +++++ 5 files changed, 221 insertions(+), 4 deletions(-) create mode 100644 packages/modules-sdk/src/loaders/register-providers.ts create mode 100644 packages/utils/src/common/format-registration-name.ts diff --git a/packages/medusa/src/loaders/helpers/module-providers.ts b/packages/medusa/src/loaders/helpers/module-providers.ts index 9eed1cdfad7f6..674a0f679bfde 100644 --- a/packages/medusa/src/loaders/helpers/module-providers.ts +++ b/packages/medusa/src/loaders/helpers/module-providers.ts @@ -104,7 +104,6 @@ function resolvePlugin(pluginName: string): { fs.readFileSync(`${resolvedPath}/package.json`, `utf-8`) ) const name = packageJSON.name || pluginName - // warnOnIncompatiblePeerDependency(name, packageJSON) return { resolve: resolvedPath, @@ -142,7 +141,6 @@ function resolvePlugin(pluginName: string): { const packageJSON = JSON.parse( fs.readFileSync(`${resolvedPath}/package.json`, `utf-8`) ) - // warnOnIncompatiblePeerDependency(packageJSON.name, packageJSON) const computedResolvedPath = resolvedPath + (process.env.DEV_MODE ? "/src" : "") diff --git a/packages/modules-sdk/src/loaders/register-providers.ts b/packages/modules-sdk/src/loaders/register-providers.ts new file mode 100644 index 0000000000000..a71085c22afe3 --- /dev/null +++ b/packages/modules-sdk/src/loaders/register-providers.ts @@ -0,0 +1,170 @@ +import { MedusaContainer } from "@medusajs/types" +import { formatRegistrationName, promiseAll } from "@medusajs/utils" +import { Lifetime, asFunction } from "awilix" +import fs from "fs" +import { sync as existsSync } from "fs-exists-cached" +import glob from "glob" +import { createRequireFromPath } from "medusa-core-utils" +import path from "path" + +type PluginDetails = { + resolve: string + name: string + id: string + options: Record + version: string +} + +export async function moduleProviderLoader({ + container, + providers, + registerServiceFn, +}) { + const resolvedProviders = new Map>() + const installedPlugins = providers.providers.map((provider) => { + let resolvedProvider + if (resolvedProviders.has(provider.resolve)) { + resolvedProvider = resolvedProviders.get(provider.resolve) + } else { + const details = resolvePlugin(provider.resolve) + details.options = provider.options + + resolvedProviders.set(provider.resolve, details) + resolvedProvider = details + } + + return resolvedProvider + }) + + await promiseAll( + installedPlugins.map(async (pluginDetails) => { + await registerServices(pluginDetails, container, registerServiceFn) + }) + ) + + return installedPlugins +} + +export async function registerServices( + pluginDetails: PluginDetails, + container: MedusaContainer, + registerServiceFn?: (klass, container, pluginDetails) => Promise +): Promise { + const files = glob.sync(`${pluginDetails.resolve}/services/[!__]*.js`, {}) + const klasses: any[] = [] + + await promiseAll( + files.map(async (fn) => { + const loaded = require(fn).default + const name = formatRegistrationName(fn) + + if (typeof loaded !== "function") { + throw new Error( + `Cannot register ${name}. Make sure to default export a service class in ${fn}` + ) + } + + if (registerServiceFn) { + // Used to register the specific type of service in the provider + await registerServiceFn(loaded, container, pluginDetails.options) + } else { + container.register({ + [name]: asFunction( + (cradle) => new loaded(cradle, pluginDetails.options), + { + lifetime: loaded.LIFE_TIME || Lifetime.SCOPED, + } + ), + }) + } + + klasses.push(loaded) + }) + ) + + return klasses +} + +function resolvePlugin(pluginName: string): { + resolve: string + id: string + name: string + options: Record + version: string +} { + // Only find plugins when we're not given an absolute path + if (!existsSync(pluginName)) { + // Find the plugin in the local plugins folder + const resolvedPath = path.resolve(`./plugins/${pluginName}`) + + if (existsSync(resolvedPath)) { + if (existsSync(`${resolvedPath}/package.json`)) { + const packageJSON = JSON.parse( + fs.readFileSync(`${resolvedPath}/package.json`, `utf-8`) + ) + const name = packageJSON.name || pluginName + + return { + resolve: resolvedPath, + name, + id: name, + options: {}, + version: + packageJSON.version || createFileContentHash(resolvedPath, `**`), + } + } else { + // Make package.json a requirement for local plugins too + throw new Error(`Plugin ${pluginName} requires a package.json file`) + } + } + } + + const rootDir = path.resolve(".") + + /** + * Here we have an absolute path to an internal plugin, or a name of a module + * which should be located in node_modules. + */ + try { + const requireSource = + rootDir !== null + ? createRequireFromPath(`${rootDir}/:internal:`) + : require + + // If the path is absolute, resolve the directory of the internal plugin, + // otherwise resolve the directory containing the package.json + const resolvedPath = path.dirname( + requireSource.resolve(`${pluginName}/package.json`) + ) + + const packageJSON = JSON.parse( + fs.readFileSync(`${resolvedPath}/package.json`, `utf-8`) + ) + + const computedResolvedPath = + resolvedPath + (process.env.DEV_MODE ? "/src" : "") + + // Add support for a plugin to output the build into a dist directory + const resolvedPathToDist = resolvedPath + "/dist" + const isDistExist = + resolvedPathToDist && + !process.env.DEV_MODE && + existsSync(resolvedPath + "/dist") + + return { + resolve: isDistExist ? resolvedPathToDist : computedResolvedPath, + id: packageJSON.name, + name: packageJSON.name, + options: {}, + version: packageJSON.version, + } + } catch (err) { + throw new Error( + `Unable to find plugin "${pluginName}". Perhaps you need to install its package?` + ) + } +} + +function createFileContentHash(path, files): string { + return path + files +} diff --git a/packages/payment/src/loaders/index.ts b/packages/payment/src/loaders/index.ts index 3614963d8c21e..0446db6943c30 100644 --- a/packages/payment/src/loaders/index.ts +++ b/packages/payment/src/loaders/index.ts @@ -1,2 +1,4 @@ export * from "./connection" export * from "./container" +export * from "./providers" + diff --git a/packages/payment/src/loaders/providers.ts b/packages/payment/src/loaders/providers.ts index e279e7fd2a182..9951eab38f7c8 100644 --- a/packages/payment/src/loaders/providers.ts +++ b/packages/payment/src/loaders/providers.ts @@ -14,7 +14,7 @@ type PaymentModuleProviders = { }[] } -const loadAuthProviderService = async (klass, container, pluginOptions) => { +const loadPaymentProviderService = async (klass, container, pluginOptions) => { if (!AbstractPaymentProcessor.isPaymentProcessor(klass.prototype)) { return } @@ -49,6 +49,6 @@ export default async ({ await moduleProviderLoader({ container, providers: options!.providers, - registerServiceFn: loadAuthProviderService, + registerServiceFn: loadPaymentProviderService, }) } diff --git a/packages/utils/src/common/format-registration-name.ts b/packages/utils/src/common/format-registration-name.ts new file mode 100644 index 0000000000000..21227593fbbee --- /dev/null +++ b/packages/utils/src/common/format-registration-name.ts @@ -0,0 +1,47 @@ +import { parse } from "path" +import { toCamelCase } from "./to-camel-case" +import { upperCaseFirst } from "./upper-case-first" + +/** + * Formats a filename into the correct container resolution name. + * Names are camelCase formatted and namespaced by the folder i.e: + * models/example-person -> examplePersonModel + * @param path - the full path of the file + * @return the formatted name + */ +export function formatRegistrationName(path: string): string { + const parsed = parse(path) + const parsedDir = parse(parsed.dir) + const rawname = parsed.name + let directoryNamespace = parsedDir.name + + if (directoryNamespace.startsWith("__")) { + const parsedCoreDir = parse(parsedDir.dir) + directoryNamespace = parsedCoreDir.name + } + + switch (directoryNamespace) { + // We strip the last character when adding the type of registration + // this is a trick for plural "ies" + case "repositories": + directoryNamespace = "repositorys" + break + case "strategies": + directoryNamespace = "strategys" + break + default: + break + } + + const upperNamespace = upperCaseFirst(directoryNamespace.slice(0, -1)) + + return formatRegistrationNameWithoutNamespace(path) + upperNamespace +} + +export function formatRegistrationNameWithoutNamespace(path: string): string { + const parsed = parse(path) + + return toCamelCase(parsed.name) +} + +export default formatRegistrationName From d2926e5c6af093a3e34b09b09bbb8c8c084504c3 Mon Sep 17 00:00:00 2001 From: olivermrbl Date: Tue, 30 Jan 2024 20:03:03 +0100 Subject: [PATCH 3/9] remove duplicate --- .../src/loaders/helpers/module-providers.ts | 171 ------------------ packages/payment/src/loaders/providers.ts | 6 +- packages/utils/src/common/index.ts | 2 + 3 files changed, 4 insertions(+), 175 deletions(-) delete mode 100644 packages/medusa/src/loaders/helpers/module-providers.ts diff --git a/packages/medusa/src/loaders/helpers/module-providers.ts b/packages/medusa/src/loaders/helpers/module-providers.ts deleted file mode 100644 index 674a0f679bfde..0000000000000 --- a/packages/medusa/src/loaders/helpers/module-providers.ts +++ /dev/null @@ -1,171 +0,0 @@ -import { promiseAll } from "@medusajs/utils" -import { Lifetime, asFunction } from "awilix" -import fs from "fs" -import { sync as existsSync } from "fs-exists-cached" -import glob from "glob" -import { createRequireFromPath } from "medusa-core-utils" -import path from "path" -import { MedusaContainer } from "../../types/global" -import { formatRegistrationName } from "../../utils/format-registration-name" - -type PluginDetails = { - resolve: string - name: string - id: string - options: Record - version: string -} - -export async function moduleProviderLoader({ - container, - providers, - registerServiceFn, -}) { - const resolvedProviders = new Map>() - const installedPlugins = providers.providers.map((provider) => { - let resolvedProvider - if (resolvedProviders.has(provider.resolve)) { - resolvedProvider = resolvedProviders.get(provider.resolve) - } else { - const details = resolvePlugin(provider.resolve) - details.options = provider.options - - resolvedProviders.set(provider.resolve, details) - resolvedProvider = details - } - - return resolvedProvider - }) - - await promiseAll( - installedPlugins.map(async (pluginDetails) => { - await registerServices(pluginDetails, container, registerServiceFn) - }) - ) - - return installedPlugins -} - -export async function registerServices( - pluginDetails: PluginDetails, - container: MedusaContainer, - registerServiceFn?: (klass, container, pluginDetails) => Promise -): Promise { - const files = glob.sync(`${pluginDetails.resolve}/services/[!__]*.js`, {}) - const klasses: any[] = [] - - await promiseAll( - files.map(async (fn) => { - const loaded = require(fn).default - const name = formatRegistrationName(fn) - - if (typeof loaded !== "function") { - throw new Error( - `Cannot register ${name}. Make sure to default export a service class in ${fn}` - ) - } - - if (registerServiceFn) { - // Used to register the specific type of service in the provider - await registerServiceFn(loaded, container, pluginDetails.options) - } else { - container.register({ - [name]: asFunction( - (cradle) => new loaded(cradle, pluginDetails.options), - { - lifetime: loaded.LIFE_TIME || Lifetime.SCOPED, - } - ), - }) - } - - klasses.push(loaded) - }) - ) - - return klasses -} - -function resolvePlugin(pluginName: string): { - resolve: string - id: string - name: string - options: Record - version: string -} { - // Only find plugins when we're not given an absolute path - if (!existsSync(pluginName)) { - // Find the plugin in the local plugins folder - const resolvedPath = path.resolve(`./plugins/${pluginName}`) - - if (existsSync(resolvedPath)) { - if (existsSync(`${resolvedPath}/package.json`)) { - const packageJSON = JSON.parse( - fs.readFileSync(`${resolvedPath}/package.json`, `utf-8`) - ) - const name = packageJSON.name || pluginName - - return { - resolve: resolvedPath, - name, - id: name, - options: {}, - version: - packageJSON.version || createFileContentHash(resolvedPath, `**`), - } - } else { - // Make package.json a requirement for local plugins too - throw new Error(`Plugin ${pluginName} requires a package.json file`) - } - } - } - - const rootDir = path.resolve(".") - - /** - * Here we have an absolute path to an internal plugin, or a name of a module - * which should be located in node_modules. - */ - try { - const requireSource = - rootDir !== null - ? createRequireFromPath(`${rootDir}/:internal:`) - : require - - // If the path is absolute, resolve the directory of the internal plugin, - // otherwise resolve the directory containing the package.json - const resolvedPath = path.dirname( - requireSource.resolve(`${pluginName}/package.json`) - ) - - const packageJSON = JSON.parse( - fs.readFileSync(`${resolvedPath}/package.json`, `utf-8`) - ) - - const computedResolvedPath = - resolvedPath + (process.env.DEV_MODE ? "/src" : "") - - // Add support for a plugin to output the build into a dist directory - const resolvedPathToDist = resolvedPath + "/dist" - const isDistExist = - resolvedPathToDist && - !process.env.DEV_MODE && - existsSync(resolvedPath + "/dist") - - return { - resolve: isDistExist ? resolvedPathToDist : computedResolvedPath, - id: packageJSON.name, - name: packageJSON.name, - options: {}, - version: packageJSON.version, - } - } catch (err) { - throw new Error( - `Unable to find plugin "${pluginName}". Perhaps you need to install its package?` - ) - } -} - -function createFileContentHash(path, files): string { - return path + files -} diff --git a/packages/payment/src/loaders/providers.ts b/packages/payment/src/loaders/providers.ts index 9951eab38f7c8..240145ffdc334 100644 --- a/packages/payment/src/loaders/providers.ts +++ b/packages/payment/src/loaders/providers.ts @@ -1,7 +1,5 @@ -import { - AbstractPaymentProcessor, - moduleProviderLoader, -} from "@medusajs/medusa" +import { AbstractPaymentProcessor } from "@medusajs/medusa" +import { moduleProviderLoader } from "@medusajs/modules-sdk" import { LoaderOptions, ModulesSdkTypes } from "@medusajs/types" import { Lifetime, asFunction } from "awilix" diff --git a/packages/utils/src/common/index.ts b/packages/utils/src/common/index.ts index 1c0dd744d49c8..e5c8ad07962b2 100644 --- a/packages/utils/src/common/index.ts +++ b/packages/utils/src/common/index.ts @@ -6,6 +6,7 @@ export * from "./create-container-like" export * from "./deduplicate" export * from "./deep-equal-obj" export * from "./errors" +export * from "./format-registration-name" export * from "./generate-entity-id" export * from "./generate-linkable-keys-map" export * from "./get-config-file" @@ -41,3 +42,4 @@ export * from "./to-pascal-case" export * from "./transaction" export * from "./upper-case-first" export * from "./wrap-handler" + From 0df5257ea21fc960abc95e61fc82981ae5e10cd0 Mon Sep 17 00:00:00 2001 From: olivermrbl Date: Tue, 30 Jan 2024 20:34:50 +0100 Subject: [PATCH 4/9] fixes --- packages/auth/src/loaders/providers.ts | 1 + packages/modules-sdk/src/loaders/index.ts | 2 ++ packages/modules-sdk/src/loaders/register-providers.ts | 6 +++++- .../__tests__/services/payment-module/index.spec.ts | 8 ++++---- .../integration-tests/utils/get-init-module-config.ts | 6 ++++++ packages/payment/src/loaders/providers.ts | 9 ++++++--- packages/payment/src/module-definition.ts | 3 ++- 7 files changed, 26 insertions(+), 9 deletions(-) diff --git a/packages/auth/src/loaders/providers.ts b/packages/auth/src/loaders/providers.ts index 4f7c11048efcb..9cb09d9788ddd 100644 --- a/packages/auth/src/loaders/providers.ts +++ b/packages/auth/src/loaders/providers.ts @@ -32,6 +32,7 @@ export default async ({ options?.providers?.map((provider) => [provider.name, provider.scopes]) ?? [] ) + // if(options?.providers?.length) { // TODO: implement plugin provider registration // } diff --git a/packages/modules-sdk/src/loaders/index.ts b/packages/modules-sdk/src/loaders/index.ts index 0ff93bba81dae..a196672b61304 100644 --- a/packages/modules-sdk/src/loaders/index.ts +++ b/packages/modules-sdk/src/loaders/index.ts @@ -1,2 +1,4 @@ export * from "./module-loader" export * from "./register-modules" +export * from "./register-providers" + diff --git a/packages/modules-sdk/src/loaders/register-providers.ts b/packages/modules-sdk/src/loaders/register-providers.ts index a71085c22afe3..c3124d6bc1d25 100644 --- a/packages/modules-sdk/src/loaders/register-providers.ts +++ b/packages/modules-sdk/src/loaders/register-providers.ts @@ -20,8 +20,12 @@ export async function moduleProviderLoader({ providers, registerServiceFn, }) { + if (!providers?.length) { + return + } + const resolvedProviders = new Map>() - const installedPlugins = providers.providers.map((provider) => { + const installedPlugins = providers.map((provider) => { let resolvedProvider if (resolvedProviders.has(provider.resolve)) { resolvedProvider = resolvedProviders.get(provider.resolve) diff --git a/packages/payment/integration-tests/__tests__/services/payment-module/index.spec.ts b/packages/payment/integration-tests/__tests__/services/payment-module/index.spec.ts index c29eb908821f8..c508a7b8fda2f 100644 --- a/packages/payment/integration-tests/__tests__/services/payment-module/index.spec.ts +++ b/packages/payment/integration-tests/__tests__/services/payment-module/index.spec.ts @@ -1,12 +1,12 @@ import { IPaymentModuleService } from "@medusajs/types" import { SqlEntityManager } from "@mikro-orm/postgresql" +import { Modules } from "@medusajs/modules-sdk" +import { initModules } from "medusa-test-utils" import { initialize } from "../../../../src/initialize" -import { DB_URL, MikroOrmWrapper } from "../../../utils" import { createPaymentCollections } from "../../../__fixtures__/payment-collection" +import { DB_URL, MikroOrmWrapper } from "../../../utils" import { getInitModuleConfig } from "../../../utils/get-init-module-config" -import { initModules } from "medusa-test-utils" -import { Modules } from "@medusajs/modules-sdk" jest.setTimeout(30000) @@ -48,7 +48,7 @@ describe("Payment Module Service", () => { }) describe("create", () => { - it("should throw an error when required params are not passed", async () => { + it.only("should throw an error when required params are not passed", async () => { let error = await service .createPaymentCollection([ { diff --git a/packages/payment/integration-tests/utils/get-init-module-config.ts b/packages/payment/integration-tests/utils/get-init-module-config.ts index 6c6c667102739..04c040e7222d6 100644 --- a/packages/payment/integration-tests/utils/get-init-module-config.ts +++ b/packages/payment/integration-tests/utils/get-init-module-config.ts @@ -10,6 +10,12 @@ export function getInitModuleConfig() { schema: process.env.MEDUSA_PAYMENT_DB_SCHEMA, }, }, + providers: [ + { + resolve: "medusa-payment-stripe", + options: {}, + }, + ], } const injectedDependencies = {} diff --git a/packages/payment/src/loaders/providers.ts b/packages/payment/src/loaders/providers.ts index 240145ffdc334..e176b00f52476 100644 --- a/packages/payment/src/loaders/providers.ts +++ b/packages/payment/src/loaders/providers.ts @@ -12,7 +12,7 @@ type PaymentModuleProviders = { }[] } -const loadPaymentProviderService = async (klass, container, pluginOptions) => { +const registrationFn = async (klass, container, pluginOptions) => { if (!AbstractPaymentProcessor.isPaymentProcessor(klass.prototype)) { return } @@ -44,9 +44,12 @@ export default async ({ ) & PaymentModuleProviders >): Promise => { + const pluginProviders = + options?.providers?.filter((provider) => provider.resolve) || [] + await moduleProviderLoader({ container, - providers: options!.providers, - registerServiceFn: loadPaymentProviderService, + providers: pluginProviders, + registerServiceFn: registrationFn, }) } diff --git a/packages/payment/src/module-definition.ts b/packages/payment/src/module-definition.ts index 3b7c8e329a897..daae25a16c531 100644 --- a/packages/payment/src/module-definition.ts +++ b/packages/payment/src/module-definition.ts @@ -4,6 +4,7 @@ import { PaymentModuleService } from "@services" import loadConnection from "./loaders/connection" import loadContainer from "./loaders/container" +import loadProviders from "./loaders/providers" import { Modules } from "@medusajs/modules-sdk" import { ModulesSdkUtils } from "@medusajs/utils" @@ -24,7 +25,7 @@ export const revertMigration = ModulesSdkUtils.buildRevertMigrationScript( ) const service = PaymentModuleService -const loaders = [loadContainer, loadConnection] as any +const loaders = [loadContainer, loadConnection, loadProviders] as any export const moduleDefinition: ModuleExports = { service, From 18f4696f627956696e6a9c7fbbf9d89a95de8b08 Mon Sep 17 00:00:00 2001 From: olivermrbl Date: Wed, 31 Jan 2024 13:32:05 +0100 Subject: [PATCH 5/9] plugin exports --- packages/medusa-payment-stripe/src/index.ts | 27 ++- .../src/services/index.ts | 6 + .../src/loaders/register-providers.ts | 167 ++++-------------- .../src/common/format-registration-name.ts | 47 ----- packages/utils/src/common/index.ts | 1 - 5 files changed, 62 insertions(+), 186 deletions(-) create mode 100644 packages/medusa-payment-stripe/src/services/index.ts delete mode 100644 packages/utils/src/common/format-registration-name.ts diff --git a/packages/medusa-payment-stripe/src/index.ts b/packages/medusa-payment-stripe/src/index.ts index 6b0f5ee0f2302..48c0d40d2fdd1 100644 --- a/packages/medusa-payment-stripe/src/index.ts +++ b/packages/medusa-payment-stripe/src/index.ts @@ -1,8 +1,21 @@ -export * from "./core/stripe-base" -export * from "./services/stripe-bancontact" -export * from "./services/stripe-blik" -export * from "./services/stripe-giropay" -export * from "./services/stripe-ideal" -export * from "./services/stripe-provider" -export * from "./services/stripe-przelewy24" +import StripeBaseService from "./core/stripe-base" +import StripeBancontactService from "./services/stripe-bancontact" +import StripeBlikService from "./services/stripe-blik" +import StripeGiropayService from "./services/stripe-giropay" +import StripeIdealService from "./services/stripe-ideal" +import StripeProviderService from "./services/stripe-provider" +import StripePrzelewy24Service from "./services/stripe-przelewy24" + export * from "./types" + +export default { + services: [ + StripeBancontactService, + StripeBaseService, + StripeBlikService, + StripeGiropayService, + StripeIdealService, + StripePrzelewy24Service, + StripeProviderService, + ], +} diff --git a/packages/medusa-payment-stripe/src/services/index.ts b/packages/medusa-payment-stripe/src/services/index.ts new file mode 100644 index 0000000000000..c7fd4cedb8582 --- /dev/null +++ b/packages/medusa-payment-stripe/src/services/index.ts @@ -0,0 +1,6 @@ +export * from "./stripe-bancontact" +export * from "./stripe-blik" +export * from "./stripe-giropay" +export * from "./stripe-ideal" +export * from "./stripe-provider" +export * from "./stripe-przelewy24" diff --git a/packages/modules-sdk/src/loaders/register-providers.ts b/packages/modules-sdk/src/loaders/register-providers.ts index c3124d6bc1d25..c41792dff70bf 100644 --- a/packages/modules-sdk/src/loaders/register-providers.ts +++ b/packages/modules-sdk/src/loaders/register-providers.ts @@ -1,11 +1,6 @@ import { MedusaContainer } from "@medusajs/types" -import { formatRegistrationName, promiseAll } from "@medusajs/utils" +import { promiseAll } from "@medusajs/utils" import { Lifetime, asFunction } from "awilix" -import fs from "fs" -import { sync as existsSync } from "fs-exists-cached" -import glob from "glob" -import { createRequireFromPath } from "medusa-core-utils" -import path from "path" type PluginDetails = { resolve: string @@ -24,151 +19,61 @@ export async function moduleProviderLoader({ return } - const resolvedProviders = new Map>() - const installedPlugins = providers.map((provider) => { - let resolvedProvider - if (resolvedProviders.has(provider.resolve)) { - resolvedProvider = resolvedProviders.get(provider.resolve) - } else { - const details = resolvePlugin(provider.resolve) - details.options = provider.options - - resolvedProviders.set(provider.resolve, details) - resolvedProvider = details - } - - return resolvedProvider - }) - await promiseAll( - installedPlugins.map(async (pluginDetails) => { - await registerServices(pluginDetails, container, registerServiceFn) + providers.map(async (pluginDetails) => { + await loadModuleProvider(container, pluginDetails, registerServiceFn) }) ) - - return installedPlugins } -export async function registerServices( - pluginDetails: PluginDetails, +export async function loadModuleProvider( container: MedusaContainer, + plugin: PluginDetails, registerServiceFn?: (klass, container, pluginDetails) => Promise -): Promise { - const files = glob.sync(`${pluginDetails.resolve}/services/[!__]*.js`, {}) - const klasses: any[] = [] - - await promiseAll( - files.map(async (fn) => { - const loaded = require(fn).default - const name = formatRegistrationName(fn) +) { + let loadedProvider: any + const loadedProviders = new Map() + + if (loadedProviders.has(plugin.resolve)) { + loadedProvider = loadedProviders.get(plugin.resolve) + } else { + try { + const path = plugin.resolve + loadedProvider = await import(path) + loadedProvider = (loadedProvider as any).default + loadedProviders.set(path, loadedProvider) + } catch (error) { + throw new Error( + `Unable to find plugin "${plugin.resolve}". Perhaps you need to install its package?` + ) + } + } - if (typeof loaded !== "function") { - throw new Error( - `Cannot register ${name}. Make sure to default export a service class in ${fn}` - ) - } + if (!loadedProvider?.services) { + throw new Error( + `No services found in plugin "${plugin.resolve}". Make sure your plugin exports a service.` + ) + } + const services = await promiseAll( + loadedProvider.services.map(async (service) => { if (registerServiceFn) { // Used to register the specific type of service in the provider - await registerServiceFn(loaded, container, pluginDetails.options) + await registerServiceFn(service, container, plugin.options) } else { container.register({ - [name]: asFunction( - (cradle) => new loaded(cradle, pluginDetails.options), + [service.name]: asFunction( + (cradle) => new service(cradle, plugin.options), { - lifetime: loaded.LIFE_TIME || Lifetime.SCOPED, + lifetime: service.LIFE_TIME || Lifetime.SCOPED, } ), }) } - klasses.push(loaded) + return service }) ) - return klasses -} - -function resolvePlugin(pluginName: string): { - resolve: string - id: string - name: string - options: Record - version: string -} { - // Only find plugins when we're not given an absolute path - if (!existsSync(pluginName)) { - // Find the plugin in the local plugins folder - const resolvedPath = path.resolve(`./plugins/${pluginName}`) - - if (existsSync(resolvedPath)) { - if (existsSync(`${resolvedPath}/package.json`)) { - const packageJSON = JSON.parse( - fs.readFileSync(`${resolvedPath}/package.json`, `utf-8`) - ) - const name = packageJSON.name || pluginName - - return { - resolve: resolvedPath, - name, - id: name, - options: {}, - version: - packageJSON.version || createFileContentHash(resolvedPath, `**`), - } - } else { - // Make package.json a requirement for local plugins too - throw new Error(`Plugin ${pluginName} requires a package.json file`) - } - } - } - - const rootDir = path.resolve(".") - - /** - * Here we have an absolute path to an internal plugin, or a name of a module - * which should be located in node_modules. - */ - try { - const requireSource = - rootDir !== null - ? createRequireFromPath(`${rootDir}/:internal:`) - : require - - // If the path is absolute, resolve the directory of the internal plugin, - // otherwise resolve the directory containing the package.json - const resolvedPath = path.dirname( - requireSource.resolve(`${pluginName}/package.json`) - ) - - const packageJSON = JSON.parse( - fs.readFileSync(`${resolvedPath}/package.json`, `utf-8`) - ) - - const computedResolvedPath = - resolvedPath + (process.env.DEV_MODE ? "/src" : "") - - // Add support for a plugin to output the build into a dist directory - const resolvedPathToDist = resolvedPath + "/dist" - const isDistExist = - resolvedPathToDist && - !process.env.DEV_MODE && - existsSync(resolvedPath + "/dist") - - return { - resolve: isDistExist ? resolvedPathToDist : computedResolvedPath, - id: packageJSON.name, - name: packageJSON.name, - options: {}, - version: packageJSON.version, - } - } catch (err) { - throw new Error( - `Unable to find plugin "${pluginName}". Perhaps you need to install its package?` - ) - } -} - -function createFileContentHash(path, files): string { - return path + files + return services } diff --git a/packages/utils/src/common/format-registration-name.ts b/packages/utils/src/common/format-registration-name.ts deleted file mode 100644 index 21227593fbbee..0000000000000 --- a/packages/utils/src/common/format-registration-name.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { parse } from "path" -import { toCamelCase } from "./to-camel-case" -import { upperCaseFirst } from "./upper-case-first" - -/** - * Formats a filename into the correct container resolution name. - * Names are camelCase formatted and namespaced by the folder i.e: - * models/example-person -> examplePersonModel - * @param path - the full path of the file - * @return the formatted name - */ -export function formatRegistrationName(path: string): string { - const parsed = parse(path) - const parsedDir = parse(parsed.dir) - const rawname = parsed.name - let directoryNamespace = parsedDir.name - - if (directoryNamespace.startsWith("__")) { - const parsedCoreDir = parse(parsedDir.dir) - directoryNamespace = parsedCoreDir.name - } - - switch (directoryNamespace) { - // We strip the last character when adding the type of registration - // this is a trick for plural "ies" - case "repositories": - directoryNamespace = "repositorys" - break - case "strategies": - directoryNamespace = "strategys" - break - default: - break - } - - const upperNamespace = upperCaseFirst(directoryNamespace.slice(0, -1)) - - return formatRegistrationNameWithoutNamespace(path) + upperNamespace -} - -export function formatRegistrationNameWithoutNamespace(path: string): string { - const parsed = parse(path) - - return toCamelCase(parsed.name) -} - -export default formatRegistrationName diff --git a/packages/utils/src/common/index.ts b/packages/utils/src/common/index.ts index e5c8ad07962b2..7dc7fd9831041 100644 --- a/packages/utils/src/common/index.ts +++ b/packages/utils/src/common/index.ts @@ -6,7 +6,6 @@ export * from "./create-container-like" export * from "./deduplicate" export * from "./deep-equal-obj" export * from "./errors" -export * from "./format-registration-name" export * from "./generate-entity-id" export * from "./generate-linkable-keys-map" export * from "./get-config-file" From f6a98157a7f66f8800f30f35a26e0fbf8bf3450a Mon Sep 17 00:00:00 2001 From: olivermrbl Date: Wed, 31 Jan 2024 14:46:42 +0100 Subject: [PATCH 6/9] allow for direct ref to plugin export --- packages/modules-sdk/src/loaders/index.ts | 2 +- .../src/loaders/module-provider-loader.ts | 87 +++++++++++++++++++ .../src/loaders/register-providers.ts | 79 ----------------- 3 files changed, 88 insertions(+), 80 deletions(-) create mode 100644 packages/modules-sdk/src/loaders/module-provider-loader.ts delete mode 100644 packages/modules-sdk/src/loaders/register-providers.ts diff --git a/packages/modules-sdk/src/loaders/index.ts b/packages/modules-sdk/src/loaders/index.ts index a196672b61304..c6f66e5cd6ec9 100644 --- a/packages/modules-sdk/src/loaders/index.ts +++ b/packages/modules-sdk/src/loaders/index.ts @@ -1,4 +1,4 @@ export * from "./module-loader" +export * from "./module-provider-loader" export * from "./register-modules" -export * from "./register-providers" diff --git a/packages/modules-sdk/src/loaders/module-provider-loader.ts b/packages/modules-sdk/src/loaders/module-provider-loader.ts new file mode 100644 index 0000000000000..8da668dc3f978 --- /dev/null +++ b/packages/modules-sdk/src/loaders/module-provider-loader.ts @@ -0,0 +1,87 @@ +import { Constructor, MedusaContainer } from "@medusajs/types" +import { isString, promiseAll } from "@medusajs/utils" +import { Lifetime, asFunction } from "awilix" + +type ModuleProviderExports = { + services: Constructor[] +} + +type ModuleProvider = { + resolve: string | ModuleProviderExports + provider_name?: string + options: Record +} + +export async function moduleProviderLoader({ + container, + providers, + registerServiceFn, +}) { + if (!providers?.length) { + return + } + + await promiseAll( + providers.map(async (pluginDetails) => { + await loadModuleProvider(container, pluginDetails, registerServiceFn) + }) + ) +} + +export async function loadModuleProvider( + container: MedusaContainer, + provider: ModuleProvider, + registerServiceFn?: (klass, container, pluginDetails) => Promise +) { + let loadedProvider: any + + const pluginName = provider.resolve ?? provider.provider_name ?? "" + + try { + if (isString(provider.resolve)) { + loadedProvider = await import(provider.resolve) + } else { + loadedProvider = provider.resolve + } + } catch (error) { + throw new Error( + `Unable to find plugin ${pluginName} -- perhaps you need to install its package?` + ) + } + + loadedProvider = (loadedProvider as any).default + + if (!loadedProvider) { + throw new Error( + `No default export found in plugin ${pluginName} -- make sure your plugin exports a service.` + ) + } + + if (!loadedProvider?.services) { + throw new Error( + `No services found in plugin ${provider.resolve} -- make sure your plugin exports a service.` + ) + } + + const services = await promiseAll( + loadedProvider.services.map(async (service) => { + if (registerServiceFn) { + // Used to register the specific type of service in the provider + await registerServiceFn(service, container, provider.options) + } else { + container.register({ + [service.name]: asFunction( + (cradle) => new service(cradle, provider.options), + { + lifetime: service.LIFE_TIME || Lifetime.SCOPED, + } + ), + }) + } + + return service + }) + ) + + return services +} diff --git a/packages/modules-sdk/src/loaders/register-providers.ts b/packages/modules-sdk/src/loaders/register-providers.ts deleted file mode 100644 index c41792dff70bf..0000000000000 --- a/packages/modules-sdk/src/loaders/register-providers.ts +++ /dev/null @@ -1,79 +0,0 @@ -import { MedusaContainer } from "@medusajs/types" -import { promiseAll } from "@medusajs/utils" -import { Lifetime, asFunction } from "awilix" - -type PluginDetails = { - resolve: string - name: string - id: string - options: Record - version: string -} - -export async function moduleProviderLoader({ - container, - providers, - registerServiceFn, -}) { - if (!providers?.length) { - return - } - - await promiseAll( - providers.map(async (pluginDetails) => { - await loadModuleProvider(container, pluginDetails, registerServiceFn) - }) - ) -} - -export async function loadModuleProvider( - container: MedusaContainer, - plugin: PluginDetails, - registerServiceFn?: (klass, container, pluginDetails) => Promise -) { - let loadedProvider: any - const loadedProviders = new Map() - - if (loadedProviders.has(plugin.resolve)) { - loadedProvider = loadedProviders.get(plugin.resolve) - } else { - try { - const path = plugin.resolve - loadedProvider = await import(path) - loadedProvider = (loadedProvider as any).default - loadedProviders.set(path, loadedProvider) - } catch (error) { - throw new Error( - `Unable to find plugin "${plugin.resolve}". Perhaps you need to install its package?` - ) - } - } - - if (!loadedProvider?.services) { - throw new Error( - `No services found in plugin "${plugin.resolve}". Make sure your plugin exports a service.` - ) - } - - const services = await promiseAll( - loadedProvider.services.map(async (service) => { - if (registerServiceFn) { - // Used to register the specific type of service in the provider - await registerServiceFn(service, container, plugin.options) - } else { - container.register({ - [service.name]: asFunction( - (cradle) => new service(cradle, plugin.options), - { - lifetime: service.LIFE_TIME || Lifetime.SCOPED, - } - ), - }) - } - - return service - }) - ) - - return services -} From 15c7afb1faa15af837baa42575a68d8a2f77a2ad Mon Sep 17 00:00:00 2001 From: olivermrbl Date: Wed, 31 Jan 2024 20:00:57 +0100 Subject: [PATCH 7/9] add tests --- .../src/loaders/__mocks__/@plugins/default.ts | 5 + .../loaders/__mocks__/@plugins/no-default.ts | 5 + .../loaders/__mocks__/@plugins/no-service.ts | 3 + .../__tests__/module-provider-loader.ts | 94 +++++++++++++++++++ .../src/loaders/module-provider-loader.ts | 27 +++--- packages/payment/src/loaders/providers.ts | 18 +--- packages/types/src/modules-sdk/index.ts | 10 ++ 7 files changed, 132 insertions(+), 30 deletions(-) create mode 100644 packages/modules-sdk/src/loaders/__mocks__/@plugins/default.ts create mode 100644 packages/modules-sdk/src/loaders/__mocks__/@plugins/no-default.ts create mode 100644 packages/modules-sdk/src/loaders/__mocks__/@plugins/no-service.ts create mode 100644 packages/modules-sdk/src/loaders/__tests__/module-provider-loader.ts diff --git a/packages/modules-sdk/src/loaders/__mocks__/@plugins/default.ts b/packages/modules-sdk/src/loaders/__mocks__/@plugins/default.ts new file mode 100644 index 0000000000000..ac1a9484ed785 --- /dev/null +++ b/packages/modules-sdk/src/loaders/__mocks__/@plugins/default.ts @@ -0,0 +1,5 @@ +const service = class TestService {} + +export default { + services: [service], +} diff --git a/packages/modules-sdk/src/loaders/__mocks__/@plugins/no-default.ts b/packages/modules-sdk/src/loaders/__mocks__/@plugins/no-default.ts new file mode 100644 index 0000000000000..855f6e0a54c0d --- /dev/null +++ b/packages/modules-sdk/src/loaders/__mocks__/@plugins/no-default.ts @@ -0,0 +1,5 @@ +const service = class TestService {} + +export const defaultExport = { + services: [service], +} diff --git a/packages/modules-sdk/src/loaders/__mocks__/@plugins/no-service.ts b/packages/modules-sdk/src/loaders/__mocks__/@plugins/no-service.ts new file mode 100644 index 0000000000000..d0c074f41fd92 --- /dev/null +++ b/packages/modules-sdk/src/loaders/__mocks__/@plugins/no-service.ts @@ -0,0 +1,3 @@ +export default { + loaders: [], +} diff --git a/packages/modules-sdk/src/loaders/__tests__/module-provider-loader.ts b/packages/modules-sdk/src/loaders/__tests__/module-provider-loader.ts new file mode 100644 index 0000000000000..75148a7e8960e --- /dev/null +++ b/packages/modules-sdk/src/loaders/__tests__/module-provider-loader.ts @@ -0,0 +1,94 @@ +import { createMedusaContainer } from "@medusajs/utils" +import { Lifetime, asFunction } from "awilix" +import { moduleProviderLoader } from "../module-provider-loader" + +const logger = { + warn: jest.fn(), + error: jest.fn(), +} as any + +describe("modules loader", () => { + let container + + afterEach(() => { + jest.clearAllMocks() + }) + + beforeEach(() => { + container = createMedusaContainer() + }) + + it("should register the provider service", async () => { + const moduleProviders = [ + { + resolve: "@plugins/default", + options: {}, + }, + ] + + await moduleProviderLoader({ container, providers: moduleProviders }) + + const testService = container.resolve("testService") + expect(testService).toBeTruthy() + expect(typeof testService).toEqual("object") + }) + + it("should register the provider service with custom register fn", async () => { + const fn = async (klass, container, details) => { + container.register({ + [`testServiceCustomRegistration`]: asFunction( + (cradle) => new klass(cradle, details.options), + { + lifetime: Lifetime.SINGLETON, + } + ), + }) + } + const moduleProviders = [ + { + resolve: "@plugins/default", + options: {}, + }, + ] + + await moduleProviderLoader({ container, providers: moduleProviders, registerServiceFn: fn }) + + const testService = container.resolve("testServiceCustomRegistration") + expect(testService).toBeTruthy() + expect(typeof testService).toEqual("object") + }) + + it("should log the errors if no service is defined", async () => { + const moduleProviders = [ + { + resolve: "@plugins/no-service", + options: {}, + }, + ] + + try { + await moduleProviderLoader({ container, providers: moduleProviders }) + } catch (error) { + expect(error.message).toBe( + "No services found in plugin @plugins/no-service -- make sure your plugin exports a service." + ) + } + }) + + it("should throw if no default export is defined", async () => { + const moduleProviders = [ + { + resolve: "@plugins/no-default", + options: {}, + }, + ] + + try { + await moduleProviderLoader({ container, providers: moduleProviders }) + } catch (error) { + expect(error.message).toBe( + "No default export found in plugin @plugins/no-default -- make sure your plugin has a default export." + ) + } + }) +}) diff --git a/packages/modules-sdk/src/loaders/module-provider-loader.ts b/packages/modules-sdk/src/loaders/module-provider-loader.ts index 8da668dc3f978..9a8b6e0083e29 100644 --- a/packages/modules-sdk/src/loaders/module-provider-loader.ts +++ b/packages/modules-sdk/src/loaders/module-provider-loader.ts @@ -1,21 +1,19 @@ -import { Constructor, MedusaContainer } from "@medusajs/types" +import { MedusaContainer, ModuleProvider } from "@medusajs/types" import { isString, promiseAll } from "@medusajs/utils" import { Lifetime, asFunction } from "awilix" -type ModuleProviderExports = { - services: Constructor[] -} - -type ModuleProvider = { - resolve: string | ModuleProviderExports - provider_name?: string - options: Record -} - export async function moduleProviderLoader({ container, providers, registerServiceFn, +}: { + container: MedusaContainer + providers: ModuleProvider[] + registerServiceFn?: ( + klass, + container: MedusaContainer, + pluginDetails: any + ) => Promise }) { if (!providers?.length) { return @@ -53,11 +51,11 @@ export async function loadModuleProvider( if (!loadedProvider) { throw new Error( - `No default export found in plugin ${pluginName} -- make sure your plugin exports a service.` + `No default export found in plugin ${pluginName} -- make sure your plugin has a default export.` ) } - if (!loadedProvider?.services) { + if (!loadedProvider.services?.length) { throw new Error( `No services found in plugin ${provider.resolve} -- make sure your plugin exports a service.` ) @@ -65,12 +63,13 @@ export async function loadModuleProvider( const services = await promiseAll( loadedProvider.services.map(async (service) => { + const name = service.name[0].toLowerCase() + service.name.slice(1) if (registerServiceFn) { // Used to register the specific type of service in the provider await registerServiceFn(service, container, provider.options) } else { container.register({ - [service.name]: asFunction( + [name]: asFunction( (cradle) => new service(cradle, provider.options), { lifetime: service.LIFE_TIME || Lifetime.SCOPED, diff --git a/packages/payment/src/loaders/providers.ts b/packages/payment/src/loaders/providers.ts index e176b00f52476..f2d500469739c 100644 --- a/packages/payment/src/loaders/providers.ts +++ b/packages/payment/src/loaders/providers.ts @@ -1,22 +1,9 @@ -import { AbstractPaymentProcessor } from "@medusajs/medusa" import { moduleProviderLoader } from "@medusajs/modules-sdk" -import { LoaderOptions, ModulesSdkTypes } from "@medusajs/types" +import { LoaderOptions, ModuleProvider, ModulesSdkTypes } from "@medusajs/types" import { Lifetime, asFunction } from "awilix" -type PaymentModuleProviders = { - providers: { - resolve: string - provider_id: string // e.g. stripe-usd - options: Record - }[] -} - const registrationFn = async (klass, container, pluginOptions) => { - if (!AbstractPaymentProcessor.isPaymentProcessor(klass.prototype)) { - return - } - container.register({ [`payment_provider_${klass.prototype}`]: asFunction( (cradle) => new klass(cradle, pluginOptions), @@ -41,8 +28,7 @@ export default async ({ ( | ModulesSdkTypes.ModuleServiceInitializeOptions | ModulesSdkTypes.ModuleServiceInitializeCustomDataLayerOptions - ) & - PaymentModuleProviders + ) & { providers: ModuleProvider[] } >): Promise => { const pluginProviders = options?.providers?.filter((provider) => provider.resolve) || [] diff --git a/packages/types/src/modules-sdk/index.ts b/packages/types/src/modules-sdk/index.ts index 5a7188cc2347e..c69a3e90a1681 100644 --- a/packages/types/src/modules-sdk/index.ts +++ b/packages/types/src/modules-sdk/index.ts @@ -290,3 +290,13 @@ export interface IModuleService { onApplicationStart?: () => Promise } } + +export type ModuleProviderExports = { + services: Constructor[] +} + +export type ModuleProvider = { + resolve: string | ModuleProviderExports + provider_name?: string + options: Record +} From a76ab8ea9f7fb2acae5b71807e7f1736d3e68456 Mon Sep 17 00:00:00 2001 From: olivermrbl Date: Wed, 31 Jan 2024 20:02:48 +0100 Subject: [PATCH 8/9] remove stripe test --- packages/medusa-payment-stripe/src/index.ts | 27 +++++-------------- .../src/services/index.ts | 6 ----- .../services/payment-module/index.spec.ts | 2 +- .../utils/get-init-module-config.ts | 6 ----- 4 files changed, 8 insertions(+), 33 deletions(-) delete mode 100644 packages/medusa-payment-stripe/src/services/index.ts diff --git a/packages/medusa-payment-stripe/src/index.ts b/packages/medusa-payment-stripe/src/index.ts index 48c0d40d2fdd1..6b0f5ee0f2302 100644 --- a/packages/medusa-payment-stripe/src/index.ts +++ b/packages/medusa-payment-stripe/src/index.ts @@ -1,21 +1,8 @@ -import StripeBaseService from "./core/stripe-base" -import StripeBancontactService from "./services/stripe-bancontact" -import StripeBlikService from "./services/stripe-blik" -import StripeGiropayService from "./services/stripe-giropay" -import StripeIdealService from "./services/stripe-ideal" -import StripeProviderService from "./services/stripe-provider" -import StripePrzelewy24Service from "./services/stripe-przelewy24" - +export * from "./core/stripe-base" +export * from "./services/stripe-bancontact" +export * from "./services/stripe-blik" +export * from "./services/stripe-giropay" +export * from "./services/stripe-ideal" +export * from "./services/stripe-provider" +export * from "./services/stripe-przelewy24" export * from "./types" - -export default { - services: [ - StripeBancontactService, - StripeBaseService, - StripeBlikService, - StripeGiropayService, - StripeIdealService, - StripePrzelewy24Service, - StripeProviderService, - ], -} diff --git a/packages/medusa-payment-stripe/src/services/index.ts b/packages/medusa-payment-stripe/src/services/index.ts deleted file mode 100644 index c7fd4cedb8582..0000000000000 --- a/packages/medusa-payment-stripe/src/services/index.ts +++ /dev/null @@ -1,6 +0,0 @@ -export * from "./stripe-bancontact" -export * from "./stripe-blik" -export * from "./stripe-giropay" -export * from "./stripe-ideal" -export * from "./stripe-provider" -export * from "./stripe-przelewy24" diff --git a/packages/payment/integration-tests/__tests__/services/payment-module/index.spec.ts b/packages/payment/integration-tests/__tests__/services/payment-module/index.spec.ts index c508a7b8fda2f..4aa4ba692695a 100644 --- a/packages/payment/integration-tests/__tests__/services/payment-module/index.spec.ts +++ b/packages/payment/integration-tests/__tests__/services/payment-module/index.spec.ts @@ -48,7 +48,7 @@ describe("Payment Module Service", () => { }) describe("create", () => { - it.only("should throw an error when required params are not passed", async () => { + it("should throw an error when required params are not passed", async () => { let error = await service .createPaymentCollection([ { diff --git a/packages/payment/integration-tests/utils/get-init-module-config.ts b/packages/payment/integration-tests/utils/get-init-module-config.ts index 04c040e7222d6..6c6c667102739 100644 --- a/packages/payment/integration-tests/utils/get-init-module-config.ts +++ b/packages/payment/integration-tests/utils/get-init-module-config.ts @@ -10,12 +10,6 @@ export function getInitModuleConfig() { schema: process.env.MEDUSA_PAYMENT_DB_SCHEMA, }, }, - providers: [ - { - resolve: "medusa-payment-stripe", - options: {}, - }, - ], } const injectedDependencies = {} From 471dde4236e6a643eeb6b99d51788e19f818f084 Mon Sep 17 00:00:00 2001 From: olivermrbl Date: Thu, 1 Feb 2024 09:45:07 +0100 Subject: [PATCH 9/9] address PR comments --- .../__tests__/module-provider-loader.ts | 14 ++++++++----- .../src/loaders/module-provider-loader.ts | 20 +++++++------------ 2 files changed, 16 insertions(+), 18 deletions(-) diff --git a/packages/modules-sdk/src/loaders/__tests__/module-provider-loader.ts b/packages/modules-sdk/src/loaders/__tests__/module-provider-loader.ts index 75148a7e8960e..f52adc10a8415 100644 --- a/packages/modules-sdk/src/loaders/__tests__/module-provider-loader.ts +++ b/packages/modules-sdk/src/loaders/__tests__/module-provider-loader.ts @@ -30,7 +30,7 @@ describe("modules loader", () => { const testService = container.resolve("testService") expect(testService).toBeTruthy() - expect(typeof testService).toEqual("object") + expect(testService.constructor.name).toEqual("TestService") }) it("should register the provider service with custom register fn", async () => { @@ -51,11 +51,15 @@ describe("modules loader", () => { }, ] - await moduleProviderLoader({ container, providers: moduleProviders, registerServiceFn: fn }) + await moduleProviderLoader({ + container, + providers: moduleProviders, + registerServiceFn: fn, + }) const testService = container.resolve("testServiceCustomRegistration") expect(testService).toBeTruthy() - expect(typeof testService).toEqual("object") + expect(testService.constructor.name).toEqual("TestService") }) it("should log the errors if no service is defined", async () => { @@ -70,7 +74,7 @@ describe("modules loader", () => { await moduleProviderLoader({ container, providers: moduleProviders }) } catch (error) { expect(error.message).toBe( - "No services found in plugin @plugins/no-service -- make sure your plugin exports a service." + "No services found in plugin @plugins/no-service -- make sure your plugin has a default export of services." ) } }) @@ -87,7 +91,7 @@ describe("modules loader", () => { await moduleProviderLoader({ container, providers: moduleProviders }) } catch (error) { expect(error.message).toBe( - "No default export found in plugin @plugins/no-default -- make sure your plugin has a default export." + "No services found in plugin @plugins/no-default -- make sure your plugin has a default export of services." ) } }) diff --git a/packages/modules-sdk/src/loaders/module-provider-loader.ts b/packages/modules-sdk/src/loaders/module-provider-loader.ts index 9a8b6e0083e29..388122712ad4d 100644 --- a/packages/modules-sdk/src/loaders/module-provider-loader.ts +++ b/packages/modules-sdk/src/loaders/module-provider-loader.ts @@ -1,5 +1,5 @@ import { MedusaContainer, ModuleProvider } from "@medusajs/types" -import { isString, promiseAll } from "@medusajs/utils" +import { isString, lowerCaseFirst, promiseAll } from "@medusajs/utils" import { Lifetime, asFunction } from "awilix" export async function moduleProviderLoader({ @@ -36,10 +36,10 @@ export async function loadModuleProvider( const pluginName = provider.resolve ?? provider.provider_name ?? "" try { + loadedProvider = provider.resolve + if (isString(provider.resolve)) { loadedProvider = await import(provider.resolve) - } else { - loadedProvider = provider.resolve } } catch (error) { throw new Error( @@ -47,23 +47,17 @@ export async function loadModuleProvider( ) } - loadedProvider = (loadedProvider as any).default - - if (!loadedProvider) { - throw new Error( - `No default export found in plugin ${pluginName} -- make sure your plugin has a default export.` - ) - } + loadedProvider = (loadedProvider as any).default ?? loadedProvider - if (!loadedProvider.services?.length) { + if (!loadedProvider?.services?.length) { throw new Error( - `No services found in plugin ${provider.resolve} -- make sure your plugin exports a service.` + `No services found in plugin ${provider.resolve} -- make sure your plugin has a default export of services.` ) } const services = await promiseAll( loadedProvider.services.map(async (service) => { - const name = service.name[0].toLowerCase() + service.name.slice(1) + const name = lowerCaseFirst(service.name) if (registerServiceFn) { // Used to register the specific type of service in the provider await registerServiceFn(service, container, provider.options)