From 9b6de8c02d58397060d3700b78b161c5361a15fc Mon Sep 17 00:00:00 2001 From: Adrien de Peretti Date: Tue, 30 Jul 2024 14:20:03 +0200 Subject: [PATCH] chore(framework): Move feature flags related resources and cleanup (#8297) **What** cleanup and move the feature flag related resources to manage them. It also include some refactoring around loading and registering the flag as well as not relying on the `glob` package anymore plus some reorganization of the code itself FIXES FRMW-2625 --- .../environment-helpers/use-db.js | 25 ++-- .../src/medusa-test-runner-utils/config.ts | 2 +- .../src/medusa-test-runner-utils/use-db.ts | 20 +-- .../core/types/src/common/config-module.ts | 12 +- packages/core/utils/src/common/index.ts | 1 + packages/core/utils/src/common/is-truthy.ts | 10 ++ packages/framework/framework/package.json | 7 + .../framework/framework/src/config/types.ts | 2 +- .../__tests__/feature-flags.spec.ts | 55 ++++---- .../src/feature-flags/feature-flag-loader.ts | 129 ++++++++++++++++++ .../src/feature-flags/flag-router.ts | 77 +++++++++++ .../framework/src/feature-flags/index.ts | 3 + .../framework/src/feature-flags/types.ts | 32 +++++ packages/framework/framework/src/index.ts | 1 + packages/medusa/src/commands/links.ts | 2 +- packages/medusa/src/commands/migrate.ts | 2 +- .../medusa/src/loaders/feature-flags/index.ts | 94 ------------- .../routing/__fixtures__/server/index.ts | 29 ++-- packages/medusa/src/loaders/index.ts | 14 +- packages/modules/link-modules/package.json | 1 + yarn.lock | 20 +++ 21 files changed, 364 insertions(+), 174 deletions(-) create mode 100644 packages/core/utils/src/common/is-truthy.ts rename packages/{medusa/src/loaders => framework/framework/src/feature-flags}/__tests__/feature-flags.spec.ts (70%) create mode 100644 packages/framework/framework/src/feature-flags/feature-flag-loader.ts create mode 100644 packages/framework/framework/src/feature-flags/flag-router.ts create mode 100644 packages/framework/framework/src/feature-flags/index.ts create mode 100644 packages/framework/framework/src/feature-flags/types.ts delete mode 100644 packages/medusa/src/loaders/feature-flags/index.ts diff --git a/integration-tests/environment-helpers/use-db.js b/integration-tests/environment-helpers/use-db.js index 72c7472e83..c0ed873fd6 100644 --- a/integration-tests/environment-helpers/use-db.js +++ b/integration-tests/environment-helpers/use-db.js @@ -97,12 +97,15 @@ module.exports = { Object.entries(env).forEach(([k, v]) => (process.env[k] = v)) } - const { configModule } = getConfigFile(cwd, `medusa-config`) + const { + featureFlagsLoader, + configLoader, + container, + pgConnectionLoader, + } = require("@medusajs/framework") - const featureFlagsLoader = - require("@medusajs/medusa/dist/loaders/feature-flags").default - - const featureFlagRouter = featureFlagsLoader(configModule) + const configModule = configLoader(cwd, `medusa-config`) + const featureFlagRouter = await featureFlagsLoader() const modelsLoader = require("@medusajs/medusa/dist/loaders/models").default const entities = modelsLoader({}, { register: false }) @@ -157,23 +160,11 @@ module.exports = { force_modules_migration || featureFlagRouter.isFeatureEnabled(MedusaV2Flag.key) ) { - const { container, pgConnectionLoader } = await import( - "@medusajs/framework" - ) - - const featureFlagLoader = - require("@medusajs/medusa/dist/loaders/feature-flags").default - - const featureFlagRouter = await featureFlagLoader(configModule) - const pgConnection = pgConnectionLoader() container.register({ - [ContainerRegistrationKeys.CONFIG_MODULE]: asValue(configModule), [ContainerRegistrationKeys.LOGGER]: asValue(console), [ContainerRegistrationKeys.MANAGER]: asValue(dbDataSource.manager), - [ContainerRegistrationKeys.PG_CONNECTION]: asValue(pgConnection), - featureFlagRouter: asValue(featureFlagRouter), }) instance.setPgConnection(pgConnection) diff --git a/packages/core/medusa-test-utils/src/medusa-test-runner-utils/config.ts b/packages/core/medusa-test-utils/src/medusa-test-runner-utils/config.ts index a05a139ae1..fece83693f 100644 --- a/packages/core/medusa-test-utils/src/medusa-test-runner-utils/config.ts +++ b/packages/core/medusa-test-utils/src/medusa-test-runner-utils/config.ts @@ -4,7 +4,7 @@ export async function configLoaderOverride( entryDirectory: string, override: { clientUrl: string; debug?: boolean } ) { - const { configManager } = await import("@medusajs/framework/config") + const { configManager } = await import("@medusajs/framework") const { configModule, error } = getConfigFile< ReturnType >(entryDirectory, "medusa-config.js") diff --git a/packages/core/medusa-test-utils/src/medusa-test-runner-utils/use-db.ts b/packages/core/medusa-test-utils/src/medusa-test-runner-utils/use-db.ts index 51ac8ce282..b0f424bd98 100644 --- a/packages/core/medusa-test-utils/src/medusa-test-runner-utils/use-db.ts +++ b/packages/core/medusa-test-utils/src/medusa-test-runner-utils/use-db.ts @@ -6,21 +6,21 @@ export async function initDb({ env = {} }: { env?: Record }) { Object.entries(env).forEach(([k, v]) => (process.env[k] = v)) } - const { configManager, pgConnectionLoader, container } = await import( - "@medusajs/framework" - ) + const { + configManager, + pgConnectionLoader, + logger, + container, + featureFlagsLoader, + } = await import("@medusajs/framework") + const configModule = configManager.config const pgConnection = pgConnectionLoader() - - const featureFlagRouter = - require("@medusajs/medusa/dist/loaders/feature-flags").default(configModule) + await featureFlagsLoader() container.register({ - [ContainerRegistrationKeys.CONFIG_MODULE]: asValue(configModule), - [ContainerRegistrationKeys.LOGGER]: asValue(console), - [ContainerRegistrationKeys.PG_CONNECTION]: asValue(pgConnection), - [ContainerRegistrationKeys.FEATURE_FLAG_ROUTER]: asValue(featureFlagRouter), + [ContainerRegistrationKeys.LOGGER]: asValue(logger), }) try { diff --git a/packages/core/types/src/common/config-module.ts b/packages/core/types/src/common/config-module.ts index 7540da0c54..28f1e4b799 100644 --- a/packages/core/types/src/common/config-module.ts +++ b/packages/core/types/src/common/config-module.ts @@ -379,13 +379,13 @@ export type ProjectConfigOptions = { /** * Configure the application's worker mode. - * + * * Workers are processes running separately from the main application. They're useful for executing long-running or resource-heavy tasks in the background, such as importing products. - * + * * With a worker, these tasks are offloaded to a separate process. So, they won't affect the performance of the main application. - * + * * ![Diagram showcasing how the server and worker work together](https://res.cloudinary.com/dza7lstvk/image/upload/fl_lossy/f_auto/r_16/ar_16:9,c_pad/v1/Medusa%20Book/medusa-worker_klkbch.jpg?_a=BATFJtAA0) - * + * * Medusa has three runtime modes: * * - Use `shared` to run the application in a single process. @@ -393,7 +393,7 @@ export type ProjectConfigOptions = { * - Use `server` to run the application server only. * * In production, it's recommended to deploy two instances: - * + * * 1. One having the `workerMode` configuration set to `server`. * 2. Another having the `workerMode` configuration set to `worker`. * @@ -874,7 +874,7 @@ export type ConfigModule = { * * ::: */ - featureFlags: Record + featureFlags: Record> } export type PluginDetails = { diff --git a/packages/core/utils/src/common/index.ts b/packages/core/utils/src/common/index.ts index 3653434693..58901ad71a 100644 --- a/packages/core/utils/src/common/index.ts +++ b/packages/core/utils/src/common/index.ts @@ -25,6 +25,7 @@ export * from "./get-set-difference" export * from "./graceful-shutdown-server" export * from "./group-by" export * from "./handle-postgres-database-error" +export * from "./is-truthy" export * from "./is-big-number" export * from "./is-boolean" export * from "./is-date" diff --git a/packages/core/utils/src/common/is-truthy.ts b/packages/core/utils/src/common/is-truthy.ts new file mode 100644 index 0000000000..aeb716f7d6 --- /dev/null +++ b/packages/core/utils/src/common/is-truthy.ts @@ -0,0 +1,10 @@ +/** + * Return true if the value is truthy and otherwise false + * @param val + */ +export function isTruthy(val: string | boolean | undefined): boolean { + if (typeof val === "string") { + return val.toLowerCase() === "true" + } + return !!val +} diff --git a/packages/framework/framework/package.json b/packages/framework/framework/package.json index 44d8a2db93..8555aa89bc 100644 --- a/packages/framework/framework/package.json +++ b/packages/framework/framework/package.json @@ -32,6 +32,12 @@ "import": "./dist/database/index.js", "require": "./dist/database/index.js", "node": "./dist/database/index.js" + }, + "./feature-flag": { + "types": "./dist/feature-flags/index.d.ts", + "import": "./dist/feature-flags/index.js", + "require": "./dist/feature-flags/index.js", + "node": "./dist/feature-flags/index.js" } }, "engines": { @@ -73,6 +79,7 @@ "express-session": "^1.17.3", "ioredis": "^5.2.5", "ioredis-mock": "8.4.0", + "medusa-telemetry": "^0.0.18", "morgan": "^1.9.1" } } diff --git a/packages/framework/framework/src/config/types.ts b/packages/framework/framework/src/config/types.ts index 3a5da201a6..8240c56158 100644 --- a/packages/framework/framework/src/config/types.ts +++ b/packages/framework/framework/src/config/types.ts @@ -856,7 +856,7 @@ export type ConfigModule = { * * ::: */ - featureFlags: Record + featureFlags: Record> } export type PluginDetails = { diff --git a/packages/medusa/src/loaders/__tests__/feature-flags.spec.ts b/packages/framework/framework/src/feature-flags/__tests__/feature-flags.spec.ts similarity index 70% rename from packages/medusa/src/loaders/__tests__/feature-flags.spec.ts rename to packages/framework/framework/src/feature-flags/__tests__/feature-flags.spec.ts index 3a331322f3..080d9a4ee3 100644 --- a/packages/medusa/src/loaders/__tests__/feature-flags.spec.ts +++ b/packages/framework/framework/src/feature-flags/__tests__/feature-flags.spec.ts @@ -1,7 +1,8 @@ import { FileSystem } from "@medusajs/utils" import { join } from "path" +import { featureFlagsLoader } from "../feature-flag-loader" +import { configManager } from "../../config" -import loadFeatureFlags from "../feature-flags" const filesystem = new FileSystem(join(__dirname, "__ff-test__")) const buildFeatureFlag = ( @@ -29,6 +30,11 @@ describe("feature flags", () => { process.env = { ...OLD_ENV } await filesystem.cleanup() + + configManager.loadConfig({ + projectConfig: {} as any, + baseDir: filesystem.basePath, + }) }) afterAll(async () => { @@ -37,29 +43,33 @@ describe("feature flags", () => { }) it("should load the flag from project", async () => { + configManager.loadConfig({ + projectConfig: { featureFlags: { flag_1: false } }, + baseDir: filesystem.basePath, + }) + await filesystem.create("flags/flag-1.js", buildFeatureFlag("flag-1", true)) - const flags = loadFeatureFlags( - { featureFlags: { flag_1: false } }, - undefined, - join(filesystem.basePath, "flags") - ) + const flags = await featureFlagsLoader(join(filesystem.basePath, "flags")) expect(flags.isFeatureEnabled("flag_1")).toEqual(false) }) it("should load a nested + simple flag from project", async () => { + configManager.loadConfig({ + projectConfig: { + featureFlags: { test: { nested: true }, simpletest: true }, + }, + baseDir: filesystem.basePath, + }) + await filesystem.create("flags/test.js", buildFeatureFlag("test", false)) await filesystem.create( "flags/simpletest.js", buildFeatureFlag("simpletest", false) ) - const flags = loadFeatureFlags( - { featureFlags: { test: { nested: true }, simpletest: true } }, - undefined, - join(filesystem.basePath, "flags") - ) + const flags = await featureFlagsLoader(join(filesystem.basePath, "flags")) expect(flags.isFeatureEnabled({ test: "nested" })).toEqual(true) expect(flags.isFeatureEnabled("simpletest")).toEqual(true) @@ -71,11 +81,7 @@ describe("feature flags", () => { buildFeatureFlag("flag-1", false) ) - const flags = loadFeatureFlags( - {}, - undefined, - join(filesystem.basePath, "flags") - ) + const flags = await featureFlagsLoader(join(filesystem.basePath, "flags")) expect(flags.isFeatureEnabled("flag_1")).toEqual(false) }) @@ -87,16 +93,17 @@ describe("feature flags", () => { "flags/flag-1.js", buildFeatureFlag("flag-1", false) ) - const flags = loadFeatureFlags( - {}, - undefined, - join(filesystem.basePath, "flags") - ) + const flags = await featureFlagsLoader(join(filesystem.basePath, "flags")) expect(flags.isFeatureEnabled("flag_1")).toEqual(false) }) it("should load mix of flags", async () => { + configManager.loadConfig({ + projectConfig: { featureFlags: { flag_2: false } }, + baseDir: filesystem.basePath, + }) + process.env.MEDUSA_FF_FLAG_3 = "true" await filesystem.create( "flags/flag-1.js", @@ -111,11 +118,7 @@ describe("feature flags", () => { buildFeatureFlag("flag-3", false) ) - const flags = loadFeatureFlags( - { featureFlags: { flag_2: false } }, - undefined, - join(filesystem.basePath, "flags") - ) + const flags = await featureFlagsLoader(join(filesystem.basePath, "flags")) expect(flags.isFeatureEnabled("flag_1")).toEqual(false) expect(flags.isFeatureEnabled("flag_2")).toEqual(false) diff --git a/packages/framework/framework/src/feature-flags/feature-flag-loader.ts b/packages/framework/framework/src/feature-flags/feature-flag-loader.ts new file mode 100644 index 0000000000..61d555b6ef --- /dev/null +++ b/packages/framework/framework/src/feature-flags/feature-flag-loader.ts @@ -0,0 +1,129 @@ +import { + ContainerRegistrationKeys, + FlagRouter, + isDefined, + isObject, + isString, + isTruthy, + objectFromStringPath, +} from "@medusajs/utils" +import { trackFeatureFlag } from "medusa-telemetry" +import { join, normalize } from "path" +import { logger } from "../logger" +import { FlagSettings } from "./types" +import { container } from "../container" +import { asFunction } from "awilix" +import { configManager } from "../config" +import { readdir } from "fs/promises" + +export const featureFlagRouter = new FlagRouter({}) + +container.register( + ContainerRegistrationKeys.FEATURE_FLAG_ROUTER, + asFunction(() => featureFlagRouter) +) + +const excludedFiles = ["index.js", "index.ts"] +const excludedExtensions = [".d.ts", ".d.ts.map", ".js.map"] +const flagConfig: Record> = {} + +function registerFlag( + flag: FlagSettings, + projectConfigFlags: Record> +) { + 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 + */ +export async function featureFlagsLoader( + sourcePath?: string +): Promise { + const { featureFlags: projectConfigFlags = {} } = configManager.config + + if (!sourcePath) { + return featureFlagRouter + } + + const flagDir = normalize(sourcePath) + + await readdir(flagDir, { recursive: true, withFileTypes: true }).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 import(join(flagDir, file.name)) + const featureFlag = fileExports.default + + if (!featureFlag) { + return + } + + registerFlag(featureFlag, projectConfigFlags) + return + }) + } + ) + + return featureFlagRouter +} diff --git a/packages/framework/framework/src/feature-flags/flag-router.ts b/packages/framework/framework/src/feature-flags/flag-router.ts new file mode 100644 index 0000000000..eb041df1ea --- /dev/null +++ b/packages/framework/framework/src/feature-flags/flag-router.ts @@ -0,0 +1,77 @@ +import { isObject, isString } from "@medusajs/utils" +import { FeatureFlagsResponse, IFlagRouter } from "./types" + +export class FlagRouter implements IFlagRouter { + private readonly flags: Record> = {} + + constructor(flags: Record>) { + this.flags = flags + } + + /** + * Check if a feature flag is enabled. + * There are two ways of using this method: + * 1. `isFeatureEnabled("myFeatureFlag")` + * 2. `isFeatureEnabled({ myNestedFeatureFlag: "someNestedFlag" })` + * We use 1. for top-level feature flags and 2. for nested feature flags. Almost all flags are top-level. + * An example of a nested flag is workflows. To use it, you would do: + * `isFeatureEnabled({ workflows: Workflows.CreateCart })` + * @param flag - The flag to check + * @return {boolean} - Whether the flag is enabled or not + */ + public isFeatureEnabled( + flag: string | string[] | Record + ): boolean { + if (isObject(flag)) { + const [nestedFlag, value] = Object.entries(flag)[0] + if (typeof this.flags[nestedFlag] === "boolean") { + return this.flags[nestedFlag] as boolean + } + return !!this.flags[nestedFlag]?.[value] + } + + const flags = (Array.isArray(flag) ? flag : [flag]) as string[] + return flags.every((flag_) => { + if (!isString(flag_)) { + throw Error("Flag must be a string an array of string or an object") + } + return !!this.flags[flag_] + }) + } + + /** + * Sets a feature flag. + * Flags take two shapes: + * `setFlag("myFeatureFlag", true)` + * `setFlag("myFeatureFlag", { nestedFlag: true })` + * These shapes are used for top-level and nested flags respectively, as explained in isFeatureEnabled. + * @param key - The key of the flag to set. + * @param value - The value of the flag to set. + * @return {void} - void + */ + public setFlag( + key: string, + value: boolean | { [key: string]: boolean } + ): void { + if (isObject(value)) { + const existing = this.flags[key] + + if (!existing) { + this.flags[key] = value + return + } + + this.flags[key] = { ...(this.flags[key] as object), ...value } + return + } + + this.flags[key] = value + } + + public listFlags(): FeatureFlagsResponse { + return Object.entries(this.flags || {}).map(([key, value]) => ({ + key, + value, + })) + } +} diff --git a/packages/framework/framework/src/feature-flags/index.ts b/packages/framework/framework/src/feature-flags/index.ts new file mode 100644 index 0000000000..4a05bac969 --- /dev/null +++ b/packages/framework/framework/src/feature-flags/index.ts @@ -0,0 +1,3 @@ +export * from "./types" +export * from "./feature-flag-loader" +export * from "./flag-router" diff --git a/packages/framework/framework/src/feature-flags/types.ts b/packages/framework/framework/src/feature-flags/types.ts new file mode 100644 index 0000000000..0131f3b7ba --- /dev/null +++ b/packages/framework/framework/src/feature-flags/types.ts @@ -0,0 +1,32 @@ +export interface IFlagRouter { + isFeatureEnabled: (key: string) => boolean + listFlags: () => FeatureFlagsResponse +} + +/** + * @schema FeatureFlagsResponse + * type: array + * items: + * type: object + * required: + * - key + * - value + * properties: + * key: + * description: The key of the feature flag. + * type: string + * value: + * description: The value of the feature flag. + * type: boolean + */ +export type FeatureFlagsResponse = { + key: string + value: boolean | Record +}[] + +export type FlagSettings = { + key: string + description: string + env_key: string + default_val: boolean +} diff --git a/packages/framework/framework/src/index.ts b/packages/framework/framework/src/index.ts index 517eb87731..156a803a4e 100644 --- a/packages/framework/framework/src/index.ts +++ b/packages/framework/framework/src/index.ts @@ -3,3 +3,4 @@ export * from "./logger" export * from "./http" export * from "./database" export * from "./container" +export * from "./feature-flags" diff --git a/packages/medusa/src/commands/links.ts b/packages/medusa/src/commands/links.ts index bcdf72f27d..700b814b34 100644 --- a/packages/medusa/src/commands/links.ts +++ b/packages/medusa/src/commands/links.ts @@ -96,7 +96,7 @@ const main = async function ({ directory }) { } try { - const container = initializeContainer(directory) + const container = await initializeContainer(directory) const configModule = container.resolve( ContainerRegistrationKeys.CONFIG_MODULE diff --git a/packages/medusa/src/commands/migrate.ts b/packages/medusa/src/commands/migrate.ts index b9e2c05305..85d4d7e9ac 100644 --- a/packages/medusa/src/commands/migrate.ts +++ b/packages/medusa/src/commands/migrate.ts @@ -45,7 +45,7 @@ const main = async function ({ directory }) { validateInputArgs({ action, modules }) - const container = initializeContainer(directory) + const container = await initializeContainer(directory) const configModule = container.resolve( ContainerRegistrationKeys.CONFIG_MODULE diff --git a/packages/medusa/src/loaders/feature-flags/index.ts b/packages/medusa/src/loaders/feature-flags/index.ts deleted file mode 100644 index 478d05894c..0000000000 --- a/packages/medusa/src/loaders/feature-flags/index.ts +++ /dev/null @@ -1,94 +0,0 @@ -import { - FlagRouter, - isObject, - isString, - objectFromStringPath, -} from "@medusajs/utils" -import glob from "glob" -import { isDefined } from "@medusajs/utils" -import { trackFeatureFlag } from "medusa-telemetry" -import path from "path" -import { FlagSettings } from "../../types/feature-flags" -import { Logger } from "../../types/global" - -const isTruthy = (val: string | boolean | undefined): boolean => { - if (typeof val === "string") { - return val.toLowerCase() === "true" - } - return !!val -} - -export const featureFlagRouter = new FlagRouter({}) - -export default ( - configModule: { - featureFlags?: Record> - } = {}, - logger?: Logger, - flagDirectory?: string -): FlagRouter => { - const { featureFlags: projectConfigFlags = {} } = configModule - - const flagDir = path.join(flagDirectory || __dirname, "*.{j,t}s") - const supportedFlags = glob.sync(flagDir, { - ignore: ["**/index.js", "**/index.ts", "**/*.d.ts"], - }) - - const flagConfig: Record> = {} - for (const flag of supportedFlags) { - const flagSettings: FlagSettings = require(flag).default - if (!flagSettings) { - continue - } - - flagConfig[flagSettings.key] = isTruthy(flagSettings.default_val) - - let from - if (isDefined(process.env[flagSettings.env_key])) { - from = "environment" - const envVal = process.env[flagSettings.env_key] - - // MEDUSA_FF_ANALYTICS="true" - flagConfig[flagSettings.key] = isTruthy(process.env[flagSettings.env_key]) - - const parsedFromEnv = isString(envVal) ? envVal.split(",") : [] - - // MEDUSA_FF_WORKFLOWS=createProducts,deleteProducts - if (parsedFromEnv.length > 1) { - flagConfig[flagSettings.key] = objectFromStringPath(parsedFromEnv) - } - } else if (isDefined(projectConfigFlags[flagSettings.key])) { - from = "project config" - - // featureFlags: { analytics: "true" | true } - flagConfig[flagSettings.key] = isTruthy( - projectConfigFlags[flagSettings.key] as string | boolean - ) - - // featureFlags: { workflows: { createProducts: true } } - if (isObject(projectConfigFlags[flagSettings.key])) { - flagConfig[flagSettings.key] = projectConfigFlags[ - flagSettings.key - ] as Record - } - } - - if (logger && from) { - logger.info( - `Using flag ${flagSettings.env_key} from ${from} with value ${ - flagConfig[flagSettings.key] - }` - ) - } - - if (flagConfig[flagSettings.key]) { - trackFeatureFlag(flagSettings.key) - } - } - - for (const flag of Object.keys(flagConfig)) { - featureFlagRouter.setFlag(flag, flagConfig[flag]) - } - - return featureFlagRouter -} diff --git a/packages/medusa/src/loaders/helpers/routing/__fixtures__/server/index.ts b/packages/medusa/src/loaders/helpers/routing/__fixtures__/server/index.ts index ff7cdcfcbe..8f87d42b5d 100644 --- a/packages/medusa/src/loaders/helpers/routing/__fixtures__/server/index.ts +++ b/packages/medusa/src/loaders/helpers/routing/__fixtures__/server/index.ts @@ -4,20 +4,23 @@ import { registerMedusaModule, } from "@medusajs/modules-sdk" import { - ContainerRegistrationKeys, - createMedusaContainer, - generateJwtToken, -} from "@medusajs/utils" + configManager, + ConfigModule, + container, + featureFlagsLoader, + logger, +} from "@medusajs/framework" +import { ContainerRegistrationKeys, generateJwtToken } from "@medusajs/utils" import { asValue } from "awilix" import express from "express" import querystring from "querystring" import supertest from "supertest" import apiLoader from "../../../../api" import { getResolvedPlugins } from "../../../../helpers/resolve-plugins" -import featureFlagLoader, { featureFlagRouter } from "../../../../feature-flags" import RoutesLoader from "../.." import { config } from "../mocks" +import { MedusaContainer } from "@medusajs/types" function asArray(resolvers) { return { @@ -44,15 +47,18 @@ export const createServer = async (rootDir) => { )[moduleKey] }) - const container = createMedusaContainer() + configManager.loadConfig({ + projectConfig: config as ConfigModule, + baseDir: rootDir, + }) - container.registerAdd = function (name, registration) { + container.registerAdd = function (this: MedusaContainer, name, registration) { const storeKey = name + "_STORE" if (this.registrations[storeKey] === undefined) { this.register(storeKey, asValue([])) } - const store = this.resolve(storeKey) + const store = this.resolve(storeKey) as Array if (this.registrations[name] === undefined) { this.register(name, asArray(store)) @@ -63,7 +69,6 @@ export const createServer = async (rootDir) => { }.bind(container) container.register(ContainerRegistrationKeys.PG_CONNECTION, asValue({})) - container.register("featureFlagRouter", asValue(featureFlagRouter)) container.register("configModule", asValue(config)) container.register({ logger: asValue({ @@ -87,11 +92,11 @@ export const createServer = async (rootDir) => { const plugins = getResolvedPlugins(rootDir, config) || [] - featureFlagLoader(config) - await moduleLoader({ container, moduleResolutions }) + await featureFlagsLoader() + await moduleLoader({ container, moduleResolutions, logger }) app.use((req, res, next) => { - req.scope = container.createScope() + req.scope = container.createScope() as MedusaContainer next() }) diff --git a/packages/medusa/src/loaders/index.ts b/packages/medusa/src/loaders/index.ts index 34e8f234cd..aef16dc043 100644 --- a/packages/medusa/src/loaders/index.ts +++ b/packages/medusa/src/loaders/index.ts @@ -3,7 +3,7 @@ import { ConfigModule, MedusaContainer, PluginDetails } from "@medusajs/types" import { ContainerRegistrationKeys, promiseAll } from "@medusajs/utils" import { asValue } from "awilix" import { Express, NextFunction, Request, Response } from "express" -import path from "path" +import path, { join } from "path" import requestIp from "request-ip" import { v4 } from "uuid" import adminLoader from "./admin" @@ -12,10 +12,10 @@ import { configLoader, container, expressLoader, + featureFlagsLoader, logger, pgConnectionLoader, } from "@medusajs/framework" -import featureFlagsLoader from "./feature-flags" import { registerJobs } from "./helpers/register-jobs" import { registerWorkflows } from "./helpers/register-workflows" import { getResolvedPlugins } from "./helpers/resolve-plugins" @@ -120,9 +120,13 @@ async function loadEntrypoints( return shutdown } -export function initializeContainer(rootDirectory: string): MedusaContainer { +export async function initializeContainer( + rootDirectory: string +): Promise { const configModule = configLoader(rootDirectory, "medusa-config.js") - const featureFlagRouter = featureFlagsLoader(configModule, logger) + const featureFlagRouter = await featureFlagsLoader( + join(__dirname, "feature-flags") + ) container.register({ [ContainerRegistrationKeys.LOGGER]: asValue(logger), @@ -143,7 +147,7 @@ export default async ({ app: Express shutdown: () => Promise }> => { - const container = initializeContainer(rootDirectory) + const container = await initializeContainer(rootDirectory) const configModule = container.resolve( ContainerRegistrationKeys.CONFIG_MODULE ) diff --git a/packages/modules/link-modules/package.json b/packages/modules/link-modules/package.json index 41bf6d18c6..7af9a697ec 100644 --- a/packages/modules/link-modules/package.json +++ b/packages/modules/link-modules/package.json @@ -32,6 +32,7 @@ "@medusajs/types": "^1.11.16", "cross-env": "^5.2.1", "jest": "^29.7.0", + "medusa-test-utils": "^1.1.44", "pg-god": "^1.0.12", "rimraf": "^5.0.1", "ts-node": "^10.9.1", diff --git a/yarn.lock b/yarn.lock index e7d2f3001a..57d701473d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4619,6 +4619,7 @@ __metadata: express-session: ^1.17.3 ioredis: ^5.2.5 ioredis-mock: 8.4.0 + medusa-telemetry: ^0.0.18 morgan: ^1.9.1 rimraf: ^3.0.2 tsc-alias: ^1.8.6 @@ -4758,6 +4759,7 @@ __metadata: awilix: ^8.0.0 cross-env: ^5.2.1 jest: ^29.7.0 + medusa-test-utils: ^1.1.44 pg-god: ^1.0.12 rimraf: ^5.0.1 ts-node: ^10.9.1 @@ -22704,6 +22706,24 @@ __metadata: languageName: unknown linkType: soft +"medusa-telemetry@npm:^0.0.18": + version: 0.0.18 + resolution: "medusa-telemetry@npm:0.0.18" + dependencies: + "@babel/runtime": ^7.22.10 + axios: ^0.21.4 + axios-retry: ^3.1.9 + boxen: ^5.0.1 + ci-info: ^3.2.0 + configstore: 5.0.1 + global: ^4.4.0 + is-docker: ^2.2.1 + remove-trailing-slash: ^0.1.1 + uuid: ^8.3.2 + checksum: 3571c3f578582667b3a48f2f3d9d27299a954fcc1e9165c2fdc441219d6faa0cd0f6be453f0edec783acea83500586c199a21d8b052e6ed3c815da85850fef7f + languageName: node + linkType: hard + "medusa-test-utils@^1.1.42, medusa-test-utils@^1.1.43, medusa-test-utils@^1.1.44, medusa-test-utils@workspace:*, medusa-test-utils@workspace:^, medusa-test-utils@workspace:packages/core/medusa-test-utils": version: 0.0.0-use.local resolution: "medusa-test-utils@workspace:packages/core/medusa-test-utils"