feat(utils): define file config (#13283)

** 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")
})
```
This commit is contained in:
Carlos R. L. Rodrigues
2025-08-26 12:22:30 +00:00
committed by GitHub
parent 4cda412243
commit e413cfefc2
183 changed files with 1089 additions and 605 deletions
@@ -1,4 +1,6 @@
import { logger } from "@medusajs/framework/logger"
import { CustomDBMigrator } from "@medusajs/framework/utils"
import {
defineConfig,
MikroORM,
@@ -54,6 +56,7 @@ export function getMikroOrmConfig({
pathTs: pathToMigrations,
silent: true,
},
extensions: [CustomDBMigrator],
})
}
@@ -1,3 +1,4 @@
import { logger } from "@medusajs/framework/logger"
import {
ExternalModuleDeclaration,
InternalModuleDeclaration,
@@ -8,7 +9,6 @@ import {
createPgConnection,
promiseAll,
} from "@medusajs/framework/utils"
import { logger } from "@medusajs/framework/logger"
export interface InitModulesOptions {
injectedDependencies?: Record<string, unknown>
@@ -24,6 +24,7 @@ export interface InitModulesOptions {
}
joinerConfig?: ModuleJoinerConfig[]
preventConnectionDestroyWarning?: boolean
cwd?: string
}
export async function initModules({
@@ -32,6 +33,7 @@ export async function initModules({
modulesConfig,
joinerConfig,
preventConnectionDestroyWarning = false,
cwd,
}: InitModulesOptions) {
const moduleSdkImports = require("@medusajs/framework/modules-sdk")
@@ -55,6 +57,7 @@ export async function initModules({
modulesConfig,
servicesConfig: joinerConfig,
injectedDependencies,
cwd,
})
await medusaApp.onApplicationStart()
@@ -1,10 +1,10 @@
import { logger } from "@medusajs/framework/logger"
import { MedusaContainer } from "@medusajs/framework/types"
import { GracefulShutdownServer, promiseAll } from "@medusajs/framework/utils"
import express from "express"
import getPort from "get-port"
import { resolve } from "path"
import { MedusaContainer } from "@medusajs/framework/types"
import { applyEnvVarsToProcess, execOrTimeout } from "./utils"
import { promiseAll, GracefulShutdownServer } from "@medusajs/framework/utils"
import { logger } from "@medusajs/framework/logger"
async function bootstrapApp({
cwd,
@@ -1,6 +1,6 @@
import type { MedusaAppLoader } from "@medusajs/framework"
import { Logger, MedusaContainer } from "@medusajs/framework/types"
import { logger } from "@medusajs/framework/logger"
import { Logger, MedusaContainer } from "@medusajs/framework/types"
import {
ContainerRegistrationKeys,
getResolvedPlugins,
@@ -11,12 +11,9 @@ import { join } from "path"
* Initiates the database connection
*/
export async function initDb() {
const { pgConnectionLoader, featureFlagsLoader } = await import(
"@medusajs/framework"
)
const { pgConnectionLoader } = await import("@medusajs/framework")
const pgConnection = await pgConnectionLoader()
await featureFlagsLoader()
return pgConnection
}
@@ -51,11 +51,13 @@ interface TestRunnerConfig {
hooks?: {
beforeServerStart?: (container: MedusaContainer) => Promise<void>
}
cwd?: string
}
class MedusaTestRunner {
private dbName: string
private schema: string
private modulesConfigPath: string
private cwd: string
private env: Record<string, any>
private debug: boolean
@@ -85,7 +87,8 @@ class MedusaTestRunner {
config.dbName ??
`medusa-${moduleName.toLowerCase()}-integration-${tempName}`
this.schema = config.schema ?? "public"
this.cwd = config.medusaConfigFile ?? process.cwd()
this.cwd = config.cwd ?? config.medusaConfigFile ?? process.cwd()
this.modulesConfigPath = config.medusaConfigFile ?? this.cwd
this.env = config.env ?? {}
this.debug = config.debug ?? false
this.inApp = config.inApp ?? false
@@ -150,7 +153,10 @@ class MedusaTestRunner {
private async setupApplication(): Promise<void> {
const { container, MedusaAppLoader } = await import("@medusajs/framework")
const appLoader = new MedusaAppLoader()
const appLoader = new MedusaAppLoader({
medusaConfigPath: this.modulesConfigPath,
cwd: this.cwd,
})
// Load plugins modules
const configModule = container.resolve(
@@ -173,7 +179,7 @@ class MedusaTestRunner {
`Migrating database with core migrations and links ${this.dbName}`
)
await migrateDatabase(appLoader)
await syncLinks(appLoader, this.cwd, container, logger)
await syncLinks(appLoader, this.modulesConfigPath, container, logger)
await clearInstances()
this.loadedApplication = await appLoader.load()
@@ -184,7 +190,7 @@ class MedusaTestRunner {
container: appContainer,
port,
} = await startApp({
cwd: this.cwd,
cwd: this.modulesConfigPath,
env: this.env,
})
@@ -270,6 +276,8 @@ class MedusaTestRunner {
const { MedusaAppLoader } = await import("@medusajs/framework")
const medusaAppLoader = new MedusaAppLoader({
container: copiedContainer,
medusaConfigPath: this.modulesConfigPath,
cwd: this.cwd,
})
await medusaAppLoader.runModulesLoader()
} catch (error) {
@@ -319,6 +327,7 @@ export function medusaIntegrationTestRunner({
inApp = false,
testSuite,
hooks,
cwd,
}: {
moduleName?: string
env?: Record<string, any>
@@ -329,6 +338,7 @@ export function medusaIntegrationTestRunner({
inApp?: boolean
testSuite: (options: MedusaSuiteOptions) => void
hooks?: TestRunnerConfig["hooks"]
cwd?: string
}) {
const runner = new MedusaTestRunner({
moduleName,
@@ -339,6 +349,7 @@ export function medusaIntegrationTestRunner({
debug,
inApp,
hooks,
cwd,
})
return describe("", () => {
@@ -27,6 +27,7 @@ function createMikroOrmWrapper(options: {
moduleModels?: (Function | DmlEntity<any, any>)[]
resolve?: string
dbConfig: any
cwd?: string
}): {
MikroOrmWrapper: TestDatabase
models: (Function | DmlEntity<any, any>)[]
@@ -36,7 +37,7 @@ function createMikroOrmWrapper(options: {
if (!options.moduleModels) {
const basePath = normalizeImportPathWithSource(
options.resolve ?? process.cwd()
options.resolve ?? options.cwd ?? process.cwd()
)
const modelsPath = fs.existsSync(`${basePath}/dist/models`)
@@ -74,6 +75,7 @@ export function moduleIntegrationTestRunner<TService = any>({
testSuite,
resolve,
injectedDependencies = {},
cwd,
}: {
moduleName: string
moduleModels?: any[]
@@ -85,6 +87,7 @@ export function moduleIntegrationTestRunner<TService = any>({
injectedDependencies?: Record<string, any>
resolve?: string
debug?: boolean
cwd?: string
testSuite: (options: SuiteOptions<TService>) => void
}) {
const moduleSdkImports = require("@medusajs/framework/modules-sdk")
@@ -107,6 +110,7 @@ export function moduleIntegrationTestRunner<TService = any>({
moduleModels,
resolve,
dbConfig,
cwd,
})
moduleModels = models
@@ -135,6 +139,7 @@ export function moduleIntegrationTestRunner<TService = any>({
databaseConfig: dbConfig,
joinerConfig,
preventConnectionDestroyWarning: true,
cwd,
}
let shutdown: () => Promise<void>