feat(admin-bundler,admin-vite-plugin,medusa): Add support for loading Admin Extensions from plugins (#10869)
Should not be merged before https://github.com/medusajs/medusa/pull/10895 **What** - Introduces a new `plugin` command to `admin-bundler`, currently not used anywhere but will be called from `medusa build:plugin` - Discovers plugins with extensions and add passes the to `admin-vite-plugin`. - Updates `admin-vite-plugin` so its able to read built admin extensions. Resolves CMRC-830, CMRC-839
This commit is contained in:
@@ -0,0 +1,7 @@
|
|||||||
|
---
|
||||||
|
"@medusajs/admin-vite-plugin": patch
|
||||||
|
"@medusajs/admin-bundler": patch
|
||||||
|
"@medusajs/medusa": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
feat(admin-bundler,admin-vite-plugin): Support loading loading admin extensions from plugins.
|
||||||
@@ -32,6 +32,7 @@
|
|||||||
"@vitejs/plugin-react": "^4.2.1",
|
"@vitejs/plugin-react": "^4.2.1",
|
||||||
"autoprefixer": "^10.4.16",
|
"autoprefixer": "^10.4.16",
|
||||||
"compression": "^1.7.4",
|
"compression": "^1.7.4",
|
||||||
|
"glob": "^11.0.0",
|
||||||
"postcss": "^8.4.32",
|
"postcss": "^8.4.32",
|
||||||
"tailwindcss": "^3.3.6",
|
"tailwindcss": "^3.3.6",
|
||||||
"vite": "^5.2.11"
|
"vite": "^5.2.11"
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import { VIRTUAL_MODULES } from "@medusajs/admin-shared"
|
|||||||
import path from "path"
|
import path from "path"
|
||||||
import { Config } from "tailwindcss"
|
import { Config } from "tailwindcss"
|
||||||
import type { InlineConfig } from "vite"
|
import type { InlineConfig } from "vite"
|
||||||
|
|
||||||
import { BundlerOptions } from "../types"
|
import { BundlerOptions } from "../types"
|
||||||
|
|
||||||
export async function getViteConfig(
|
export async function getViteConfig(
|
||||||
|
|||||||
@@ -0,0 +1,57 @@
|
|||||||
|
import { readFileSync } from "fs"
|
||||||
|
import { glob } from "glob"
|
||||||
|
import path from "path"
|
||||||
|
import { UserConfig } from "vite"
|
||||||
|
|
||||||
|
export async function plugin() {
|
||||||
|
const vite = await import("vite")
|
||||||
|
const entries = await glob("src/admin/**/*.{ts,tsx,js,jsx}")
|
||||||
|
|
||||||
|
const entryPoints = entries.reduce((acc, entry) => {
|
||||||
|
// Convert src/admin/routes/brands/page.tsx -> admin/routes/brands/page
|
||||||
|
const outPath = entry
|
||||||
|
.replace(/^src\//, "")
|
||||||
|
.replace(/\.(ts|tsx|js|jsx)$/, "")
|
||||||
|
|
||||||
|
acc[outPath] = path.resolve(process.cwd(), entry)
|
||||||
|
return acc
|
||||||
|
}, {} as Record<string, string>)
|
||||||
|
|
||||||
|
const pkg = JSON.parse(
|
||||||
|
readFileSync(path.resolve(process.cwd(), "package.json"), "utf-8")
|
||||||
|
)
|
||||||
|
const external = new Set([
|
||||||
|
...Object.keys(pkg.dependencies || {}),
|
||||||
|
...Object.keys(pkg.peerDependencies || {}),
|
||||||
|
"react",
|
||||||
|
"react-dom",
|
||||||
|
"react/jsx-runtime",
|
||||||
|
"react-router-dom",
|
||||||
|
"@medusajs/admin-sdk",
|
||||||
|
])
|
||||||
|
|
||||||
|
const pluginConfig: UserConfig = {
|
||||||
|
build: {
|
||||||
|
lib: {
|
||||||
|
entry: entryPoints,
|
||||||
|
formats: ["es"],
|
||||||
|
},
|
||||||
|
minify: false,
|
||||||
|
outDir: path.resolve(process.cwd(), "dist"),
|
||||||
|
rollupOptions: {
|
||||||
|
external: [...external],
|
||||||
|
output: {
|
||||||
|
globals: {
|
||||||
|
react: "React",
|
||||||
|
"react-dom": "React-dom",
|
||||||
|
"react/jsx-runtime": "react/jsx-runtime",
|
||||||
|
},
|
||||||
|
preserveModules: true,
|
||||||
|
entryFileNames: `[name].js`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
await vite.build(pluginConfig)
|
||||||
|
}
|
||||||
@@ -17,11 +17,13 @@ import {
|
|||||||
isTemplateLiteral,
|
isTemplateLiteral,
|
||||||
isVariableDeclaration,
|
isVariableDeclaration,
|
||||||
isVariableDeclarator,
|
isVariableDeclarator,
|
||||||
|
Node,
|
||||||
ObjectExpression,
|
ObjectExpression,
|
||||||
ObjectMethod,
|
ObjectMethod,
|
||||||
ObjectProperty,
|
ObjectProperty,
|
||||||
SpreadElement,
|
SpreadElement,
|
||||||
StringLiteral,
|
StringLiteral,
|
||||||
|
VariableDeclarator,
|
||||||
} from "@babel/types"
|
} from "@babel/types"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -58,6 +60,7 @@ export type {
|
|||||||
ExportDefaultDeclaration,
|
ExportDefaultDeclaration,
|
||||||
ExportNamedDeclaration,
|
ExportNamedDeclaration,
|
||||||
File,
|
File,
|
||||||
|
Node,
|
||||||
NodePath,
|
NodePath,
|
||||||
ObjectExpression,
|
ObjectExpression,
|
||||||
ObjectMethod,
|
ObjectMethod,
|
||||||
@@ -66,4 +69,5 @@ export type {
|
|||||||
ParserOptions,
|
ParserOptions,
|
||||||
SpreadElement,
|
SpreadElement,
|
||||||
StringLiteral,
|
StringLiteral,
|
||||||
|
VariableDeclarator,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,16 @@
|
|||||||
|
import {
|
||||||
|
NESTED_ROUTE_POSITIONS,
|
||||||
|
NestedRoutePosition,
|
||||||
|
} from "@medusajs/admin-shared"
|
||||||
import fs from "fs/promises"
|
import fs from "fs/promises"
|
||||||
import { outdent } from "outdent"
|
import { outdent } from "outdent"
|
||||||
import {
|
import {
|
||||||
File,
|
File,
|
||||||
isIdentifier,
|
isIdentifier,
|
||||||
isObjectProperty,
|
isObjectProperty,
|
||||||
|
isStringLiteral,
|
||||||
|
Node,
|
||||||
|
ObjectProperty,
|
||||||
parse,
|
parse,
|
||||||
ParseResult,
|
ParseResult,
|
||||||
traverse,
|
traverse,
|
||||||
@@ -16,7 +23,6 @@ import {
|
|||||||
normalizePath,
|
normalizePath,
|
||||||
} from "../utils"
|
} from "../utils"
|
||||||
import { getRoute } from "./helpers"
|
import { getRoute } from "./helpers"
|
||||||
import { NESTED_ROUTE_POSITIONS } from "@medusajs/admin-shared"
|
|
||||||
|
|
||||||
type RouteConfig = {
|
type RouteConfig = {
|
||||||
label: boolean
|
label: boolean
|
||||||
@@ -142,48 +148,47 @@ async function getRouteConfig(file: string): Promise<RouteConfig | null> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let config: RouteConfig | null = null
|
let config: RouteConfig | null = null
|
||||||
|
let configFound = false
|
||||||
|
|
||||||
try {
|
try {
|
||||||
traverse(ast, {
|
traverse(ast, {
|
||||||
ExportNamedDeclaration(path) {
|
/**
|
||||||
|
* For bundled files, the config will not be a named export,
|
||||||
|
* but instead a variable declaration.
|
||||||
|
*/
|
||||||
|
VariableDeclarator(path) {
|
||||||
|
if (configFound) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
const properties = getConfigObjectProperties(path)
|
const properties = getConfigObjectProperties(path)
|
||||||
if (!properties) {
|
if (!properties) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const hasProperty = (name: string) =>
|
config = processConfigProperties(properties, file)
|
||||||
properties.some(
|
|
||||||
(prop) => isObjectProperty(prop) && isIdentifier(prop.key, { name })
|
|
||||||
)
|
|
||||||
|
|
||||||
const hasLabel = hasProperty("label")
|
if (config) {
|
||||||
if (!hasLabel) {
|
configFound = true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* For unbundled files, the `config` will always be a named export.
|
||||||
|
*/
|
||||||
|
ExportNamedDeclaration(path) {
|
||||||
|
if (configFound) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const nested = properties.find(
|
const properties = getConfigObjectProperties(path)
|
||||||
(prop) =>
|
if (!properties) {
|
||||||
isObjectProperty(prop) && isIdentifier(prop.key, { name: "nested" })
|
|
||||||
)
|
|
||||||
|
|
||||||
const nestedValue = nested ? (nested as any).value.value : undefined
|
|
||||||
|
|
||||||
if (nestedValue && !NESTED_ROUTE_POSITIONS.includes(nestedValue)) {
|
|
||||||
logger.error(
|
|
||||||
`Invalid nested route position: "${nestedValue}". Allowed values are: ${NESTED_ROUTE_POSITIONS.join(
|
|
||||||
", "
|
|
||||||
)}`,
|
|
||||||
{
|
|
||||||
file,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
config = {
|
config = processConfigProperties(properties, file)
|
||||||
label: hasLabel,
|
|
||||||
icon: hasProperty("icon"),
|
if (config) {
|
||||||
nested: nestedValue,
|
configFound = true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
@@ -197,6 +202,51 @@ async function getRouteConfig(file: string): Promise<RouteConfig | null> {
|
|||||||
return config
|
return config
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function processConfigProperties(
|
||||||
|
properties: Node[],
|
||||||
|
file: string
|
||||||
|
): RouteConfig | null {
|
||||||
|
const hasProperty = (name: string) =>
|
||||||
|
properties.some(
|
||||||
|
(prop) => isObjectProperty(prop) && isIdentifier(prop.key, { name })
|
||||||
|
)
|
||||||
|
|
||||||
|
const hasLabel = hasProperty("label")
|
||||||
|
if (!hasLabel) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
const nested = properties.find(
|
||||||
|
(prop) =>
|
||||||
|
isObjectProperty(prop) && isIdentifier(prop.key, { name: "nested" })
|
||||||
|
) as ObjectProperty | undefined
|
||||||
|
|
||||||
|
let nestedValue: string | undefined = undefined
|
||||||
|
|
||||||
|
if (isStringLiteral(nested?.value)) {
|
||||||
|
nestedValue = nested.value.value
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
nestedValue &&
|
||||||
|
!NESTED_ROUTE_POSITIONS.includes(nestedValue as NestedRoutePosition)
|
||||||
|
) {
|
||||||
|
logger.error(
|
||||||
|
`Invalid nested route position: "${nestedValue}". Allowed values are: ${NESTED_ROUTE_POSITIONS.join(
|
||||||
|
", "
|
||||||
|
)}`,
|
||||||
|
{ file }
|
||||||
|
)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
label: hasLabel,
|
||||||
|
icon: hasProperty("icon"),
|
||||||
|
nested: nestedValue,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function generateRouteConfigName(index: number): string {
|
function generateRouteConfigName(index: number): string {
|
||||||
return `RouteConfig${index}`
|
return `RouteConfig${index}`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,7 +23,6 @@ type RouteResult = {
|
|||||||
export async function generateRoutes(sources: Set<string>) {
|
export async function generateRoutes(sources: Set<string>) {
|
||||||
const files = await getFilesFromSources(sources)
|
const files = await getFilesFromSources(sources)
|
||||||
const results = await getRouteResults(files)
|
const results = await getRouteResults(files)
|
||||||
|
|
||||||
const imports = results.map((result) => result.imports).flat()
|
const imports = results.map((result) => result.imports).flat()
|
||||||
const code = generateCode(results)
|
const code = generateCode(results)
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,16 @@
|
|||||||
import { normalizePath } from "../utils"
|
import { normalizePath, VALID_FILE_EXTENSIONS } from "../utils"
|
||||||
|
|
||||||
export function getRoute(file: string): string {
|
export function getRoute(file: string): string {
|
||||||
const importPath = normalizePath(file)
|
const importPath = normalizePath(file)
|
||||||
return importPath
|
return importPath
|
||||||
.replace(/.*\/admin\/(routes)/, "")
|
.replace(/.*\/admin\/(routes)/, "")
|
||||||
.replace(/\[([^\]]+)\]/g, ":$1")
|
.replace(/\[([^\]]+)\]/g, ":$1")
|
||||||
.replace(/\/page\.(tsx|jsx)/, "")
|
.replace(
|
||||||
|
new RegExp(
|
||||||
|
`/page\\.(${VALID_FILE_EXTENSIONS.map((ext) => ext.slice(1)).join(
|
||||||
|
"|"
|
||||||
|
)})$`
|
||||||
|
),
|
||||||
|
""
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import {
|
|||||||
type ExportNamedDeclaration,
|
type ExportNamedDeclaration,
|
||||||
type NodePath,
|
type NodePath,
|
||||||
type ParserOptions,
|
type ParserOptions,
|
||||||
|
type VariableDeclarator,
|
||||||
} from "./babel"
|
} from "./babel"
|
||||||
|
|
||||||
export function normalizePath(file: string) {
|
export function normalizePath(file: string) {
|
||||||
@@ -48,7 +49,7 @@ export function generateModule(code: string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const VALID_FILE_EXTENSIONS = [".tsx", ".jsx"]
|
export const VALID_FILE_EXTENSIONS = [".tsx", ".jsx", ".js"]
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Crawls a directory and returns all files that match the criteria.
|
* Crawls a directory and returns all files that match the criteria.
|
||||||
@@ -96,8 +97,25 @@ export async function crawl(
|
|||||||
* Extracts and returns the properties of a `config` object from a named export declaration.
|
* Extracts and returns the properties of a `config` object from a named export declaration.
|
||||||
*/
|
*/
|
||||||
export function getConfigObjectProperties(
|
export function getConfigObjectProperties(
|
||||||
path: NodePath<ExportNamedDeclaration>
|
path: NodePath<ExportNamedDeclaration | VariableDeclarator>
|
||||||
) {
|
) {
|
||||||
|
if (isVariableDeclarator(path.node)) {
|
||||||
|
const configDeclaration = isIdentifier(path.node.id, { name: "config" })
|
||||||
|
? path.node
|
||||||
|
: null
|
||||||
|
|
||||||
|
if (
|
||||||
|
configDeclaration &&
|
||||||
|
isCallExpression(configDeclaration.init) &&
|
||||||
|
configDeclaration.init.arguments.length > 0 &&
|
||||||
|
isObjectExpression(configDeclaration.init.arguments[0])
|
||||||
|
) {
|
||||||
|
return configDeclaration.init.arguments[0].properties
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
const declaration = path.node.declaration
|
const declaration = path.node.declaration
|
||||||
|
|
||||||
if (isVariableDeclaration(declaration)) {
|
if (isVariableDeclaration(declaration)) {
|
||||||
@@ -126,6 +144,30 @@ export async function hasDefaultExport(
|
|||||||
ExportDefaultDeclaration() {
|
ExportDefaultDeclaration() {
|
||||||
hasDefaultExport = true
|
hasDefaultExport = true
|
||||||
},
|
},
|
||||||
|
AssignmentExpression(path) {
|
||||||
|
if (
|
||||||
|
path.node.left.type === "MemberExpression" &&
|
||||||
|
path.node.left.object.type === "Identifier" &&
|
||||||
|
path.node.left.object.name === "exports" &&
|
||||||
|
path.node.left.property.type === "Identifier" &&
|
||||||
|
path.node.left.property.name === "default"
|
||||||
|
) {
|
||||||
|
hasDefaultExport = true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
ExportNamedDeclaration(path) {
|
||||||
|
const specifiers = path.node.specifiers
|
||||||
|
if (
|
||||||
|
specifiers?.some(
|
||||||
|
(s) =>
|
||||||
|
s.type === "ExportSpecifier" &&
|
||||||
|
s.exported.type === "Identifier" &&
|
||||||
|
s.exported.name === "default"
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
hasDefaultExport = true
|
||||||
|
}
|
||||||
|
},
|
||||||
})
|
})
|
||||||
return hasDefaultExport
|
return hasDefaultExport
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,18 +6,13 @@ import {
|
|||||||
isArrayExpression,
|
isArrayExpression,
|
||||||
isStringLiteral,
|
isStringLiteral,
|
||||||
isTemplateLiteral,
|
isTemplateLiteral,
|
||||||
ObjectProperty,
|
Node,
|
||||||
parse,
|
parse,
|
||||||
ParseResult,
|
ParseResult,
|
||||||
traverse,
|
traverse,
|
||||||
} from "../babel"
|
} from "../babel"
|
||||||
import { logger } from "../logger"
|
import { logger } from "../logger"
|
||||||
import {
|
import { getParserOptions, hasDefaultExport, normalizePath } from "../utils"
|
||||||
getConfigObjectProperties,
|
|
||||||
getParserOptions,
|
|
||||||
hasDefaultExport,
|
|
||||||
normalizePath,
|
|
||||||
} from "../utils"
|
|
||||||
import { getWidgetFilesFromSources } from "./helpers"
|
import { getWidgetFilesFromSources } from "./helpers"
|
||||||
|
|
||||||
type WidgetConfig = {
|
type WidgetConfig = {
|
||||||
@@ -155,51 +150,106 @@ async function getWidgetZone(
|
|||||||
): Promise<InjectionZone[] | null> {
|
): Promise<InjectionZone[] | null> {
|
||||||
const zones: string[] = []
|
const zones: string[] = []
|
||||||
|
|
||||||
|
/**
|
||||||
|
* We need to keep track of whether we have found a zone in the file.
|
||||||
|
* This is to avoid processing the same config both using the `ExportNamedDeclaration`
|
||||||
|
* and `VariableDeclarator` paths, which would be the case for the unbundled files.
|
||||||
|
*/
|
||||||
|
let zoneFound = false
|
||||||
|
|
||||||
traverse(ast, {
|
traverse(ast, {
|
||||||
ExportNamedDeclaration(path) {
|
/**
|
||||||
const properties = getConfigObjectProperties(path)
|
* In case we are processing a bundled file, the `config` will most likely
|
||||||
if (!properties) {
|
* not be a named export. Instead we look for a `VariableDeclaration` named
|
||||||
|
* `config` and extract the `zone` property from it.
|
||||||
|
*/
|
||||||
|
VariableDeclarator(path) {
|
||||||
|
if (zoneFound) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const zoneProperty = properties.find(
|
if (
|
||||||
(p) =>
|
path.node.id.type === "Identifier" &&
|
||||||
p.type === "ObjectProperty" &&
|
path.node.id.name === "config" &&
|
||||||
p.key.type === "Identifier" &&
|
path.node.init?.type === "CallExpression"
|
||||||
p.key.name === "zone"
|
) {
|
||||||
) as ObjectProperty | undefined
|
const arg = path.node.init.arguments[0]
|
||||||
|
if (arg?.type === "ObjectExpression") {
|
||||||
if (!zoneProperty) {
|
const zoneProperty = arg.properties.find(
|
||||||
logger.warn(`'zone' property is missing from the widget config.`, {
|
(p: any) => p.type === "ObjectProperty" && p.key.name === "zone"
|
||||||
file,
|
)
|
||||||
})
|
if (zoneProperty?.type === "ObjectProperty") {
|
||||||
return
|
extractZoneValues(zoneProperty.value, zones, file)
|
||||||
}
|
zoneFound = true
|
||||||
|
|
||||||
if (isTemplateLiteral(zoneProperty.value)) {
|
|
||||||
logger.warn(
|
|
||||||
`'zone' property cannot be a template literal (e.g. \`product.details.after\`).`,
|
|
||||||
{ file }
|
|
||||||
)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isStringLiteral(zoneProperty.value)) {
|
|
||||||
zones.push(zoneProperty.value.value)
|
|
||||||
} else if (isArrayExpression(zoneProperty.value)) {
|
|
||||||
const values: string[] = []
|
|
||||||
|
|
||||||
for (const element of zoneProperty.value.elements) {
|
|
||||||
if (isStringLiteral(element)) {
|
|
||||||
values.push(element.value)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* For unbundled files, the `config` will always be a named export.
|
||||||
|
*/
|
||||||
|
ExportNamedDeclaration(path) {
|
||||||
|
if (zoneFound) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
zones.push(...values)
|
const declaration = path.node.declaration
|
||||||
|
if (
|
||||||
|
declaration?.type === "VariableDeclaration" &&
|
||||||
|
declaration.declarations[0]?.type === "VariableDeclarator" &&
|
||||||
|
declaration.declarations[0].id.type === "Identifier" &&
|
||||||
|
declaration.declarations[0].id.name === "config" &&
|
||||||
|
declaration.declarations[0].init?.type === "CallExpression"
|
||||||
|
) {
|
||||||
|
const arg = declaration.declarations[0].init.arguments[0]
|
||||||
|
if (arg?.type === "ObjectExpression") {
|
||||||
|
const zoneProperty = arg.properties.find(
|
||||||
|
(p: any) => p.type === "ObjectProperty" && p.key.name === "zone"
|
||||||
|
)
|
||||||
|
if (zoneProperty?.type === "ObjectProperty") {
|
||||||
|
extractZoneValues(zoneProperty.value, zones, file)
|
||||||
|
zoneFound = true
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
if (!zoneFound) {
|
||||||
|
logger.warn(`'zone' property is missing from the widget config.`, { file })
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
const validatedZones = zones.filter(isValidInjectionZone)
|
const validatedZones = zones.filter(isValidInjectionZone)
|
||||||
return validatedZones.length > 0 ? validatedZones : null
|
|
||||||
|
if (validatedZones.length === 0) {
|
||||||
|
logger.warn(`'zone' property is not a valid injection zone.`, {
|
||||||
|
file,
|
||||||
|
})
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return validatedZones
|
||||||
|
}
|
||||||
|
|
||||||
|
function extractZoneValues(value: Node, zones: string[], file: string) {
|
||||||
|
if (isTemplateLiteral(value)) {
|
||||||
|
logger.warn(
|
||||||
|
`'zone' property cannot be a template literal (e.g. \`product.details.after\`).`,
|
||||||
|
{ file }
|
||||||
|
)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isStringLiteral(value)) {
|
||||||
|
zones.push(value.value)
|
||||||
|
} else if (isArrayExpression(value)) {
|
||||||
|
const values = value.elements
|
||||||
|
.filter((e) => isStringLiteral(e))
|
||||||
|
.map((e) => e.value)
|
||||||
|
zones.push(...values)
|
||||||
|
} else {
|
||||||
|
logger.warn(`'zone' property is not a string or array.`, { file })
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
import { logger } from "@medusajs/framework/logger"
|
import { logger } from "@medusajs/framework/logger"
|
||||||
import { AdminOptions, ConfigModule } from "@medusajs/framework/types"
|
import {
|
||||||
|
AdminOptions,
|
||||||
|
ConfigModule,
|
||||||
|
PluginDetails,
|
||||||
|
} from "@medusajs/framework/types"
|
||||||
import { Express } from "express"
|
import { Express } from "express"
|
||||||
import fs from "fs"
|
import fs from "fs"
|
||||||
import path from "path"
|
import path from "path"
|
||||||
@@ -9,6 +13,7 @@ type Options = {
|
|||||||
app: Express
|
app: Express
|
||||||
configModule: ConfigModule
|
configModule: ConfigModule
|
||||||
rootDirectory: string
|
rootDirectory: string
|
||||||
|
plugins: PluginDetails[]
|
||||||
}
|
}
|
||||||
|
|
||||||
type IntializedOptions = Required<Pick<AdminOptions, "path" | "disable">> &
|
type IntializedOptions = Required<Pick<AdminOptions, "path" | "disable">> &
|
||||||
@@ -23,16 +28,18 @@ export default async function adminLoader({
|
|||||||
app,
|
app,
|
||||||
configModule,
|
configModule,
|
||||||
rootDirectory,
|
rootDirectory,
|
||||||
|
plugins,
|
||||||
}: Options) {
|
}: Options) {
|
||||||
const { admin } = configModule
|
const { admin } = configModule
|
||||||
|
|
||||||
const sources: string[] = []
|
const sources: string[] = []
|
||||||
|
|
||||||
const projectSource = path.join(rootDirectory, "src", "admin")
|
for (const plugin of plugins) {
|
||||||
|
const pluginSource = path.join(plugin.resolve, "admin")
|
||||||
|
|
||||||
// check if the projectSource exists
|
if (fs.existsSync(pluginSource)) {
|
||||||
if (fs.existsSync(projectSource)) {
|
sources.push(pluginSource)
|
||||||
sources.push(projectSource)
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const adminOptions: IntializedOptions = {
|
const adminOptions: IntializedOptions = {
|
||||||
|
|||||||
@@ -1,3 +1,12 @@
|
|||||||
|
import { container, MedusaAppLoader } from "@medusajs/framework"
|
||||||
|
import { configLoader } from "@medusajs/framework/config"
|
||||||
|
import { pgConnectionLoader } from "@medusajs/framework/database"
|
||||||
|
import { featureFlagsLoader } from "@medusajs/framework/feature-flags"
|
||||||
|
import { expressLoader } from "@medusajs/framework/http"
|
||||||
|
import { JobLoader } from "@medusajs/framework/jobs"
|
||||||
|
import { LinkLoader } from "@medusajs/framework/links"
|
||||||
|
import { logger } from "@medusajs/framework/logger"
|
||||||
|
import { SubscriberLoader } from "@medusajs/framework/subscribers"
|
||||||
import {
|
import {
|
||||||
ConfigModule,
|
ConfigModule,
|
||||||
LoadedModule,
|
LoadedModule,
|
||||||
@@ -10,6 +19,7 @@ import {
|
|||||||
mergePluginModules,
|
mergePluginModules,
|
||||||
promiseAll,
|
promiseAll,
|
||||||
} from "@medusajs/framework/utils"
|
} from "@medusajs/framework/utils"
|
||||||
|
import { WorkflowLoader } from "@medusajs/framework/workflows"
|
||||||
import { asValue } from "awilix"
|
import { asValue } from "awilix"
|
||||||
import { Express, NextFunction, Request, Response } from "express"
|
import { Express, NextFunction, Request, Response } from "express"
|
||||||
import { join } from "path"
|
import { join } from "path"
|
||||||
@@ -17,16 +27,6 @@ import requestIp from "request-ip"
|
|||||||
import { v4 } from "uuid"
|
import { v4 } from "uuid"
|
||||||
import adminLoader from "./admin"
|
import adminLoader from "./admin"
|
||||||
import apiLoader from "./api"
|
import apiLoader from "./api"
|
||||||
import { configLoader } from "@medusajs/framework/config"
|
|
||||||
import { expressLoader } from "@medusajs/framework/http"
|
|
||||||
import { JobLoader } from "@medusajs/framework/jobs"
|
|
||||||
import { LinkLoader } from "@medusajs/framework/links"
|
|
||||||
import { logger } from "@medusajs/framework/logger"
|
|
||||||
import { container, MedusaAppLoader } from "@medusajs/framework"
|
|
||||||
import { pgConnectionLoader } from "@medusajs/framework/database"
|
|
||||||
import { SubscriberLoader } from "@medusajs/framework/subscribers"
|
|
||||||
import { WorkflowLoader } from "@medusajs/framework/workflows"
|
|
||||||
import { featureFlagsLoader } from "@medusajs/framework/feature-flags"
|
|
||||||
import { getResolvedPlugins } from "./helpers/resolve-plugins"
|
import { getResolvedPlugins } from "./helpers/resolve-plugins"
|
||||||
|
|
||||||
type Options = {
|
type Options = {
|
||||||
@@ -107,7 +107,7 @@ async function loadEntrypoints(
|
|||||||
next()
|
next()
|
||||||
})
|
})
|
||||||
|
|
||||||
await adminLoader({ app: expressApp, configModule, rootDirectory })
|
await adminLoader({ app: expressApp, configModule, rootDirectory, plugins })
|
||||||
await apiLoader({
|
await apiLoader({
|
||||||
container,
|
container,
|
||||||
plugins,
|
plugins,
|
||||||
|
|||||||
Reference in New Issue
Block a user