fix(framework): handle deprecated Dirent path (#10179)

What:

- `Dirent` class from `NodeJS` has different properties in different versions, causing issues in our loaders.
- Util `readDirRecursive` was introduced, avoiding old node versions to break

FIXES: https://github.com/medusajs/medusa/issues/8419
This commit is contained in:
Carlos R. L. Rodrigues authored and GitHub committed 2024-11-20 20:25:48 +00:00
1 parent ec1ab4db87
commit 42c08fa8e0
11 files changed
+153 -73

No files matched your search

+6
View File
@@ -0,0 +1,6 @@
---
"@medusajs/framework": patch
"@medusajs/utils": patch
---
Fix loaders path
@@ -1,3 +1,4 @@
import { trackFeatureFlag } from "@medusajs/telemetry"
import { import {
ContainerRegistrationKeys, ContainerRegistrationKeys,
dynamicImport, dynamicImport,
@@ -7,15 +8,14 @@ import {
isString, isString,
isTruthy, isTruthy,
objectFromStringPath, objectFromStringPath,
readDirRecursive,
} from "@medusajs/utils" } from "@medusajs/utils"
import { trackFeatureFlag } from "@medusajs/telemetry" import { asFunction } from "awilix"
import { join, normalize } from "path" import { join, normalize } from "path"
import { configManager } from "../config"
import { container } from "../container"
import { logger } from "../logger" import { logger } from "../logger"
import { FlagSettings } from "./types" import { FlagSettings } from "./types"
import { container } from "../container"
import { asFunction } from "awilix"
import { configManager } from "../config"
import { readdir } from "fs/promises"
export const featureFlagRouter = new FlagRouter({}) export const featureFlagRouter = new FlagRouter({})
@@ -95,36 +95,34 @@ export async function featureFlagsLoader(
const flagDir = normalize(sourcePath) const flagDir = normalize(sourcePath)
await readdir(flagDir, { recursive: true, withFileTypes: true }).then( await readDirRecursive(flagDir).then(async (files) => {
async (files) => { if (!files?.length) {
if (!files?.length) { return
}
files.map(async (file) => {
if (file.isDirectory()) {
return await featureFlagsLoader(join(flagDir, file.name))
}
if (
excludedExtensions.some((ext) => file.name.endsWith(ext)) ||
excludedFiles.includes(file.name)
) {
return return
} }
files.map(async (file) => { const fileExports = await dynamicImport(join(flagDir, file.name))
if (file.isDirectory()) { const featureFlag = fileExports.default
return await featureFlagsLoader(join(flagDir, file.name))
}
if ( if (!featureFlag) {
excludedExtensions.some((ext) => file.name.endsWith(ext)) ||
excludedFiles.includes(file.name)
) {
return
}
const fileExports = await dynamicImport(join(flagDir, file.name))
const featureFlag = fileExports.default
if (!featureFlag) {
return
}
registerFlag(featureFlag, projectConfigFlags)
return return
}) }
}
) registerFlag(featureFlag, projectConfigFlags)
return
})
})
return featureFlagRouter return featureFlagRouter
} }
+5 -6
View File
@@ -2,6 +2,7 @@ import {
dynamicImport, dynamicImport,
parseCorsOrigins, parseCorsOrigins,
promiseAll, promiseAll,
readDirRecursive,
resolveExports, resolveExports,
wrapHandler, wrapHandler,
} from "@medusajs/utils" } from "@medusajs/utils"
@@ -14,6 +15,7 @@ import {
text, text,
urlencoded, urlencoded,
} from "express" } from "express"
import { Dirent } from "fs"
import { readdir } from "fs/promises" import { readdir } from "fs/promises"
import { extname, join, parse, sep } from "path" import { extname, join, parse, sep } from "path"
import { configManager } from "../config" import { configManager } from "../config"
@@ -527,11 +529,8 @@ export class ApiRoutesLoader {
protected async createRoutesMap(): Promise<void> { protected async createRoutesMap(): Promise<void> {
await promiseAll( await promiseAll(
await readdir(this.#sourceDir, { await readDirRecursive(this.#sourceDir).then((entries) => {
recursive: true, const fileEntries = entries.filter((entry: Dirent) => {
withFileTypes: true,
}).then((entries) => {
const fileEntries = entries.filter((entry) => {
const fullPathFromSource = join(entry.path, entry.name).replace( const fullPathFromSource = join(entry.path, entry.name).replace(
this.#sourceDir, this.#sourceDir,
"" ""
@@ -549,7 +548,7 @@ export class ApiRoutesLoader {
) )
}) })
return fileEntries.map(async (entry) => { return fileEntries.map(async (entry: Dirent) => {
const path = join(entry.path, entry.name) const path = join(entry.path, entry.name)
return this.createRoutesDescriptor(path) return this.createRoutesDescriptor(path)
}) })
@@ -5,13 +5,15 @@ import {
isObject, isObject,
MedusaError, MedusaError,
promiseAll, promiseAll,
readDirRecursive,
} from "@medusajs/utils" } from "@medusajs/utils"
import { import {
createStep, createStep,
createWorkflow, createWorkflow,
StepResponse, StepResponse,
} from "@medusajs/workflows-sdk" } from "@medusajs/workflows-sdk"
import { access, readdir } from "fs/promises" import { Dirent } from "fs"
import { access } from "fs/promises"
import { join } from "path" import { join } from "path"
import { logger } from "../logger" import { logger } from "../logger"
@@ -138,11 +140,8 @@ export class JobLoader {
return return
} }
return await readdir(sourcePath, { return await readDirRecursive(sourcePath).then(async (entries) => {
recursive: true, const fileEntries = entries.filter((entry: Dirent) => {
withFileTypes: true,
}).then(async (entries) => {
const fileEntries = entries.filter((entry) => {
return ( return (
!entry.isDirectory() && !entry.isDirectory() &&
!this.#excludes.some((exclude) => exclude.test(entry.name)) !this.#excludes.some((exclude) => exclude.test(entry.name))
@@ -152,7 +151,7 @@ export class JobLoader {
logger.debug(`Registering jobs from ${sourcePath}.`) logger.debug(`Registering jobs from ${sourcePath}.`)
return await promiseAll( return await promiseAll(
fileEntries.map(async (entry) => { fileEntries.map(async (entry: Dirent) => {
const fullPath = join(entry.path, entry.name) const fullPath = join(entry.path, entry.name)
const module_ = await dynamicImport(fullPath) const module_ = await dynamicImport(fullPath)
@@ -1,7 +1,8 @@
import { dynamicImport, promiseAll } from "@medusajs/utils" import { dynamicImport, promiseAll, readDirRecursive } from "@medusajs/utils"
import { logger } from "../logger" import { Dirent } from "fs"
import { access, readdir } from "fs/promises" import { access } from "fs/promises"
import { join } from "path" import { join } from "path"
import { logger } from "../logger"
export class LinkLoader { export class LinkLoader {
/** /**
@@ -43,11 +44,8 @@ export class LinkLoader {
return return
} }
return await readdir(sourcePath, { return await readDirRecursive(sourcePath).then(async (entries) => {
recursive: true, const fileEntries = entries.filter((entry: Dirent) => {
withFileTypes: true,
}).then(async (entries) => {
const fileEntries = entries.filter((entry) => {
return ( return (
!entry.isDirectory() && !entry.isDirectory() &&
!this.#excludes.some((exclude) => exclude.test(entry.name)) !this.#excludes.some((exclude) => exclude.test(entry.name))
@@ -57,7 +55,7 @@ export class LinkLoader {
logger.debug(`Registering links from ${sourcePath}.`) logger.debug(`Registering links from ${sourcePath}.`)
return await promiseAll( return await promiseAll(
fileEntries.map(async (entry) => { fileEntries.map(async (entry: Dirent) => {
const fullPath = join(entry.path, entry.name) const fullPath = join(entry.path, entry.name)
return await dynamicImport(fullPath) return await dynamicImport(fullPath)
}) })
@@ -4,11 +4,13 @@ import {
kebabCase, kebabCase,
Modules, Modules,
promiseAll, promiseAll,
readDirRecursive,
resolveExports, resolveExports,
} from "@medusajs/utils" } from "@medusajs/utils"
import { access, readdir } from "fs/promises" import { access } from "fs/promises"
import { join, parse } from "path" import { join, parse } from "path"
import { Dirent } from "fs"
import { configManager } from "../config" import { configManager } from "../config"
import { container } from "../container" import { container } from "../container"
import { logger } from "../logger" import { logger } from "../logger"
@@ -137,10 +139,7 @@ export class SubscriberLoader {
} }
private async createMap(dirPath: string) { private async createMap(dirPath: string) {
const promises = await readdir(dirPath, { const promises = await readDirRecursive(dirPath).then(async (entries) => {
recursive: true,
withFileTypes: true,
}).then(async (entries) => {
const fileEntries = entries.filter((entry) => { const fileEntries = entries.filter((entry) => {
return ( return (
!entry.isDirectory() && !entry.isDirectory() &&
@@ -150,7 +149,7 @@ export class SubscriberLoader {
logger.debug(`Registering subscribers from ${dirPath}.`) logger.debug(`Registering subscribers from ${dirPath}.`)
return fileEntries.flatMap(async (entry) => { return fileEntries.flatMap(async (entry: Dirent) => {
const fullPath = join(entry.path, entry.name) const fullPath = join(entry.path, entry.name)
return await this.createDescriptor(fullPath) return await this.createDescriptor(fullPath)
}) })
@@ -1,7 +1,8 @@
import { dynamicImport, promiseAll } from "@medusajs/utils" import { dynamicImport, promiseAll, readDirRecursive } from "@medusajs/utils"
import { logger } from "../logger" import { Dirent } from "fs"
import { access, readdir } from "fs/promises" import { access } from "fs/promises"
import { join } from "path" import { join } from "path"
import { logger } from "../logger"
export class WorkflowLoader { export class WorkflowLoader {
/** /**
@@ -43,11 +44,8 @@ export class WorkflowLoader {
return return
} }
return await readdir(sourcePath, { return await readDirRecursive(sourcePath).then(async (entries) => {
recursive: true, const fileEntries = entries.filter((entry: Dirent) => {
withFileTypes: true,
}).then(async (entries) => {
const fileEntries = entries.filter((entry) => {
return ( return (
!entry.isDirectory() && !entry.isDirectory() &&
!this.#excludes.some((exclude) => exclude.test(entry.name)) !this.#excludes.some((exclude) => exclude.test(entry.name))
@@ -57,7 +55,7 @@ export class WorkflowLoader {
logger.debug(`Registering workflows from ${sourcePath}.`) logger.debug(`Registering workflows from ${sourcePath}.`)
return await promiseAll( return await promiseAll(
fileEntries.map(async (entry) => { fileEntries.map(async (entry: Dirent) => {
const fullPath = join(entry.path, entry.name) const fullPath = join(entry.path, entry.name)
return await dynamicImport(fullPath) return await dynamicImport(fullPath)
}) })
@@ -0,0 +1,61 @@
import { readdir } from "fs/promises"
import { join } from "path"
import { readDirRecursive } from "../read-dir-recursive"
jest.mock("fs/promises")
jest.mock("path")
describe("readDirRecursive", () => {
it("should recursively read directories and return all entries", async () => {
const mockReaddir = readdir as jest.MockedFunction<typeof readdir>
const mockJoin = join as jest.MockedFunction<typeof join>
// dir structure
const dirStructure = {
"/root": [
{ name: "file1.txt", isDirectory: () => false },
{ name: "subdir", isDirectory: () => true },
],
"/root/subdir": [
{ name: "file2.txt", isDirectory: () => false },
{ name: "nested", isDirectory: () => true },
],
"/root/subdir/nested": [{ name: "file3.txt", isDirectory: () => false }],
}
mockReaddir.mockImplementation((dir) => {
return dirStructure[dir as string] ?? []
})
mockJoin.mockImplementation((...paths) => paths.join("/"))
const result = await readDirRecursive("/root")
expect(result).toEqual([
{ name: "file1.txt", isDirectory: expect.any(Function), path: "/root" },
{ name: "subdir", isDirectory: expect.any(Function), path: "/root" },
{
name: "file2.txt",
isDirectory: expect.any(Function),
path: "/root/subdir",
},
{
name: "nested",
isDirectory: expect.any(Function),
path: "/root/subdir",
},
{
name: "file3.txt",
isDirectory: expect.any(Function),
path: "/root/subdir/nested",
},
])
expect(mockReaddir).toHaveBeenCalledWith("/root", { withFileTypes: true })
expect(mockReaddir).toHaveBeenCalledWith("/root/subdir", {
withFileTypes: true,
})
expect(mockReaddir).toHaveBeenCalledWith("/root/subdir/nested", {
withFileTypes: true,
})
})
})
@@ -1,4 +1,3 @@
import { dirname, join } from "path"
import { import {
constants, constants,
type Dirent, type Dirent,
@@ -8,8 +7,10 @@ import {
type StatOptions, type StatOptions,
type WriteFileOptions, type WriteFileOptions,
} from "fs" } from "fs"
import { dirname, join } from "path"
import { readDirRecursive } from "./read-dir-recursive"
const { rm, stat, mkdir, access, readdir, readFile, writeFile } = promises const { rm, stat, mkdir, access, readFile, writeFile } = promises
export type JSONFileOptions = WriteFileOptions & { export type JSONFileOptions = WriteFileOptions & {
spaces?: number | string spaces?: number | string
@@ -127,10 +128,7 @@ export class FileSystem {
*/ */
readDir(dirPath?: string): Promise<Dirent[]> { readDir(dirPath?: string): Promise<Dirent[]> {
const location = dirPath ? this.makePath(dirPath) : this.basePath const location = dirPath ? this.makePath(dirPath) : this.basePath
return readdir(location, { return readDirRecursive(location)
recursive: true,
withFileTypes: true,
})
} }
/** /**
+1
View File
@@ -56,6 +56,7 @@ export * from "./pick-value-from-object"
export * from "./plurailze" export * from "./plurailze"
export * from "./prefix-array-items" export * from "./prefix-array-items"
export * from "./promise-all" export * from "./promise-all"
export * from "./read-dir-recursive"
export * from "./remote-query-object-from-string" export * from "./remote-query-object-from-string"
export * from "./remote-query-object-to-string" export * from "./remote-query-object-to-string"
export * from "./remove-nullisih" export * from "./remove-nullisih"
@@ -0,0 +1,23 @@
import { Dirent } from "fs"
import { readdir } from "fs/promises"
import { join } from "path"
export async function readDirRecursive(dir: string): Promise<Dirent[]> {
let allEntries: Dirent[] = []
const readRecursive = async (dir) => {
const entries = await readdir(dir, { withFileTypes: true })
for (const entry of entries) {
const fullPath = join(dir, entry.name)
entry.path = dir
allEntries.push(entry)
if (entry.isDirectory()) {
await readRecursive(fullPath)
}
}
}
await readRecursive(dir)
return allEntries
}