Fix/load medusa proj (#6639)

**What**
- Runs loadMedusaV2 when v2 flag is enabled.
- V2 loader loads only project-level framework features. E.g., project APIs, subscribers, jobs, and workflows.
- No plugin support yet.
This commit is contained in:
Sebastian Rindom
2024-03-14 13:38:57 +01:00
committed by GitHub
parent 04a532e5ef
commit 3dd55efd15
4 changed files with 260 additions and 3 deletions

View File

@@ -1,3 +1,4 @@
export * from "./auth"
export * from "./api-key" export * from "./api-key"
export * from "./customer" export * from "./customer"
export * from "./customer-group" export * from "./customer-group"

View File

@@ -35,6 +35,7 @@ import searchIndexLoader from "./search-index"
import servicesLoader from "./services" import servicesLoader from "./services"
import strategiesLoader from "./strategies" import strategiesLoader from "./strategies"
import subscribersLoader from "./subscribers" import subscribersLoader from "./subscribers"
import medusaProjectApisLoader from "./load-medusa-project-apis"
type Options = { type Options = {
directory: string directory: string
@@ -81,7 +82,12 @@ async function loadLegacyModulesEntities(configModules, container) {
} }
} }
async function loadMedusaV2({ configModule, featureFlagRouter, expressApp }) { async function loadMedusaV2({
rootDirectory,
configModule,
featureFlagRouter,
expressApp,
}) {
const container = createMedusaContainer() const container = createMedusaContainer()
// Add additional information to context of request // Add additional information to context of request
@@ -124,6 +130,14 @@ async function loadMedusaV2({ configModule, featureFlagRouter, expressApp }) {
featureFlagRouter, featureFlagRouter,
}) })
medusaProjectApisLoader({
rootDirectory,
container,
app: expressApp,
configModule,
activityId: "medusa-project-apis",
})
return { return {
container, container,
app: expressApp, app: expressApp,
@@ -146,7 +160,12 @@ export default async ({
track("FEATURE_FLAGS_LOADED") track("FEATURE_FLAGS_LOADED")
if (featureFlagRouter.isFeatureEnabled(MedusaV2Flag.key)) { if (featureFlagRouter.isFeatureEnabled(MedusaV2Flag.key)) {
return await loadMedusaV2({ configModule, featureFlagRouter, expressApp }) return await loadMedusaV2({
rootDirectory,
configModule,
featureFlagRouter,
expressApp,
})
} }
const container = createMedusaContainer() const container = createMedusaContainer()

View File

@@ -0,0 +1,200 @@
import { promiseAll } from "@medusajs/utils"
import { Express } from "express"
import glob from "glob"
import _ from "lodash"
import { trackInstallation } from "medusa-telemetry"
import { EOL } from "os"
import path from "path"
import { ConfigModule, Logger, MedusaContainer } from "../types/global"
import ScheduledJobsLoader from "./helpers/jobs"
import { RoutesLoader } from "./helpers/routing"
import { SubscriberLoader } from "./helpers/subscribers"
import logger from "./logger"
type Options = {
rootDirectory: string
container: MedusaContainer
configModule: ConfigModule
app: Express
activityId: string
}
type PluginDetails = {
resolve: string
name: string
id: string
options: Record<string, unknown>
version: string
}
export const MEDUSA_PROJECT_NAME = "project-plugin"
/**
* Registers all services in the services directory
*/
export default async ({
rootDirectory,
container,
app,
configModule,
activityId,
}: Options): Promise<void> => {
const resolved = getResolvedPlugins(rootDirectory, configModule) || []
await promiseAll(
resolved.map(async (pluginDetails) => {
await registerApi(pluginDetails, app, container, configModule, activityId)
await registerSubscribers(pluginDetails, container, activityId)
await registerWorkflows(pluginDetails)
})
)
await promiseAll(
resolved.map(async (pluginDetails) => runLoaders(pluginDetails, container))
)
if (configModule.projectConfig.redis_url) {
await Promise.all(
resolved.map(async (pluginDetails) => {
await registerScheduledJobs(pluginDetails, container)
})
)
} else {
logger.warn(
"You don't have Redis configured. Scheduled jobs will not be enabled."
)
}
resolved.forEach((plugin) => trackInstallation(plugin.name, "plugin"))
}
function getResolvedPlugins(
rootDirectory: string,
configModule: ConfigModule,
extensionDirectoryPath = "dist"
): undefined | PluginDetails[] {
const extensionDirectory = path.join(rootDirectory, extensionDirectoryPath)
return [
{
resolve: extensionDirectory,
name: MEDUSA_PROJECT_NAME,
id: createPluginId(MEDUSA_PROJECT_NAME),
options: configModule,
version: createFileContentHash(process.cwd(), `**`),
},
]
}
async function runLoaders(
pluginDetails: PluginDetails,
container: MedusaContainer
): Promise<void> {
const loaderFiles = glob.sync(
`${pluginDetails.resolve}/loaders/[!__]*.js`,
{}
)
await promiseAll(
loaderFiles.map(async (loader) => {
try {
const module = require(loader).default
if (typeof module === "function") {
await module(container, pluginDetails.options)
}
} catch (err) {
const logger = container.resolve<Logger>("logger")
logger.warn(`Running loader failed: ${err.message}`)
return Promise.resolve()
}
})
)
}
async function registerScheduledJobs(
pluginDetails: PluginDetails,
container: MedusaContainer
): Promise<void> {
await new ScheduledJobsLoader(
path.join(pluginDetails.resolve, "jobs"),
container,
pluginDetails.options
).load()
}
/**
* Registers the plugin's api routes.
*/
async function registerApi(
pluginDetails: PluginDetails,
app: Express,
container: MedusaContainer,
configmodule: ConfigModule,
activityId: string
): Promise<Express> {
const logger = container.resolve<Logger>("logger")
const projectName =
pluginDetails.name === MEDUSA_PROJECT_NAME
? "your Medusa project"
: `${pluginDetails.name}`
logger.progress(activityId, `Registering custom endpoints for ${projectName}`)
try {
/**
* Register the plugin's API routes using the file based routing.
*/
await new RoutesLoader({
app,
rootDir: path.join(pluginDetails.resolve, "api"),
activityId: activityId,
configModule: configmodule,
}).load()
} catch (err) {
logger.warn(
`An error occurred while registering API Routes in ${projectName}${
err.stack ? EOL + err.stack : ""
}`
)
}
return app
}
/**
* Registers a plugin's subscribers at the right location in our container.
* Subscribers are registered directly in the container.
* @param {object} pluginDetails - the plugin details including plugin options,
* version, id, resolved path, etc. See resolvePlugin
* @param {object} container - the container where the services will be
* registered
* @return {void}
*/
async function registerSubscribers(
pluginDetails: PluginDetails,
container: MedusaContainer,
activityId: string
): Promise<void> {
await new SubscriberLoader(
path.join(pluginDetails.resolve, "subscribers"),
container,
pluginDetails.options,
activityId
).load()
}
/**
* import files from the workflows directory to run the registration of the wofklows
* @param pluginDetails
*/
async function registerWorkflows(pluginDetails: PluginDetails): Promise<void> {
const files = glob.sync(`${pluginDetails.resolve}/workflows/*.js`, {})
await Promise.all(files.map(async (file) => import(file)))
}
// TODO: Create unique id for each plugin
function createPluginId(name: string): string {
return name
}
function createFileContentHash(path, files): string {
return path + files
}

View File

@@ -1,6 +1,7 @@
import { import {
ExternalModuleDeclaration, ExternalModuleDeclaration,
InternalModuleDeclaration, InternalModuleDeclaration,
MODULE_RESOURCE_TYPE,
MODULE_SCOPE, MODULE_SCOPE,
ModuleDefinition, ModuleDefinition,
ModuleExports, ModuleExports,
@@ -25,7 +26,11 @@ export const registerMedusaModule = (
const modDefinition = definition ?? ModulesDefinition[moduleKey] const modDefinition = definition ?? ModulesDefinition[moduleKey]
if (modDefinition === undefined) { if (modDefinition === undefined) {
throw new Error(`Module: ${moduleKey} is not defined.`) moduleResolutions[moduleKey] = getCustomModuleResolution(
moduleKey,
moduleDeclaration as InternalModuleDeclaration
)
return moduleResolutions
} }
const modDeclaration = const modDeclaration =
@@ -53,6 +58,38 @@ export const registerMedusaModule = (
return moduleResolutions return moduleResolutions
} }
function getCustomModuleResolution(
key: string,
moduleConfig: InternalModuleDeclaration | string
): ModuleResolution {
const isString = typeof moduleConfig === "string"
const resolutionPath = resolveCwd(
isString ? moduleConfig : (moduleConfig.resolve as string)
)
return {
resolutionPath,
definition: {
key,
label: `Custom: ${key}`,
isRequired: false,
defaultPackage: "",
dependencies: [],
registrationName: key,
defaultModuleDeclaration: {
resources: MODULE_RESOURCE_TYPE.SHARED,
scope: MODULE_SCOPE.INTERNAL,
},
},
moduleDeclaration: {
resources: MODULE_RESOURCE_TYPE.SHARED,
scope: MODULE_SCOPE.INTERNAL,
},
dependencies: [],
options: {},
}
}
export const registerMedusaLinkModule = ( export const registerMedusaLinkModule = (
definition: ModuleDefinition, definition: ModuleDefinition,
moduleDeclaration: Partial<InternalModuleDeclaration>, moduleDeclaration: Partial<InternalModuleDeclaration>,