Files
medusa-store/packages/admin/admin-vite-plugin/src/custom-fields/generate-custom-field-links.ts
Kasper Fabricius Kristensen 813efeae51 fix(admin-vite-plugin): Normalize file paths and add tests (#9595)
**What**
- #9338 had a regression which caused the import path in some virtual modules to be invalid on Windows.
- This PR fixes the issue so we now again create the correct import paths, and adds tests to prevent this from slipping in again.
2024-10-15 16:48:56 +00:00

169 lines
3.6 KiB
TypeScript

import { CustomFieldModel } from "@medusajs/admin-shared"
import fs from "fs/promises"
import {
ExportDefaultDeclaration,
File,
isIdentifier,
isObjectProperty,
NodePath,
ObjectProperty,
parse,
ParseResult,
traverse,
} from "../babel"
import { logger } from "../logger"
import { crawl, getParserOptions, normalizePath } from "../utils"
import { getConfigArgument, getModel } from "./helpers"
type ParsedCustomFieldLink = {
import: string
model: CustomFieldModel
link: string
}
export async function generateCustomFieldLinks(sources: Set<string>) {
const files = await getFilesFromSources(sources)
const results = await getCustomFieldLinkResults(files)
const imports = results.map((result) => result.import)
const code = generateCode(results)
return {
imports,
code,
}
}
async function getFilesFromSources(sources: Set<string>): Promise<string[]> {
const files = (
await Promise.all(
Array.from(sources).map(async (source) =>
crawl(`${source}/custom-fields`)
)
)
).flat()
return files
}
function generateCode(results: ParsedCustomFieldLink[]): string {
const groupedByModel = new Map<CustomFieldModel, ParsedCustomFieldLink[]>()
results.forEach((result) => {
const model = result.model
if (!groupedByModel.has(model)) {
groupedByModel.set(model, [])
}
groupedByModel.get(model)!.push(result)
})
const segments: string[] = []
groupedByModel.forEach((results, model) => {
const links = results.map((result) => result.link).join(",\n")
segments.push(`
${model}: [
${links}
],
`)
})
return `
links: {
${segments.join("\n")}
}
`
}
async function getCustomFieldLinkResults(
files: string[]
): Promise<ParsedCustomFieldLink[]> {
return (
await Promise.all(files.map(async (file, index) => parseFile(file, index)))
).filter(Boolean) as ParsedCustomFieldLink[]
}
async function parseFile(
file: string,
index: number
): Promise<ParsedCustomFieldLink | null> {
const content = await fs.readFile(file, "utf8")
let ast: ParseResult<File>
try {
ast = parse(content, getParserOptions(file))
} catch (e) {
logger.error(`An error occurred while parsing the file`, { file, error: e })
return null
}
const import_ = generateImport(file, index)
let link: string | null = null
let model: CustomFieldModel | null = null
try {
traverse(ast, {
ExportDefaultDeclaration(path) {
const _model = getModel(path, file)
if (!_model) {
return
}
model = _model
link = getLink(path, index, file)
},
})
} catch (err) {
logger.error(`An error occurred while traversing the file.`, {
file,
error: err,
})
return null
}
if (!link || !model) {
return null
}
return {
import: import_,
model,
link,
}
}
function generateCustomFieldConfigName(index: number): string {
return `CustomFieldConfig${index}`
}
function generateImport(file: string, index: number): string {
const path = normalizePath(file)
return `import ${generateCustomFieldConfigName(index)} from "${path}"`
}
function getLink(
path: NodePath<ExportDefaultDeclaration>,
index: number,
file: string
): string | null {
const configArgument = getConfigArgument(path)
if (!configArgument) {
return null
}
const linkProperty = configArgument.properties.find(
(p) => isObjectProperty(p) && isIdentifier(p.key, { name: "link" })
) as ObjectProperty | undefined
if (!linkProperty) {
logger.warn(`'link' is missing.`, { file })
return null
}
const import_ = generateCustomFieldConfigName(index)
return `${import_}.link`
}