diff --git a/packages/core/utils/src/common/__tests__/define-config.spec.ts b/packages/core/utils/src/common/__tests__/define-config.spec.ts index 2ceacf2f94..a1be108eb9 100644 --- a/packages/core/utils/src/common/__tests__/define-config.spec.ts +++ b/packages/core/utils/src/common/__tests__/define-config.spec.ts @@ -958,4 +958,188 @@ describe("defineConfig", function () { } `) }) + + it("should include cloud-based modules when in cloud execution context", function () { + const originalEnv = { ...process.env } + + process.env.EXECUTION_CONTEXT = "medusa-cloud" + process.env.REDIS_URL = "redis://localhost:6379" + process.env.S3_FILE_URL = "https://s3.amazonaws.com/medusa-cloud-test" + process.env.S3_PREFIX = "test" + process.env.S3_REGION = "us-east-1" + process.env.S3_BUCKET = "medusa-cloud-test" + process.env.S3_ENDPOINT = "https://s3.amazonaws.com" + const res = defineConfig({}) + + process.env = { ...originalEnv } + + expect(res).toMatchInlineSnapshot(` + { + "admin": { + "backendUrl": "/", + "path": "/app", + }, + "featureFlags": {}, + "modules": { + "api_key": { + "resolve": "@medusajs/medusa/api-key", + }, + "auth": { + "options": { + "providers": [ + { + "id": "emailpass", + "resolve": "@medusajs/medusa/auth-emailpass", + }, + ], + }, + "resolve": "@medusajs/medusa/auth", + }, + "cache": { + "options": { + "redisUrl": "redis://localhost:6379", + }, + "resolve": "@medusajs/medusa/cache-redis", + }, + "cart": { + "resolve": "@medusajs/medusa/cart", + }, + "currency": { + "resolve": "@medusajs/medusa/currency", + }, + "customer": { + "resolve": "@medusajs/medusa/customer", + }, + "event_bus": { + "options": { + "redisUrl": "redis://localhost:6379", + }, + "resolve": "@medusajs/medusa/event-bus-redis", + }, + "file": { + "options": { + "providers": [ + { + "id": "s3", + "options": { + "authentication_method": "s3-iam-role", + "bucket": "medusa-cloud-test", + "endpoint": "https://s3.amazonaws.com", + "file_url": "https://s3.amazonaws.com/medusa-cloud-test", + "prefix": "test", + "region": "us-east-1", + }, + "resolve": "@medusajs/medusa/file-s3", + }, + ], + }, + "resolve": "@medusajs/medusa/file", + }, + "fulfillment": { + "options": { + "providers": [ + { + "id": "manual", + "resolve": "@medusajs/medusa/fulfillment-manual", + }, + ], + }, + "resolve": "@medusajs/medusa/fulfillment", + }, + "inventory": { + "resolve": "@medusajs/medusa/inventory", + }, + "locking": { + "options": { + "redisUrl": "redis://localhost:6379", + }, + "resolve": "@medusajs/medusa/locking-redis", + }, + "notification": { + "options": { + "providers": [ + { + "id": "local", + "options": { + "channels": [ + "feed", + ], + "name": "Local Notification Provider", + }, + "resolve": "@medusajs/medusa/notification-local", + }, + ], + }, + "resolve": "@medusajs/medusa/notification", + }, + "order": { + "resolve": "@medusajs/medusa/order", + }, + "payment": { + "resolve": "@medusajs/medusa/payment", + }, + "pricing": { + "resolve": "@medusajs/medusa/pricing", + }, + "product": { + "resolve": "@medusajs/medusa/product", + }, + "promotion": { + "resolve": "@medusajs/medusa/promotion", + }, + "region": { + "resolve": "@medusajs/medusa/region", + }, + "sales_channel": { + "resolve": "@medusajs/medusa/sales-channel", + }, + "stock_location": { + "resolve": "@medusajs/medusa/stock-location", + }, + "store": { + "resolve": "@medusajs/medusa/store", + }, + "tax": { + "resolve": "@medusajs/medusa/tax", + }, + "user": { + "options": { + "jwt_secret": "supersecret", + }, + "resolve": "@medusajs/medusa/user", + }, + "workflows": { + "options": { + "redis": { + "url": "redis://localhost:6379", + }, + }, + "resolve": "@medusajs/medusa/workflow-engine-redis", + }, + }, + "plugins": [], + "projectConfig": { + "databaseUrl": "postgres://localhost/medusa-starter-default", + "http": { + "adminCors": "http://localhost:7000,http://localhost:7001,http://localhost:5173", + "authCors": "http://localhost:7000,http://localhost:7001,http://localhost:5173", + "cookieSecret": "supersecret", + "jwtSecret": "supersecret", + "restrictedFields": { + "store": [ + ${DEFAULT_STORE_RESTRICTED_FIELDS.map((v) => `"${v}"`).join( + ",\n " + )}, + ], + }, + "storeCors": "http://localhost:8000", + }, + "redisOptions": { + "retryStrategy": [Function], + }, + "redisUrl": "redis://localhost:6379", + }, + } + `) + }) }) diff --git a/packages/core/utils/src/common/define-config.ts b/packages/core/utils/src/common/define-config.ts index 117f262f0a..92d8f2ecc3 100644 --- a/packages/core/utils/src/common/define-config.ts +++ b/packages/core/utils/src/common/define-config.ts @@ -8,12 +8,14 @@ import { MODULE_PACKAGE_NAMES, Modules, REVERSED_MODULE_PACKAGE_NAMES, + TEMPORARY_REDIS_MODULE_PACKAGE_NAMES, } from "../modules-sdk" import { isObject } from "./is-object" import { isString } from "./is-string" import { normalizeImportPathWithSource } from "./normalize-import-path-with-source" import { resolveExports } from "./resolve-exports" +const MEDUSA_CLOUD_EXECUTION_CONTEXT = "medusa-cloud" const DEFAULT_SECRET = "supersecret" const DEFAULT_ADMIN_URL = "/" const DEFAULT_STORE_CORS = "http://localhost:8000" @@ -39,71 +41,19 @@ export const DEFAULT_STORE_RESTRICTED_FIELDS = [ * to override configuration as needed. */ export function defineConfig(config: InputConfig = {}): ConfigModule { - const { http, redisOptions, ...restOfProjectConfig } = - config.projectConfig || {} - - /** - * The defaults to use for the project config. They are shallow merged - * with the user defined config. However, - */ - const projectConfig: ConfigModule["projectConfig"] = { - databaseUrl: process.env.DATABASE_URL || DEFAULT_DATABASE_URL, - http: { - storeCors: process.env.STORE_CORS || DEFAULT_STORE_CORS, - adminCors: process.env.ADMIN_CORS || DEFAULT_ADMIN_CORS, - authCors: process.env.AUTH_CORS || DEFAULT_ADMIN_CORS, - jwtSecret: process.env.JWT_SECRET || DEFAULT_SECRET, - cookieSecret: process.env.COOKIE_SECRET || DEFAULT_SECRET, - restrictedFields: { - store: DEFAULT_STORE_RESTRICTED_FIELDS, - }, - ...http, - }, - redisOptions: { - retryStrategy(retries) { - /** - * Exponentially increase delay with every retry - * attempt. Max to 4s - */ - const delay = Math.min(Math.pow(2, retries) * 50, 4000) - - /** - * Add a random jitter to not choke the server when multiple - * clients are retrying at the same time - */ - const jitter = Math.floor(Math.random() * 200) - return delay + jitter - }, - ...redisOptions, - }, - ...restOfProjectConfig, + const options = { + isCloud: process.env.EXECUTION_CONTEXT === MEDUSA_CLOUD_EXECUTION_CONTEXT, } - /** - * The defaults to use for the admin config. They are shallow merged - * with the user defined config - */ - const admin: ConfigModule["admin"] = { - backendUrl: process.env.MEDUSA_BACKEND_URL || DEFAULT_ADMIN_URL, - path: "/app", - ...config.admin, - } - - /** - * The defaults to use for the feature flags config. They are shallow merged - * with the user defined config - */ - const featureFlags: ConfigModule["featureFlags"] = { - ...config.featureFlags, - } - - const modules = resolveModules(config.modules) + const projectConfig = normalizeProjectConfig(config.projectConfig, options) + const adminConfig = normalizeAdminConfig(config.admin) + const modules = resolveModules(config.modules, options) return { projectConfig, - featureFlags, + featureFlags: (config.featureFlags ?? {}) as ConfigModule["featureFlags"], plugins: config.plugins || [], - admin, + admin: adminConfig, modules: modules, } } @@ -180,18 +130,10 @@ export function transformModules( * @param configModules */ function resolveModules( - configModules: InputConfig["modules"] + configModules: InputConfig["modules"], + { isCloud }: { isCloud: boolean } ): Exclude { - /** - * The default set of modules to always use. The end user can swap - * the modules by providing an alternate implementation via their - * config. But they can never remove a module from this list. - */ - const modules: InputConfig["modules"] = [ - { resolve: MODULE_PACKAGE_NAMES[Modules.CACHE] }, - { resolve: MODULE_PACKAGE_NAMES[Modules.EVENT_BUS] }, - { resolve: MODULE_PACKAGE_NAMES[Modules.WORKFLOW_ENGINE] }, - { resolve: MODULE_PACKAGE_NAMES[Modules.LOCKING] }, + const sharedModules = [ { resolve: MODULE_PACKAGE_NAMES[Modules.STOCK_LOCATION] }, { resolve: MODULE_PACKAGE_NAMES[Modules.INVENTORY] }, { resolve: MODULE_PACKAGE_NAMES[Modules.PRODUCT] }, @@ -226,17 +168,6 @@ function resolveModules( jwt_secret: process.env.JWT_SECRET ?? DEFAULT_SECRET, }, }, - { - resolve: MODULE_PACKAGE_NAMES[Modules.FILE], - options: { - providers: [ - { - resolve: "@medusajs/medusa/file-local", - id: "local", - }, - ], - }, - }, { resolve: MODULE_PACKAGE_NAMES[Modules.FULFILLMENT], options: { @@ -265,6 +196,77 @@ function resolveModules( }, ] + const defaultModules = [ + ...sharedModules, + { resolve: MODULE_PACKAGE_NAMES[Modules.CACHE] }, + { resolve: MODULE_PACKAGE_NAMES[Modules.EVENT_BUS] }, + { resolve: MODULE_PACKAGE_NAMES[Modules.WORKFLOW_ENGINE] }, + { resolve: MODULE_PACKAGE_NAMES[Modules.LOCKING] }, + + { + resolve: MODULE_PACKAGE_NAMES[Modules.FILE], + options: { + providers: [ + { + resolve: "@medusajs/medusa/file-local", + id: "local", + }, + ], + }, + }, + ] + + const cloudModules = [ + ...sharedModules, + { + resolve: TEMPORARY_REDIS_MODULE_PACKAGE_NAMES[Modules.WORKFLOW_ENGINE], + options: { + redis: { url: process.env.REDIS_URL }, + }, + }, + { + resolve: TEMPORARY_REDIS_MODULE_PACKAGE_NAMES[Modules.CACHE], + options: { redisUrl: process.env.REDIS_URL }, + }, + { + resolve: TEMPORARY_REDIS_MODULE_PACKAGE_NAMES[Modules.EVENT_BUS], + options: { redisUrl: process.env.REDIS_URL }, + }, + { + resolve: TEMPORARY_REDIS_MODULE_PACKAGE_NAMES[Modules.LOCKING], + options: { redisUrl: process.env.REDIS_URL }, + }, + + { + resolve: MODULE_PACKAGE_NAMES[Modules.FILE], + options: { + providers: [ + { + id: "s3", + resolve: "@medusajs/medusa/file-s3", + options: { + authentication_method: "s3-iam-role", + file_url: process.env.S3_FILE_URL, + prefix: process.env.S3_PREFIX, + region: process.env.S3_REGION, + bucket: process.env.S3_BUCKET, + endpoint: process.env.S3_ENDPOINT, + }, + }, + ], + }, + }, + ] + + /** + * The default set of modules to always use. The end user can swap + * the modules by providing an alternate implementation via their + * config. But they can never remove a module from this list. + */ + const modules: InputConfig["modules"] = isCloud + ? cloudModules + : defaultModules + /** * Backward compatibility for the old way of defining modules (object vs array) */ @@ -293,3 +295,62 @@ function resolveModules( return transformModules(modules) } + +function normalizeProjectConfig( + projectConfig: InputConfig["projectConfig"], + { isCloud }: { isCloud: boolean } +): ConfigModule["projectConfig"] { + const { http, redisOptions, ...restOfProjectConfig } = projectConfig || {} + + /** + * The defaults to use for the project config. They are shallow merged + * with the user defined config. + */ + return { + ...(isCloud ? { redisUrl: process.env.REDIS_URL } : {}), + databaseUrl: process.env.DATABASE_URL || DEFAULT_DATABASE_URL, + http: { + storeCors: process.env.STORE_CORS || DEFAULT_STORE_CORS, + adminCors: process.env.ADMIN_CORS || DEFAULT_ADMIN_CORS, + authCors: process.env.AUTH_CORS || DEFAULT_ADMIN_CORS, + jwtSecret: process.env.JWT_SECRET || DEFAULT_SECRET, + cookieSecret: process.env.COOKIE_SECRET || DEFAULT_SECRET, + restrictedFields: { + store: DEFAULT_STORE_RESTRICTED_FIELDS, + }, + ...http, + }, + redisOptions: { + retryStrategy(retries) { + /** + * Exponentially increase delay with every retry + * attempt. Max to 4s + */ + const delay = Math.min(Math.pow(2, retries) * 50, 4000) + + /** + * Add a random jitter to not choke the server when multiple + * clients are retrying at the same time + */ + const jitter = Math.floor(Math.random() * 200) + return delay + jitter + }, + ...redisOptions, + }, + ...restOfProjectConfig, + } +} + +function normalizeAdminConfig( + adminConfig: InputConfig["admin"] +): ConfigModule["admin"] { + /** + * The defaults to use for the admin config. They are shallow merged + * with the user defined config + */ + return { + backendUrl: process.env.MEDUSA_BACKEND_URL || DEFAULT_ADMIN_URL, + path: "/app", + ...adminConfig, + } +} diff --git a/packages/core/utils/src/modules-sdk/definition.ts b/packages/core/utils/src/modules-sdk/definition.ts index 300f9e0009..a364b489df 100644 --- a/packages/core/utils/src/modules-sdk/definition.ts +++ b/packages/core/utils/src/modules-sdk/definition.ts @@ -64,11 +64,25 @@ export const REVERSED_MODULE_PACKAGE_NAMES = Object.entries( }, {}) // TODO: temporary fix until the event bus, cache and workflow engine are migrated to use providers and therefore only a single resolution will be good -REVERSED_MODULE_PACKAGE_NAMES["@medusajs/medusa/event-bus-redis"] = - Modules.EVENT_BUS -REVERSED_MODULE_PACKAGE_NAMES["@medusajs/medusa/cache-redis"] = Modules.CACHE -REVERSED_MODULE_PACKAGE_NAMES["@medusajs/medusa/workflow-engine-redis"] = - Modules.WORKFLOW_ENGINE +export const TEMPORARY_REDIS_MODULE_PACKAGE_NAMES = { + [Modules.EVENT_BUS]: "@medusajs/medusa/event-bus-redis", + [Modules.CACHE]: "@medusajs/medusa/cache-redis", + [Modules.WORKFLOW_ENGINE]: "@medusajs/medusa/workflow-engine-redis", + [Modules.LOCKING]: "@medusajs/medusa/locking-redis", +} + +REVERSED_MODULE_PACKAGE_NAMES[ + TEMPORARY_REDIS_MODULE_PACKAGE_NAMES[Modules.EVENT_BUS] +] = Modules.EVENT_BUS +REVERSED_MODULE_PACKAGE_NAMES[ + TEMPORARY_REDIS_MODULE_PACKAGE_NAMES[Modules.CACHE] +] = Modules.CACHE +REVERSED_MODULE_PACKAGE_NAMES[ + TEMPORARY_REDIS_MODULE_PACKAGE_NAMES[Modules.WORKFLOW_ENGINE] +] = Modules.WORKFLOW_ENGINE +REVERSED_MODULE_PACKAGE_NAMES[ + TEMPORARY_REDIS_MODULE_PACKAGE_NAMES[Modules.LOCKING] +] = Modules.LOCKING /** * Making modules be referenced as a type as well.