Skip to content

Commit

Permalink
feat: add support for loading admin extensions from the source (medus…
Browse files Browse the repository at this point in the history
  • Loading branch information
thetutlage authored Jan 16, 2025
1 parent 8792d0c commit c75678d
Show file tree
Hide file tree
Showing 6 changed files with 130 additions and 8 deletions.
7 changes: 7 additions & 0 deletions .changeset/neat-lamps-check.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@medusajs/medusa": patch
"@medusajs/framework": patch
"@medusajs/types": patch
---

feat: add support for loading admin extensions from the source
30 changes: 27 additions & 3 deletions packages/core/framework/src/build-tools/compiler.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { AdminOptions, ConfigModule, Logger } from "@medusajs/types"
import { getConfigFile } from "@medusajs/utils"
import { access, constants, copyFile, rm } from "fs/promises"
import path from "path"
import { getConfigFile } from "@medusajs/utils"
import type { AdminOptions, ConfigModule, Logger } from "@medusajs/types"
import { rm, access, constants, copyFile, writeFile } from "fs/promises"
import type tsStatic from "typescript"

/**
Expand All @@ -27,6 +27,7 @@ export class Compiler {
#adminSourceFolder: string
#pluginsDistFolder: string
#backendIgnoreFiles: string[]
#pluginOptionsPath: string
#adminOnlyDistFolder: string
#tsCompiler?: typeof tsStatic

Expand All @@ -37,6 +38,10 @@ export class Compiler {
this.#adminSourceFolder = path.join(this.#projectRoot, "src/admin")
this.#adminOnlyDistFolder = path.join(this.#projectRoot, ".medusa/admin")
this.#pluginsDistFolder = path.join(this.#projectRoot, ".medusa/server")
this.#pluginOptionsPath = path.join(
this.#projectRoot,
".medusa/server/medusa-plugin-options.json"
)
this.#backendIgnoreFiles = [
"integration-tests",
"test",
Expand Down Expand Up @@ -152,6 +157,24 @@ export class Compiler {
return { configFilePath, configModule }
}

/**
* Creates medusa-plugin-options.json file that contains some
* metadata related to the plugin, which could be helpful
* for MedusaJS loaders during development
*/
async #createPluginOptionsFile() {
await writeFile(
this.#pluginOptionsPath,
JSON.stringify(
{
srcDir: path.join(this.#projectRoot, "src"),
},
null,
2
)
)
}

/**
* Prints typescript diagnostic messages
*/
Expand Down Expand Up @@ -440,6 +463,7 @@ export class Compiler {
* a file has changed.
*/
async developPluginBackend(onFileChange?: () => void) {
await this.#createPluginOptionsFile()
const ts = await this.#loadTSCompiler()

/**
Expand Down
1 change: 1 addition & 0 deletions packages/core/types/src/common/config-module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -968,6 +968,7 @@ export type InputConfig = Partial<

export type PluginDetails = {
resolve: string
adminResolve: string
name: string
id: string
options: Record<string, unknown>
Expand Down
67 changes: 67 additions & 0 deletions packages/medusa/src/loaders/__tests__/get-resolved-plugins.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ describe("getResolvedPlugins | relative paths", () => {
expect(plugins).toEqual([
{
resolve: path.join(fs.basePath, "./plugins/dummy/.medusa/server/src"),
adminResolve: path.join(
fs.basePath,
"./plugins/dummy/.medusa/server/src/admin"
),
name: "my-dummy-plugin",
id: "my-dummy-plugin",
options: { apiKey: "asecret" },
Expand Down Expand Up @@ -71,6 +75,10 @@ describe("getResolvedPlugins | relative paths", () => {
expect(plugins).toEqual([
{
resolve: path.join(fs.basePath, "./plugins/dummy/.medusa/server/src"),
adminResolve: path.join(
fs.basePath,
"./plugins/dummy/.medusa/server/src/admin"
),
name: "my-dummy-plugin",
id: "my-dummy-plugin",
options: { apiKey: "asecret" },
Expand Down Expand Up @@ -108,6 +116,57 @@ describe("getResolvedPlugins | relative paths", () => {
`Unable to resolve plugin "./plugins/dummy". Make sure the plugin directory has a package.json file`
)
})

test("resolve admin source from medusa-plugin-options file", async () => {
await fs.createJson("plugins/dummy/package.json", {
name: "my-dummy-plugin",
version: "1.0.0",
})
await fs.create(
"plugins/dummy/.medusa/server/src/modules/blog/index.js",
``
)
await fs.createJson(
"plugins/dummy/.medusa/server/medusa-plugin-options.json",
{
srcDir: path.join(fs.basePath, "plugins/dummy/src"),
}
)

const plugins = await getResolvedPlugins(
fs.basePath,
defineConfig({
plugins: [
{
resolve: "./plugins/dummy",
options: {
apiKey: "asecret",
},
},
],
}),
false
)

expect(plugins).toEqual([
{
resolve: path.join(fs.basePath, "./plugins/dummy/.medusa/server/src"),
adminResolve: path.join(fs.basePath, "./plugins/dummy/src/admin"),
name: "my-dummy-plugin",
id: "my-dummy-plugin",
options: { apiKey: "asecret" },
version: "1.0.0",
modules: [
{
options: {
apiKey: "asecret",
},
resolve: "./plugins/dummy/.medusa/server/src/modules/blog",
},
],
},
])
})
})

describe("getResolvedPlugins | package reference", () => {
Expand Down Expand Up @@ -139,6 +198,10 @@ describe("getResolvedPlugins | package reference", () => {
fs.basePath,
"node_modules/@plugins/dummy/.medusa/server/src"
),
adminResolve: path.join(
fs.basePath,
"node_modules/@plugins/dummy/.medusa/server/src/admin"
),
name: "my-dummy-plugin",
id: "my-dummy-plugin",
options: { apiKey: "asecret" },
Expand Down Expand Up @@ -180,6 +243,10 @@ describe("getResolvedPlugins | package reference", () => {
fs.basePath,
"node_modules/@plugins/dummy/.medusa/server/src"
),
adminResolve: path.join(
fs.basePath,
"node_modules/@plugins/dummy/.medusa/server/src/admin"
),
name: "my-dummy-plugin",
id: "my-dummy-plugin",
options: { apiKey: "asecret" },
Expand Down
7 changes: 2 additions & 5 deletions packages/medusa/src/loaders/admin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,9 @@ export default async function adminLoader({
const { admin } = configModule

const sources: string[] = []

for (const plugin of plugins) {
const pluginSource = path.join(plugin.resolve, "admin")

if (fs.existsSync(pluginSource)) {
sources.push(pluginSource)
if (fs.existsSync(plugin.adminResolve)) {
sources.push(plugin.adminResolve)
}
}

Expand Down
26 changes: 26 additions & 0 deletions packages/medusa/src/loaders/helpers/resolve-plugins.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import { ConfigModule, PluginDetails } from "@medusajs/framework/types"

const MEDUSA_APP_SOURCE_PATH = "src"
const MEDUSA_PLUGIN_SOURCE_PATH = ".medusa/server/src"
const MEDUSA_PLUGIN_OPTIONS_FILE_PATH =
".medusa/server/medusa-plugin-options.json"
export const MEDUSA_PROJECT_NAME = "project-plugin"

function createPluginId(name: string): string {
Expand Down Expand Up @@ -41,6 +43,27 @@ async function resolvePluginPkgFile(
}
}

/**
* Reads the "medusa-plugin-options.json" file from the plugin root
* directory and returns its contents as an object.
*/
async function resolvePluginOptions(
pluginRootDir: string
): Promise<Record<string, any>> {
try {
const contents = await fs.readFile(
path.join(pluginRootDir, MEDUSA_PLUGIN_OPTIONS_FILE_PATH),
"utf-8"
)
return JSON.parse(contents)
} catch (error) {
if (error.code === "MODULE_NOT_FOUND" || error.code === "ENOENT") {
return {}
}
throw error
}
}

/**
* Finds the correct path for the plugin. If it is a local plugin it will be
* found in the plugins folder. Otherwise we will look for the plugin in the
Expand All @@ -60,6 +83,7 @@ async function resolvePlugin(
const name = pkgJSON.contents.name || pluginPath

const resolve = path.join(resolvedPath, MEDUSA_PLUGIN_SOURCE_PATH)
const pluginStaticOptions = await resolvePluginOptions(resolvedPath)
const modules = await readDir(path.join(resolve, "modules"), {
ignoreMissing: true,
})
Expand All @@ -71,6 +95,7 @@ async function resolvePlugin(
id: createPluginId(name),
options: pluginOptions,
version: pkgJSON.contents.version || "0.0.0",
adminResolve: path.join(pluginStaticOptions.srcDir ?? resolve, "admin"),
modules: modules.map((mod) => {
return {
resolve: `${pluginPath}/${MEDUSA_PLUGIN_SOURCE_PATH}/modules/${mod.name}`,
Expand Down Expand Up @@ -100,6 +125,7 @@ export async function getResolvedPlugins(
resolve: extensionDirectory,
name: MEDUSA_PROJECT_NAME,
id: createPluginId(MEDUSA_PROJECT_NAME),
adminResolve: path.join(extensionDirectory, "admin"),
options: configModule,
version: createFileContentHash(process.cwd(), `**`),
})
Expand Down

0 comments on commit c75678d

Please sign in to comment.