diff --git a/.eslintignore b/.eslintignore index 28af48ab6d..27481c657b 100644 --- a/.eslintignore +++ b/.eslintignore @@ -33,6 +33,8 @@ packages/* !packages/fulfillment !packages/fulfillment-manual +!packages/framework + **/models/* **/scripts/* diff --git a/.eslintrc.js b/.eslintrc.js index 559d5f480e..ff4a8c6aba 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -86,23 +86,18 @@ module.exports = { "./packages/admin-next/admin-shared/tsconfig.json", "./packages/admin-next/admin-vite-plugin/tsconfig.json", - "./packages/inventory/tsconfig.spec.json", - "./packages/stock-location/tsconfig.spec.json", - - "./packages/core-flows/tsconfig.spec.json", - - "./packages/cli/create-medusa-app/tsconfig.spec.json", - "./packages/cli/medusa-cli/tsconfig.spec/json", + "./packages/cli/create-medusa-app/tsconfig.json", + "./packages/cli/medusa-cli/tsconfig.spec.json", "./packages/cli/medusa-dev-cli/tsconfig.spec.json", "./packages/cli/oas/medusa-oas-cli/tsconfig.spec.json", "./packages/core/orchestration/tsconfig.json", "./packages/core/workflows-sdk/tsconfig.spec.json", - "./packages/core/modules-sdk/tsconfig.spec.json", - "./packages/core/js-sdk/tsconfig.spec.json", - "./packages/core/types/tsconfig.spec.json", + "./packages/core/modules-sdk/tsconfig.json", + "./packages/core/js-sdk/tsconfig.json", + "./packages/core/types/tsconfig.json", "./packages/core/utils/tsconfig.spec.json", - "./packages/core/medusa-test-utils/tsconfig.spec.json", + "./packages/core/medusa-test-utils/tsconfig.json", "./packages/modules/product/tsconfig.json", "./packages/modules/event-bus-local/tsconfig.spec.json", @@ -137,6 +132,8 @@ module.exports = { "./packages/modules/providers/file-s3/tsconfig.spec.json", "./packages/modules/providers/fulfillment-manual/tsconfig.spec.json", "./packages/modules/providers/payment-stripe/tsconfig.spec.json", + + "./packages/framework/framework/tsconfig.json", ], }, rules: { diff --git a/integration-tests/api/package.json b/integration-tests/api/package.json index bb79684b06..76c4bed9d4 100644 --- a/integration-tests/api/package.json +++ b/integration-tests/api/package.json @@ -5,8 +5,8 @@ "license": "MIT", "private": true, "scripts": { - "test:integration": "jest --silent=false --no-cache --maxWorkers=50% --bail --detectOpenHandles --forceExit --logHeapUsage", - "test:integration:chunk": "jest --silent --no-cache --bail --maxWorkers=50% --forceExit --detectOpenHandles --testPathPattern=$(echo $CHUNKS | jq -r \".[${CHUNK}] | .[]\")", + "test:integration": "NODE_OPTIONS=--experimental-vm-modules jest --silent=false --no-cache --maxWorkers=50% --bail --detectOpenHandles --forceExit --logHeapUsage", + "test:integration:chunk": "NODE_OPTIONS=--experimental-vm-modules jest --silent --no-cache --bail --maxWorkers=50% --forceExit --detectOpenHandles --testPathPattern=$(echo $CHUNKS | jq -r \".[${CHUNK}] | .[]\")", "build": "tsc ./src/* --allowJs --outDir ./dist" }, "dependencies": { diff --git a/integration-tests/http/jest.config.js b/integration-tests/http/jest.config.js index 733b7a626e..e9e8a528c5 100644 --- a/integration-tests/http/jest.config.js +++ b/integration-tests/http/jest.config.js @@ -11,6 +11,7 @@ module.exports = { jsc: { parser: { syntax: "typescript", decorators: true }, transform: { decoratorMetadata: true }, + target: "es2021", }, }, ], diff --git a/integration-tests/http/package.json b/integration-tests/http/package.json index 48eba21a8b..e353640ee2 100644 --- a/integration-tests/http/package.json +++ b/integration-tests/http/package.json @@ -5,8 +5,8 @@ "license": "MIT", "private": true, "scripts": { - "test:integration": "jest --no-cache --maxWorkers=50% --bail --detectOpenHandles --forceExit --logHeapUsage", - "test:integration:chunk": "jest --silent --no-cache --bail --maxWorkers=50% --forceExit --testPathPattern=$(echo $CHUNKS | jq -r \".[${CHUNK}] | .[]\")", + "test:integration": "NODE_OPTIONS=--experimental-vm-modules jest --no-cache --maxWorkers=50% --bail --detectOpenHandles --forceExit --logHeapUsage -- __tests__/payment-collection/admin/payment-sessions.spec.ts", + "test:integration:chunk": "NODE_OPTIONS=--experimental-vm-modules jest --silent --no-cache --bail --maxWorkers=50% --forceExit --testPathPattern=$(echo $CHUNKS | jq -r \".[${CHUNK}] | .[]\")", "build": "tsc ./src/* --allowJs --outDir ./dist" }, "dependencies": { diff --git a/integration-tests/modules/package.json b/integration-tests/modules/package.json index a55b7cbe7a..704a891cba 100644 --- a/integration-tests/modules/package.json +++ b/integration-tests/modules/package.json @@ -5,8 +5,8 @@ "license": "MIT", "private": true, "scripts": { - "test:integration": "jest --silent=false --no-cache --maxWorkers=50% --bail --detectOpenHandles --forceExit --logHeapUsage", - "test:integration:chunk": "jest --silent --no-cache --bail --maxWorkers=50% --forceExit --testPathPattern=$(echo $CHUNKS | jq -r \".[${CHUNK}] | .[]\")", + "test:integration": "NODE_OPTIONS=--experimental-vm-modules jest --silent=false --no-cache --maxWorkers=50% --bail --detectOpenHandles --forceExit --logHeapUsage", + "test:integration:chunk": "NODE_OPTIONS=--experimental-vm-modules jest --silent --no-cache --bail --maxWorkers=50% --forceExit --testPathPattern=$(echo $CHUNKS | jq -r \".[${CHUNK}] | .[]\")", "build": "tsc --allowJs --outDir ./dist" }, "dependencies": { diff --git a/package.json b/package.json index 0bde02e770..d2c35acd98 100644 --- a/package.json +++ b/package.json @@ -7,6 +7,7 @@ "packages/modules/*", "packages/modules/providers/*", "packages/core/*", + "packages/framework/*", "packages/cli/*", "packages/cli/oas/*", "packages/*", diff --git a/packages/core/core-flows/src/auth/steps/set-auth-app-metadata.ts b/packages/core/core-flows/src/auth/steps/set-auth-app-metadata.ts index 445d4f04dd..969e7ba254 100644 --- a/packages/core/core-flows/src/auth/steps/set-auth-app-metadata.ts +++ b/packages/core/core-flows/src/auth/steps/set-auth-app-metadata.ts @@ -1,7 +1,7 @@ -import { StepResponse, createStep } from "@medusajs/workflows-sdk" +import { createStep, StepResponse } from "@medusajs/workflows-sdk" import { IAuthModuleService } from "@medusajs/types" -import { ModuleRegistrationName, isDefined } from "@medusajs/utils" +import { isDefined, ModuleRegistrationName } from "@medusajs/utils" type StepInput = { authIdentityId: string diff --git a/packages/core/medusa-test-utils/package.json b/packages/core/medusa-test-utils/package.json index 2ff2e2e165..74e8e574e9 100644 --- a/packages/core/medusa-test-utils/package.json +++ b/packages/core/medusa-test-utils/package.json @@ -21,7 +21,7 @@ "files": [ "dist" ], - "author": "Sebastian Rindom", + "author": "Medusa", "license": "MIT", "devDependencies": { "@medusajs/types": "^1.11.16", @@ -31,6 +31,7 @@ "typescript": "^5.1.6" }, "peerDependencies": { + "@medusajs/framework": "workspace:^", "@medusajs/medusa": ">1.19", "@medusajs/modules-sdk": "^1.12.10", "axios": "^0.28.0", @@ -39,6 +40,9 @@ "pg-god": "^1.0.12" }, "peerDependenciesMeta": { + "@medusajs/framework": { + "optional": true + }, "@medusajs/medusa": { "optional": true }, 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 new file mode 100644 index 0000000000..05036ae953 --- /dev/null +++ b/packages/core/medusa-test-utils/src/medusa-test-runner-utils/config.ts @@ -0,0 +1,31 @@ +import { getConfigFile } from "@medusajs/utils" + +export async function configLoaderOverride( + entryDirectory: string, + override: { clientUrl: string; debug?: boolean } +) { + // in case it is not install as it is optional and required only when using this util + // @ts-ignore + const { configManager } = await import("@medusajs/framework/config") + const { configModule, error } = getConfigFile< + ReturnType + >(entryDirectory, "medusa-config.js") + + if (error) { + throw new Error(error.message || "Error during config loading") + } + + configModule.projectConfig.databaseUrl = override.clientUrl + configModule.projectConfig.databaseLogging = !!override.debug + configModule.projectConfig.databaseDriverOptions = + override.clientUrl.includes("localhost") + ? {} + : { + connection: { + ssl: { rejectUnauthorized: false }, + }, + idle_in_transaction_session_timeout: 20000, + } + + configManager.loadConfig(configModule) +} 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 f868a16df7..ddb2124899 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 @@ -5,21 +5,17 @@ import { } from "@medusajs/utils" import { asValue } from "awilix" -export async function initDb({ - cwd, - env = {}, -}: { - cwd: string - env?: Record -}) { +export async function initDb({ env = {} }: { env?: Record }) { if (isObject(env)) { Object.entries(env).forEach(([k, v]) => (process.env[k] = v)) } const container = createMedusaContainer() - const configModule = - await require("@medusajs/medusa/dist/loaders/config").default(cwd) + // in case it is not install as it is optional and required only when using this util + // @ts-ignore + const { configManager } = await import("@medusajs/framework/config") + const configModule = configManager.config const pgConnection = await require("@medusajs/medusa/dist/loaders/pg-connection").default({ diff --git a/packages/core/medusa-test-utils/src/medusa-test-runner.ts b/packages/core/medusa-test-utils/src/medusa-test-runner.ts index 935992ebf7..084d7aaf3d 100644 --- a/packages/core/medusa-test-utils/src/medusa-test-runner.ts +++ b/packages/core/medusa-test-utils/src/medusa-test-runner.ts @@ -7,6 +7,7 @@ import { createDatabase, dropDatabase } from "pg-god" import { getDatabaseURL } from "./database" import { startApp } from "./medusa-test-runner-utils/bootstrap-app" import { initDb } from "./medusa-test-runner-utils/use-db" +import { configLoaderOverride } from "./medusa-test-runner-utils/config" const axios = require("axios").default @@ -102,27 +103,6 @@ export function medusaIntegrationTestRunner({ debug, } - const originalConfigLoader = - require("@medusajs/medusa/dist/loaders/config").default - require("@medusajs/medusa/dist/loaders/config").default = ( - rootDirectory: string - ) => { - const config = originalConfigLoader(rootDirectory) - config.projectConfig.databaseUrl = dbConfig.clientUrl - config.projectConfig.databaseLogging = !!dbConfig.debug - config.projectConfig.databaseDriverOptions = dbConfig.clientUrl.includes( - "localhost" - ) - ? {} - : { - connection: { - ssl: { rejectUnauthorized: false }, - }, - idle_in_transaction_session_timeout: 20000, - } - return config - } - const cwd = process.cwd() let shutdown = async () => void 0 @@ -159,12 +139,13 @@ export function medusaIntegrationTestRunner({ let isFirstTime = true const beforeAll_ = async () => { + await configLoaderOverride(cwd, dbConfig) + console.log(`Creating database ${dbName}`) await dbUtils.create(dbName) try { dbUtils.pgConnection_ = await initDb({ - cwd, env, }) } catch (error) { diff --git a/packages/core/medusa-test-utils/tsconfig.json b/packages/core/medusa-test-utils/tsconfig.json index 7bb5adc8dc..2b841a9130 100644 --- a/packages/core/medusa-test-utils/tsconfig.json +++ b/packages/core/medusa-test-utils/tsconfig.json @@ -6,8 +6,8 @@ "esModuleInterop": true, "declarationMap": true, "declaration": true, - "module": "commonjs", - "moduleResolution": "node", + "module": "NodeNext", + "moduleResolution": "NodeNext", "emitDecoratorMetadata": true, "experimentalDecorators": true, "sourceMap": true, diff --git a/packages/core/utils/src/common/deep-copy.ts b/packages/core/utils/src/common/deep-copy.ts index 035b22d13a..a7e35f865d 100644 --- a/packages/core/utils/src/common/deep-copy.ts +++ b/packages/core/utils/src/common/deep-copy.ts @@ -6,9 +6,10 @@ import { isObject } from "./is-object" * * @param obj */ -export function deepCopy = Record>( - obj: T | T[] -): T | T[] { +export function deepCopy< + T extends Record | Record[] = Record, + TOutput = T extends [] ? T[] : T +>(obj: T): TOutput { if (obj === null || typeof obj !== "object") { return obj } @@ -18,14 +19,14 @@ export function deepCopy = Record>( for (let i = 0; i < obj.length; i++) { copy[i] = deepCopy(obj[i]) } - return copy + return copy as TOutput } if (isObject(obj)) { const copy: Record = {} for (let attr in obj) { if (obj.hasOwnProperty(attr)) { - copy[attr] = deepCopy(obj[attr]) + copy[attr] = deepCopy(obj[attr] as T) } } return copy diff --git a/packages/framework/framework/README.md b/packages/framework/framework/README.md new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/framework/framework/jest.config.js b/packages/framework/framework/jest.config.js new file mode 100644 index 0000000000..747af0cb05 --- /dev/null +++ b/packages/framework/framework/jest.config.js @@ -0,0 +1,17 @@ +module.exports = { + moduleNameMapper: {}, + transform: { + "^.+\\.[jt]s$": [ + "@swc/jest", + { + jsc: { + parser: { syntax: "typescript", decorators: true }, + transform: { decoratorMetadata: true }, + }, + }, + ], + }, + testEnvironment: `node`, + moduleFileExtensions: [`js`, `ts`], + modulePathIgnorePatterns: ["dist/"], +} diff --git a/packages/framework/framework/package.json b/packages/framework/framework/package.json new file mode 100644 index 0000000000..f6617fe747 --- /dev/null +++ b/packages/framework/framework/package.json @@ -0,0 +1,66 @@ +{ + "name": "@medusajs/framework", + "version": "0.0.0", + "description": "Framework", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "files": [ + "dist" + ], + "exports": { + ".": { + "node": "./dist/index.js", + "import": "./dist/index.js", + "require": "./dist/index.js", + "types": "./dist/index.d.ts", + "default": "./dist/index.js" + }, + "./config": { + "types": "./dist/config/index.d.ts", + "import": "./dist/config/index.js", + "require": "./dist/config/index.js", + "node": "./dist/config/index.js" + }, + "./logger": { + "types": "./dist/logger/index.d.ts", + "import": "./dist/logger/index.js", + "require": "./dist/logger/index.js", + "node": "./dist/logger/index.js" + } + }, + "engines": { + "node": ">=20" + }, + "repository": { + "type": "git", + "url": "https://github.com/medusajs/medusa", + "directory": "packages/framework/framework" + }, + "publishConfig": { + "access": "public" + }, + "author": "Medusa", + "license": "MIT", + "scripts": { + "watch": "tsc --build --watch -p ./tsconfig.build.json", + "watch:test": "tsc --build tsconfig.spec.json --watch", + "prepublishOnly": "cross-env NODE_ENV=production tsc -p ./tsconfig.build.json && tsc-alias -p ./tsconfig.build.json", + "build": "rimraf dist && tsc --build && tsc-alias", + "test": "jest --runInBand --bail --passWithNoTests --forceExit -- src", + "test:integration": "jest --forceExit -- integration-tests/**/__tests__/**/*.ts" + }, + "devDependencies": { + "@medusajs/types": "workspace:^", + "cross-env": "^7.0.3", + "ioredis": "^5.2.5", + "rimraf": "^3.0.2", + "tsc-alias": "^1.8.6", + "typescript": "^5.1.6", + "vite": "^5.2.11" + }, + "dependencies": { + "@medusajs/medusa-cli": "workspace:^", + "@medusajs/utils": "workspace:^", + "awilix": "^8.0.0" + } +} diff --git a/packages/framework/framework/src/config/config.ts b/packages/framework/framework/src/config/config.ts new file mode 100644 index 0000000000..117341a41f --- /dev/null +++ b/packages/framework/framework/src/config/config.ts @@ -0,0 +1,157 @@ +import { ConfigModule } from "./types" +import { deepCopy, isDefined } from "@medusajs/utils" +import { Logger } from "@medusajs/types" + +export class ConfigManager { + /** + * A flag to specify if we are in production or not, determine whether an error would be critical and thrown or just logged as a warning in developement + * @private + */ + readonly #isProduction: boolean = ["production", "prod"].includes( + process.env.NODE_ENV || "" + ) + + /** + * The worker mode + * @private + */ + readonly #envWorkMode?: ConfigModule["projectConfig"]["workerMode"] = process + .env.MEDUSA_WORKER_MODE as ConfigModule["projectConfig"]["workerMode"] + + /** + * The logger instance to use + * @private + */ + readonly #logger: Logger + + /** + * The config object after loading it + * @private + */ + #config!: ConfigModule + + get config(): ConfigModule { + if (!this.#config) { + this.rejectErrors( + `Config not loaded. Make sure the config have been loaded first using the 'configLoader' or 'configManager.loadConfig'.` + ) + } + return this.#config + } + + constructor({ logger }: { logger: Logger }) { + this.#logger = logger + } + + /** + * Rejects an error either by throwing when in production or by logging the error as a warning + * @param error + * @protected + */ + protected rejectErrors(error: string): never | void { + if (this.#isProduction) { + throw new Error(`[config] ⚠️ ${error}`) + } + + this.#logger.warn(error) + } + + /** + * Builds the http config object and assign the defaults if needed + * @param projectConfig + * @protected + */ + protected buildHttpConfig( + projectConfig: Partial + ): ConfigModule["projectConfig"]["http"] { + const http = (projectConfig.http ?? + {}) as ConfigModule["projectConfig"]["http"] + + http.jwtExpiresIn = http?.jwtExpiresIn ?? "1d" + http.authCors = http.authCors ?? "" + http.storeCors = http.storeCors ?? "" + http.adminCors = http.adminCors ?? "" + + http.jwtSecret = http?.jwtSecret ?? process.env.JWT_SECRET + + if (!http.jwtSecret) { + this.rejectErrors( + `http.jwtSecret not found.${ + this.#isProduction ? "" : "Using default 'supersecret'." + }` + ) + + http.jwtSecret = "supersecret" + } + + http.cookieSecret = (projectConfig.http?.cookieSecret ?? + process.env.COOKIE_SECRET)! + + if (!http.cookieSecret) { + this.rejectErrors( + `http.cookieSecret not found.${ + this.#isProduction ? "" : " Using default 'supersecret'." + }` + ) + + http.cookieSecret = "supersecret" + } + + return http + } + + /** + * Normalizes the project config object and assign the defaults if needed + * @param projectConfig + * @protected + */ + protected normalizeProjectConfig( + projectConfig: Partial + ): ConfigModule["projectConfig"] { + const outputConfig: ConfigModule["projectConfig"] = deepCopy(projectConfig) + + if (!outputConfig?.redisUrl) { + console.log(`redisUrl not found. A fake redis instance will be used.`) + } + + outputConfig.http = this.buildHttpConfig(projectConfig) + + let workerMode = outputConfig?.workerMode! + + if (!isDefined(workerMode)) { + const env = this.#envWorkMode + if (isDefined(env)) { + const workerModes = ["shared", "worker", "server"] + if (workerModes.includes(env)) { + workerMode = env + } + } else { + workerMode = "shared" + } + } + + return { + ...outputConfig, + workerMode, + } + } + + /** + * Prepare the full configuration after validation and normalization + */ + loadConfig(rawConfig: Partial = {}): ConfigModule { + const projectConfig = this.normalizeProjectConfig( + rawConfig.projectConfig ?? {} + ) + + this.#config = { + projectConfig, + admin: rawConfig.admin ?? {}, + modules: rawConfig.modules ?? {}, + featureFlags: rawConfig.featureFlags ?? {}, + plugins: rawConfig.plugins ?? [], + } + + return this.#config + } +} diff --git a/packages/framework/framework/src/config/index.ts b/packages/framework/framework/src/config/index.ts new file mode 100644 index 0000000000..85b6201418 --- /dev/null +++ b/packages/framework/framework/src/config/index.ts @@ -0,0 +1,3 @@ +export * from "./loader" +export * from "./config" +export * from "./types" diff --git a/packages/framework/framework/src/config/loader.ts b/packages/framework/framework/src/config/loader.ts new file mode 100644 index 0000000000..9a811d3395 --- /dev/null +++ b/packages/framework/framework/src/config/loader.ts @@ -0,0 +1,39 @@ +import { ConfigModule } from "./types" +import { getConfigFile } from "@medusajs/utils" +import { logger } from "../logger" +import { ConfigManager } from "./config" + +const handleConfigError = (error: Error): void => { + logger.error(`Error in loading config: ${error.message}`) + if (error.stack) { + logger.error(error.stack) + } + process.exit(1) +} + +// TODO: Later on we could store the config manager into the unique container +export const configManager = new ConfigManager({ + logger, +}) + +/** + * Loads the config file and returns the config module after validating, normalizing the configurations + * + * @param entryDirectory The directory to find the config file from + * @param configFileName The name of the config file to search for in the entry directory + */ +export function configLoader( + entryDirectory: string, + configFileName: string +): ConfigModule { + const { configModule, error } = getConfigFile( + entryDirectory, + configFileName + ) + + if (error) { + handleConfigError(error) + } + + return configManager.loadConfig(configModule) +} diff --git a/packages/framework/framework/src/config/types.ts b/packages/framework/framework/src/config/types.ts new file mode 100644 index 0000000000..3a5da201a6 --- /dev/null +++ b/packages/framework/framework/src/config/types.ts @@ -0,0 +1,868 @@ +import { + ExternalModuleDeclaration, + InternalModuleDeclaration, +} from "@medusajs/types" + +import type { RedisOptions } from "ioredis" +import type { InlineConfig } from "vite" + +/** + * @interface + * + * Admin dashboard configurations. + */ +export type AdminOptions = { + /** + * Whether to disable the admin dashboard. If set to `true`, the admin dashboard is disabled, + * in both development and production environments. The default value is `false`. + */ + disable?: boolean + /** + * The path to the admin dashboard. The default value is `/app`. + * + * The value cannot be one of the reserved paths: + * - `/admin` + * - `/store` + * - `/auth` + * - `/` + */ + path?: `/${string}` + /** + * The directory where the admin build is output. This is where the build process places the generated files. + * The default value is `./build`. + */ + outDir?: string + /** + * The URL of your Medusa backend. Defaults to an empty string, which means requests will hit the same server that serves the dashboard. + */ + backendUrl?: string + /** + * Configure the Vite configuration for the admin dashboard. This function receives the default Vite configuration + * and returns the modified configuration. The default value is `undefined`. + * + * @privateRemarks TODO Add example + */ + vite?: (config: InlineConfig) => InlineConfig +} + +/** + * @interface + * + * Options to pass to `express-session`. + */ +type SessionOptions = { + /** + * The name of the session ID cookie to set in the response (and read from in the request). The default value is `connect.sid`. + * Refer to [express-session’s documentation](https://www.npmjs.com/package/express-session#name) for more details. + */ + name?: string + /** + * Whether the session should be saved back to the session store, even if the session was never modified during the request. The default value is `true`. + * Refer to [express-session’s documentation](https://www.npmjs.com/package/express-session#resave) for more details. + */ + resave?: boolean + /** + * Whether the session identifier cookie should be force-set on every response. The default value is `false`. + * Refer to [express-session’s documentation](https://www.npmjs.com/package/express-session#rolling) for more details. + */ + rolling?: boolean + /** + * Whether a session that is "uninitialized" is forced to be saved to the store. The default value is `true`. + * Refer to [express-session’s documentation](https://www.npmjs.com/package/express-session#saveUninitialized) for more details. + */ + saveUninitialized?: boolean + /** + * The secret to sign the session ID cookie. By default, the value of `http.cookieSecret` is used. + * Refer to [express-session’s documentation](https://www.npmjs.com/package/express-session#secret) for details. + */ + secret?: string + /** + * Used when calculating the `Expires` `Set-Cookie` attribute of cookies. By default, its value is `10 * 60 * 60 * 1000`. + * Refer to [express-session’s documentation](https://www.npmjs.com/package/express-session#cookiemaxage) for details. + */ + ttl?: number +} + +/** + * @interface + * + * HTTP compression configurations. + */ +export type HttpCompressionOptions = { + /** + * Whether HTTP compression is enabled. By default, it's `false`. + */ + enabled?: boolean + /** + * The level of zlib compression to apply to responses. A higher level will result in better compression but will take longer to complete. + * A lower level will result in less compression but will be much faster. The default value is `6`. + */ + level?: number + /** + * How much memory should be allocated to the internal compression state. It's an integer in the range of 1 (minimum level) and 9 (maximum level). + * The default value is `8`. + */ + memLevel?: number + /** + * The minimum response body size that compression is applied on. Its value can be the number of bytes or any string accepted by the + * [bytes](https://www.npmjs.com/package/bytes) module. The default value is `1024`. + */ + threshold?: number | string +} + +/** + * @interface + * + * Essential configurations related to the Medusa backend, such as database and CORS configurations. + */ +export type ProjectConfigOptions = { + /** + * The name of the database to connect to. If specified in `databaseUrl`, then it’s not required to include it. + * + * Make sure to create the PostgreSQL database before using it. You can check how to create a database in + * [PostgreSQL's documentation](https://www.postgresql.org/docs/current/sql-createdatabase.html). + * + * @example + * ```js title="medusa-config.js" + * module.exports = defineConfig({ + * projectConfig: { + * databaseName: process.env.DATABASE_NAME || + * "medusa-store", + * // ... + * }, + * // ... + * }) + * ``` + */ + databaseName?: string + + /** + * The connection URL of the database. The format of the connection URL for PostgreSQL is: + * + * ```bash + * postgres://[user][:password]@[host][:port]/[dbname] + * ``` + * + * Where: + * + * - `[user]`: (required) your PostgreSQL username. If not specified, the system's username is used by default. The database user that you use must have create privileges. If you're using the `postgres` superuser, then it should have these privileges by default. Otherwise, make sure to grant your user create privileges. You can learn how to do that in [PostgreSQL's documentation](https://www.postgresql.org/docs/current/ddl-priv.html). + * - `[:password]`: an optional password for the user. When provided, make sure to put `:` before the password. + * - `[host]`: (required) your PostgreSQL host. When run locally, it should be `localhost`. + * - `[:port]`: an optional port that the PostgreSQL server is listening on. By default, it's `5432`. When provided, make sure to put `:` before the port. + * - `[dbname]`: (required) the name of the database. + * + * You can learn more about the connection URL format in [PostgreSQL’s documentation](https://www.postgresql.org/docs/current/libpq-connect.html). + * + * @example + * For example, set the following database URL in your environment variables: + * + * ```bash + * DATABASE_URL=postgres://postgres@localhost/medusa-store + * ``` + * + * Then, use the value in `medusa-config.js`: + * + * ```js title="medusa-config.js" + * module.exports = defineConfig({ + * projectConfig: { + * databaseUrl: process.env.DATABASE_URL, + * // ... + * }, + * // ... + * }) + * ``` + */ + databaseUrl?: string + + /** + * The database schema to connect to. This is not required to provide if you’re using the default schema, which is `public`. + * + * ```js title="medusa-config.js" + * module.exports = defineConfig({ + * projectConfig: { + * databaseSchema: process.env.DATABASE_SCHEMA || + * "custom", + * // ... + * }, + * // ... + * }) + * ``` + */ + databaseSchema?: string + + /** + * This configuration specifies what database messages to log. Its value can be one of the following: + * + * - (default) A boolean value that indicates whether any messages should be logged. + * - The string value `all` that indicates all types of messages should be logged. + * - An array of log-level strings to indicate which type of messages to show in the logs. The strings can be `query`, `schema`, `error`, `warn`, `info`, `log`, or `migration`. Refer to [Typeorm’s documentation](https://typeorm.io/logging#logging-options) for more details on what each of these values means. + * + * If this configuration isn't set, its default value is `false`, meaning no database messages are logged. + * + * @example + * ```js title="medusa-config.js" + * module.exports = defineConfig({ + * projectConfig: { + * databaseLogging: boolean + * // ... + * }, + * // ... + * }) + * ``` + */ + databaseLogging?: boolean + + /** + * @ignore + * @deprecated + * + * @privateRemarks + * only postgres is supported, so this config has no effect + */ + databaseType?: string + + /** + * An object that includes additional configurations to pass to the database connection for v2. You can pass any configuration. One defined configuration to pass is + * `ssl` which enables support for TLS/SSL connections. + * + * This is useful for production databases, which can be supported by setting the `rejectUnauthorized` attribute of `ssl` object to `false`. + * During development, it’s recommended not to pass this option. + * + * @example + * ```js title="medusa-config.js" + * module.exports = defineConfig({ + * projectConfig: { + * databaseDriverOptions: process.env.NODE_ENV !== "development" ? + * { ssl: { rejectUnauthorized: false } } : {} + * // ... + * }, + * // ... + * }) + * ``` + */ + databaseDriverOptions?: Record & { + connection?: { + /** + * Configure support for TLS/SSL connection + */ + ssl?: { + /** + * Whether to fail connection if the server certificate is verified against the list of supplied CAs and the hostname and no match is found. + */ + rejectUnauthorized?: false + } + } + } + + /** + * Used to specify the URL to connect to Redis. This is only used for scheduled jobs. If you omit this configuration, scheduled jobs won't work. + * + * :::note + * + * You must first have Redis installed. You can refer to [Redis's installation guide](https://redis.io/docs/getting-started/installation/). + * + * ::: + * + * The Redis connection URL has the following format: + * + * ```bash + * redis[s]://[[username][:password]@][host][:port][/db-number] + * ``` + * + * For a local Redis installation, the connection URL should be `redis://localhost:6379` unless you’ve made any changes to the Redis configuration during installation. + * + * @example + * ```js title="medusa-config.js" + * module.exports = defineConfig({ + * projectConfig: { + * redisUrl: process.env.REDIS_URL || + * "redis://localhost:6379", + * // ... + * }, + * // ... + * }) + * ``` + */ + redisUrl?: string + + /** + * The prefix set on all keys stored in Redis. The default value is `sess:`. + * + * If this configuration option is provided, it is prepended to `sess:`. + * + * @example + * ```js title="medusa-config.js" + * module.exports = defineConfig({ + * projectConfig: { + * redisPrefix: process.env.REDIS_URL || "medusa:", + * // ... + * }, + * // ... + * }) + * ``` + */ + redisPrefix?: string + + /** + * An object of options to pass ioredis. You can refer to [ioredis’s RedisOptions documentation](https://redis.github.io/ioredis/index.html#RedisOptions) + * for the list of available options. + * + * @example + * ```js title="medusa-config.js" + * module.exports = defineConfig({ + * projectConfig: { + * redisOptions: { + * connectionName: process.env.REDIS_CONNECTION_NAME || + * "medusa", + * } + * // ... + * }, + * // ... + * }) + * ``` + */ + redisOptions?: RedisOptions + + /** + * An object of options to pass to [express-session](https://www.npmjs.com/package/express-session). + * + * @example + * ```js title="medusa-config.js" + * module.exports = defineConfig({ + * projectConfig: { + * sessionOptions: { + * name: process.env.SESSION_NAME || "custom", + * } + * // ... + * }, + * // ... + * }) + * ``` + */ + sessionOptions?: SessionOptions + + /** + * Configure HTTP compression from the application layer. If you have access to the HTTP server, the recommended approach would be to enable it there. + * However, some platforms don't offer access to the HTTP layer and in those cases, this is a good alternative. + * + * If you enable HTTP compression and you want to disable it for specific API Routes, you can pass in the request header `"x-no-compression": true`. + * + * @ignore + * + * @deprecated use {@link http }'s `compression` property instead. + * + */ + httpCompression?: HttpCompressionOptions + + /** + * Configure the number of staged jobs that are polled from the database. Default is `1000`. + * + * @example + * ```js title="medusa-config.js" + * module.exports = defineConfig({ + * projectConfig: { + * jobsBatchSize: 100 + * // ... + * }, + * // ... + * }) + * ``` + */ + jobsBatchSize?: number + + /** + * Configure the application's worker mode. Default is `shared`. + * + * - Use `shared` to run the application in a single process. + * - Use `worker` to run the a worker process only. + * - Use `server` to run the application server only. + * + * Learn more in [this guide](https://docs.medusajs.com/development/medusa-worker). + * + * @example + * ```js title="medusa-config.js" + * module.exports = defineConfig({ + * projectConfig: { + * workerMode: "shared" + * // ... + * }, + * // ... + * }) + * ``` + */ + workerMode?: "shared" | "worker" | "server" + + /** + * Configure the application's http-specific settings + * + * @example + * ```js title="medusa-config.js" + * module.exports = defineConfig({ + * projectConfig: { + * http: { + * cookieSecret: "supersecret", + * compression: { + * // ... + * } + * } + * // ... + * }, + * // ... + * }) + * ``` + */ + http: { + /** + * A random string used to create authentication tokens in the http layer. Although this configuration option is not required, it’s highly recommended to set it for better security. + * + * In a development environment, if this option is not set the default secret is `supersecret` However, in production, if this configuration is not set an error, an + * error is thrown and the backend crashes. + * + * @example + * ```js title="medusa-config.js" + * module.exports = defineConfig({ + * projectConfig: { + * http: { + * jwtSecret: "supersecret", + * } + * // ... + * }, + * // ... + * }) + * ``` + */ + jwtSecret?: string + /** + * The expiration time for the JWT token. If not provided, the default value is `24h`. + * + * @example + * ```js title="medusa-config.js" + * module.exports = defineConfig({ + * projectConfig: { + * http: { + * jwtExpiresIn: "2d" + * } + * // ... + * }, + * // ... + * }) + * ``` + */ + jwtExpiresIn?: string + /** + * A random string used to create cookie tokens in the http layer. Although this configuration option is not required, it’s highly recommended to set it for better security. + * + * In a development environment, if this option is not set, the default secret is `supersecret` However, in production, if this configuration is not set, an error is thrown and + * the backend crashes. + * + * @example + * ```js title="medusa-config.js" + * module.exports = defineConfig({ + * projectConfig: { + * http: { + * cookieSecret: "supersecret" + * } + * // ... + * }, + * // ... + * }) + * ``` + */ + cookieSecret?: string + /** + * The Medusa backend’s API Routes are protected by Cross-Origin Resource Sharing (CORS). So, only allowed URLs or URLs matching a specified pattern can send requests to the backend’s API Routes. + * + * `cors` is a string used to specify the accepted URLs or patterns for API Routes starting with `/auth`. It can either be one accepted origin, or a comma-separated list of accepted origins. + * + * Every origin in that list must either be: + * + * 1. A URL. For example, `http://localhost:7001`. The URL must not end with a backslash; + * 2. Or a regular expression pattern that can match more than one origin. For example, `.example.com`. The regex pattern that the backend tests for is `^([\/~@;%#'])(.*?)\1([gimsuy]*)$`. + * + * @example + * Some example values of common use cases: + * + * ```bash + * # Allow different ports locally starting with 700 + * AUTH_CORS=/http:\/\/localhost:700\d+$/ + * + * # Allow any origin ending with vercel.app. For example, admin.vercel.app + * AUTH_CORS=/vercel\.app$/ + * + * # Allow all HTTP requests + * AUTH_CORS=/http:\/\/.+/ + * ``` + * + * Then, set the configuration in `medusa-config.js`: + * + * ```js title="medusa-config.js" + * module.exports = defineConfig({ + * projectConfig: { + * http: { + * authCors: process.env.AUTH_CORS + * } + * // ... + * }, + * // ... + * }) + * ``` + * + * If you’re adding the value directly within `medusa-config.js`, make sure to add an extra escaping `/` for every backslash in the pattern. For example: + * + * ```js title="medusa-config.js" + * module.exports = defineConfig({ + * projectConfig: { + * http: { + * authCors: "/http:\\/\\/localhost:700\\d+$/", + * } + * // ... + * }, + * // ... + * }) + * ``` + */ + authCors: string + /** + * + * Configure HTTP compression from the application layer. If you have access to the HTTP server, the recommended approach would be to enable it there. + * However, some platforms don't offer access to the HTTP layer and in those cases, this is a good alternative. + * + * Its value is an object that has the following properties: + * + * If you enable HTTP compression and you want to disable it for specific API Routes, you can pass in the request header `"x-no-compression": true`. + * + * @example + * ```js title="medusa-config.js" + * module.exports = defineConfig({ + * projectConfig: { + * http: { + * compression: { + * enabled: true, + * level: 6, + * memLevel: 8, + * threshold: 1024 + * } + * } + * // ... + * }, + * // ... + * }) + * ``` + */ + compression?: HttpCompressionOptions + /** + * The Medusa backend’s API Routes are protected by Cross-Origin Resource Sharing (CORS). So, only allowed URLs or URLs matching a specified pattern can send requests to the backend’s API Routes. + * + * `store_cors` is a string used to specify the accepted URLs or patterns for store API Routes. It can either be one accepted origin, or a comma-separated list of accepted origins. + * + * Every origin in that list must either be: + * + * 1. A URL. For example, `http://localhost:8000`. The URL must not end with a backslash; + * 2. Or a regular expression pattern that can match more than one origin. For example, `.example.com`. The regex pattern that the backend tests for is `^([\/~@;%#'])(.*?)\1([gimsuy]*)$`. + * + * @example + * Some example values of common use cases: + * + * ```bash + * # Allow different ports locally starting with 800 + * STORE_CORS=/http:\/\/localhost:800\d+$/ + * + * # Allow any origin ending with vercel.app. For example, storefront.vercel.app + * STORE_CORS=/vercel\.app$/ + * + * # Allow all HTTP requests + * STORE_CORS=/http:\/\/.+/ + * ``` + * + * Then, set the configuration in `medusa-config.js`: + * + * ```js title="medusa-config.js" + * module.exports = defineConfig({ + * projectConfig: { + * http: { + * storeCors: process.env.STORE_CORS, + * } + * // ... + * }, + * // ... + * }) + * ``` + * + * If you’re adding the value directly within `medusa-config.js`, make sure to add an extra escaping `/` for every backslash in the pattern. For example: + * + * ```js title="medusa-config.js" + * module.exports = defineConfig({ + * projectConfig: { + * http: { + * storeCors: "/vercel\\.app$/", + * } + * // ... + * }, + * // ... + * }) + * ``` + */ + storeCors: string + + /** + * The Medusa backend’s API Routes are protected by Cross-Origin Resource Sharing (CORS). So, only allowed URLs or URLs matching a specified pattern can send requests to the backend’s API Routes. + * + * `admin_cors` is a string used to specify the accepted URLs or patterns for admin API Routes. It can either be one accepted origin, or a comma-separated list of accepted origins. + * + * Every origin in that list must either be: + * + * 1. A URL. For example, `http://localhost:7001`. The URL must not end with a backslash; + * 2. Or a regular expression pattern that can match more than one origin. For example, `.example.com`. The regex pattern that the backend tests for is `^([\/~@;%#'])(.*?)\1([gimsuy]*)$`. + * + * @example + * Some example values of common use cases: + * + * ```bash + * # Allow different ports locally starting with 700 + * ADMIN_CORS=/http:\/\/localhost:700\d+$/ + * + * # Allow any origin ending with vercel.app. For example, admin.vercel.app + * ADMIN_CORS=/vercel\.app$/ + * + * # Allow all HTTP requests + * ADMIN_CORS=/http:\/\/.+/ + * ``` + * + * Then, set the configuration in `medusa-config.js`: + * + * ```js title="medusa-config.js" + * module.exports = defineConfig({ + * projectConfig: { + * http: { + * adminCors: process.env.ADMIN_CORS, + * } + * // ... + * }, + * // ... + * }) + * ``` + * + * If you’re adding the value directly within `medusa-config.js`, make sure to add an extra escaping `/` for every backslash in the pattern. For example: + * + * ```js title="medusa-config.js" + * module.exports = defineConfig({ + * projectConfig: { + * http: { + * adminCors: "/vercel\\.app$/", + * } + * // ... + * }, + * // ... + * }) + * ``` + */ + adminCors: string + + /** + * Optionally you can specify the supported authentication providers per actor type (such as user, customer, or any custom actors). + * For example, you only want to allow SSO logins for `users` to the admin, while you want to allow email/password logins for `customers` to the storefront. + * + * `authMethodsPerActor` is a a map where the actor type (eg. 'user') is the key, and an array of supported auth providers as the value. + * + * + * @example + * Some example values of common use cases: + * + * Then, set the configuration in `medusa-config.js`: + * + * ```js title="medusa-config.js" + * module.exports = defineConfig({ + * projectConfig: { + * http: { + * authMethodsPerActor: { + * user: ["email"], + * customer: ["emailpas", "google"] + * } + * } + * // ... + * }, + * // ... + * }) + * ``` + */ + authMethodsPerActor?: Record + } +} + +/** + * @interface + * + * The configurations for your Medusa backend are in `medusa-config.js` located in the root of your Medusa project. The configurations include database, modules, and plugin configurations, among other configurations. + * + * `medusa-config.js` exports an object having the following properties: + * + * - {@link ConfigModule.projectConfig | projectConfig} (required): An object that holds general configurations related to the Medusa backend, such as database or CORS configurations. + * - {@link ConfigModule.admin | admin}: An object that holds admin-related configurations. + * - {@link ConfigModule.plugins | plugins}: An array of plugin configurations that defines what plugins are installed and optionally specifies each of their configurations. + * - {@link ConfigModule.modules | modules}: An object that defines what modules are installed and optionally specifies each of their configurations. + * - {@link ConfigModule.featureFlags | featureFlags}: An object that enables or disables features guarded by a feature flag. + * + * For example: + * + * ```js title="medusa-config.js" + * module.exports = defineConfig({ + * projectConfig: { + * // ... + * }, + * admin: { + * // ... + * }, + * modules: { + * // ... + * }, + * featureFlags: { + * // ... + * } + * }) + * ``` + * + * --- + * + * ## Environment Variables + * + * It's highly recommended to store the values of configurations in environment variables, then reference them within `medusa-config.js`. + * + * During development, you can set your environment variables in the `.env` file at the root of your Medusa backend project. In production, + * setting the environment variables depends on the hosting provider. + * + * --- + */ +export type ConfigModule = { + /** + * This property holds essential configurations related to the Medusa backend, such as database and CORS configurations. + */ + projectConfig: ProjectConfigOptions + + /** + * Admin dashboard configurations. + * + * @example + * ```js title="medusa-config.js" + * module.exports = defineConfig({ + * admin: { + * backendUrl: process.env.MEDUSA_BACKEND_URL || + * "http://localhost:9000" + * }, + * // ... + * }) + * ``` + */ + admin?: AdminOptions + + /** + * On your Medusa backend, you can use [Plugins](https://docs.medusajs.com/development/plugins/overview) to add custom features or integrate third-party services. + * For example, installing a plugin to use Stripe as a payment processor. + * + * Aside from installing the plugin with NPM, you need to pass the plugin you installed into the `plugins` array defined in `medusa-config.js`. + * + * The items in the array can either be: + * + * - A string, which is the name of the plugin to add. You can pass a plugin as a string if it doesn’t require any configurations. + * - An object having the following properties: + * - `resolve`: The name of the plugin. + * - `options`: An object that includes the plugin’s options. These options vary for each plugin, and you should refer to the plugin’s documentation for available options. + * + * @example + * ```js title="medusa-config.js" + * module.exports = { + * plugins: [ + * `medusa-my-plugin-1`, + * { + * resolve: `medusa-my-plugin`, + * options: { + * apiKey: process.env.MY_API_KEY || + * `test`, + * }, + * }, + * // ... + * ], + * // ... + * } + * ``` + * + * @ignore + * + * @privateRemarks + * Added the `@\ignore` tag for now so it's not generated in the main docs until we figure out what to do with plugins + */ + plugins: ( + | { + resolve: string + options: Record + } + | string + )[] + + /** + * In Medusa, commerce and core logic are modularized to allow developers to extend or replace certain [modules](https://docs.medusajs.com/development/modules/overview) + * with custom implementations. + * + * Aside from installing the module with NPM, you must add it to the exported object in `medusa-config.js`. + * + * The keys of the `modules` configuration object refer to the module's registration name. Its value can be one of the following: + * + * 1. A boolean value indicating whether the module type is enabled. This is only supported for Medusa's commerce and architectural modules; + * 2. Or an object having the following properties: + * 1. `resolve`: a string indicating the path to the module relative to `src`, or the module's NPM package name. + * 2. `options`: (optional) an object indicating the options to pass to the module. + * 3. `definition`: (optional) an object of extra configurations, such as `isQueryable` used when a module has relationships. + * + * @example + * ```js title="medusa-config.js" + * module.exports = defineConfig({ + * modules: { + * helloModuleService: { + * resolve: "./modules/hello" + * } + * } + * // ... + * }) + * ``` + */ + modules?: Record< + string, + boolean | Partial + > + + /** + * Some features in the Medusa backend are guarded by a feature flag. This ensures constant shipping of new features while maintaining the engine’s stability. + * + * You can specify whether a feature should or shouldn’t be used in your backend by enabling its feature flag. Feature flags can be enabled through either environment + * variables or through this configuration exported in `medusa-config.js`. + * + * The `featureFlags` configuration is an object. Its properties are the names of the feature flags. Each property’s 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). + * + * @example + * ```js title="medusa-config.js" + * module.exports = defineConfig({ + * featureFlags: { + * product_categories: true, + * // ... + * } + * // ... + * }) + * ``` + * + * :::note + * + * After enabling a feature flag, make sure to run migrations as it may require making changes to the database. + * + * ::: + */ + featureFlags: Record +} + +export type PluginDetails = { + resolve: string + name: string + id: string + options: Record + version: string +} diff --git a/packages/framework/framework/src/index.ts b/packages/framework/framework/src/index.ts new file mode 100644 index 0000000000..a66208bada --- /dev/null +++ b/packages/framework/framework/src/index.ts @@ -0,0 +1,2 @@ +export * from "./config" +export * from "./logger" diff --git a/packages/framework/framework/src/logger/index.ts b/packages/framework/framework/src/logger/index.ts new file mode 100644 index 0000000000..f089361cf8 --- /dev/null +++ b/packages/framework/framework/src/logger/index.ts @@ -0,0 +1,3 @@ +import logger from "@medusajs/medusa-cli/dist/reporter" + +export { logger } diff --git a/packages/framework/framework/tsconfig.build.json b/packages/framework/framework/tsconfig.build.json new file mode 100644 index 0000000000..5c0452cf63 --- /dev/null +++ b/packages/framework/framework/tsconfig.build.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig", + "include": ["src"], + "exclude": [ + "dist", + "./src/**/__tests__", + "./src/**/__mocks__", + "./src/**/__fixtures__", + "node_modules" + ], +} diff --git a/packages/framework/framework/tsconfig.json b/packages/framework/framework/tsconfig.json new file mode 100644 index 0000000000..373e623fc7 --- /dev/null +++ b/packages/framework/framework/tsconfig.json @@ -0,0 +1,32 @@ +{ + "compilerOptions": { + "lib": ["ESNext"], + "target": "ESNext", + "outDir": "./dist", + "esModuleInterop": true, + "declarationMap": true, + "declaration": true, + "module": "commonjs", + "moduleResolution": "node", + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "sourceMap": false, + "noImplicitReturns": true, + "strictNullChecks": true, + "strictFunctionTypes": true, + "noImplicitThis": true, + "allowJs": true, + "skipLibCheck": true, + "downlevelIteration": true, + "baseUrl": ".", + "resolveJsonModule": true, + "paths": { + }, + }, + + "include": ["src"], + "exclude": [ + "dist", + "node_modules" + ] +} diff --git a/packages/framework/framework/tsconfig.spec.json b/packages/framework/framework/tsconfig.spec.json new file mode 100644 index 0000000000..48e47e8cbb --- /dev/null +++ b/packages/framework/framework/tsconfig.spec.json @@ -0,0 +1,8 @@ +{ + "extends": "./tsconfig.json", + "include": ["src", "integration-tests"], + "exclude": ["node_modules", "dist"], + "compilerOptions": { + "sourceMap": true + } +} diff --git a/packages/medusa/package.json b/packages/medusa/package.json index 1574ade71f..073e020b4d 100644 --- a/packages/medusa/package.json +++ b/packages/medusa/package.json @@ -45,6 +45,7 @@ "@inquirer/checkbox": "^2.3.11", "@medusajs/admin-sdk": "0.0.1", "@medusajs/core-flows": "^0.0.9", + "@medusajs/framework": "workspace:^", "@medusajs/link-modules": "^0.2.11", "@medusajs/medusa-cli": "^1.3.22", "@medusajs/modules-sdk": "^1.12.11", diff --git a/packages/medusa/src/loaders/config.ts b/packages/medusa/src/loaders/config.ts deleted file mode 100644 index 9950749df1..0000000000 --- a/packages/medusa/src/loaders/config.ts +++ /dev/null @@ -1,106 +0,0 @@ -import { ConfigModule } from "@medusajs/types" -import { getConfigFile, isDefined } from "@medusajs/utils" -import logger from "./logger" - -const isProduction = ["production", "prod"].includes(process.env.NODE_ENV || "") - -const errorHandler = isProduction - ? (msg: string): never => { - throw new Error(msg) - } - : console.log - -export const handleConfigError = (error: Error): void => { - logger.error(`Error in loading config: ${error.message}`) - if (error.stack) { - logger.error(error.stack) - } - process.exit(1) -} - -const buildHttpConfig = (projectConfig: ConfigModule["projectConfig"]) => { - const http = projectConfig.http ?? {} - - http.jwtExpiresIn = http?.jwtExpiresIn ?? "1d" - http.authCors = http.authCors ?? "" - http.storeCors = http.storeCors ?? "" - http.adminCors = http.adminCors ?? "" - - http.jwtSecret = http?.jwtSecret ?? process.env.JWT_SECRET - - if (!http.jwtSecret) { - errorHandler( - `[medusa-config] ⚠️ http.jwtSecret not found.${ - isProduction ? "" : "Using default 'supersecret'." - }` - ) - - http.jwtSecret = "supersecret" - } - - http.cookieSecret = - projectConfig.http?.cookieSecret ?? process.env.COOKIE_SECRET - - if (!http.cookieSecret) { - errorHandler( - `[medusa-config] ⚠️ http.cookieSecret not found.${ - isProduction ? "" : " Using default 'supersecret'." - }` - ) - - http.cookieSecret = "supersecret" - } - - return http -} - -const normalizeProjectConfig = ( - projectConfig: ConfigModule["projectConfig"] -) => { - if (!projectConfig?.redisUrl) { - console.log( - `[medusa-config] ⚠️ redisUrl not found. A fake redis instance will be used.` - ) - } - - projectConfig.http = buildHttpConfig(projectConfig) - - let workedMode = projectConfig?.workerMode - - if (!isDefined(workedMode)) { - const env = process.env.MEDUSA_WORKER_MODE - if (isDefined(env)) { - if (env === "shared" || env === "worker" || env === "server") { - workedMode = env - } - } else { - workedMode = "shared" - } - } - - return { - ...projectConfig, - workerMode: workedMode, - } -} - -export default (rootDirectory: string): ConfigModule => { - const { configModule, error } = getConfigFile( - rootDirectory, - `medusa-config` - ) - - if (error) { - handleConfigError(error) - } - - const projectConfig = normalizeProjectConfig(configModule.projectConfig) - - return { - projectConfig, - admin: configModule?.admin ?? {}, - modules: configModule.modules ?? {}, - featureFlags: configModule?.featureFlags ?? {}, - plugins: configModule?.plugins ?? [], - } -} diff --git a/packages/medusa/src/loaders/index.ts b/packages/medusa/src/loaders/index.ts index ad3217a729..51d581083b 100644 --- a/packages/medusa/src/loaders/index.ts +++ b/packages/medusa/src/loaders/index.ts @@ -12,7 +12,7 @@ import requestIp from "request-ip" import { v4 } from "uuid" import adminLoader from "./admin" import apiLoader from "./api" -import loadConfig from "./config" +import { configLoader, logger } from "@medusajs/framework" import expressLoader from "./express" import featureFlagsLoader from "./feature-flags" import { registerJobs } from "./helpers/register-jobs" @@ -20,7 +20,6 @@ import { registerWorkflows } from "./helpers/register-workflows" import { getResolvedPlugins } from "./helpers/resolve-plugins" import { resolvePluginsLinks } from "./helpers/resolve-plugins-links" import { SubscriberLoader } from "./helpers/subscribers" -import Logger from "./logger" import loadMedusaApp from "./medusa-app" import registerPgConnection from "./pg-connection" @@ -124,11 +123,11 @@ async function loadEntrypoints( export async function initializeContainer(rootDirectory: string) { const container = createMedusaContainer() - const configModule = loadConfig(rootDirectory) - const featureFlagRouter = featureFlagsLoader(configModule, Logger) + const configModule = configLoader(rootDirectory, "medusa-config.js") + const featureFlagRouter = featureFlagsLoader(configModule, logger) container.register({ - [ContainerRegistrationKeys.LOGGER]: asValue(Logger), + [ContainerRegistrationKeys.LOGGER]: asValue(logger), [ContainerRegistrationKeys.FEATURE_FLAG_ROUTER]: asValue(featureFlagRouter), [ContainerRegistrationKeys.CONFIG_MODULE]: asValue(configModule), [ContainerRegistrationKeys.REMOTE_QUERY]: asValue(null), diff --git a/yarn.lock b/yarn.lock index 832da5ad0f..0fe78584a8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4604,6 +4604,23 @@ __metadata: languageName: unknown linkType: soft +"@medusajs/framework@workspace:^, @medusajs/framework@workspace:packages/framework/framework": + version: 0.0.0-use.local + resolution: "@medusajs/framework@workspace:packages/framework/framework" + dependencies: + "@medusajs/medusa-cli": "workspace:^" + "@medusajs/types": "workspace:^" + "@medusajs/utils": "workspace:^" + awilix: ^8.0.0 + cross-env: ^7.0.3 + ioredis: ^5.2.5 + rimraf: ^3.0.2 + tsc-alias: ^1.8.6 + typescript: ^5.1.6 + vite: ^5.2.11 + languageName: unknown + linkType: soft + "@medusajs/fulfillment-manual@workspace:*, @medusajs/fulfillment-manual@workspace:^, @medusajs/fulfillment-manual@workspace:packages/modules/providers/fulfillment-manual": version: 0.0.0-use.local resolution: "@medusajs/fulfillment-manual@workspace:packages/modules/providers/fulfillment-manual" @@ -4743,7 +4760,7 @@ __metadata: languageName: unknown linkType: soft -"@medusajs/medusa-cli@^1.3.22, @medusajs/medusa-cli@workspace:packages/cli/medusa-cli": +"@medusajs/medusa-cli@^1.3.22, @medusajs/medusa-cli@workspace:^, @medusajs/medusa-cli@workspace:packages/cli/medusa-cli": version: 0.0.0-use.local resolution: "@medusajs/medusa-cli@workspace:packages/cli/medusa-cli" dependencies: @@ -4822,6 +4839,7 @@ __metadata: "@inquirer/checkbox": ^2.3.11 "@medusajs/admin-sdk": 0.0.1 "@medusajs/core-flows": ^0.0.9 + "@medusajs/framework": "workspace:^" "@medusajs/link-modules": ^0.2.11 "@medusajs/medusa-cli": ^1.3.22 "@medusajs/modules-sdk": ^1.12.11 @@ -22694,6 +22712,7 @@ __metadata: rimraf: ^3.0.2 typescript: ^5.1.6 peerDependencies: + "@medusajs/framework": "workspace:^" "@medusajs/medusa": ">1.19" "@medusajs/modules-sdk": ^1.12.10 axios: ^0.28.0 @@ -22701,6 +22720,8 @@ __metadata: get-port: ^5.1.0 pg-god: ^1.0.12 peerDependenciesMeta: + "@medusajs/framework": + optional: true "@medusajs/medusa": optional: true axios: