@medusajs/admin-vite-plugin
Advanced tools
Comparing version 0.0.2-preview-20240515175257 to 0.0.2-preview-20240523131811
@@ -1,8 +0,12 @@ | ||
import { PluginOption } from 'vite'; | ||
import * as Vite from 'vite'; | ||
type InjectArgs = { | ||
type MedusaVitePluginOptions = { | ||
/** | ||
* A list of directories to source extensions from. | ||
*/ | ||
sources?: string[]; | ||
}; | ||
declare function inject(args?: InjectArgs): PluginOption; | ||
type MedusaVitePlugin = (config?: MedusaVitePluginOptions) => Vite.Plugin; | ||
declare const medusaVitePlugin: MedusaVitePlugin; | ||
export { inject as default }; | ||
export { type MedusaVitePlugin, medusaVitePlugin as default }; |
@@ -33,8 +33,8 @@ "use strict"; | ||
__export(src_exports, { | ||
default: () => inject | ||
default: () => src_default | ||
}); | ||
module.exports = __toCommonJS(src_exports); | ||
var import_parser = require("@babel/parser"); | ||
var import_traverse = __toESM(require("@babel/traverse")); | ||
var import_chokidar = __toESM(require("chokidar")); | ||
// src/plugin.ts | ||
var import_admin_shared = require("@medusajs/admin-shared"); | ||
var import_fdir = require("fdir"); | ||
@@ -45,541 +45,421 @@ var import_promises = __toESM(require("fs/promises")); | ||
// ../admin-shared/dist/constants.js | ||
var injectionZones = [ | ||
// Order injection zones | ||
"order.details.before", | ||
"order.details.after", | ||
"order.list.before", | ||
"order.list.after", | ||
// Draft order injection zones | ||
"draft_order.list.before", | ||
"draft_order.list.after", | ||
"draft_order.details.before", | ||
"draft_order.details.after", | ||
// Customer injection zones | ||
"customer.details.before", | ||
"customer.details.after", | ||
"customer.list.before", | ||
"customer.list.after", | ||
// Customer group injection zones | ||
"customer_group.details.before", | ||
"customer_group.details.after", | ||
"customer_group.list.before", | ||
"customer_group.list.after", | ||
// Product injection zones | ||
"product.details.before", | ||
"product.details.after", | ||
"product.list.before", | ||
"product.list.after", | ||
"product.details.side.before", | ||
"product.details.side.after", | ||
// Product collection injection zones | ||
"product_collection.details.before", | ||
"product_collection.details.after", | ||
"product_collection.list.before", | ||
"product_collection.list.after", | ||
// Product category injection zones | ||
"product_category.details.before", | ||
"product_category.details.after", | ||
"product_category.list.before", | ||
"product_category.list.after", | ||
// Price list injection zones | ||
"price_list.details.before", | ||
"price_list.details.after", | ||
"price_list.list.before", | ||
"price_list.list.after", | ||
// Discount injection zones | ||
"discount.details.before", | ||
"discount.details.after", | ||
"discount.list.before", | ||
"discount.list.after", | ||
// Promotion injection zones | ||
"promotion.details.before", | ||
"promotion.details.after", | ||
"promotion.list.before", | ||
"promotion.list.after", | ||
// Gift card injection zones | ||
"gift_card.details.before", | ||
"gift_card.details.after", | ||
"gift_card.list.before", | ||
"gift_card.list.after", | ||
"custom_gift_card.before", | ||
"custom_gift_card.after", | ||
// Login | ||
"login.before", | ||
"login.after" | ||
]; | ||
// src/babel.ts | ||
var import_parser = require("@babel/parser"); | ||
var import_traverse = __toESM(require("@babel/traverse")); | ||
var traverse; | ||
if (typeof import_traverse.default === "function") { | ||
traverse = import_traverse.default; | ||
} else { | ||
traverse = import_traverse.default.default; | ||
} | ||
// src/index.ts | ||
var traverse = import_traverse.default.default; | ||
var VIRTUAL_PREFIX = "/@virtual/medusajs-admin-vite-plugin/"; | ||
var IMPORT_PREFIX = "medusa-admin:"; | ||
var WIDGET_MODULE = `${IMPORT_PREFIX}widgets/`; | ||
var WIDGET_MODULES = injectionZones.map((zone) => { | ||
return `${WIDGET_MODULE}${zone.replace(/\./g, "/")}`; | ||
}); | ||
var ROUTE_PAGE_MODULE = `${IMPORT_PREFIX}routes/pages`; | ||
var ROUTE_LINK_MODULE = `${IMPORT_PREFIX}routes/links`; | ||
var ROUTE_MODULES = [ROUTE_PAGE_MODULE, ROUTE_LINK_MODULE]; | ||
var SETTING_PAGE_MODULE = `${IMPORT_PREFIX}settings/pages`; | ||
var SETTING_CARD_MODULE = `${IMPORT_PREFIX}settings/cards`; | ||
var SETTING_MODULE = [SETTING_PAGE_MODULE, SETTING_CARD_MODULE]; | ||
var MODULES = [...WIDGET_MODULES, ...ROUTE_MODULES, ...SETTING_MODULE]; | ||
function inject(args) { | ||
const _extensionGraph = /* @__PURE__ */ new Map(); | ||
const _sources = /* @__PURE__ */ new Set([...args?.sources || []]); | ||
let server; | ||
let watcher; | ||
let logger; | ||
async function traverseDirectory(dir, file, depth) { | ||
const baseDepth = dir.split(import_path.default.sep).length; | ||
const crawler = new import_fdir.fdir().withBasePath().exclude((dirName) => dirName.startsWith("_")).filter((path2) => path2.endsWith(".tsx") || path2.endsWith(".jsx")); | ||
if (file) { | ||
crawler.filter( | ||
(path2) => path2.endsWith(`${file}.tsx`) || path2.endsWith(`${file}.jsx`) | ||
); | ||
} | ||
if (depth) { | ||
crawler.filter((file2) => { | ||
const directoryDepth = file2.split(import_path.default.sep).length - 1; | ||
if (depth.max && directoryDepth > baseDepth + depth.max) { | ||
return false; | ||
} | ||
if (directoryDepth < baseDepth + depth.min) { | ||
return false; | ||
} | ||
return true; | ||
}); | ||
} | ||
return await crawler.crawl(dir).withPromise(); | ||
// src/plugin.ts | ||
var VALID_FILE_EXTENSIONS = [".tsx", ".jsx"]; | ||
function getModuleType(file) { | ||
const normalizedPath = import_path.default.normalize(file); | ||
if (normalizedPath.includes(import_path.default.normalize("/admin/widgets/"))) { | ||
return "widget"; | ||
} else if (normalizedPath.includes(import_path.default.normalize("/admin/routes/"))) { | ||
return "route"; | ||
} else { | ||
return "none"; | ||
} | ||
function generateModule(code) { | ||
const magicString = new import_magic_string.default(code); | ||
return { | ||
code: magicString.toString(), | ||
map: magicString.generateMap({ hires: true }) | ||
}; | ||
} | ||
function getParserOptions(file) { | ||
const options = { | ||
sourceType: "module", | ||
plugins: ["jsx"] | ||
}; | ||
if (file.endsWith(".tsx")) { | ||
options.plugins?.push("typescript"); | ||
} | ||
function validateDefaultExport(path2, ast) { | ||
let hasComponentExport = false; | ||
const declaration = path2.node.declaration; | ||
if (declaration && (declaration.type === "Identifier" || declaration.type === "FunctionDeclaration")) { | ||
const exportName = declaration.type === "Identifier" ? declaration.name : declaration.id && declaration.id.name; | ||
if (exportName) { | ||
try { | ||
traverse(ast, { | ||
VariableDeclarator({ node, scope }) { | ||
let isDefaultExport = false; | ||
if (node.id.type === "Identifier" && node.id.name === exportName) { | ||
isDefaultExport = true; | ||
} | ||
if (!isDefaultExport) { | ||
return; | ||
} | ||
traverse( | ||
node, | ||
{ | ||
ReturnStatement(path3) { | ||
if (path3.node.argument?.type === "JSXElement" || path3.node.argument?.type === "JSXFragment") { | ||
hasComponentExport = true; | ||
} | ||
} | ||
}, | ||
scope | ||
); | ||
} | ||
}); | ||
} catch (e) { | ||
console.error( | ||
`An error occured while validating the default export of '${path2}'. The following error must be resolved before continuing: | ||
${e}` | ||
); | ||
return false; | ||
} | ||
return options; | ||
} | ||
function generateModule(code) { | ||
const magicString = new import_magic_string.default(code); | ||
return { | ||
code: magicString.toString(), | ||
map: magicString.generateMap({ hires: true }) | ||
}; | ||
} | ||
async function crawl(dir, file, depth) { | ||
const dirDepth = dir.split(import_path.default.sep).length; | ||
const crawler = new import_fdir.fdir().withBasePath().exclude((dirName) => dirName.startsWith("_")).filter((path2) => { | ||
return VALID_FILE_EXTENSIONS.some((ext) => path2.endsWith(ext)); | ||
}); | ||
if (file) { | ||
crawler.filter((path2) => { | ||
return VALID_FILE_EXTENSIONS.some((ext) => path2.endsWith(file + ext)); | ||
}); | ||
} | ||
if (depth) { | ||
crawler.filter((file2) => { | ||
const pathDepth = file2.split(import_path.default.sep).length - 1; | ||
if (depth.max && pathDepth > dirDepth + depth.max) { | ||
return false; | ||
} | ||
if (pathDepth < dirDepth + depth.min) { | ||
return false; | ||
} | ||
return true; | ||
}); | ||
} | ||
return crawler.crawl(dir).withPromise(); | ||
} | ||
function getConfigObjectProperties(path2) { | ||
const declaration = path2.node.declaration; | ||
if (declaration && declaration.type === "VariableDeclaration") { | ||
const configDeclaration = declaration.declarations.find( | ||
(d) => d.type === "VariableDeclarator" && d.id.type === "Identifier" && d.id.name === "config" | ||
); | ||
if (configDeclaration && configDeclaration.init?.type === "CallExpression" && configDeclaration.init.arguments.length > 0 && configDeclaration.init.arguments[0].type === "ObjectExpression") { | ||
return configDeclaration.init.arguments[0].properties; | ||
} | ||
return hasComponentExport; | ||
} | ||
function getProperties(path2) { | ||
const declaration = path2.node.declaration; | ||
if (declaration && declaration.type === "VariableDeclaration") { | ||
const configDeclaration = declaration.declarations.find( | ||
(d) => d.type === "VariableDeclarator" && d.id.type === "Identifier" && d.id.name === "config" | ||
); | ||
if (configDeclaration && configDeclaration.init?.type === "ObjectExpression") { | ||
return configDeclaration.init.properties; | ||
return null; | ||
} | ||
function isDefaultExportComponent(path2, ast) { | ||
let hasComponentExport = false; | ||
const declaration = path2.node.declaration; | ||
if (declaration && (declaration.type === "Identifier" || declaration.type === "FunctionDeclaration")) { | ||
const exportName = declaration.type === "Identifier" ? declaration.name : declaration.id && declaration.id.name; | ||
if (exportName) { | ||
try { | ||
traverse(ast, { | ||
VariableDeclarator({ node, scope }) { | ||
let isDefaultExport = false; | ||
if (node.id.type === "Identifier" && node.id.name === exportName) { | ||
isDefaultExport = true; | ||
} | ||
if (!isDefaultExport) { | ||
return; | ||
} | ||
traverse( | ||
node, | ||
{ | ||
ReturnStatement(path3) { | ||
if (path3.node.argument?.type === "JSXElement" || path3.node.argument?.type === "JSXFragment") { | ||
hasComponentExport = true; | ||
} | ||
} | ||
}, | ||
scope | ||
); | ||
} | ||
}); | ||
} catch (e) { | ||
return false; | ||
} | ||
} | ||
return null; | ||
} | ||
function validateInjectionZone(zone) { | ||
return injectionZones.includes(zone); | ||
return hasComponentExport; | ||
} | ||
function validateWidgetConfig(path2, zone) { | ||
let zoneIsValid = false; | ||
let zoneValue = null; | ||
const properties = getConfigObjectProperties(path2); | ||
if (!properties) { | ||
return { zoneIsValid, zoneValue }; | ||
} | ||
function validateWidgetConfig(path2, zone) { | ||
const properties = getProperties(path2); | ||
if (!properties) { | ||
return { zoneIsValid: false, zoneValue: void 0 }; | ||
} | ||
const zoneProperty = properties.find( | ||
(p) => p.type === "ObjectProperty" && p.key.type === "Identifier" && p.key.name === "zone" | ||
); | ||
if (!zoneProperty) { | ||
return { zoneIsValid: false, zoneValue: void 0 }; | ||
} | ||
let zoneIsValid = false; | ||
let zoneValue = void 0; | ||
if (zoneProperty.value.type === "StringLiteral") { | ||
zoneIsValid = !zone ? validateInjectionZone(zoneProperty.value.value) : zone === zoneProperty.value.value; | ||
zoneValue = zoneProperty.value.value; | ||
} else if (zoneProperty.value.type === "ArrayExpression") { | ||
zoneIsValid = zoneProperty.value.elements.every((_zone) => { | ||
if (!_zone || _zone.type !== "StringLiteral") { | ||
return false; | ||
} | ||
const isZoneMatch = !zone ? true : zone === _zone.value; | ||
return validateInjectionZone(_zone.value) && isZoneMatch; | ||
}); | ||
zoneValue = zoneProperty.value.elements.map((e) => { | ||
if (e && e.type === "StringLiteral") { | ||
return e.value; | ||
} | ||
}).filter(Boolean); | ||
} | ||
const zoneProperty = properties.find( | ||
(p) => p.type === "ObjectProperty" && p.key.type === "Identifier" && p.key.name === "zone" | ||
); | ||
if (!zoneProperty) { | ||
return { zoneIsValid, zoneValue }; | ||
} | ||
async function validateWidget(file, zone) { | ||
const content = await import_promises.default.readFile(file, "utf-8"); | ||
const parserOptions = { | ||
sourceType: "module", | ||
plugins: ["jsx"] | ||
}; | ||
if (file.endsWith(".tsx")) { | ||
parserOptions.plugins?.push("typescript"); | ||
if (zoneProperty.value.type === "StringLiteral") { | ||
zoneIsValid = !zone ? (0, import_admin_shared.isValidInjectionZone)(zoneProperty.value.value) : zone === zoneProperty.value.value; | ||
zoneValue = zoneProperty.value.value; | ||
} else if (zoneProperty.value.type === "ArrayExpression") { | ||
zoneIsValid = zoneProperty.value.elements.every((e) => { | ||
if (!e || e.type !== "StringLiteral") { | ||
return false; | ||
} | ||
const isZoneMatch = !zone ? true : zone === e.value; | ||
return (0, import_admin_shared.isValidInjectionZone)(e.value) && isZoneMatch; | ||
}); | ||
const values = []; | ||
for (const element of zoneProperty.value.elements) { | ||
if (element && element.type === "StringLiteral") { | ||
values.push(element.value); | ||
} | ||
} | ||
let ast; | ||
try { | ||
ast = (0, import_parser.parse)(content, parserOptions); | ||
} catch (err) { | ||
logger.error( | ||
`An error occured while parsing the content of ${file}: | ||
${err}`, | ||
{ | ||
error: err, | ||
timestamp: true | ||
} | ||
); | ||
return { isValidWidget: false, zoneValue: void 0 }; | ||
} | ||
let hasDefaultExport = false; | ||
let hasNamedExport = false; | ||
let zoneValue; | ||
try { | ||
traverse(ast, { | ||
ExportDefaultDeclaration(path2) { | ||
hasDefaultExport = validateDefaultExport(path2, ast); | ||
}, | ||
ExportNamedDeclaration(path2) { | ||
const { zoneIsValid, zoneValue: value } = validateWidgetConfig( | ||
path2, | ||
zone | ||
); | ||
hasNamedExport = zoneIsValid; | ||
zoneValue = value; | ||
} | ||
}); | ||
} catch (err) { | ||
logger.error(`An error occured while validating the content of ${file}`, { | ||
error: err, | ||
timestamp: true | ||
}); | ||
return { isValidWidget: false, zoneValue: void 0 }; | ||
} | ||
return { isValidWidget: hasDefaultExport && hasNamedExport, zoneValue }; | ||
zoneValue = values; | ||
} | ||
async function generateWidgetEntrypoint(zone) { | ||
const files = (await Promise.all( | ||
Array.from(_sources).map( | ||
async (source) => traverseDirectory(`${source}/widgets`) | ||
) | ||
)).flat(); | ||
const validatedWidgets = (await Promise.all( | ||
files.map(async (widget) => { | ||
const { isValidWidget } = await validateWidget(widget, zone); | ||
return isValidWidget ? widget : null; | ||
}) | ||
)).filter(Boolean); | ||
if (!validatedWidgets.length) { | ||
const code2 = `export default { | ||
return { zoneIsValid, zoneValue }; | ||
} | ||
async function validateWidget(file, zone) { | ||
let _zoneValue = null; | ||
const content = await import_promises.default.readFile(file, "utf-8"); | ||
const parserOptions = getParserOptions(file); | ||
let ast; | ||
try { | ||
ast = (0, import_parser.parse)(content, parserOptions); | ||
} catch (e) { | ||
return { valid: false, zone: _zoneValue }; | ||
} | ||
let hasDefaultExport = false; | ||
let hasNamedExport = false; | ||
try { | ||
traverse(ast, { | ||
ExportDefaultDeclaration(path2) { | ||
hasDefaultExport = isDefaultExportComponent(path2, ast); | ||
}, | ||
ExportNamedDeclaration(path2) { | ||
const { zoneIsValid, zoneValue } = validateWidgetConfig(path2, zone); | ||
hasNamedExport = zoneIsValid; | ||
_zoneValue = zoneValue; | ||
} | ||
}); | ||
} catch (err) { | ||
return { valid: false, zone: _zoneValue }; | ||
} | ||
return { valid: hasNamedExport && hasDefaultExport, zone: _zoneValue }; | ||
} | ||
async function generateWidgetEntrypoint(sources, zone) { | ||
const files = (await Promise.all( | ||
Array.from(sources).map(async (source) => crawl(`${source}/widgets`)) | ||
)).flat(); | ||
const validatedWidgets = (await Promise.all( | ||
files.map(async (widget) => { | ||
const { valid } = await validateWidget(widget, zone); | ||
return valid ? widget : null; | ||
}) | ||
)).filter(Boolean); | ||
if (!validatedWidgets.length) { | ||
const code2 = `export default { | ||
widgets: [], | ||
}`; | ||
return { module: generateModule(code2), paths: [] }; | ||
} | ||
const importString = validatedWidgets.map((path2, index) => `import WidgetExt${index} from "${path2}";`).join("\n"); | ||
const exportString = `export default { | ||
return { module: generateModule(code2), paths: [] }; | ||
} | ||
const importString = validatedWidgets.map((path2, index) => `import WidgetExt${index} from "${path2}";`).join("\n"); | ||
const exportString = `export default { | ||
widgets: [${validatedWidgets.map((_, index) => `{ Component: WidgetExt${index} }`).join(", ")}], | ||
}`; | ||
const code = `${importString} | ||
const code = `${importString} | ||
${exportString}`; | ||
return { module: generateModule(code), paths: validatedWidgets }; | ||
return { module: generateModule(code), paths: validatedWidgets }; | ||
} | ||
function validateRouteConfig(path2, resolveMenuItem) { | ||
const properties = getConfigObjectProperties(path2); | ||
if (!properties && resolveMenuItem) { | ||
return false; | ||
} | ||
function validateRouteConfig(path2, requireLink) { | ||
const properties = getProperties(path2); | ||
if (!properties) { | ||
return false; | ||
} | ||
const linkProperty = properties.find( | ||
(p) => p.type === "ObjectProperty" && p.key.type === "Identifier" && p.key.name === "link" | ||
); | ||
if (!linkProperty && !requireLink) { | ||
return true; | ||
} | ||
const linkValue = linkProperty?.value; | ||
if (!linkValue) { | ||
return false; | ||
} | ||
let labelIsValid = false; | ||
if (linkValue.properties.some( | ||
(p) => p.type === "ObjectProperty" && p.key.type === "Identifier" && p.key.name === "label" && p.value.type === "StringLiteral" | ||
)) { | ||
labelIsValid = true; | ||
} | ||
return labelIsValid; | ||
if (!properties) { | ||
return true; | ||
} | ||
async function validateRoute(file, requireLink) { | ||
const content = await import_promises.default.readFile(file, "utf-8"); | ||
const parserOptions = { | ||
sourceType: "module", | ||
plugins: ["jsx"] | ||
}; | ||
if (file.endsWith(".tsx")) { | ||
parserOptions.plugins?.push("typescript"); | ||
} | ||
let ast; | ||
try { | ||
ast = (0, import_parser.parse)(content, parserOptions); | ||
} catch (err) { | ||
logger.error("An error occured while validating a route.", { | ||
error: err, | ||
timestamp: true | ||
}); | ||
return false; | ||
} | ||
let hasDefaultExport = false; | ||
let hasNamedExport = false; | ||
try { | ||
traverse(ast, { | ||
ExportDefaultDeclaration(path2) { | ||
hasDefaultExport = validateDefaultExport(path2, ast); | ||
}, | ||
ExportNamedDeclaration(path2) { | ||
hasNamedExport = validateRouteConfig(path2, requireLink); | ||
} | ||
}); | ||
} catch (err) { | ||
logger.error("An error occured while validating a route.", { | ||
error: err, | ||
timestamp: true | ||
}); | ||
return false; | ||
} | ||
return hasDefaultExport && hasNamedExport; | ||
const labelProperty = properties.find( | ||
(p) => p.type === "ObjectProperty" && p.key.type === "Identifier" && p.key.name === "label" | ||
); | ||
const labelIsValid = !labelProperty || labelProperty.value.type === "StringLiteral"; | ||
return labelIsValid; | ||
} | ||
async function validateRoute(file, resolveMenuItem = false) { | ||
const content = await import_promises.default.readFile(file, "utf-8"); | ||
const parserOptions = getParserOptions(file); | ||
let ast; | ||
try { | ||
ast = (0, import_parser.parse)(content, parserOptions); | ||
} catch (_e) { | ||
return false; | ||
} | ||
function createPath(file) { | ||
return file.replace(/.*\/admin\/(routes|settings)/, "").replace(/\[([^\]]+)\]/g, ":$1").replace(/\/page\.(tsx|jsx)/, ""); | ||
let hasDefaultExport = false; | ||
let hasNamedExport = resolveMenuItem ? false : true; | ||
try { | ||
traverse(ast, { | ||
ExportDefaultDeclaration(path2) { | ||
hasDefaultExport = isDefaultExportComponent(path2, ast); | ||
}, | ||
ExportNamedDeclaration(path2) { | ||
hasNamedExport = validateRouteConfig(path2, resolveMenuItem); | ||
} | ||
}); | ||
} catch (_e) { | ||
return false; | ||
} | ||
async function generateRouteEntrypoint(get) { | ||
const files = (await Promise.all( | ||
Array.from(_sources).map( | ||
async (source) => traverseDirectory(`${source}/routes`, "page", { min: 1 }) | ||
) | ||
)).flat(); | ||
const validatedRoutes = (await Promise.all( | ||
files.map(async (route) => { | ||
const isValid = await validateRoute(route, get === "link"); | ||
return isValid ? route : null; | ||
}) | ||
)).filter(Boolean); | ||
if (!validatedRoutes.length) { | ||
const code2 = `export default { | ||
${get}s: [], | ||
return hasNamedExport && hasDefaultExport; | ||
} | ||
function createRoutePath(file) { | ||
return file.replace(/.*\/admin\/(routes|settings)/, "").replace(/\[([^\]]+)\]/g, ":$1").replace(/\/page\.(tsx|jsx)/, ""); | ||
} | ||
async function generateRouteEntrypoint(sources, type, base = "") { | ||
const files = (await Promise.all( | ||
Array.from(sources).map( | ||
async (source) => crawl(`${source}/routes`, "page", { min: 1 }) | ||
) | ||
)).flat(); | ||
const validatedRoutes = (await Promise.all( | ||
files.map(async (route) => { | ||
const valid = await validateRoute(route, type === "link"); | ||
return valid ? route : null; | ||
}) | ||
)).filter(Boolean); | ||
if (!validatedRoutes.length) { | ||
const code2 = `export default { | ||
${type}s: [], | ||
}`; | ||
return { module: generateModule(code2), paths: [] }; | ||
} | ||
const importString = validatedRoutes.map((path2, index) => { | ||
return get === "page" ? `import RouteExt${index} from "${path2}";` : `import { config as routeConfig${index} } from "${path2}";`; | ||
}).join("\n"); | ||
const exportString = `export default { | ||
${get}s: [${validatedRoutes.map((file, index) => { | ||
return get === "page" ? `{ path: "${createPath(file)}", file: "${file}" }` : `{ path: "${createPath(file)}", ...routeConfig${index}.link }`; | ||
}).join(", ")}], | ||
}`; | ||
const code = `${importString} | ||
${exportString}`; | ||
return { module: generateModule(code), paths: validatedRoutes }; | ||
return { module: generateModule(code2), paths: [] }; | ||
} | ||
async function validateSetting(file) { | ||
const content = await import_promises.default.readFile(file, "utf-8"); | ||
const parserOptions = { | ||
sourceType: "module", | ||
plugins: ["jsx"] | ||
}; | ||
if (file.endsWith(".tsx")) { | ||
parserOptions.plugins?.push("typescript"); | ||
} | ||
let ast; | ||
try { | ||
ast = (0, import_parser.parse)(content, parserOptions); | ||
} catch (err) { | ||
logger.error("An error occured while validating a setting.", { | ||
error: err, | ||
timestamp: true | ||
}); | ||
return false; | ||
} | ||
let hasDefaultExport = false; | ||
let hasNamedExport = false; | ||
try { | ||
traverse(ast, { | ||
ExportDefaultDeclaration(path2) { | ||
hasDefaultExport = validateDefaultExport(path2, ast); | ||
}, | ||
ExportNamedDeclaration(path2) { | ||
hasNamedExport = validateSettingConfig(path2); | ||
} | ||
}); | ||
} catch (err) { | ||
logger.error("An error occured while validating a setting.", { | ||
error: err, | ||
timestamp: true | ||
}); | ||
return false; | ||
} | ||
return hasDefaultExport && hasNamedExport; | ||
} | ||
function validateSettingConfig(path2) { | ||
const properties = getProperties(path2); | ||
if (!properties) { | ||
return false; | ||
} | ||
const cardProperty = properties.find( | ||
(p) => p.type === "ObjectProperty" && p.key.type === "Identifier" && p.key.name === "card" | ||
); | ||
if (!cardProperty) { | ||
return false; | ||
} | ||
const cardValue = cardProperty.value; | ||
let hasLabel = false; | ||
let hasDescription = false; | ||
if (cardValue.properties.some( | ||
(p) => p.type === "ObjectProperty" && p.key.type === "Identifier" && p.key.name === "label" && p.value.type === "StringLiteral" | ||
)) { | ||
hasLabel = true; | ||
} | ||
if (cardValue.properties.some( | ||
(p) => p.type === "ObjectProperty" && p.key.type === "Identifier" && p.key.name === "description" && p.value.type === "StringLiteral" | ||
)) { | ||
hasDescription = true; | ||
} | ||
return hasLabel && hasDescription; | ||
} | ||
async function generateSettingEntrypoint(get) { | ||
const files = (await Promise.all( | ||
Array.from(_sources).map( | ||
async (source) => traverseDirectory(`${source}/settings`, "page", { min: 1, max: 1 }) | ||
) | ||
)).flat(); | ||
const validatedSettings = (await Promise.all( | ||
files.map(async (setting) => { | ||
const isValid = await validateSetting(setting); | ||
return isValid ? setting : null; | ||
}) | ||
)).filter(Boolean); | ||
if (!validatedSettings.length) { | ||
const code2 = `export default { | ||
${get}s: [], | ||
}`; | ||
return { module: generateModule(code2), paths: [] }; | ||
} | ||
const importString = validatedSettings.map((path2, index) => { | ||
return get === "page" ? `import SettingExt${index} from "${path2}";` : `import { config as settingConfig${index} } from "${path2}";`; | ||
}).join("\n"); | ||
const exportString = `export default { | ||
${get}s: [${validatedSettings.map((file, index) => { | ||
return get === "page" ? `{ path: "${createPath(file)}", file: "${file}" }` : `{ path: "${createPath(file)}", ...settingConfig${index}.card }`; | ||
}).join(", ")}], | ||
const importString = validatedRoutes.map((path2, index) => { | ||
return type === "page" ? `import RouteExt${index} from "${path2}";` : `import { config as routeConfig${index} } from "${path2}";`; | ||
}).join("\n"); | ||
const exportString = `export default { | ||
${type}s: [${validatedRoutes.map((file, index) => { | ||
return type === "page" ? `{ path: "${createRoutePath(file)}", file: "${base + file}" }` : `{ path: "${createRoutePath(file)}", ...routeConfig${index} }`; | ||
}).join(", ")}], | ||
}`; | ||
const code = `${importString} | ||
const code = `${importString} | ||
${exportString}`; | ||
return { module: generateModule(code), paths: validatedSettings }; | ||
} | ||
async function loadModule(options) { | ||
switch (options.type) { | ||
return { module: generateModule(code), paths: validatedRoutes }; | ||
} | ||
var medusaVitePlugin = (options) => { | ||
const _extensionGraph = /* @__PURE__ */ new Map(); | ||
const _sources = new Set(options?.sources ?? []); | ||
let _base = ""; | ||
let server; | ||
let watcher; | ||
async function loadModule(options2) { | ||
switch (options2.type) { | ||
case "widget": { | ||
return await generateWidgetEntrypoint(options.get); | ||
return await generateWidgetEntrypoint(_sources, options2.get); | ||
} | ||
case "route": { | ||
return await generateRouteEntrypoint(options.get); | ||
} | ||
case "setting": { | ||
return await generateSettingEntrypoint(options.get); | ||
} | ||
case "route": | ||
return await generateRouteEntrypoint(_sources, options2.get, _base); | ||
default: | ||
return null; | ||
} | ||
} | ||
function getExtensionType(file) { | ||
const normalizedPath = import_path.default.normalize(file); | ||
if (normalizedPath.includes(import_path.default.normalize("/admin/widgets/"))) { | ||
return "widget"; | ||
} else if (normalizedPath.includes(import_path.default.normalize("/admin/routes/"))) { | ||
return "route"; | ||
} else if (normalizedPath.includes(import_path.default.normalize("/admin/settings/"))) { | ||
return "setting"; | ||
} else { | ||
return "none"; | ||
} | ||
} | ||
async function handleWidgetChange(file) { | ||
const { isValidWidget, zoneValue } = await validateWidget(file); | ||
if (!isValidWidget || !zoneValue) { | ||
_extensionGraph.delete(file); | ||
async function register(id, options2) { | ||
const result = await loadModule(options2); | ||
if (!result) { | ||
return; | ||
} | ||
const zoneValues = Array.isArray(zoneValue) ? zoneValue : [zoneValue]; | ||
for (const zone of zoneValues) { | ||
const zonePath = zone.replace(/\./g, "/"); | ||
const moduleId = `${VIRTUAL_PREFIX}${WIDGET_MODULE}${zonePath}`; | ||
const module2 = server.moduleGraph.getModuleById(moduleId); | ||
if (module2) { | ||
await server.reloadModule(module2); | ||
} | ||
const { module: module2, paths } = result; | ||
for (const path2 of paths) { | ||
const ids = _extensionGraph.get(path2) || /* @__PURE__ */ new Set(); | ||
ids.add(id); | ||
_extensionGraph.set(path2, ids); | ||
} | ||
return module2; | ||
} | ||
async function handleRouteChange(file) { | ||
const isValidRoute = await validateRoute(file, false); | ||
if (!isValidRoute) { | ||
_extensionGraph.delete(file); | ||
return; | ||
async function handleWidgetChange(file, event) { | ||
const { valid, zone } = await validateWidget(file); | ||
const zoneValues = Array.isArray(zone) ? zone : [zone]; | ||
if (event === "change") { | ||
if (!valid) { | ||
const extensionIds = _extensionGraph.get(file); | ||
_extensionGraph.delete(file); | ||
if (!extensionIds) { | ||
return; | ||
} | ||
for (const moduleId of extensionIds) { | ||
const module2 = server?.moduleGraph.getModuleById(moduleId); | ||
if (module2) { | ||
await server?.reloadModule(module2); | ||
} | ||
} | ||
return; | ||
} | ||
if (!_extensionGraph.has(file)) { | ||
const imports = /* @__PURE__ */ new Set(); | ||
for (const zoneValue of zoneValues) { | ||
const zonePath = (0, import_admin_shared.getWidgetImport)(zoneValue); | ||
const moduleId = (0, import_admin_shared.getVirtualId)(zonePath); | ||
const resolvedModuleId = (0, import_admin_shared.resolveVirtualId)(moduleId); | ||
const module2 = server?.moduleGraph.getModuleById(resolvedModuleId); | ||
if (module2) { | ||
imports.add(resolvedModuleId); | ||
await server?.reloadModule(module2); | ||
} | ||
} | ||
_extensionGraph.set(file, imports); | ||
} | ||
} | ||
for (const moduleId of ROUTE_MODULES) { | ||
const fullModuleId = `${VIRTUAL_PREFIX}${moduleId}`; | ||
const module2 = server.moduleGraph.getModuleById(fullModuleId); | ||
if (module2) { | ||
await server.reloadModule(module2); | ||
if (event === "add") { | ||
if (!valid) { | ||
return; | ||
} | ||
const imports = /* @__PURE__ */ new Set(); | ||
for (const zoneValue of zoneValues) { | ||
const zonePath = (0, import_admin_shared.getWidgetImport)(zoneValue); | ||
const moduleId = (0, import_admin_shared.getVirtualId)(zonePath); | ||
const resolvedModuleId = (0, import_admin_shared.resolveVirtualId)(moduleId); | ||
const module2 = server?.moduleGraph.getModuleById(resolvedModuleId); | ||
if (module2) { | ||
imports.add(resolvedModuleId); | ||
await server?.reloadModule(module2); | ||
} | ||
} | ||
_extensionGraph.set(file, imports); | ||
} | ||
} | ||
async function handleSettingChange(file) { | ||
const isValidSetting = await validateSetting(file); | ||
if (!isValidSetting) { | ||
_extensionGraph.delete(file); | ||
return; | ||
async function handleRouteChange(file, event) { | ||
const valid = await validateRoute(file); | ||
if (event === "change") { | ||
if (!valid) { | ||
const extensionIds = _extensionGraph.get(file); | ||
_extensionGraph.delete(file); | ||
if (!extensionIds) { | ||
return; | ||
} | ||
for (const moduleId of extensionIds) { | ||
const module2 = server?.moduleGraph.getModuleById(moduleId); | ||
if (module2) { | ||
await server?.reloadModule(module2); | ||
} | ||
} | ||
return; | ||
} | ||
if (!_extensionGraph.has(file)) { | ||
const moduleId = (0, import_admin_shared.getVirtualId)(file); | ||
const resolvedModuleId = (0, import_admin_shared.resolveVirtualId)(moduleId); | ||
const module2 = server?.moduleGraph.getModuleById(resolvedModuleId); | ||
if (module2) { | ||
await server?.reloadModule(module2); | ||
} | ||
} | ||
if (_extensionGraph.has(file)) { | ||
const modules = _extensionGraph.get(file); | ||
if (!modules) { | ||
return; | ||
} | ||
for (const moduleId of modules) { | ||
const module2 = server?.moduleGraph.getModuleById(moduleId); | ||
if (module2) { | ||
await server?.reloadModule(module2); | ||
} | ||
} | ||
} | ||
} | ||
for (const moduleId of SETTING_MODULE) { | ||
const fullModuleId = `${VIRTUAL_PREFIX}${moduleId}`; | ||
const module2 = server.moduleGraph.getModuleById(fullModuleId); | ||
if (module2) { | ||
await server.reloadModule(module2); | ||
if (event === "add") { | ||
if (!valid) { | ||
return; | ||
} | ||
const imports = /* @__PURE__ */ new Set(); | ||
for (const resolvedModuleId of import_admin_shared.RESOLVED_ROUTE_MODULES) { | ||
const module2 = server?.moduleGraph.getModuleById(resolvedModuleId); | ||
if (module2) { | ||
imports.add(resolvedModuleId); | ||
await server?.reloadModule(module2); | ||
} | ||
} | ||
_extensionGraph.set(file, imports); | ||
} | ||
} | ||
async function handleExtensionUnlink(file) { | ||
const moduleIds = _extensionGraph.get(file); | ||
async function handleAddOrChange(path2, event) { | ||
const type = getModuleType(path2); | ||
switch (type) { | ||
case "widget": | ||
await handleWidgetChange(path2, event); | ||
break; | ||
case "route": | ||
await handleRouteChange(path2, event); | ||
break; | ||
default: | ||
break; | ||
} | ||
} | ||
async function handleUnlink(path2) { | ||
const moduleIds = _extensionGraph.get(path2); | ||
_extensionGraph.delete(path2); | ||
if (!moduleIds) { | ||
@@ -589,73 +469,41 @@ return; | ||
for (const moduleId of moduleIds) { | ||
const module2 = server.moduleGraph.getModuleById(moduleId); | ||
const module2 = server?.moduleGraph.getModuleById(moduleId); | ||
if (module2) { | ||
_extensionGraph.delete(file); | ||
await server.reloadModule(module2); | ||
await server?.reloadModule(module2); | ||
} | ||
} | ||
} | ||
async function loadModuleAndUpdateGraph(id, options) { | ||
const { module: module2, paths } = await loadModule(options); | ||
for (const path2 of paths) { | ||
const ids = _extensionGraph.get(path2) || /* @__PURE__ */ new Set(); | ||
ids.add(id); | ||
_extensionGraph.set(path2, ids); | ||
} | ||
return module2; | ||
} | ||
return { | ||
name: "@medusajs/admin-vite-plugin", | ||
configureServer(s) { | ||
server = s; | ||
logger = s.config.logger; | ||
watcher = import_chokidar.default.watch(Array.from(_sources), { | ||
persistent: true, | ||
ignoreInitial: true | ||
enforce: "pre", | ||
configResolved(config) { | ||
if (config.server?.middlewareMode) { | ||
_base = `${config.base}@fs`; | ||
} | ||
}, | ||
configureServer(_server) { | ||
server = _server; | ||
watcher = _server.watcher; | ||
_sources.forEach((source) => { | ||
watcher?.add(source); | ||
}); | ||
watcher.on("add", async (file) => { | ||
const type = getExtensionType(file); | ||
if (type === "none") { | ||
return; | ||
watcher.on("all", async (event, path2) => { | ||
switch (event) { | ||
case "add": | ||
case "change": { | ||
await handleAddOrChange(path2, event); | ||
break; | ||
} | ||
case "unlinkDir": | ||
case "unlink": | ||
await handleUnlink(path2); | ||
break; | ||
default: | ||
break; | ||
} | ||
if (type === "widget") { | ||
await handleWidgetChange(file); | ||
return; | ||
} | ||
if (type === "route") { | ||
await handleRouteChange(file); | ||
return; | ||
} | ||
if (type === "setting") { | ||
await handleSettingChange(file); | ||
return; | ||
} | ||
return; | ||
}); | ||
watcher.on("change", async (file) => { | ||
const type = getExtensionType(file); | ||
if (type === "none") { | ||
return; | ||
} | ||
if (type === "widget") { | ||
await handleWidgetChange(file); | ||
return; | ||
} | ||
if (type === "route") { | ||
await handleRouteChange(file); | ||
return; | ||
} | ||
if (type === "setting") { | ||
await handleSettingChange(file); | ||
return; | ||
} | ||
return; | ||
}); | ||
watcher.on("unlink", async (file) => { | ||
await handleExtensionUnlink(file); | ||
return; | ||
}); | ||
}, | ||
resolveId(id) { | ||
if (MODULES.includes(id)) { | ||
return VIRTUAL_PREFIX + id; | ||
if (import_admin_shared.VIRTUAL_MODULES.includes(id)) { | ||
return (0, import_admin_shared.resolveVirtualId)(id); | ||
} | ||
@@ -665,21 +513,10 @@ return null; | ||
async load(id) { | ||
if (!id.startsWith(VIRTUAL_PREFIX)) { | ||
return null; | ||
if (import_admin_shared.RESOLVED_WIDGET_MODULES.includes(id)) { | ||
const zone = (0, import_admin_shared.getWidgetZone)(id); | ||
return register(id, { type: "widget", get: zone }); | ||
} | ||
const idNoPrefix = id.slice(VIRTUAL_PREFIX.length); | ||
const moduleMap = { | ||
[ROUTE_PAGE_MODULE]: { type: "route", get: "page" }, | ||
[ROUTE_LINK_MODULE]: { type: "route", get: "link" }, | ||
[SETTING_PAGE_MODULE]: { type: "setting", get: "page" }, | ||
[SETTING_CARD_MODULE]: { type: "setting", get: "card" } | ||
}; | ||
if (WIDGET_MODULES.includes(idNoPrefix)) { | ||
const zone = idNoPrefix.replace(WIDGET_MODULE, "").replace(/\//g, "."); | ||
return loadModuleAndUpdateGraph(id, { type: "widget", get: zone }); | ||
if (import_admin_shared.RESOLVED_ROUTE_MODULES.includes(id)) { | ||
const type = id.includes("link") ? "link" : "page"; | ||
return register(id, { type: "route", get: type }); | ||
} | ||
const moduleOptions = moduleMap[idNoPrefix]; | ||
if (moduleOptions) { | ||
return loadModuleAndUpdateGraph(id, moduleOptions); | ||
} | ||
return null; | ||
}, | ||
@@ -692,2 +529,5 @@ async closeBundle() { | ||
}; | ||
} | ||
}; | ||
// src/index.ts | ||
var src_default = medusaVitePlugin; |
{ | ||
"name": "@medusajs/admin-vite-plugin", | ||
"version": "0.0.2-preview-20240515175257", | ||
"version": "0.0.2-preview-20240523131811", | ||
"main": "dist/index.js", | ||
"module": "dist/index.mjs", | ||
"types": "dist/index.d.ts", | ||
"module": "dist/index.mjs", | ||
"exports": { | ||
".": { | ||
"import": "./dist/index.mjs", | ||
"require": "./dist/index.js" | ||
"require": "./dist/index.js", | ||
"types": "./dist/index.d.ts" | ||
} | ||
@@ -18,7 +19,7 @@ }, | ||
"scripts": { | ||
"build": "tsup" | ||
"build": "tsup", | ||
"watch": "tsup --watch" | ||
}, | ||
"devDependencies": { | ||
"@babel/types": "7.22.5", | ||
"@medusajs/admin-shared": "0.0.2-preview-20240515175257", | ||
"@types/babel__traverse": "7.20.5", | ||
@@ -36,2 +37,3 @@ "@types/node": "^20.10.4", | ||
"@babel/traverse": "7.23.5", | ||
"@medusajs/admin-shared": "0.0.2-preview-20240523131811", | ||
"chokidar": "3.5.3", | ||
@@ -38,0 +40,0 @@ "fdir": "6.1.1", |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
6
35226
7
1034
+ Added@medusajs/admin-shared@0.0.2-preview-20240523131811(transitive)