feat(medusa): Prevent build command from throwing on missing config (#14540)

**What**
Prevent the build command from failing on mising config
This commit is contained in:
Adrien de Peretti
2026-01-15 10:00:15 +01:00
committed by GitHub
parent 250b6fdf22
commit 8890f28470
6 changed files with 83 additions and 17 deletions

View File

@@ -38,4 +38,35 @@ describe("configLoader", () => {
expect(configModule.projectConfig.databaseName).toBe("foo")
expect(configModule.projectConfig.workerMode).toBe("worker")
})
it("should load config without throwing errors when throwOnError is false", async () => {
await configLoader(entryDirectory, "medusa-config", {
throwOnError: false,
})
const configModule = container.resolve(
ContainerRegistrationKeys.CONFIG_MODULE
)
expect(configModule).toBeDefined()
expect(configModule.projectConfig).toBeDefined()
})
it("should pass throwOnError option through to buildHttpConfig", async () => {
// When throwOnError is false, missing jwtSecret and cookieSecret should not cause errors
await configLoader(entryDirectory, "medusa-config-2", {
throwOnError: false,
})
const configModule = container.resolve(
ContainerRegistrationKeys.CONFIG_MODULE
)
expect(configModule).toBeDefined()
expect(configModule.projectConfig.databaseName).toBe("foo")
// http config should still be built with defaults even without throwing errors
expect(configModule.projectConfig.http).toBeDefined()
expect(configModule.projectConfig.http.jwtSecret).toBe("supersecret")
expect(configModule.projectConfig.http.cookieSecret).toBe("supersecret")
})
})

View File

@@ -70,8 +70,12 @@ export class ConfigManager {
* @protected
*/
protected buildHttpConfig(
projectConfig: Partial<ConfigModule["projectConfig"]>
projectConfig: Partial<ConfigModule["projectConfig"]>,
options?: {
throwOnError?: boolean
}
): ConfigModule["projectConfig"]["http"] {
const { throwOnError = true } = options ?? {}
const http = (projectConfig.http ??
{}) as ConfigModule["projectConfig"]["http"]
@@ -87,6 +91,7 @@ export class ConfigManager {
http.jwtPublicKey = http?.jwtPublicKey ?? process.env.JWT_PUBLIC_KEY
if (
throwOnError &&
http?.jwtPublicKey &&
((http.jwtVerifyOptions && !http.jwtVerifyOptions.algorithms?.length) ||
(http.jwtOptions && !http.jwtOptions.algorithm))
@@ -97,11 +102,13 @@ export class ConfigManager {
}
if (!http.jwtSecret) {
this.rejectErrors(
`http.jwtSecret not found.${
this.#isProduction ? "" : "Using default 'supersecret'."
}`
)
if (throwOnError) {
this.rejectErrors(
`http.jwtSecret not found.${
this.#isProduction ? "" : "Using default 'supersecret'."
}`
)
}
http.jwtSecret = "supersecret"
}
@@ -110,11 +117,13 @@ export class ConfigManager {
process.env.COOKIE_SECRET)!
if (!http.cookieSecret) {
this.rejectErrors(
`http.cookieSecret not found.${
this.#isProduction ? "" : " Using default 'supersecret'."
}`
)
if (throwOnError) {
this.rejectErrors(
`http.cookieSecret not found.${
this.#isProduction ? "" : " Using default 'supersecret'."
}`
)
}
http.cookieSecret = "supersecret"
}
@@ -128,7 +137,10 @@ export class ConfigManager {
* @protected
*/
protected normalizeProjectConfig(
config: Partial<ConfigModule>
config: Partial<ConfigModule>,
options?: {
throwOnError?: boolean
}
): ConfigModule["projectConfig"] {
const projConfig = config?.projectConfig ?? {}
const outputConfig = deepCopy(projConfig) as ConfigModule["projectConfig"]
@@ -140,7 +152,9 @@ export class ConfigManager {
)
}
outputConfig.http = this.buildHttpConfig(projConfig)
outputConfig.http = this.buildHttpConfig(projConfig, {
throwOnError: options?.throwOnError,
})
let workerMode = outputConfig?.workerMode!
@@ -168,13 +182,17 @@ export class ConfigManager {
loadConfig({
projectConfig = {},
baseDir,
throwOnError = true,
}: {
projectConfig: Partial<ConfigModule>
baseDir: string
throwOnError?: boolean
}): ConfigModule {
this.#baseDir = baseDir
const normalizedProjectConfig = this.normalizeProjectConfig(projectConfig)
const normalizedProjectConfig = this.normalizeProjectConfig(projectConfig, {
throwOnError,
})
this.#config = {
projectConfig: normalizedProjectConfig,

View File

@@ -25,22 +25,29 @@ container.register(
*
* @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
* @param options.throwOnError When false, missing config files and validation errors won't throw.
* Useful for build/compile commands. Defaults to true.
*/
export async function configLoader(
entryDirectory: string,
configFileName: string = "medusa-config"
configFileName: string = "medusa-config",
options?: {
throwOnError?: boolean
}
): Promise<ConfigModule> {
const { throwOnError = true } = options ?? {}
const config = await getConfigFile<ConfigModule>(
entryDirectory,
configFileName
)
if (config.error) {
if (config.error && throwOnError) {
handleConfigError(config.error)
}
return configManager.loadConfig({
projectConfig: config.configModule!,
baseDir: entryDirectory,
throwOnError,
})
}