Skip to content

Commit

Permalink
feat(admin-bundler,admin-vite-plugin): Add support for built extensio…
Browse files Browse the repository at this point in the history
…n to admin-vite-plugin
  • Loading branch information
kasperkristensen committed Jan 8, 2025
1 parent e41ef05 commit d996087
Show file tree
Hide file tree
Showing 6 changed files with 115 additions and 33 deletions.
6 changes: 6 additions & 0 deletions .changeset/stupid-plums-buy.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@medusajs/admin-vite-plugin": patch
"@medusajs/admin-bundler": patch
---

feat(admin-bundler,admin-vite-plugin): Support loading built admin extension from admin-vite-plugin
2 changes: 2 additions & 0 deletions packages/admin/admin-vite-plugin/src/babel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import {
ObjectProperty,
SpreadElement,
StringLiteral,
VariableDeclarator,
} from "@babel/types"

/**
Expand Down Expand Up @@ -68,4 +69,5 @@ export type {
ParserOptions,
SpreadElement,
StringLiteral,
VariableDeclarator,
}
106 changes: 78 additions & 28 deletions packages/admin/admin-vite-plugin/src/routes/generate-menu-items.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,16 @@
import {
NESTED_ROUTE_POSITIONS,
NestedRoutePosition,
} from "@medusajs/admin-shared"
import fs from "fs/promises"
import { outdent } from "outdent"
import {
File,
isIdentifier,
isObjectProperty,
isStringLiteral,
Node,
ObjectProperty,
parse,
ParseResult,
traverse,
Expand All @@ -16,7 +23,6 @@ import {
normalizePath,
} from "../utils"
import { getRoute } from "./helpers"
import { NESTED_ROUTE_POSITIONS } from "@medusajs/admin-shared"

type RouteConfig = {
label: boolean
Expand Down Expand Up @@ -142,48 +148,47 @@ async function getRouteConfig(file: string): Promise<RouteConfig | null> {
}

let config: RouteConfig | null = null
let configFound = false

try {
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)
if (!properties) {
return
}

const hasProperty = (name: string) =>
properties.some(
(prop) => isObjectProperty(prop) && isIdentifier(prop.key, { name })
)
config = processConfigProperties(properties, file)

const hasLabel = hasProperty("label")
if (!hasLabel) {
if (config) {
configFound = true
}
},
/**
* For unbundled files, the `config` will always be a named export.
*/
ExportNamedDeclaration(path) {
if (configFound) {
return
}

const nested = properties.find(
(prop) =>
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,
}
)
const properties = getConfigObjectProperties(path)
if (!properties) {
return
}

config = {
label: hasLabel,
icon: hasProperty("icon"),
nested: nestedValue,
config = processConfigProperties(properties, file)

if (config) {
configFound = true
}
},
})
Expand All @@ -197,6 +202,51 @@ async function getRouteConfig(file: string): Promise<RouteConfig | null> {
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

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 {
return `RouteConfig${index}`
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ type RouteResult = {
export async function generateRoutes(sources: Set<string>) {
const files = await getFilesFromSources(sources)
const results = await getRouteResults(files)

const imports = results.map((result) => result.imports).flat()
const code = generateCode(results)

Expand Down
11 changes: 9 additions & 2 deletions packages/admin/admin-vite-plugin/src/routes/helpers.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,16 @@
import { normalizePath } from "../utils"
import { normalizePath, VALID_FILE_EXTENSIONS } from "../utils"

export function getRoute(file: string): string {
const importPath = normalizePath(file)
return importPath
.replace(/.*\/admin\/(routes)/, "")
.replace(/\[([^\]]+)\]/g, ":$1")
.replace(/\/page\.(tsx|jsx)/, "")
.replace(
new RegExp(
`/page\\.(${VALID_FILE_EXTENSIONS.map((ext) => ext.slice(1)).join(
"|"
)})$`
),
""
)
}
22 changes: 20 additions & 2 deletions packages/admin/admin-vite-plugin/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
type ExportNamedDeclaration,
type NodePath,
type ParserOptions,
type VariableDeclarator,
} from "./babel"

export function normalizePath(file: string) {
Expand Down Expand Up @@ -48,7 +49,7 @@ export function generateModule(code: string) {
}
}

const VALID_FILE_EXTENSIONS = [".tsx", ".jsx", ".js"]
export const VALID_FILE_EXTENSIONS = [".tsx", ".jsx", ".js"]

/**
* Crawls a directory and returns all files that match the criteria.
Expand Down Expand Up @@ -96,8 +97,25 @@ export async function crawl(
* Extracts and returns the properties of a `config` object from a named export declaration.
*/
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

if (isVariableDeclaration(declaration)) {
Expand Down

0 comments on commit d996087

Please sign in to comment.