feat: Add support for generating a cloud-ready config out of the box (#11850)
This commit is contained in:
@@ -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",
|
||||
},
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -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<ConfigModule["modules"], undefined> {
|
||||
/**
|
||||
* 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,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user