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 09:22:30 -03:00
committed by GitHub
parent 4cda412243
commit e413cfefc2
183 changed files with 1089 additions and 605 deletions

View File

@@ -1,7 +1,4 @@
import {
MedusaRequest,
MedusaResponse,
} from "@medusajs/framework/http"
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
import { ContainerRegistrationKeys } from "@medusajs/framework/utils"
export const AUTHENTICATE = false
@@ -13,14 +10,14 @@ export const GET = async (
const featureFlagRouter = req.scope.resolve(
ContainerRegistrationKeys.FEATURE_FLAG_ROUTER
) as any
const flags = featureFlagRouter.listFlags()
// Convert array of flags to a simple key-value object
const featureFlags: Record<string, boolean> = {}
flags.forEach(flag => {
flags.forEach((flag) => {
featureFlags[flag.key] = flag.value
})
res.json({ feature_flags: featureFlags })
}
}

View File

@@ -1,10 +1,11 @@
import {
featureFlagRouter,
validateAndTransformBody,
validateAndTransformQuery,
} from "@medusajs/framework"
import multer from "multer"
import { maybeApplyLinkFilter, MiddlewareRoute } from "@medusajs/framework/http"
import { FeatureFlag } from "@medusajs/framework/utils"
import multer from "multer"
import IndexEngineFeatureFlag from "../../../feature-flags/index-engine"
import { DEFAULT_BATCH_ENDPOINTS_SIZE_LIMIT } from "../../../utils/middlewares"
import { createBatchBody } from "../../utils/validators"
import * as QueryConfig from "./query-config"
@@ -33,7 +34,6 @@ import {
CreateProduct,
CreateProductVariant,
} from "./validators"
import IndexEngineFeatureFlag from "../../../loaders/feature-flags/index-engine"
const upload = multer({ storage: multer.memoryStorage() })
@@ -47,7 +47,7 @@ export const adminProductRoutesMiddlewares: MiddlewareRoute[] = [
QueryConfig.listProductQueryConfig
),
(req, res, next) => {
if (featureFlagRouter.isFeatureEnabled(IndexEngineFeatureFlag.key)) {
if (FeatureFlag.isFeatureEnabled(IndexEngineFeatureFlag.key)) {
return next()
}

View File

@@ -1,5 +1,4 @@
import { createProductsWorkflow } from "@medusajs/core-flows"
import { featureFlagRouter } from "@medusajs/framework"
import {
AuthenticatedMedusaRequest,
MedusaResponse,
@@ -7,15 +6,19 @@ import {
refetchEntity,
} from "@medusajs/framework/http"
import { AdditionalData, HttpTypes } from "@medusajs/framework/types"
import { ContainerRegistrationKeys, isPresent } from "@medusajs/framework/utils"
import IndexEngineFeatureFlag from "../../../loaders/feature-flags/index-engine"
import {
ContainerRegistrationKeys,
FeatureFlag,
isPresent,
} from "@medusajs/framework/utils"
import IndexEngineFeatureFlag from "../../../feature-flags/index-engine"
import { remapKeysForProduct, remapProductResponse } from "./helpers"
export const GET = async (
req: AuthenticatedMedusaRequest<HttpTypes.AdminProductListParams>,
res: MedusaResponse<HttpTypes.AdminProductListResponse>
) => {
if (featureFlagRouter.isFeatureEnabled(IndexEngineFeatureFlag.key)) {
if (FeatureFlag.isFeatureEnabled(IndexEngineFeatureFlag.key)) {
// Use regular list when no filters are provided
// TODO: Tags and categories are not supported by the index engine yet
if (

View File

@@ -1,10 +1,10 @@
import {
MedusaRequest,
MedusaResponse,
MedusaNextFunction
import {
MedusaNextFunction,
MedusaRequest,
MedusaResponse,
} from "@medusajs/framework/http"
import { ContainerRegistrationKeys } from "@medusajs/framework/utils"
import ViewConfigurationsFeatureFlag from "../../../../../loaders/feature-flags/view-configurations"
import ViewConfigurationsFeatureFlag from "../../../../../feature-flags/view-configurations"
export const ensureViewConfigurationsEnabled = async (
req: MedusaRequest,
@@ -14,14 +14,14 @@ export const ensureViewConfigurationsEnabled = async (
const flagRouter = req.scope.resolve(
ContainerRegistrationKeys.FEATURE_FLAG_ROUTER
) as any
if (!flagRouter.isFeatureEnabled(ViewConfigurationsFeatureFlag.key)) {
res.status(404).json({
type: "not_found",
message: "Route not found"
message: "Route not found",
})
return
}
next()
}
}