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:
1 parent
4cda412243
commit
e413cfefc2
183 files changed
+1089
-605
No files matched your search
@@ -1,7 +1,7 @@
|
||||
import { FileSystem } from "@medusajs/utils"
|
||||
import { join } from "path"
|
||||
import { featureFlagsLoader } from "../feature-flag-loader"
|
||||
import { configManager } from "../../config"
|
||||
import { featureFlagsLoader } from "../feature-flag-loader"
|
||||
|
||||
const filesystem = new FileSystem(join(__dirname, "__ff-test__"))
|
||||
|
||||
@@ -48,9 +48,12 @@ describe("feature flags", () => {
|
||||
baseDir: filesystem.basePath,
|
||||
})
|
||||
|
||||
await filesystem.create("flags/flag-1.js", buildFeatureFlag("flag-1", true))
|
||||
await filesystem.create(
|
||||
"feature-flags/flag-1.js",
|
||||
buildFeatureFlag("flag-1", true)
|
||||
)
|
||||
|
||||
const flags = await featureFlagsLoader(join(filesystem.basePath, "flags"))
|
||||
const flags = await featureFlagsLoader(join(filesystem.basePath))
|
||||
|
||||
expect(flags.isFeatureEnabled("flag_1")).toEqual(false)
|
||||
})
|
||||
@@ -63,13 +66,16 @@ describe("feature flags", () => {
|
||||
baseDir: filesystem.basePath,
|
||||
})
|
||||
|
||||
await filesystem.create("flags/test.js", buildFeatureFlag("test", false))
|
||||
await filesystem.create(
|
||||
"flags/simpletest.js",
|
||||
"feature-flags/test.js",
|
||||
buildFeatureFlag("test", false)
|
||||
)
|
||||
await filesystem.create(
|
||||
"feature-flags/simpletest.js",
|
||||
buildFeatureFlag("simpletest", false)
|
||||
)
|
||||
|
||||
const flags = await featureFlagsLoader(join(filesystem.basePath, "flags"))
|
||||
const flags = await featureFlagsLoader(join(filesystem.basePath))
|
||||
|
||||
expect(flags.isFeatureEnabled({ test: "nested" })).toEqual(true)
|
||||
expect(flags.isFeatureEnabled("simpletest")).toEqual(true)
|
||||
@@ -77,11 +83,11 @@ describe("feature flags", () => {
|
||||
|
||||
it("should load the default feature flags", async () => {
|
||||
await filesystem.create(
|
||||
"flags/flag-1.js",
|
||||
"feature-flags/flag-1.js",
|
||||
buildFeatureFlag("flag-1", false)
|
||||
)
|
||||
|
||||
const flags = await featureFlagsLoader(join(filesystem.basePath, "flags"))
|
||||
const flags = await featureFlagsLoader(join(filesystem.basePath))
|
||||
|
||||
expect(flags.isFeatureEnabled("flag_1")).toEqual(false)
|
||||
})
|
||||
@@ -90,10 +96,10 @@ describe("feature flags", () => {
|
||||
process.env.MEDUSA_FF_FLAG_1 = "false"
|
||||
|
||||
await filesystem.create(
|
||||
"flags/flag-1.js",
|
||||
"feature-flags/flag-1.js",
|
||||
buildFeatureFlag("flag-1", false)
|
||||
)
|
||||
const flags = await featureFlagsLoader(join(filesystem.basePath, "flags"))
|
||||
const flags = await featureFlagsLoader(join(filesystem.basePath))
|
||||
|
||||
expect(flags.isFeatureEnabled("flag_1")).toEqual(false)
|
||||
})
|
||||
@@ -106,19 +112,19 @@ describe("feature flags", () => {
|
||||
|
||||
process.env.MEDUSA_FF_FLAG_3 = "true"
|
||||
await filesystem.create(
|
||||
"flags/flag-1.js",
|
||||
"feature-flags/flag-1.js",
|
||||
buildFeatureFlag("flag-1", false)
|
||||
)
|
||||
await filesystem.create(
|
||||
"flags/flag-2.js",
|
||||
"feature-flags/flag-2.js",
|
||||
buildFeatureFlag("flag-2", false)
|
||||
)
|
||||
await filesystem.create(
|
||||
"flags/flag-3.js",
|
||||
"feature-flags/flag-3.js",
|
||||
buildFeatureFlag("flag-3", false)
|
||||
)
|
||||
|
||||
const flags = await featureFlagsLoader(join(filesystem.basePath, "flags"))
|
||||
const flags = await featureFlagsLoader(join(filesystem.basePath))
|
||||
|
||||
expect(flags.isFeatureEnabled("flag_1")).toEqual(false)
|
||||
expect(flags.isFeatureEnabled("flag_2")).toEqual(false)
|
||||
|
||||
@@ -1,85 +1,23 @@
|
||||
import { trackFeatureFlag } from "@medusajs/telemetry"
|
||||
import {
|
||||
ContainerRegistrationKeys,
|
||||
dynamicImport,
|
||||
discoverFeatureFlagsFromDir,
|
||||
FeatureFlag,
|
||||
FlagRouter,
|
||||
isDefined,
|
||||
isObject,
|
||||
isString,
|
||||
isTruthy,
|
||||
objectFromStringPath,
|
||||
readDirRecursive,
|
||||
registerFeatureFlag,
|
||||
} from "@medusajs/utils"
|
||||
import { asFunction } from "awilix"
|
||||
import { join, normalize } from "path"
|
||||
import { normalize } from "path"
|
||||
import { configManager } from "../config"
|
||||
import { container } from "../container"
|
||||
import { logger } from "../logger"
|
||||
import { FlagSettings } from "./types"
|
||||
|
||||
export const featureFlagRouter = new FlagRouter({})
|
||||
|
||||
container.register(
|
||||
ContainerRegistrationKeys.FEATURE_FLAG_ROUTER,
|
||||
asFunction(() => featureFlagRouter)
|
||||
asFunction(() => FeatureFlag)
|
||||
)
|
||||
|
||||
const excludedFiles = ["index.js", "index.ts"]
|
||||
const excludedExtensions = [".d.ts", ".d.ts.map", ".js.map"]
|
||||
const flagConfig: Record<string, boolean | Record<string, boolean>> = {}
|
||||
|
||||
function registerFlag(
|
||||
flag: FlagSettings,
|
||||
projectConfigFlags: Record<string, string | boolean | Record<string, boolean>>
|
||||
) {
|
||||
flagConfig[flag.key] = isTruthy(flag.default_val)
|
||||
|
||||
let from
|
||||
if (isDefined(process.env[flag.env_key])) {
|
||||
from = "environment"
|
||||
const envVal = process.env[flag.env_key]
|
||||
|
||||
// MEDUSA_FF_ANALYTICS="true"
|
||||
flagConfig[flag.key] = isTruthy(process.env[flag.env_key])
|
||||
|
||||
const parsedFromEnv = isString(envVal) ? envVal.split(",") : []
|
||||
|
||||
// MEDUSA_FF_WORKFLOWS=createProducts,deleteProducts
|
||||
if (parsedFromEnv.length > 1) {
|
||||
flagConfig[flag.key] = objectFromStringPath(parsedFromEnv)
|
||||
}
|
||||
} else if (isDefined(projectConfigFlags[flag.key])) {
|
||||
from = "project config"
|
||||
|
||||
// featureFlags: { analytics: "true" | true }
|
||||
flagConfig[flag.key] = isTruthy(
|
||||
projectConfigFlags[flag.key] as string | boolean
|
||||
)
|
||||
|
||||
// featureFlags: { workflows: { createProducts: true } }
|
||||
if (isObject(projectConfigFlags[flag.key])) {
|
||||
flagConfig[flag.key] = projectConfigFlags[flag.key] as Record<
|
||||
string,
|
||||
boolean
|
||||
>
|
||||
}
|
||||
}
|
||||
|
||||
if (logger && from) {
|
||||
logger.info(
|
||||
`Using flag ${flag.env_key} from ${from} with value ${
|
||||
flagConfig[flag.key]
|
||||
}`
|
||||
)
|
||||
}
|
||||
|
||||
if (flagConfig[flag.key]) {
|
||||
trackFeatureFlag(flag.key)
|
||||
}
|
||||
|
||||
featureFlagRouter.setFlag(flag.key, flagConfig[flag.key])
|
||||
}
|
||||
|
||||
/**
|
||||
* Load feature flags from a directory and from the already loaded config under the hood
|
||||
* @param sourcePath
|
||||
@@ -90,39 +28,21 @@ export async function featureFlagsLoader(
|
||||
const { featureFlags: projectConfigFlags = {} } = configManager.config
|
||||
|
||||
if (!sourcePath) {
|
||||
return featureFlagRouter
|
||||
return FeatureFlag
|
||||
}
|
||||
|
||||
const flagDir = normalize(sourcePath)
|
||||
|
||||
await readDirRecursive(flagDir).then(async (files) => {
|
||||
if (!files?.length) {
|
||||
return
|
||||
}
|
||||
|
||||
files.map(async (file) => {
|
||||
if (file.isDirectory()) {
|
||||
return await featureFlagsLoader(join(flagDir, file.name))
|
||||
}
|
||||
|
||||
if (
|
||||
excludedExtensions.some((ext) => file.name.endsWith(ext)) ||
|
||||
excludedFiles.includes(file.name)
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
const fileExports = await dynamicImport(join(flagDir, file.name))
|
||||
const featureFlag = fileExports.default
|
||||
|
||||
if (!featureFlag) {
|
||||
return
|
||||
}
|
||||
|
||||
registerFlag(featureFlag, projectConfigFlags)
|
||||
return
|
||||
const discovered = await discoverFeatureFlagsFromDir(flagDir)
|
||||
for (const def of discovered) {
|
||||
registerFeatureFlag({
|
||||
flag: def as FlagSettings,
|
||||
projectConfigFlags,
|
||||
router: FeatureFlag,
|
||||
logger,
|
||||
track: (key) => trackFeatureFlag(key),
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
return featureFlagRouter
|
||||
return FeatureFlag
|
||||
}
|
||||
@@ -35,12 +35,12 @@ export const createServer = async (rootDir) => {
|
||||
|
||||
const moduleResolutions = {}
|
||||
Object.entries(ModulesDefinition).forEach(([moduleKey, module]) => {
|
||||
moduleResolutions[moduleKey] = registerMedusaModule(
|
||||
moduleResolutions[moduleKey] = registerMedusaModule({
|
||||
moduleKey,
|
||||
module.defaultModuleDeclaration,
|
||||
undefined,
|
||||
module
|
||||
)[moduleKey]
|
||||
moduleDeclaration: module.defaultModuleDeclaration,
|
||||
moduleExports: undefined,
|
||||
definition: module,
|
||||
})[moduleKey]
|
||||
})
|
||||
|
||||
configManager.loadConfig({
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
import zod from "zod"
|
||||
import { dynamicImport, FileSystem, isFileSkipped } from "@medusajs/utils"
|
||||
import { join } from "path"
|
||||
import { dynamicImport, FileSystem } from "@medusajs/utils"
|
||||
import zod from "zod"
|
||||
|
||||
import { logger } from "../logger"
|
||||
import {
|
||||
type MiddlewaresConfig,
|
||||
type BodyParserConfigRoute,
|
||||
type MiddlewareDescriptor,
|
||||
type MedusaErrorHandlerFunction,
|
||||
type AdditionalDataValidatorRoute,
|
||||
type BodyParserConfigRoute,
|
||||
HTTP_METHODS,
|
||||
type MedusaErrorHandlerFunction,
|
||||
type MiddlewareDescriptor,
|
||||
type MiddlewaresConfig,
|
||||
} from "./types"
|
||||
|
||||
/**
|
||||
@@ -51,6 +51,10 @@ export class MiddlewareFileLoader {
|
||||
async #processMiddlewareFile(absolutePath: string): Promise<void> {
|
||||
const middlewareExports = await dynamicImport(absolutePath)
|
||||
|
||||
if (isFileSkipped(middlewareExports)) {
|
||||
return
|
||||
}
|
||||
|
||||
const middlewareConfig = middlewareExports.default
|
||||
if (!middlewareConfig) {
|
||||
logger.warn(
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { dynamicImport, readDirRecursive } from "@medusajs/utils"
|
||||
import { dynamicImport, isFileSkipped, readDirRecursive } from "@medusajs/utils"
|
||||
import { join, parse, sep } from "path"
|
||||
import { logger } from "../logger"
|
||||
import { HTTP_METHODS, type RouteDescriptor, type RouteVerb } from "./types"
|
||||
@@ -92,6 +92,10 @@ export class RoutesLoader {
|
||||
): Promise<RouteDescriptor[]> {
|
||||
const routeExports = await dynamicImport(absolutePath)
|
||||
|
||||
if (isFileSkipped(routeExports)) {
|
||||
return []
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the route type based upon its prefix.
|
||||
*/
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { SchedulerOptions } from "@medusajs/orchestration"
|
||||
import { MedusaContainer } from "@medusajs/types"
|
||||
import { isObject, MedusaError } from "@medusajs/utils"
|
||||
import { isFileSkipped, isObject, MedusaError } from "@medusajs/utils"
|
||||
import {
|
||||
createStep,
|
||||
createWorkflow,
|
||||
@@ -31,6 +31,10 @@ export class JobLoader extends ResourceLoader {
|
||||
config: CronJobConfig
|
||||
}
|
||||
) {
|
||||
if (isFileSkipped(fileExports)) {
|
||||
return
|
||||
}
|
||||
|
||||
this.validateConfig(fileExports.config)
|
||||
logger.debug(`Registering job from ${path}.`)
|
||||
this.register({
|
||||
|
||||
@@ -49,20 +49,29 @@ export class MedusaAppLoader {
|
||||
| RegisterModuleJoinerConfig
|
||||
| RegisterModuleJoinerConfig[]
|
||||
|
||||
readonly #medusaConfigPath?: string
|
||||
readonly #cwd?: string
|
||||
|
||||
// TODO: Adjust all loaders to accept an optional container such that in test env it is possible if needed to provide a specific container otherwise use the main container
|
||||
// Maybe also adjust the different places to resolve the config from the container instead of the configManager for the same reason
|
||||
// To be discussed
|
||||
constructor({
|
||||
container,
|
||||
customLinksModules,
|
||||
medusaConfigPath,
|
||||
cwd,
|
||||
}: {
|
||||
container?: MedusaContainer
|
||||
customLinksModules?:
|
||||
| RegisterModuleJoinerConfig
|
||||
| RegisterModuleJoinerConfig[]
|
||||
medusaConfigPath?: string
|
||||
cwd?: string
|
||||
} = {}) {
|
||||
this.#container = container ?? mainContainer
|
||||
this.#customLinksModules = customLinksModules ?? []
|
||||
this.#medusaConfigPath = medusaConfigPath
|
||||
this.#cwd = cwd
|
||||
}
|
||||
|
||||
protected mergeDefaultModules(
|
||||
@@ -172,6 +181,8 @@ export class MedusaAppLoader {
|
||||
linkModules: this.#customLinksModules,
|
||||
sharedResourcesConfig,
|
||||
injectedDependencies,
|
||||
medusaConfigPath: this.#medusaConfigPath,
|
||||
cwd: this.#cwd,
|
||||
}
|
||||
|
||||
if (action === "revert") {
|
||||
@@ -197,6 +208,8 @@ export class MedusaAppLoader {
|
||||
linkModules: this.#customLinksModules,
|
||||
sharedResourcesConfig,
|
||||
injectedDependencies,
|
||||
medusaConfigPath: this.#medusaConfigPath,
|
||||
cwd: this.#cwd,
|
||||
}
|
||||
|
||||
return await MedusaAppGetLinksExecutionPlanner(migrationOptions)
|
||||
@@ -217,6 +230,8 @@ export class MedusaAppLoader {
|
||||
sharedResourcesConfig,
|
||||
injectedDependencies,
|
||||
loaderOnly: true,
|
||||
medusaConfigPath: this.#medusaConfigPath,
|
||||
cwd: this.#cwd,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -255,6 +270,8 @@ export class MedusaAppLoader {
|
||||
linkModules: this.#customLinksModules,
|
||||
sharedResourcesConfig,
|
||||
injectedDependencies,
|
||||
medusaConfigPath: this.#medusaConfigPath,
|
||||
cwd: this.#cwd,
|
||||
})
|
||||
|
||||
if (!config.registerInContainer) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { MedusaContainer } from "@medusajs/types"
|
||||
import { dynamicImport, Modules } from "@medusajs/utils"
|
||||
import { dynamicImport, isFileSkipped, Modules } from "@medusajs/utils"
|
||||
import { basename } from "path"
|
||||
import { logger } from "../logger"
|
||||
import { Migrator } from "./migrator"
|
||||
@@ -28,6 +28,10 @@ export class MigrationScriptsMigrator extends Migrator {
|
||||
for (const script of scriptPaths) {
|
||||
const scriptFn = await dynamicImport(script)
|
||||
|
||||
if (isFileSkipped(scriptFn)) {
|
||||
continue
|
||||
}
|
||||
|
||||
if (!scriptFn.default) {
|
||||
throw new Error(
|
||||
`Failed to load migration script ${script}. No default export found.`
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import { Event, IEventBusModuleService, Subscriber } from "@medusajs/types"
|
||||
import { kebabCase, Modules } from "@medusajs/utils"
|
||||
import { isFileSkipped, kebabCase, Modules } from "@medusajs/utils"
|
||||
import { parse } from "path"
|
||||
|
||||
import { configManager } from "../config"
|
||||
import { container } from "../container"
|
||||
import { logger } from "../logger"
|
||||
import { SubscriberArgs, SubscriberConfig } from "./types"
|
||||
import { ResourceLoader } from "../utils/resource-loader"
|
||||
import { SubscriberArgs, SubscriberConfig } from "./types"
|
||||
|
||||
type SubscriberHandler<T> = (args: SubscriberArgs<T>) => Promise<void>
|
||||
|
||||
@@ -42,6 +42,10 @@ export class SubscriberLoader extends ResourceLoader {
|
||||
path: string,
|
||||
fileExports: Record<string, unknown>
|
||||
) {
|
||||
if (isFileSkipped(fileExports)) {
|
||||
return
|
||||
}
|
||||
|
||||
const isValid = this.validateSubscriber(fileExports, path)
|
||||
|
||||
logger.debug(`Registering subscribers from ${path}.`)
|
||||
|
||||
@@ -86,7 +86,7 @@ export abstract class ResourceLoader {
|
||||
})
|
||||
|
||||
const resources = await promiseAll(promises)
|
||||
return resources.flat()
|
||||
return resources.flat().filter(Boolean)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { isFileSkipped } from "@medusajs/utils"
|
||||
import { MedusaWorkflow } from "@medusajs/workflows-sdk"
|
||||
import { logger } from "../logger"
|
||||
import { ResourceLoader } from "../utils/resource-loader"
|
||||
|
||||
@@ -12,6 +14,17 @@ export class WorkflowLoader extends ResourceLoader {
|
||||
path: string,
|
||||
fileExports: Record<string, unknown>
|
||||
) {
|
||||
if (isFileSkipped(fileExports)) {
|
||||
const exportedFns = Object.keys(fileExports)
|
||||
for (const exportedFn of exportedFns) {
|
||||
const fn = fileExports[exportedFn] as any
|
||||
if (fn?.getName?.()) {
|
||||
MedusaWorkflow.unregisterWorkflow(fn.getName())
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
logger.debug(`Registering workflows from ${path}.`)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
export * from "./definitions"
|
||||
export * from "./link"
|
||||
export * from "./loaders"
|
||||
export * from "./medusa-app"
|
||||
export * from "./medusa-module"
|
||||
export * from "./link"
|
||||
export * from "./remote-query"
|
||||
export * from "./types"
|
||||
export * from "./utils"
|
||||
@@ -1,4 +1,4 @@
|
||||
import { InternalModuleDeclaration, ModuleDefinition } from "@medusajs/types"
|
||||
import { ModuleDefinition } from "@medusajs/types"
|
||||
import { ModulesDefinition } from "../../definitions"
|
||||
import { MODULE_SCOPE } from "../../types"
|
||||
import { registerMedusaModule } from "../register-modules"
|
||||
@@ -35,7 +35,9 @@ describe("module definitions loader", () => {
|
||||
[defaultDefinition.key]: defaultDefinition,
|
||||
})
|
||||
|
||||
const res = registerMedusaModule(defaultDefinition.key)
|
||||
const res = registerMedusaModule({
|
||||
moduleKey: defaultDefinition.key,
|
||||
})
|
||||
|
||||
expect(res[defaultDefinition.key]).toEqual(
|
||||
expect.objectContaining({
|
||||
@@ -50,10 +52,13 @@ describe("module definitions loader", () => {
|
||||
})
|
||||
|
||||
it("Resolves a custom module without pre-defined definition", () => {
|
||||
const res = registerMedusaModule("customModulesABC", {
|
||||
resolve: testServiceResolved,
|
||||
options: {
|
||||
test: 123,
|
||||
const res = registerMedusaModule({
|
||||
moduleKey: "customModulesABC",
|
||||
moduleDeclaration: {
|
||||
resolve: testServiceResolved,
|
||||
options: {
|
||||
test: 123,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
@@ -80,7 +85,10 @@ describe("module definitions loader", () => {
|
||||
[defaultDefinition.key]: defaultDefinition,
|
||||
})
|
||||
|
||||
const res = registerMedusaModule(defaultDefinition.key, false)
|
||||
const res = registerMedusaModule({
|
||||
moduleKey: defaultDefinition.key,
|
||||
moduleDeclaration: false,
|
||||
})
|
||||
|
||||
expect(res[defaultDefinition.key]).toEqual(
|
||||
expect.objectContaining({
|
||||
@@ -98,7 +106,10 @@ describe("module definitions loader", () => {
|
||||
})
|
||||
|
||||
try {
|
||||
registerMedusaModule(defaultDefinition.key, false)
|
||||
registerMedusaModule({
|
||||
moduleKey: defaultDefinition.key,
|
||||
moduleDeclaration: false,
|
||||
})
|
||||
} catch (err) {
|
||||
expect(err.message).toEqual(
|
||||
`Module: ${defaultDefinition.label} is required`
|
||||
@@ -118,7 +129,9 @@ describe("module definitions loader", () => {
|
||||
[defaultDefinition.key]: definition,
|
||||
})
|
||||
|
||||
const res = registerMedusaModule(defaultDefinition.key)
|
||||
const res = registerMedusaModule({
|
||||
moduleKey: defaultDefinition.key,
|
||||
})
|
||||
|
||||
expect(res[defaultDefinition.key]).toEqual(
|
||||
expect.objectContaining({
|
||||
@@ -138,10 +151,10 @@ describe("module definitions loader", () => {
|
||||
[defaultDefinition.key]: defaultDefinition,
|
||||
})
|
||||
|
||||
const res = registerMedusaModule(
|
||||
defaultDefinition.key,
|
||||
defaultDefinition.defaultPackage
|
||||
)
|
||||
const res = registerMedusaModule({
|
||||
moduleKey: defaultDefinition.key,
|
||||
moduleDeclaration: defaultDefinition.defaultPackage,
|
||||
})
|
||||
|
||||
expect(res[defaultDefinition.key]).toEqual(
|
||||
expect.objectContaining({
|
||||
@@ -162,10 +175,13 @@ describe("module definitions loader", () => {
|
||||
[defaultDefinition.key]: defaultDefinition,
|
||||
})
|
||||
|
||||
const res = registerMedusaModule(defaultDefinition.key, {
|
||||
scope: MODULE_SCOPE.INTERNAL,
|
||||
resolve: defaultDefinition.defaultPackage,
|
||||
} as InternalModuleDeclaration)
|
||||
const res = registerMedusaModule({
|
||||
moduleKey: defaultDefinition.key,
|
||||
moduleDeclaration: {
|
||||
scope: MODULE_SCOPE.INTERNAL,
|
||||
resolve: defaultDefinition.defaultPackage as string,
|
||||
},
|
||||
})
|
||||
|
||||
expect(res[defaultDefinition.key]).toEqual(
|
||||
expect.objectContaining({
|
||||
@@ -186,9 +202,12 @@ describe("module definitions loader", () => {
|
||||
[defaultDefinition.key]: defaultDefinition,
|
||||
})
|
||||
|
||||
const res = registerMedusaModule(defaultDefinition.key, {
|
||||
options: { test: 123 },
|
||||
} as any)
|
||||
const res = registerMedusaModule({
|
||||
moduleKey: defaultDefinition.key,
|
||||
moduleDeclaration: {
|
||||
options: { test: 123 },
|
||||
},
|
||||
})
|
||||
|
||||
expect(res[defaultDefinition.key]).toEqual(
|
||||
expect.objectContaining({
|
||||
@@ -209,11 +228,14 @@ describe("module definitions loader", () => {
|
||||
[defaultDefinition.key]: defaultDefinition,
|
||||
})
|
||||
|
||||
const res = registerMedusaModule(defaultDefinition.key, {
|
||||
resolve: defaultDefinition.defaultPackage,
|
||||
options: { test: 123 },
|
||||
scope: "internal",
|
||||
} as any)
|
||||
const res = registerMedusaModule({
|
||||
moduleKey: defaultDefinition.key,
|
||||
moduleDeclaration: {
|
||||
scope: MODULE_SCOPE.INTERNAL,
|
||||
resolve: defaultDefinition.defaultPackage as string,
|
||||
options: { test: 123 },
|
||||
},
|
||||
})
|
||||
|
||||
expect(res[defaultDefinition.key]).toEqual(
|
||||
expect.objectContaining({
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { MedusaContainer, ModuleProvider } from "@medusajs/types"
|
||||
import {
|
||||
dynamicImport,
|
||||
isFileSkipped,
|
||||
isString,
|
||||
lowerCaseFirst,
|
||||
normalizeImportPathWithSource,
|
||||
@@ -53,6 +54,10 @@ export async function loadModuleProvider(
|
||||
)
|
||||
}
|
||||
|
||||
if (isFileSkipped(loadedProvider)) {
|
||||
return
|
||||
}
|
||||
|
||||
loadedProvider = (loadedProvider as any).default ?? loadedProvider
|
||||
|
||||
if (!loadedProvider?.services?.length) {
|
||||
|
||||
@@ -14,15 +14,22 @@ import {
|
||||
import { ModulesDefinition } from "../definitions"
|
||||
import { MODULE_SCOPE } from "../types"
|
||||
|
||||
export const registerMedusaModule = (
|
||||
moduleKey: string,
|
||||
export const registerMedusaModule = ({
|
||||
moduleKey,
|
||||
moduleDeclaration,
|
||||
moduleExports,
|
||||
definition,
|
||||
cwd,
|
||||
}: {
|
||||
moduleKey: string
|
||||
moduleDeclaration?:
|
||||
| Partial<InternalModuleDeclaration | ExternalModuleDeclaration>
|
||||
| string
|
||||
| false,
|
||||
moduleExports?: ModuleExports,
|
||||
| false
|
||||
moduleExports?: ModuleExports
|
||||
definition?: ModuleDefinition
|
||||
): Record<string, ModuleResolution> => {
|
||||
cwd?: string
|
||||
}): Record<string, ModuleResolution> => {
|
||||
const moduleResolutions = {} as Record<string, ModuleResolution>
|
||||
|
||||
const modDefinition = definition ?? ModulesDefinition[moduleKey]
|
||||
@@ -46,7 +53,8 @@ export const registerMedusaModule = (
|
||||
if (modDefinition === undefined) {
|
||||
moduleResolutions[moduleKey] = getCustomModuleResolution(
|
||||
moduleKey,
|
||||
moduleDeclaration as InternalModuleDeclaration
|
||||
moduleDeclaration as InternalModuleDeclaration,
|
||||
cwd
|
||||
)
|
||||
return moduleResolutions
|
||||
}
|
||||
@@ -54,7 +62,8 @@ export const registerMedusaModule = (
|
||||
moduleResolutions[moduleKey] = getInternalModuleResolution(
|
||||
modDefinition,
|
||||
moduleDeclaration as InternalModuleDeclaration,
|
||||
moduleExports
|
||||
moduleExports,
|
||||
cwd
|
||||
)
|
||||
|
||||
return moduleResolutions
|
||||
@@ -62,13 +71,15 @@ export const registerMedusaModule = (
|
||||
|
||||
function getCustomModuleResolution(
|
||||
key: string,
|
||||
moduleConfig: InternalModuleDeclaration | string
|
||||
moduleConfig: InternalModuleDeclaration | string,
|
||||
cwd: string = process.cwd()
|
||||
): ModuleResolution {
|
||||
const originalPath = normalizeImportPathWithSource(
|
||||
(isString(moduleConfig) ? moduleConfig : moduleConfig.resolve) as string
|
||||
(isString(moduleConfig) ? moduleConfig : moduleConfig.resolve) as string,
|
||||
cwd
|
||||
)
|
||||
const resolutionPath = require.resolve(originalPath, {
|
||||
paths: [process.cwd()],
|
||||
paths: [cwd],
|
||||
})
|
||||
|
||||
const conf = isObject(moduleConfig)
|
||||
@@ -100,14 +111,16 @@ function getCustomModuleResolution(
|
||||
export const registerMedusaLinkModule = (
|
||||
definition: ModuleDefinition,
|
||||
moduleDeclaration: Partial<InternalModuleDeclaration>,
|
||||
moduleExports?: ModuleExports
|
||||
moduleExports?: ModuleExports,
|
||||
cwd: string = process.cwd()
|
||||
): Record<string, ModuleResolution> => {
|
||||
const moduleResolutions = {} as Record<string, ModuleResolution>
|
||||
|
||||
moduleResolutions[definition.key] = getInternalModuleResolution(
|
||||
definition,
|
||||
moduleDeclaration as InternalModuleDeclaration,
|
||||
moduleExports
|
||||
moduleExports,
|
||||
cwd
|
||||
)
|
||||
|
||||
return moduleResolutions
|
||||
@@ -116,7 +129,8 @@ export const registerMedusaLinkModule = (
|
||||
function getInternalModuleResolution(
|
||||
definition: ModuleDefinition,
|
||||
moduleConfig: InternalModuleDeclaration | string | false,
|
||||
moduleExports?: ModuleExports
|
||||
moduleExports?: ModuleExports,
|
||||
cwd: string = process.cwd()
|
||||
): ModuleResolution {
|
||||
if (typeof moduleConfig === "boolean") {
|
||||
if (!moduleConfig && definition.isRequired) {
|
||||
@@ -140,10 +154,11 @@ function getInternalModuleResolution(
|
||||
const isStr = isString(moduleConfig)
|
||||
if (isStr || (isObj && moduleConfig.resolve)) {
|
||||
const originalPath = normalizeImportPathWithSource(
|
||||
(isString(moduleConfig) ? moduleConfig : moduleConfig.resolve) as string
|
||||
(isString(moduleConfig) ? moduleConfig : moduleConfig.resolve) as string,
|
||||
cwd
|
||||
)
|
||||
resolutionPath = require.resolve(originalPath, {
|
||||
paths: [process.cwd()],
|
||||
paths: [cwd],
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@ import { ModuleProviderService as ModuleServiceWithProviderProvider1 } from "../
|
||||
import { ModuleProvider2Service as ModuleServiceWithProviderProvider2 } from "../__fixtures__/module-with-providers/provider-2"
|
||||
import { loadInternalModule, loadResources } from "../load-internal"
|
||||
|
||||
const container = createMedusaContainer()
|
||||
describe("load internal", () => {
|
||||
describe("loadResources", () => {
|
||||
describe("when loading the module resources from a path", () => {
|
||||
@@ -43,6 +44,7 @@ describe("load internal", () => {
|
||||
).toBeUndefined()
|
||||
|
||||
const resources = await loadResources({
|
||||
container,
|
||||
moduleResolution,
|
||||
discoveryPath: moduleResolution.resolutionPath as string,
|
||||
})
|
||||
@@ -125,6 +127,7 @@ describe("load internal", () => {
|
||||
).toBeUndefined()
|
||||
|
||||
const resources = await loadResources({
|
||||
container,
|
||||
moduleResolution,
|
||||
discoveryPath: moduleResolution.resolutionPath as string,
|
||||
})
|
||||
@@ -207,6 +210,7 @@ describe("load internal", () => {
|
||||
).toBeUndefined()
|
||||
|
||||
const resources = await loadResources({
|
||||
container,
|
||||
moduleResolution,
|
||||
discoveryPath: moduleResolution.resolutionPath as string,
|
||||
})
|
||||
@@ -288,6 +292,7 @@ describe("load internal", () => {
|
||||
).toBeDefined()
|
||||
|
||||
const resources = await loadResources({
|
||||
container,
|
||||
moduleResolution,
|
||||
discoveryPath: moduleResolution.resolutionPath as string,
|
||||
})
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import {
|
||||
ConfigModule,
|
||||
Constructor,
|
||||
IModuleService,
|
||||
InternalModuleDeclaration,
|
||||
@@ -16,14 +17,18 @@ import {
|
||||
ContainerRegistrationKeys,
|
||||
createMedusaContainer,
|
||||
defineJoinerConfig,
|
||||
discoverFeatureFlagsFromDir,
|
||||
DmlEntity,
|
||||
dynamicImport,
|
||||
FeatureFlag,
|
||||
getProviderRegistrationKey,
|
||||
isFileSkipped,
|
||||
isString,
|
||||
MedusaModuleProviderType,
|
||||
MedusaModuleType,
|
||||
Modules,
|
||||
ModulesSdkUtils,
|
||||
registerFeatureFlag,
|
||||
stringifyCircular,
|
||||
toMikroOrmEntities,
|
||||
} from "@medusajs/utils"
|
||||
@@ -193,6 +198,7 @@ export async function loadInternalModule(args: {
|
||||
|
||||
if (loadedModule.discoveryPath) {
|
||||
moduleResources = await loadResources({
|
||||
container,
|
||||
moduleResolution: resolution,
|
||||
discoveryPath: loadedModule.discoveryPath,
|
||||
logger,
|
||||
@@ -379,6 +385,7 @@ export async function loadInternalModule(args: {
|
||||
}
|
||||
|
||||
export async function loadModuleMigrations(
|
||||
container: MedusaContainer,
|
||||
resolution: ModuleResolution,
|
||||
moduleExports?: ModuleExports
|
||||
): Promise<{
|
||||
@@ -449,6 +456,7 @@ export async function loadModuleMigrations(
|
||||
|
||||
if (!runMigrationsCustom || !revertMigrationCustom) {
|
||||
const moduleResources = await loadResources({
|
||||
container,
|
||||
moduleResolution: resolution,
|
||||
discoveryPath: loadedModule.discoveryPath,
|
||||
loadedModuleLoaders: loadedModule?.loaders,
|
||||
@@ -536,17 +544,21 @@ async function importAllFromDir(path: string) {
|
||||
|
||||
return (
|
||||
await Promise.all(filesToLoad.map((filePath) => dynamicImport(filePath)))
|
||||
).flatMap((value) => {
|
||||
return Object.values(value)
|
||||
})
|
||||
)
|
||||
.filter((value) => !isFileSkipped(value))
|
||||
.flatMap((value) => {
|
||||
return Object.values(value)
|
||||
})
|
||||
}
|
||||
|
||||
export async function loadResources({
|
||||
container,
|
||||
moduleResolution,
|
||||
discoveryPath,
|
||||
logger,
|
||||
loadedModuleLoaders,
|
||||
}: {
|
||||
container: MedusaContainer
|
||||
moduleResolution: ModuleResolution
|
||||
discoveryPath: string
|
||||
logger?: Logger
|
||||
@@ -566,8 +578,31 @@ export async function loadResources({
|
||||
return []
|
||||
}
|
||||
|
||||
const flagDir = resolve(normalizedPath)
|
||||
const discovered = await discoverFeatureFlagsFromDir(flagDir, 1)
|
||||
|
||||
const configModule = container.resolve(
|
||||
ContainerRegistrationKeys.CONFIG_MODULE,
|
||||
{
|
||||
allowUnregistered: true,
|
||||
}
|
||||
) as ConfigModule
|
||||
|
||||
for (const def of discovered) {
|
||||
registerFeatureFlag({
|
||||
flag: def,
|
||||
projectConfigFlags: configModule?.featureFlags ?? {},
|
||||
router: FeatureFlag,
|
||||
logger,
|
||||
})
|
||||
}
|
||||
|
||||
const [moduleService, services, models, repositories] = await Promise.all([
|
||||
dynamicImport(modulePath).then((moduleExports) => {
|
||||
if (isFileSkipped(moduleExports)) {
|
||||
return
|
||||
}
|
||||
|
||||
const mod = moduleExports.default ?? moduleExports
|
||||
return mod.service
|
||||
}),
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
import { RemoteFetchDataCallback } from "@medusajs/orchestration"
|
||||
import {
|
||||
ConfigModule,
|
||||
ExternalModuleDeclaration,
|
||||
FlagSettings,
|
||||
IIndexService,
|
||||
ILinkMigrationsPlanner,
|
||||
InternalModuleDeclaration,
|
||||
LoadedModule,
|
||||
Logger,
|
||||
MedusaContainer,
|
||||
ModuleBootstrapDeclaration,
|
||||
ModuleDefinition,
|
||||
@@ -16,7 +19,9 @@ import {
|
||||
import {
|
||||
ContainerRegistrationKeys,
|
||||
createMedusaContainer,
|
||||
discoverFeatureFlagsFromDir,
|
||||
dynamicImport,
|
||||
FeatureFlag,
|
||||
GraphQLUtils,
|
||||
isObject,
|
||||
isSharedConnectionSymbol,
|
||||
@@ -26,6 +31,7 @@ import {
|
||||
Modules,
|
||||
ModulesSdkUtils,
|
||||
promiseAll,
|
||||
registerFeatureFlag,
|
||||
} from "@medusajs/utils"
|
||||
import { asValue } from "awilix"
|
||||
import { Link } from "./link"
|
||||
@@ -79,6 +85,7 @@ export async function loadModules(args: {
|
||||
migrationOnly?: boolean
|
||||
loaderOnly?: boolean
|
||||
workerMode?: "shared" | "worker" | "server"
|
||||
cwd?: string
|
||||
}) {
|
||||
const {
|
||||
modulesConfig,
|
||||
@@ -87,6 +94,7 @@ export async function loadModules(args: {
|
||||
migrationOnly = false,
|
||||
loaderOnly = false,
|
||||
workerMode = "shared" as ModuleBootstrapOptions["workerMode"],
|
||||
cwd,
|
||||
} = args
|
||||
|
||||
const allModules = {} as any
|
||||
@@ -155,6 +163,7 @@ export async function loadModules(args: {
|
||||
migrationOnly,
|
||||
loaderOnly,
|
||||
workerMode,
|
||||
cwd,
|
||||
})) as LoadedModule[]
|
||||
|
||||
if (loaderOnly) {
|
||||
@@ -285,7 +294,7 @@ export type MedusaAppOptions = {
|
||||
sharedResourcesConfig?: SharedResources
|
||||
loadedModules?: LoadedModule[]
|
||||
servicesConfig?: ModuleJoinerConfig[]
|
||||
modulesConfigPath?: string
|
||||
medusaConfigPath?: string
|
||||
modulesConfigFileName?: string
|
||||
modulesConfig?: MedusaModuleConfig
|
||||
linkModules?: RegisterModuleJoinerConfig | RegisterModuleJoinerConfig[]
|
||||
@@ -296,13 +305,14 @@ export type MedusaAppOptions = {
|
||||
* Forces the modules bootstrapper to only run the modules loaders and return prematurely
|
||||
*/
|
||||
loaderOnly?: boolean
|
||||
cwd?: string
|
||||
}
|
||||
|
||||
async function MedusaApp_({
|
||||
sharedContainer,
|
||||
sharedResourcesConfig,
|
||||
servicesConfig,
|
||||
modulesConfigPath,
|
||||
medusaConfigPath,
|
||||
modulesConfigFileName,
|
||||
modulesConfig,
|
||||
linkModules,
|
||||
@@ -311,11 +321,32 @@ async function MedusaApp_({
|
||||
migrationOnly = false,
|
||||
loaderOnly = false,
|
||||
workerMode = "shared",
|
||||
cwd = process.cwd(),
|
||||
}: MedusaAppOptions & {
|
||||
migrationOnly?: boolean
|
||||
} = {}): Promise<MedusaAppOutput> {
|
||||
const sharedContainer_ = createMedusaContainer({}, sharedContainer)
|
||||
|
||||
const config = sharedContainer_.resolve(
|
||||
ContainerRegistrationKeys.CONFIG_MODULE,
|
||||
{
|
||||
allowUnregistered: true,
|
||||
}
|
||||
) as ConfigModule
|
||||
const logger = sharedContainer_.resolve(ContainerRegistrationKeys.LOGGER, {
|
||||
allowUnregistered: true,
|
||||
}) as Logger
|
||||
|
||||
const discovered = await discoverFeatureFlagsFromDir(cwd)
|
||||
for (const def of discovered) {
|
||||
registerFeatureFlag({
|
||||
flag: def as FlagSettings,
|
||||
projectConfigFlags: config?.featureFlags ?? {},
|
||||
router: FeatureFlag,
|
||||
logger,
|
||||
})
|
||||
}
|
||||
|
||||
const onApplicationShutdown = async () => {
|
||||
await promiseAll([
|
||||
MedusaModule.onApplicationShutdown(),
|
||||
@@ -335,8 +366,7 @@ async function MedusaApp_({
|
||||
modulesConfig ??
|
||||
(
|
||||
await dynamicImport(
|
||||
modulesConfigPath ??
|
||||
process.cwd() + (modulesConfigFileName ?? "/modules-config")
|
||||
medusaConfigPath ?? cwd + (modulesConfigFileName ?? "/modules-config")
|
||||
)
|
||||
).default
|
||||
|
||||
@@ -391,6 +421,7 @@ async function MedusaApp_({
|
||||
migrationOnly,
|
||||
loaderOnly,
|
||||
workerMode,
|
||||
cwd,
|
||||
})
|
||||
|
||||
if (loaderOnly) {
|
||||
@@ -512,6 +543,7 @@ async function MedusaApp_({
|
||||
container: sharedContainer,
|
||||
options: moduleResolution.options,
|
||||
moduleExports: moduleResolution.moduleExports as ModuleExports,
|
||||
cwd,
|
||||
}
|
||||
|
||||
if (action === "revert") {
|
||||
|
||||
@@ -58,6 +58,7 @@ export type MigrationOptions = {
|
||||
container?: MedusaContainer
|
||||
options?: Record<string, any>
|
||||
moduleExports?: ModuleExports
|
||||
cwd?: string
|
||||
}
|
||||
|
||||
export type ModuleBootstrapOptions = {
|
||||
@@ -80,6 +81,7 @@ export type ModuleBootstrapOptions = {
|
||||
*/
|
||||
loaderOnly?: boolean
|
||||
workerMode?: "shared" | "worker" | "server"
|
||||
cwd?: string
|
||||
}
|
||||
|
||||
export type LinkModuleBootstrapOptions = {
|
||||
@@ -87,6 +89,7 @@ export type LinkModuleBootstrapOptions = {
|
||||
declaration?: InternalModuleDeclaration
|
||||
moduleExports?: ModuleExports
|
||||
injectedDependencies?: Record<string, any>
|
||||
cwd?: string
|
||||
}
|
||||
|
||||
export type RegisterModuleJoinerConfig =
|
||||
@@ -289,10 +292,12 @@ class MedusaModule {
|
||||
migrationOnly,
|
||||
loaderOnly,
|
||||
workerMode,
|
||||
cwd,
|
||||
}: {
|
||||
migrationOnly?: boolean
|
||||
loaderOnly?: boolean
|
||||
workerMode?: ModuleBootstrapOptions["workerMode"]
|
||||
cwd?: string
|
||||
}
|
||||
): Promise<
|
||||
{
|
||||
@@ -303,6 +308,7 @@ class MedusaModule {
|
||||
migrationOnly,
|
||||
loaderOnly,
|
||||
workerMode,
|
||||
cwd,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -330,6 +336,7 @@ class MedusaModule {
|
||||
migrationOnly,
|
||||
loaderOnly,
|
||||
workerMode,
|
||||
cwd,
|
||||
}: ModuleBootstrapOptions): Promise<{
|
||||
[key: string]: T
|
||||
}> {
|
||||
@@ -349,6 +356,7 @@ class MedusaModule {
|
||||
migrationOnly,
|
||||
loaderOnly,
|
||||
workerMode,
|
||||
cwd,
|
||||
}
|
||||
)
|
||||
|
||||
@@ -369,16 +377,18 @@ class MedusaModule {
|
||||
protected static async bootstrap_<T>(
|
||||
modulesOptions: Omit<
|
||||
ModuleBootstrapOptions,
|
||||
"migrationOnly" | "loaderOnly" | "workerMode"
|
||||
"migrationOnly" | "loaderOnly" | "workerMode" | "cwd"
|
||||
>[],
|
||||
{
|
||||
migrationOnly,
|
||||
loaderOnly,
|
||||
workerMode,
|
||||
cwd = process.cwd(),
|
||||
}: {
|
||||
migrationOnly?: boolean
|
||||
loaderOnly?: boolean
|
||||
workerMode?: "shared" | "worker" | "server"
|
||||
cwd?: string
|
||||
}
|
||||
): Promise<
|
||||
{
|
||||
@@ -464,12 +474,13 @@ class MedusaModule {
|
||||
}
|
||||
}
|
||||
|
||||
const moduleResolutions = registerMedusaModule(
|
||||
const moduleResolutions = registerMedusaModule({
|
||||
moduleKey,
|
||||
modDeclaration!,
|
||||
moduleDeclaration: modDeclaration!,
|
||||
moduleExports,
|
||||
moduleDefinition
|
||||
)
|
||||
definition: moduleDefinition,
|
||||
cwd,
|
||||
})
|
||||
|
||||
const logger_ =
|
||||
container.resolve(ContainerRegistrationKeys.LOGGER, {
|
||||
@@ -617,6 +628,7 @@ class MedusaModule {
|
||||
declaration,
|
||||
moduleExports,
|
||||
injectedDependencies,
|
||||
cwd,
|
||||
}: LinkModuleBootstrapOptions): Promise<{
|
||||
[key: string]: unknown
|
||||
}> {
|
||||
@@ -672,7 +684,8 @@ class MedusaModule {
|
||||
const moduleResolutions = registerMedusaLinkModule(
|
||||
moduleDefinition,
|
||||
modDeclaration as InternalModuleDeclaration,
|
||||
moduleExports
|
||||
moduleExports,
|
||||
cwd
|
||||
)
|
||||
|
||||
const logger_ =
|
||||
@@ -749,11 +762,16 @@ class MedusaModule {
|
||||
moduleExports,
|
||||
moduleKey,
|
||||
modulePath,
|
||||
cwd,
|
||||
}: MigrationOptions): Promise<void> {
|
||||
const moduleResolutions = registerMedusaModule(moduleKey, {
|
||||
scope: MODULE_SCOPE.INTERNAL,
|
||||
resolve: modulePath,
|
||||
options,
|
||||
const moduleResolutions = registerMedusaModule({
|
||||
moduleKey,
|
||||
moduleDeclaration: {
|
||||
scope: MODULE_SCOPE.INTERNAL,
|
||||
resolve: modulePath,
|
||||
options,
|
||||
},
|
||||
cwd,
|
||||
})
|
||||
|
||||
const logger_ =
|
||||
@@ -765,6 +783,7 @@ class MedusaModule {
|
||||
|
||||
for (const mod in moduleResolutions) {
|
||||
const { generateMigration } = await loadModuleMigrations(
|
||||
container,
|
||||
moduleResolutions[mod],
|
||||
moduleExports
|
||||
)
|
||||
@@ -785,11 +804,16 @@ class MedusaModule {
|
||||
moduleExports,
|
||||
moduleKey,
|
||||
modulePath,
|
||||
cwd,
|
||||
}: MigrationOptions): Promise<void> {
|
||||
const moduleResolutions = registerMedusaModule(moduleKey, {
|
||||
scope: MODULE_SCOPE.INTERNAL,
|
||||
resolve: modulePath,
|
||||
options,
|
||||
const moduleResolutions = registerMedusaModule({
|
||||
moduleKey,
|
||||
moduleDeclaration: {
|
||||
scope: MODULE_SCOPE.INTERNAL,
|
||||
resolve: modulePath,
|
||||
options,
|
||||
},
|
||||
cwd,
|
||||
})
|
||||
|
||||
const logger_ =
|
||||
@@ -801,6 +825,7 @@ class MedusaModule {
|
||||
|
||||
for (const mod in moduleResolutions) {
|
||||
const { runMigrations } = await loadModuleMigrations(
|
||||
container,
|
||||
moduleResolutions[mod],
|
||||
moduleExports
|
||||
)
|
||||
@@ -821,11 +846,16 @@ class MedusaModule {
|
||||
moduleExports,
|
||||
moduleKey,
|
||||
modulePath,
|
||||
cwd,
|
||||
}: MigrationOptions): Promise<void> {
|
||||
const moduleResolutions = registerMedusaModule(moduleKey, {
|
||||
scope: MODULE_SCOPE.INTERNAL,
|
||||
resolve: modulePath,
|
||||
options,
|
||||
const moduleResolutions = registerMedusaModule({
|
||||
moduleKey,
|
||||
moduleDeclaration: {
|
||||
scope: MODULE_SCOPE.INTERNAL,
|
||||
resolve: modulePath,
|
||||
options,
|
||||
},
|
||||
cwd,
|
||||
})
|
||||
|
||||
const logger_ =
|
||||
@@ -837,6 +867,7 @@ class MedusaModule {
|
||||
|
||||
for (const mod in moduleResolutions) {
|
||||
const { revertMigration } = await loadModuleMigrations(
|
||||
container,
|
||||
moduleResolutions[mod],
|
||||
moduleExports
|
||||
)
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
/**
|
||||
* The configuration accepted by the "defineFileConfig" helper
|
||||
*/
|
||||
export type InputFileConfig = {
|
||||
path?: string
|
||||
isDisabled(): boolean
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
import type { Secret, SignOptions, VerifyOptions } from "jsonwebtoken"
|
||||
import {
|
||||
ExternalModuleDeclaration,
|
||||
InternalModuleDeclaration,
|
||||
} from "../modules-sdk"
|
||||
import type { SignOptions, Secret, VerifyOptions } from "jsonwebtoken"
|
||||
|
||||
import type { RedisOptions } from "ioredis"
|
||||
import { ConnectionOptions } from "node:tls"
|
||||
@@ -1027,7 +1027,7 @@ export type ConfigModule = {
|
||||
*
|
||||
* The `featureFlags`'s value is an object. Its properties are the names of the feature flags, and their value is a boolean indicating whether the feature flag is enabled.
|
||||
*
|
||||
* You can find available feature flags and their key name [here](https://github.com/medusajs/medusa/tree/develop/packages/medusa/src/loaders/feature-flags).
|
||||
* You can find available feature flags and their key name [here](https://github.com/medusajs/medusa/tree/develop/packages/medusa/src/feature-flags).
|
||||
*
|
||||
* @example
|
||||
* ```ts title="medusa-config.ts"
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
export * from "./batch"
|
||||
export * from "./camel-case"
|
||||
export * from "./common"
|
||||
export * from "./config-file"
|
||||
export * from "./config-module"
|
||||
export * from "./medusa-cli"
|
||||
export * from "./medusa-container"
|
||||
|
||||
@@ -167,7 +167,6 @@ describe("flattenObjectToKeyValuePairs", function () {
|
||||
}
|
||||
|
||||
const keyValueParis = flattenObjectToKeyValuePairs(cart)
|
||||
console.log(JSON.stringify(keyValueParis, null, 2))
|
||||
expect(keyValueParis).toEqual({
|
||||
id: "cart_01JRDH08QD8CZ0KJDVE410KM1J",
|
||||
currency_code: "usd",
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
import { InputFileConfig } from "@medusajs/types"
|
||||
import { getCallerFilePath } from "./get-caller-file-path"
|
||||
|
||||
export const MEDUSA_SKIP_FILE = Symbol.for("__MEDUSA_SKIP_FILE__")
|
||||
/**
|
||||
* The "defineFileConfig" helper can be used to define the configuration
|
||||
* of any file auto-loaded by Medusa.
|
||||
*
|
||||
* It is used to avoid loading files that are not required. Like a feature flag
|
||||
* that is disabled.
|
||||
*/
|
||||
const FILE_CONFIGS = new Map()
|
||||
export function defineFileConfig(config?: InputFileConfig) {
|
||||
const filePath = config?.path ?? getCallerFilePath()
|
||||
FILE_CONFIGS.set(filePath, config)
|
||||
}
|
||||
|
||||
export function getDefinedFileConfig(path?: string) {
|
||||
return FILE_CONFIGS.get(path)
|
||||
}
|
||||
|
||||
export function isFileDisabled(path?: string) {
|
||||
return !!getDefinedFileConfig(path)?.isDisabled?.()
|
||||
}
|
||||
|
||||
export function isFileSkipped(exported: unknown) {
|
||||
return !!exported?.[MEDUSA_SKIP_FILE]
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
import { isFileDisabled, MEDUSA_SKIP_FILE } from "./define-file-config"
|
||||
import { resolveExports } from "./resolve-exports"
|
||||
|
||||
/**
|
||||
@@ -13,5 +14,12 @@ import { resolveExports } from "./resolve-exports"
|
||||
*/
|
||||
export async function dynamicImport(path: string): Promise<any> {
|
||||
const module = require(path)
|
||||
return resolveExports(module)
|
||||
|
||||
const exported = resolveExports(module)
|
||||
|
||||
if (isFileDisabled(path)) {
|
||||
exported[MEDUSA_SKIP_FILE] = true
|
||||
}
|
||||
|
||||
return exported
|
||||
}
|
||||
@@ -15,6 +15,7 @@ export * from "./deep-equal-obj"
|
||||
export * from "./deep-flat-map"
|
||||
export * from "./deep-merge"
|
||||
export * from "./define-config"
|
||||
export * from "./define-file-config"
|
||||
export * from "./dynamic-import"
|
||||
export * from "./env-editor"
|
||||
export * from "./errors"
|
||||
@@ -50,6 +51,7 @@ export * from "./map-object-to"
|
||||
export * from "./medusa-container"
|
||||
export * from "./merge-metadata"
|
||||
export * from "./merge-plugin-modules"
|
||||
export * from "./normalize-csv-value"
|
||||
export * from "./normalize-import-path-with-source"
|
||||
export * from "./object-from-string-path"
|
||||
export * from "./object-to-string-path"
|
||||
@@ -69,6 +71,7 @@ export * from "./remove-nullisih"
|
||||
export * from "./remove-undefined"
|
||||
export * from "./remove-undefined-properties"
|
||||
export * from "./resolve-exports"
|
||||
export * from "./retry-execution"
|
||||
export * from "./rules"
|
||||
export * from "./selector-constraints-to-string"
|
||||
export * from "./serialize-error"
|
||||
@@ -88,5 +91,3 @@ export * from "./upper-case-first"
|
||||
export * from "./validate-handle"
|
||||
export * from "./validate-module-name"
|
||||
export * from "./wrap-handler"
|
||||
export * from "./normalize-csv-value"
|
||||
export * from "./retry-execution"
|
||||
@@ -5,7 +5,8 @@ import { join } from "path"
|
||||
* @param path
|
||||
*/
|
||||
export function normalizeImportPathWithSource(
|
||||
path: string | undefined
|
||||
path: string | undefined,
|
||||
cwd: string = process.cwd()
|
||||
): string {
|
||||
let normalizePath = path
|
||||
|
||||
@@ -19,7 +20,7 @@ export function normalizeImportPathWithSource(
|
||||
* "./src" directory inside it.
|
||||
*/
|
||||
let sourceDir = normalizePath.startsWith("./src") ? "./" : "./src"
|
||||
normalizePath = join(process.cwd(), sourceDir, normalizePath)
|
||||
normalizePath = join(cwd, sourceDir, normalizePath)
|
||||
}
|
||||
|
||||
return normalizePath ?? ""
|
||||
|
||||
@@ -25,10 +25,12 @@ export async function readDirRecursive(
|
||||
dir: string,
|
||||
options?: {
|
||||
ignoreMissing?: boolean
|
||||
maxDepth?: number
|
||||
}
|
||||
): Promise<Dirent[]> {
|
||||
let allEntries: Dirent[] = []
|
||||
const readRecursive = async (dir: string) => {
|
||||
const readRecursive = async (dir: string, depth: number = 1) => {
|
||||
const maxDepth = options?.maxDepth ?? Infinity
|
||||
try {
|
||||
const entries = await readdir(dir, { withFileTypes: true })
|
||||
for (const entry of entries) {
|
||||
@@ -38,14 +40,15 @@ export async function readDirRecursive(
|
||||
})
|
||||
allEntries.push(entry)
|
||||
|
||||
if (entry.isDirectory()) {
|
||||
await readRecursive(fullPath)
|
||||
if (entry.isDirectory() && depth < maxDepth) {
|
||||
await readRecursive(fullPath, depth + 1)
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
if (options?.ignoreMissing && error.code === "ENOENT") {
|
||||
return
|
||||
}
|
||||
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
export * from "./mikro-orm/base-entity"
|
||||
export * from "./mikro-orm/big-number-field"
|
||||
export * from "./mikro-orm/custom-db-migrator"
|
||||
export * from "./mikro-orm/decorators/searchable"
|
||||
export * from "./mikro-orm/mikro-orm-create-connection"
|
||||
export * from "./mikro-orm/mikro-orm-free-text-search-filter"
|
||||
export * from "./mikro-orm/mikro-orm-repository"
|
||||
export * from "./mikro-orm/mikro-orm-soft-deletable-filter"
|
||||
export * from "./mikro-orm/mikro-orm-serializer"
|
||||
export * from "./mikro-orm/base-entity"
|
||||
export * from "./mikro-orm/mikro-orm-soft-deletable-filter"
|
||||
export * from "./mikro-orm/utils"
|
||||
export * from "./mikro-orm/decorators/searchable"
|
||||
export * from "./utils"
|
||||
@@ -0,0 +1,59 @@
|
||||
import { Constructor } from "@medusajs/types"
|
||||
import { MikroORM, Utils } from "@mikro-orm/core"
|
||||
import {
|
||||
Migrator as BaseMigrator,
|
||||
Migration,
|
||||
UmzugMigration,
|
||||
} from "@mikro-orm/migrations"
|
||||
import { isFileDisabled, isFileSkipped } from "../../common/define-file-config"
|
||||
import { dynamicImport } from "../../common/dynamic-import"
|
||||
|
||||
export class CustomDBMigrator extends BaseMigrator {
|
||||
static register(orm: MikroORM): void {
|
||||
orm.config.registerExtension(
|
||||
"@mikro-orm/migrator",
|
||||
() => new CustomDBMigrator(orm.em as any)
|
||||
)
|
||||
}
|
||||
|
||||
resolve(params) {
|
||||
require(params.path)
|
||||
if (isFileDisabled(params.path)) {
|
||||
return {
|
||||
name: "Noop",
|
||||
up: () => {},
|
||||
down: () => {},
|
||||
} as any
|
||||
}
|
||||
|
||||
const $this = this as any
|
||||
const createMigrationHandler = async (method) => {
|
||||
const migration = await Utils.dynamicImport(params.path)
|
||||
const MigrationClass = Object.values(
|
||||
migration
|
||||
)[0] as Constructor<Migration>
|
||||
const instance = new MigrationClass($this.driver, $this.config)
|
||||
await $this.runner.run(instance, method)
|
||||
}
|
||||
|
||||
return {
|
||||
name: $this.storage.getMigrationName(params.name),
|
||||
up: () => createMigrationHandler("up"),
|
||||
down: () => createMigrationHandler("down"),
|
||||
}
|
||||
}
|
||||
|
||||
async getPendingMigrations(): Promise<UmzugMigration[]> {
|
||||
const pending = await super.getPendingMigrations()
|
||||
|
||||
// Filter out migrations that are disabled by file config
|
||||
return pending.filter(async (pendingFile: UmzugMigration) => {
|
||||
const migration = await dynamicImport(pendingFile.path!)
|
||||
if (isFileSkipped(migration)) {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@ import { Filter as MikroORMFilter } from "@mikro-orm/core"
|
||||
import { TSMigrationGenerator } from "@mikro-orm/migrations"
|
||||
import { isString, retryExecution, stringifyCircular } from "../../common"
|
||||
import { normalizeMigrationSQL } from "../utils"
|
||||
import { CustomDBMigrator } from "./custom-db-migrator"
|
||||
|
||||
type FilterDef = Parameters<typeof MikroORMFilter>[0]
|
||||
|
||||
@@ -107,6 +108,7 @@ export async function mikroOrmCreateConnection(
|
||||
false
|
||||
),
|
||||
},
|
||||
extensions: [CustomDBMigrator],
|
||||
// We don't want to do any DB checks when establishing the connection. This happens once when creating the pg_connection, and it can happen again explicitly if necessary.
|
||||
connect: false,
|
||||
ensureDatabase: false,
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
import { FeatureFlagTypes } from "@medusajs/types"
|
||||
|
||||
export const AnalyticsFeatureFlag: FeatureFlagTypes.FlagSettings = {
|
||||
key: "analytics",
|
||||
default_val: true,
|
||||
env_key: "MEDUSA_FF_ANALYTICS",
|
||||
description:
|
||||
"Enable Medusa to collect data on usage, errors and performance for the purpose of improving the product",
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
import { FlagSettings } from "@medusajs/types"
|
||||
import { readdir } from "fs/promises"
|
||||
import { join, normalize } from "path"
|
||||
import { dynamicImport, isString, readDirRecursive } from "../common"
|
||||
|
||||
const excludedFiles = ["index.js", "index.ts"]
|
||||
const excludedExtensions = [".d.ts", ".d.ts.map", ".js.map"]
|
||||
|
||||
function isFeatureFlag(flag: unknown): flag is FlagSettings {
|
||||
const f = flag as any
|
||||
return !!f && isString(f.key) && isString(f.env_key)
|
||||
}
|
||||
|
||||
/**
|
||||
* Discover feature flag definitions from a directory and subdirectories
|
||||
*/
|
||||
export async function discoverFeatureFlagsFromDir(
|
||||
sourcePath?: string,
|
||||
maxDepth: number = 2
|
||||
): Promise<FlagSettings[]> {
|
||||
if (!sourcePath) {
|
||||
return []
|
||||
}
|
||||
|
||||
const root = normalize(sourcePath)
|
||||
const discovered: FlagSettings[] = []
|
||||
|
||||
const allEntries = await readDirRecursive(root, {
|
||||
ignoreMissing: true,
|
||||
maxDepth,
|
||||
})
|
||||
|
||||
const featureFlagDirs = allEntries
|
||||
.filter((e) => e.isDirectory() && e.name === "feature-flags")
|
||||
.map((e) => join((e as any).path as string, e.name))
|
||||
|
||||
if (!featureFlagDirs.length) {
|
||||
return discovered
|
||||
}
|
||||
|
||||
await Promise.all(
|
||||
featureFlagDirs.map(async (scanDir) => {
|
||||
const entries = await readdir(scanDir, { withFileTypes: true })
|
||||
await Promise.all(
|
||||
entries.map(async (entry) => {
|
||||
if (entry.isDirectory()) {
|
||||
return
|
||||
}
|
||||
|
||||
if (
|
||||
excludedExtensions.some((ext) => entry.name.endsWith(ext)) ||
|
||||
excludedFiles.includes(entry.name)
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
const fileExports = await dynamicImport(join(scanDir, entry.name))
|
||||
const values = Object.values(fileExports)
|
||||
for (const value of values) {
|
||||
if (isFeatureFlag(value)) {
|
||||
discovered.push(value)
|
||||
}
|
||||
}
|
||||
})
|
||||
)
|
||||
})
|
||||
)
|
||||
|
||||
return discovered
|
||||
}
|
||||
+3
-1
@@ -1,5 +1,5 @@
|
||||
import { FeatureFlagTypes } from "@medusajs/types"
|
||||
import { isObject, isString } from "../../common"
|
||||
import { isObject, isString } from "../common"
|
||||
|
||||
export class FlagRouter implements FeatureFlagTypes.IFlagRouter {
|
||||
private readonly flags: Record<string, boolean | Record<string, boolean>> = {}
|
||||
@@ -75,3 +75,5 @@ export class FlagRouter implements FeatureFlagTypes.IFlagRouter {
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
export const FeatureFlag = new FlagRouter({})
|
||||
@@ -1,10 +1,3 @@
|
||||
export * from "./analytics"
|
||||
export * from "./many-to-many-inventory"
|
||||
export * from "./medusa-v2"
|
||||
export * from "./order-editing"
|
||||
export * from "./product-categories"
|
||||
export * from "./publishable-api-keys"
|
||||
export * from "./sales-channels"
|
||||
export * from "./tax-inclusive-pricing"
|
||||
export * from "./utils"
|
||||
export * from "./workflows"
|
||||
export * from "./discover-feature-flags"
|
||||
export * from "./flag-router"
|
||||
export * from "./register-flag"
|
||||
@@ -1,9 +0,0 @@
|
||||
import { FeatureFlagTypes } from "@medusajs/types"
|
||||
|
||||
export const ManyToManyInventoryFeatureFlag: FeatureFlagTypes.FlagSettings = {
|
||||
key: "many_to_many_inventory",
|
||||
default_val: false,
|
||||
env_key: "MEDUSA_FF_MANY_TO_MANY_INVENTORY",
|
||||
description:
|
||||
"Enable capability to have many to many relationship between inventory items and variants",
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
import { FeatureFlagTypes } from "@medusajs/types"
|
||||
|
||||
export const MedusaV2Flag: FeatureFlagTypes.FlagSettings = {
|
||||
key: "medusa_v2",
|
||||
default_val: false,
|
||||
env_key: "MEDUSA_FF_MEDUSA_V2",
|
||||
description: "[WIP] Enable Medusa V2",
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
import { FeatureFlagTypes } from "@medusajs/types"
|
||||
|
||||
export const OrderEditingFeatureFlag: FeatureFlagTypes.FlagSettings = {
|
||||
key: "order_editing",
|
||||
default_val: true,
|
||||
env_key: "MEDUSA_FF_ORDER_EDITING",
|
||||
description: "[WIP] Enable the order editing feature",
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
import { FeatureFlagTypes } from "@medusajs/types"
|
||||
|
||||
export const ProductCategoryFeatureFlag: FeatureFlagTypes.FlagSettings = {
|
||||
key: "product_categories",
|
||||
default_val: false,
|
||||
env_key: "MEDUSA_FF_PRODUCT_CATEGORIES",
|
||||
description: "[WIP] Enable the product categories feature",
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
import { FeatureFlagTypes } from "@medusajs/types"
|
||||
|
||||
export const PublishableAPIKeysFeatureFlag: FeatureFlagTypes.FlagSettings = {
|
||||
key: "publishable_api_keys",
|
||||
default_val: true,
|
||||
env_key: "MEDUSA_FF_PUBLISHABLE_API_KEYS",
|
||||
description: "[WIP] Enable the publishable API keys feature",
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
import { FlagSettings, Logger } from "@medusajs/types"
|
||||
import {
|
||||
isDefined,
|
||||
isObject,
|
||||
isString,
|
||||
isTruthy,
|
||||
objectFromStringPath,
|
||||
} from "../common"
|
||||
import { FlagRouter } from "./flag-router"
|
||||
|
||||
export type RegisterFeatureFlagOptions = {
|
||||
flag: FlagSettings
|
||||
projectConfigFlags: Record<string, string | boolean | Record<string, boolean>>
|
||||
router: FlagRouter
|
||||
logger?: Logger
|
||||
track?: (key: string) => void
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a feature flag on the provided router.
|
||||
* Resolving precedence:
|
||||
* - env overrides
|
||||
* - project config overrides
|
||||
* - default value
|
||||
*/
|
||||
export function registerFeatureFlag(options: RegisterFeatureFlagOptions) {
|
||||
const { flag, projectConfigFlags, router, logger, track } = options
|
||||
|
||||
let value: boolean | Record<string, boolean> = isTruthy(flag.default_val)
|
||||
let from: string | undefined
|
||||
|
||||
if (isDefined(process.env[flag.env_key])) {
|
||||
from = "environment"
|
||||
const envVal = process.env[flag.env_key]
|
||||
|
||||
value = isTruthy(envVal)
|
||||
|
||||
const parsedFromEnv = isString(envVal) ? envVal.split(",") : []
|
||||
if (parsedFromEnv.length > 1) {
|
||||
value = objectFromStringPath(parsedFromEnv)
|
||||
}
|
||||
} else if (isDefined(projectConfigFlags[flag.key])) {
|
||||
from = "project config"
|
||||
|
||||
const pc = projectConfigFlags[flag.key] as string | boolean
|
||||
value = isTruthy(pc)
|
||||
|
||||
if (isObject(projectConfigFlags[flag.key])) {
|
||||
value = projectConfigFlags[flag.key] as Record<string, boolean>
|
||||
}
|
||||
}
|
||||
|
||||
if (logger && from) {
|
||||
logger.info(
|
||||
`Using flag ${flag.env_key} from ${from} with value ${JSON.stringify(
|
||||
value
|
||||
)}`
|
||||
)
|
||||
}
|
||||
|
||||
if (track && value === true) {
|
||||
track(flag.key)
|
||||
}
|
||||
|
||||
router.setFlag(flag.key, value)
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
import { FeatureFlagTypes } from "@medusajs/types"
|
||||
|
||||
export const SalesChannelFeatureFlag: FeatureFlagTypes.FlagSettings = {
|
||||
key: "sales_channels",
|
||||
default_val: true,
|
||||
env_key: "MEDUSA_FF_SALES_CHANNELS",
|
||||
description: "[WIP] Enable the sales channels feature",
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
import { FeatureFlagTypes } from "@medusajs/types"
|
||||
|
||||
export const TaxInclusivePricingFeatureFlag: FeatureFlagTypes.FlagSettings = {
|
||||
key: "tax_inclusive_pricing",
|
||||
default_val: false,
|
||||
env_key: "MEDUSA_FF_TAX_INCLUSIVE_PRICING",
|
||||
description: "[WIP] Enable tax inclusive pricing",
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
export * from "./flag-router"
|
||||
@@ -1,8 +0,0 @@
|
||||
import { FeatureFlagTypes } from "@medusajs/types"
|
||||
|
||||
export const WorkflowsFeatureFlag: FeatureFlagTypes.FlagSettings = {
|
||||
key: "workflows",
|
||||
default_val: false,
|
||||
env_key: "MEDUSA_FF_WORKFLOWS",
|
||||
description: "[WIP] Enable workflows",
|
||||
}
|
||||
@@ -9,6 +9,7 @@ import { EventEmitter } from "events"
|
||||
import { access, mkdir, rename, writeFile } from "fs/promises"
|
||||
import { dirname, join } from "path"
|
||||
import { readDir } from "../common"
|
||||
import { CustomDBMigrator } from "../dal/mikro-orm/custom-db-migrator"
|
||||
|
||||
/**
|
||||
* Events emitted by the migrations class
|
||||
@@ -50,6 +51,7 @@ export class Migrations extends EventEmitter<MigrationsEvents> {
|
||||
...this.#configOrConnection.migrations,
|
||||
silent: true,
|
||||
},
|
||||
extensions: [CustomDBMigrator],
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { CustomDBMigrator } from "../../dal/mikro-orm/custom-db-migrator"
|
||||
import { defineMikroOrmCliConfig } from "../mikro-orm-cli-config-builder"
|
||||
|
||||
const moduleName = "myTestService"
|
||||
@@ -28,6 +29,7 @@ describe("defineMikroOrmCliConfig", () => {
|
||||
generator: expect.any(Function),
|
||||
snapshotName: ".snapshot-medusa-my-test",
|
||||
},
|
||||
extensions: [CustomDBMigrator],
|
||||
})
|
||||
})
|
||||
|
||||
@@ -47,6 +49,7 @@ describe("defineMikroOrmCliConfig", () => {
|
||||
generator: expect.any(Function),
|
||||
snapshotName: ".snapshot-medusa-my-test",
|
||||
},
|
||||
extensions: [CustomDBMigrator],
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -2,9 +2,9 @@ import { LoaderOptions, Logger, ModulesSdkTypes } from "@medusajs/types"
|
||||
import { EntitySchema } from "@mikro-orm/core"
|
||||
import { EOL } from "os"
|
||||
import { resolve } from "path"
|
||||
import { dynamicImport, isFileSkipped } from "../../common"
|
||||
import { mikroOrmCreateConnection } from "../../dal"
|
||||
import { loadDatabaseConfig } from "../load-module-database-config"
|
||||
import { dynamicImport } from "../../common"
|
||||
|
||||
/**
|
||||
* Utility function to build a seed script that will insert the seed data.
|
||||
@@ -52,6 +52,10 @@ export function buildSeedScript({
|
||||
}
|
||||
)
|
||||
|
||||
if (isFileSkipped(dataSeed)) {
|
||||
return
|
||||
}
|
||||
|
||||
const dbData = loadDatabaseConfig(moduleName, options)!
|
||||
const entities = Object.values(models) as unknown as EntitySchema[]
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ import type {
|
||||
} from "@mikro-orm/core"
|
||||
import { defineConfig } from "@mikro-orm/postgresql"
|
||||
import { kebabCase } from "../common"
|
||||
import { CustomTsMigrationGenerator } from "../dal"
|
||||
import { CustomDBMigrator, CustomTsMigrationGenerator } from "../dal"
|
||||
import { DmlEntity, toMikroOrmEntities } from "../dml"
|
||||
|
||||
type Options = Partial<Omit<MikroORMOptions, "entities" | "entitiesTs">> & {
|
||||
@@ -65,5 +65,6 @@ export function defineMikroOrmCliConfig(
|
||||
generator: CustomTsMigrationGenerator,
|
||||
...options.migrations,
|
||||
},
|
||||
extensions: [CustomDBMigrator],
|
||||
}) as ReturnedOptions
|
||||
}
|
||||
@@ -22,6 +22,10 @@ class MedusaWorkflow {
|
||||
MedusaWorkflow.workflows[workflowId] = exportedWorkflow
|
||||
}
|
||||
|
||||
static unregisterWorkflow(workflowId) {
|
||||
delete MedusaWorkflow.workflows[workflowId]
|
||||
}
|
||||
|
||||
static getWorkflow(workflowId): ExportedWorkflow {
|
||||
return MedusaWorkflow.workflows[workflowId] as unknown as ExportedWorkflow
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user