Skip to content

Commit

Permalink
Fix/load medusa proj (#6639)
Browse files Browse the repository at this point in the history
**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.
  • Loading branch information
srindom authored Mar 14, 2024
1 parent 04a532e commit 3dd55ef
Show file tree
Hide file tree
Showing 4 changed files with 260 additions and 3 deletions.
1 change: 1 addition & 0 deletions packages/core-flows/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from "./auth"
export * from "./api-key"
export * from "./customer"
export * from "./customer-group"
Expand Down
23 changes: 21 additions & 2 deletions packages/medusa/src/loaders/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import searchIndexLoader from "./search-index"
import servicesLoader from "./services"
import strategiesLoader from "./strategies"
import subscribersLoader from "./subscribers"
import medusaProjectApisLoader from "./load-medusa-project-apis"

type Options = {
directory: string
Expand Down Expand Up @@ -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()

// Add additional information to context of request
Expand Down Expand Up @@ -124,6 +130,14 @@ async function loadMedusaV2({ configModule, featureFlagRouter, expressApp }) {
featureFlagRouter,
})

medusaProjectApisLoader({
rootDirectory,
container,
app: expressApp,
configModule,
activityId: "medusa-project-apis",
})

return {
container,
app: expressApp,
Expand All @@ -146,7 +160,12 @@ export default async ({
track("FEATURE_FLAGS_LOADED")

if (featureFlagRouter.isFeatureEnabled(MedusaV2Flag.key)) {
return await loadMedusaV2({ configModule, featureFlagRouter, expressApp })
return await loadMedusaV2({
rootDirectory,
configModule,
featureFlagRouter,
expressApp,
})
}

const container = createMedusaContainer()
Expand Down
200 changes: 200 additions & 0 deletions packages/medusa/src/loaders/load-medusa-project-apis.ts
Original file line number Diff line number Diff line change
@@ -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
}
39 changes: 38 additions & 1 deletion packages/modules-sdk/src/loaders/register-modules.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {
ExternalModuleDeclaration,
InternalModuleDeclaration,
MODULE_RESOURCE_TYPE,
MODULE_SCOPE,
ModuleDefinition,
ModuleExports,
Expand All @@ -25,7 +26,11 @@ export const registerMedusaModule = (
const modDefinition = definition ?? ModulesDefinition[moduleKey]

if (modDefinition === undefined) {
throw new Error(`Module: ${moduleKey} is not defined.`)
moduleResolutions[moduleKey] = getCustomModuleResolution(
moduleKey,
moduleDeclaration as InternalModuleDeclaration
)
return moduleResolutions
}

const modDeclaration =
Expand Down Expand Up @@ -53,6 +58,38 @@ export const registerMedusaModule = (
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 = (
definition: ModuleDefinition,
moduleDeclaration: Partial<InternalModuleDeclaration>,
Expand Down

0 comments on commit 3dd55ef

Please sign in to comment.