feat: add support for loading admin extensions from the source (#10975)
This commit is contained in:
7
.changeset/neat-lamps-check.md
Normal file
7
.changeset/neat-lamps-check.md
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
---
|
||||||
|
"@medusajs/medusa": patch
|
||||||
|
"@medusajs/framework": patch
|
||||||
|
"@medusajs/types": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
feat: add support for loading admin extensions from the source
|
||||||
@@ -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 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"
|
import type tsStatic from "typescript"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -27,6 +27,7 @@ export class Compiler {
|
|||||||
#adminSourceFolder: string
|
#adminSourceFolder: string
|
||||||
#pluginsDistFolder: string
|
#pluginsDistFolder: string
|
||||||
#backendIgnoreFiles: string[]
|
#backendIgnoreFiles: string[]
|
||||||
|
#pluginOptionsPath: string
|
||||||
#adminOnlyDistFolder: string
|
#adminOnlyDistFolder: string
|
||||||
#tsCompiler?: typeof tsStatic
|
#tsCompiler?: typeof tsStatic
|
||||||
|
|
||||||
@@ -37,6 +38,10 @@ export class Compiler {
|
|||||||
this.#adminSourceFolder = path.join(this.#projectRoot, "src/admin")
|
this.#adminSourceFolder = path.join(this.#projectRoot, "src/admin")
|
||||||
this.#adminOnlyDistFolder = path.join(this.#projectRoot, ".medusa/admin")
|
this.#adminOnlyDistFolder = path.join(this.#projectRoot, ".medusa/admin")
|
||||||
this.#pluginsDistFolder = path.join(this.#projectRoot, ".medusa/server")
|
this.#pluginsDistFolder = path.join(this.#projectRoot, ".medusa/server")
|
||||||
|
this.#pluginOptionsPath = path.join(
|
||||||
|
this.#projectRoot,
|
||||||
|
".medusa/server/medusa-plugin-options.json"
|
||||||
|
)
|
||||||
this.#backendIgnoreFiles = [
|
this.#backendIgnoreFiles = [
|
||||||
"integration-tests",
|
"integration-tests",
|
||||||
"test",
|
"test",
|
||||||
@@ -152,6 +157,24 @@ export class Compiler {
|
|||||||
return { configFilePath, configModule }
|
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
|
* Prints typescript diagnostic messages
|
||||||
*/
|
*/
|
||||||
@@ -440,6 +463,7 @@ export class Compiler {
|
|||||||
* a file has changed.
|
* a file has changed.
|
||||||
*/
|
*/
|
||||||
async developPluginBackend(onFileChange?: () => void) {
|
async developPluginBackend(onFileChange?: () => void) {
|
||||||
|
await this.#createPluginOptionsFile()
|
||||||
const ts = await this.#loadTSCompiler()
|
const ts = await this.#loadTSCompiler()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -968,6 +968,7 @@ export type InputConfig = Partial<
|
|||||||
|
|
||||||
export type PluginDetails = {
|
export type PluginDetails = {
|
||||||
resolve: string
|
resolve: string
|
||||||
|
adminResolve: string
|
||||||
name: string
|
name: string
|
||||||
id: string
|
id: string
|
||||||
options: Record<string, unknown>
|
options: Record<string, unknown>
|
||||||
|
|||||||
@@ -34,6 +34,10 @@ describe("getResolvedPlugins | relative paths", () => {
|
|||||||
expect(plugins).toEqual([
|
expect(plugins).toEqual([
|
||||||
{
|
{
|
||||||
resolve: path.join(fs.basePath, "./plugins/dummy/.medusa/server/src"),
|
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",
|
name: "my-dummy-plugin",
|
||||||
id: "my-dummy-plugin",
|
id: "my-dummy-plugin",
|
||||||
options: { apiKey: "asecret" },
|
options: { apiKey: "asecret" },
|
||||||
@@ -71,6 +75,10 @@ describe("getResolvedPlugins | relative paths", () => {
|
|||||||
expect(plugins).toEqual([
|
expect(plugins).toEqual([
|
||||||
{
|
{
|
||||||
resolve: path.join(fs.basePath, "./plugins/dummy/.medusa/server/src"),
|
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",
|
name: "my-dummy-plugin",
|
||||||
id: "my-dummy-plugin",
|
id: "my-dummy-plugin",
|
||||||
options: { apiKey: "asecret" },
|
options: { apiKey: "asecret" },
|
||||||
@@ -108,6 +116,57 @@ describe("getResolvedPlugins | relative paths", () => {
|
|||||||
`Unable to resolve plugin "./plugins/dummy". Make sure the plugin directory has a package.json file`
|
`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", () => {
|
describe("getResolvedPlugins | package reference", () => {
|
||||||
@@ -139,6 +198,10 @@ describe("getResolvedPlugins | package reference", () => {
|
|||||||
fs.basePath,
|
fs.basePath,
|
||||||
"node_modules/@plugins/dummy/.medusa/server/src"
|
"node_modules/@plugins/dummy/.medusa/server/src"
|
||||||
),
|
),
|
||||||
|
adminResolve: path.join(
|
||||||
|
fs.basePath,
|
||||||
|
"node_modules/@plugins/dummy/.medusa/server/src/admin"
|
||||||
|
),
|
||||||
name: "my-dummy-plugin",
|
name: "my-dummy-plugin",
|
||||||
id: "my-dummy-plugin",
|
id: "my-dummy-plugin",
|
||||||
options: { apiKey: "asecret" },
|
options: { apiKey: "asecret" },
|
||||||
@@ -180,6 +243,10 @@ describe("getResolvedPlugins | package reference", () => {
|
|||||||
fs.basePath,
|
fs.basePath,
|
||||||
"node_modules/@plugins/dummy/.medusa/server/src"
|
"node_modules/@plugins/dummy/.medusa/server/src"
|
||||||
),
|
),
|
||||||
|
adminResolve: path.join(
|
||||||
|
fs.basePath,
|
||||||
|
"node_modules/@plugins/dummy/.medusa/server/src/admin"
|
||||||
|
),
|
||||||
name: "my-dummy-plugin",
|
name: "my-dummy-plugin",
|
||||||
id: "my-dummy-plugin",
|
id: "my-dummy-plugin",
|
||||||
options: { apiKey: "asecret" },
|
options: { apiKey: "asecret" },
|
||||||
|
|||||||
@@ -33,12 +33,9 @@ export default async function adminLoader({
|
|||||||
const { admin } = configModule
|
const { admin } = configModule
|
||||||
|
|
||||||
const sources: string[] = []
|
const sources: string[] = []
|
||||||
|
|
||||||
for (const plugin of plugins) {
|
for (const plugin of plugins) {
|
||||||
const pluginSource = path.join(plugin.resolve, "admin")
|
if (fs.existsSync(plugin.adminResolve)) {
|
||||||
|
sources.push(plugin.adminResolve)
|
||||||
if (fs.existsSync(pluginSource)) {
|
|
||||||
sources.push(pluginSource)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ import { ConfigModule, PluginDetails } from "@medusajs/framework/types"
|
|||||||
|
|
||||||
const MEDUSA_APP_SOURCE_PATH = "src"
|
const MEDUSA_APP_SOURCE_PATH = "src"
|
||||||
const MEDUSA_PLUGIN_SOURCE_PATH = ".medusa/server/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"
|
export const MEDUSA_PROJECT_NAME = "project-plugin"
|
||||||
|
|
||||||
function createPluginId(name: string): string {
|
function createPluginId(name: string): string {
|
||||||
@@ -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
|
* 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
|
* found in the plugins folder. Otherwise we will look for the plugin in the
|
||||||
@@ -60,6 +83,7 @@ async function resolvePlugin(
|
|||||||
const name = pkgJSON.contents.name || pluginPath
|
const name = pkgJSON.contents.name || pluginPath
|
||||||
|
|
||||||
const resolve = path.join(resolvedPath, MEDUSA_PLUGIN_SOURCE_PATH)
|
const resolve = path.join(resolvedPath, MEDUSA_PLUGIN_SOURCE_PATH)
|
||||||
|
const pluginStaticOptions = await resolvePluginOptions(resolvedPath)
|
||||||
const modules = await readDir(path.join(resolve, "modules"), {
|
const modules = await readDir(path.join(resolve, "modules"), {
|
||||||
ignoreMissing: true,
|
ignoreMissing: true,
|
||||||
})
|
})
|
||||||
@@ -71,6 +95,7 @@ async function resolvePlugin(
|
|||||||
id: createPluginId(name),
|
id: createPluginId(name),
|
||||||
options: pluginOptions,
|
options: pluginOptions,
|
||||||
version: pkgJSON.contents.version || "0.0.0",
|
version: pkgJSON.contents.version || "0.0.0",
|
||||||
|
adminResolve: path.join(pluginStaticOptions.srcDir ?? resolve, "admin"),
|
||||||
modules: modules.map((mod) => {
|
modules: modules.map((mod) => {
|
||||||
return {
|
return {
|
||||||
resolve: `${pluginPath}/${MEDUSA_PLUGIN_SOURCE_PATH}/modules/${mod.name}`,
|
resolve: `${pluginPath}/${MEDUSA_PLUGIN_SOURCE_PATH}/modules/${mod.name}`,
|
||||||
@@ -100,6 +125,7 @@ export async function getResolvedPlugins(
|
|||||||
resolve: extensionDirectory,
|
resolve: extensionDirectory,
|
||||||
name: MEDUSA_PROJECT_NAME,
|
name: MEDUSA_PROJECT_NAME,
|
||||||
id: createPluginId(MEDUSA_PROJECT_NAME),
|
id: createPluginId(MEDUSA_PROJECT_NAME),
|
||||||
|
adminResolve: path.join(extensionDirectory, "admin"),
|
||||||
options: configModule,
|
options: configModule,
|
||||||
version: createFileContentHash(process.cwd(), `**`),
|
version: createFileContentHash(process.cwd(), `**`),
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user