** What
- Allow auto-loaded Medusa files to export a config object.
- Currently supports isDisabled to control loading.
- new instance `FeatureFlag` exported by `@medusajs/framework/utils`
- `feature-flags` is now a supported folder for medusa projects, modules, providers and plugins. They will be loaded and added to `FeatureFlag`
** Why
- Enables conditional loading of routes, migrations, jobs, subscribers, workflows, and other files based on feature flags.
```ts
// /src/feature-flags
import { FlagSettings } from "@medusajs/framework/feature-flags"
const CustomFeatureFlag: FlagSettings = {
key: "custom_feature",
default_val: false,
env_key: "FF_MY_CUSTOM_FEATURE",
description: "Enable xyz",
}
export default CustomFeatureFlag
```
```ts
// /src/modules/my-custom-module/migration/Migration20250822135845.ts
import { FeatureFlag } from "@medusajs/framework/utils"
export class Migration20250822135845 extends Migration {
override async up(){ }
override async down(){ }
}
defineFileConfig({
isDisabled: () => !FeatureFlag.isFeatureEnabled("custom_feature")
})
```
203 lines
4.7 KiB
TypeScript
203 lines
4.7 KiB
TypeScript
import {
|
|
ContainerRegistrationKeys,
|
|
DmlEntity,
|
|
isSharedConnectionSymbol,
|
|
loadModels,
|
|
Modules,
|
|
ModulesSdkUtils,
|
|
normalizeImportPathWithSource,
|
|
toMikroOrmEntities,
|
|
} from "@medusajs/framework/utils"
|
|
import * as fs from "fs"
|
|
import { getDatabaseURL, getMikroOrmWrapper, TestDatabase } from "./database"
|
|
import { initModules, InitModulesOptions } from "./init-modules"
|
|
import { default as MockEventBusService } from "./mock-event-bus-service"
|
|
|
|
export interface SuiteOptions<TService = unknown> {
|
|
MikroOrmWrapper: TestDatabase
|
|
medusaApp: any
|
|
service: TService
|
|
dbConfig: {
|
|
schema: string
|
|
clientUrl: string
|
|
}
|
|
}
|
|
|
|
function createMikroOrmWrapper(options: {
|
|
moduleModels?: (Function | DmlEntity<any, any>)[]
|
|
resolve?: string
|
|
dbConfig: any
|
|
cwd?: string
|
|
}): {
|
|
MikroOrmWrapper: TestDatabase
|
|
models: (Function | DmlEntity<any, any>)[]
|
|
} {
|
|
let moduleModels: (Function | DmlEntity<any, any>)[] =
|
|
options.moduleModels ?? []
|
|
|
|
if (!options.moduleModels) {
|
|
const basePath = normalizeImportPathWithSource(
|
|
options.resolve ?? options.cwd ?? process.cwd()
|
|
)
|
|
|
|
const modelsPath = fs.existsSync(`${basePath}/dist/models`)
|
|
? "/dist/models"
|
|
: fs.existsSync(`${basePath}/models`)
|
|
? "/models"
|
|
: ""
|
|
|
|
if (modelsPath) {
|
|
moduleModels = loadModels(`${basePath}${modelsPath}`)
|
|
} else {
|
|
moduleModels = []
|
|
}
|
|
}
|
|
|
|
moduleModels = toMikroOrmEntities(moduleModels)
|
|
|
|
const MikroOrmWrapper = getMikroOrmWrapper({
|
|
mikroOrmEntities: moduleModels,
|
|
clientUrl: options.dbConfig.clientUrl,
|
|
schema: options.dbConfig.schema,
|
|
})
|
|
|
|
return { MikroOrmWrapper, models: moduleModels }
|
|
}
|
|
|
|
export function moduleIntegrationTestRunner<TService = any>({
|
|
moduleName,
|
|
moduleModels,
|
|
moduleOptions = {},
|
|
moduleDependencies,
|
|
joinerConfig = [],
|
|
schema = "public",
|
|
debug = false,
|
|
testSuite,
|
|
resolve,
|
|
injectedDependencies = {},
|
|
cwd,
|
|
}: {
|
|
moduleName: string
|
|
moduleModels?: any[]
|
|
moduleOptions?: Record<string, any>
|
|
moduleDependencies?: string[]
|
|
joinerConfig?: any[]
|
|
schema?: string
|
|
dbName?: string
|
|
injectedDependencies?: Record<string, any>
|
|
resolve?: string
|
|
debug?: boolean
|
|
cwd?: string
|
|
testSuite: (options: SuiteOptions<TService>) => void
|
|
}) {
|
|
const moduleSdkImports = require("@medusajs/framework/modules-sdk")
|
|
|
|
process.env.LOG_LEVEL = "error"
|
|
|
|
const tempName = parseInt(process.env.JEST_WORKER_ID || "1")
|
|
const dbName = `medusa-${moduleName.toLowerCase()}-integration-${tempName}`
|
|
|
|
const dbConfig = {
|
|
clientUrl: getDatabaseURL(dbName),
|
|
schema,
|
|
debug,
|
|
}
|
|
|
|
// Use a unique connection for all the entire suite
|
|
const connection = ModulesSdkUtils.createPgConnection(dbConfig)
|
|
|
|
const { MikroOrmWrapper, models } = createMikroOrmWrapper({
|
|
moduleModels,
|
|
resolve,
|
|
dbConfig,
|
|
cwd,
|
|
})
|
|
|
|
moduleModels = models
|
|
|
|
const modulesConfig_ = {
|
|
[moduleName]: {
|
|
definition: moduleSdkImports.ModulesDefinition[moduleName],
|
|
resolve,
|
|
dependencies: moduleDependencies,
|
|
options: {
|
|
database: dbConfig,
|
|
...moduleOptions,
|
|
[isSharedConnectionSymbol]: true,
|
|
},
|
|
},
|
|
}
|
|
|
|
const moduleOptions_: InitModulesOptions = {
|
|
injectedDependencies: {
|
|
[ContainerRegistrationKeys.PG_CONNECTION]: connection,
|
|
[Modules.EVENT_BUS]: new MockEventBusService(),
|
|
[ContainerRegistrationKeys.LOGGER]: console,
|
|
...injectedDependencies,
|
|
},
|
|
modulesConfig: modulesConfig_,
|
|
databaseConfig: dbConfig,
|
|
joinerConfig,
|
|
preventConnectionDestroyWarning: true,
|
|
cwd,
|
|
}
|
|
|
|
let shutdown: () => Promise<void>
|
|
let moduleService
|
|
let medusaApp = {}
|
|
|
|
const options = {
|
|
MikroOrmWrapper,
|
|
medusaApp: new Proxy(
|
|
{},
|
|
{
|
|
get: (target, prop) => {
|
|
return medusaApp[prop]
|
|
},
|
|
}
|
|
),
|
|
service: new Proxy(
|
|
{},
|
|
{
|
|
get: (target, prop) => {
|
|
return moduleService[prop]
|
|
},
|
|
}
|
|
),
|
|
dbConfig: {
|
|
schema,
|
|
clientUrl: dbConfig.clientUrl,
|
|
},
|
|
} as SuiteOptions<TService>
|
|
|
|
const beforeEach_ = async () => {
|
|
if (moduleModels.length) {
|
|
await MikroOrmWrapper.setupDatabase()
|
|
}
|
|
const output = await initModules(moduleOptions_)
|
|
shutdown = output.shutdown
|
|
medusaApp = output.medusaApp
|
|
moduleService = output.medusaApp.modules[moduleName]
|
|
}
|
|
|
|
const afterEach_ = async () => {
|
|
if (moduleModels.length) {
|
|
await MikroOrmWrapper.clearDatabase()
|
|
}
|
|
await shutdown()
|
|
moduleService = {}
|
|
medusaApp = {}
|
|
}
|
|
|
|
return describe("", () => {
|
|
beforeEach(beforeEach_)
|
|
afterEach(afterEach_)
|
|
afterAll(async () => {
|
|
await (connection as any).context?.destroy()
|
|
await (connection as any).destroy()
|
|
})
|
|
|
|
testSuite(options)
|
|
})
|
|
}
|