feat(medusa, types): Improve DX of model extensions (#4398)
This commit is contained in:
1 parent
1e88b4d5d9
commit
9760d4a96c
17 files changed
+405
-39
No files matched your search
@@ -0,0 +1,6 @@
|
|||||||
|
---
|
||||||
|
"@medusajs/medusa": patch
|
||||||
|
"@medusajs/utils": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
feat(medusa, utils): improve devx for core entity customizations
|
||||||
@@ -18,6 +18,7 @@ module.exports = {
|
|||||||
transform: {
|
transform: {
|
||||||
"^.+\\.[jt]s?$": "ts-jest",
|
"^.+\\.[jt]s?$": "ts-jest",
|
||||||
},
|
},
|
||||||
|
modulePathIgnorePatterns: ["__fixtures__"],
|
||||||
testEnvironment: `node`,
|
testEnvironment: `node`,
|
||||||
moduleFileExtensions: [`js`, `jsx`, `ts`, `tsx`, `json`],
|
moduleFileExtensions: [`js`, `jsx`, `ts`, `tsx`, `json`],
|
||||||
setupFilesAfterEnv: ["<rootDir>/setupTests.js"],
|
setupFilesAfterEnv: ["<rootDir>/setupTests.js"],
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
module.exports = {
|
||||||
|
featureFlags: {},
|
||||||
|
projectConfig: {
|
||||||
|
database_url: "postgres://localhost/medusa-store",
|
||||||
|
database_logging: false
|
||||||
|
},
|
||||||
|
plugins: [],
|
||||||
|
modules: {},
|
||||||
|
};
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
import { Column, Entity } from "typeorm"
|
||||||
|
import {
|
||||||
|
// alias the core entity to not cause a naming conflict
|
||||||
|
Product as MedusaProduct,
|
||||||
|
} from "@medusajs/medusa"
|
||||||
|
|
||||||
|
@Entity()
|
||||||
|
export class Product extends MedusaProduct {
|
||||||
|
@Column()
|
||||||
|
custom_attribute: string = 'test'
|
||||||
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
import { createMedusaContainer } from "medusa-core-utils"
|
||||||
|
import path from "path"
|
||||||
|
import { asValue } from "awilix"
|
||||||
|
|
||||||
|
import modelsLoader from "../models"
|
||||||
|
|
||||||
|
describe("models loader", () => {
|
||||||
|
const container = createMedusaContainer()
|
||||||
|
container.register("db_entities", asValue([]))
|
||||||
|
let models
|
||||||
|
let error
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
try {
|
||||||
|
models = await modelsLoader({
|
||||||
|
container,
|
||||||
|
isTest: true,
|
||||||
|
coreTestPathGlob: "../models/{product,product-variant}.ts",
|
||||||
|
rootDirectory: path.join(__dirname, "__fixtures__/customizations"),
|
||||||
|
extensionPathGlob: "models/{product,product-variant}.ts",
|
||||||
|
})
|
||||||
|
} catch (e) {
|
||||||
|
error = e
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
it("error should be falsy & register 2 models", () => {
|
||||||
|
expect(error).toBeFalsy()
|
||||||
|
expect(models).toHaveLength(2)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("ensure that the product model is an extended model", () => {
|
||||||
|
const productModel = models.find((model) => model.name === "Product")
|
||||||
|
|
||||||
|
expect(new productModel().custom_attribute).toEqual("test")
|
||||||
|
})
|
||||||
|
|
||||||
|
it("ensure that the extended product model is registered in db_entities", () => {
|
||||||
|
const entities = container.resolve("db_entities_STORE")
|
||||||
|
const productModelResolver = entities.find(
|
||||||
|
(entity) => entity.resolve().name === "Product"
|
||||||
|
)
|
||||||
|
const productModel = productModelResolver.resolve()
|
||||||
|
|
||||||
|
expect(new productModel().custom_attribute).toEqual("test")
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
import { createMedusaContainer } from "medusa-core-utils"
|
||||||
|
import path from "path"
|
||||||
|
import { asValue } from "awilix"
|
||||||
|
|
||||||
|
import { registerPluginModels } from "../plugins"
|
||||||
|
import configModule from './__fixtures__/customizations/medusa-config'
|
||||||
|
|
||||||
|
describe("plugin models loader", () => {
|
||||||
|
const container = createMedusaContainer()
|
||||||
|
container.register("db_entities", asValue([]))
|
||||||
|
|
||||||
|
let models
|
||||||
|
let error
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
try {
|
||||||
|
await registerPluginModels({
|
||||||
|
configModule: configModule,
|
||||||
|
container,
|
||||||
|
rootDirectory: path.join(__dirname, '__fixtures__/customizations'),
|
||||||
|
extensionDirectoryPath: './',
|
||||||
|
pathGlob: "/models/*.ts",
|
||||||
|
})
|
||||||
|
} catch (e) {
|
||||||
|
error = e
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
it("ensure that the product model is registered from the user's respository", () => {
|
||||||
|
const entities = container.resolve("db_entities_STORE")
|
||||||
|
const productModelResolver = entities.find(entity => entity.resolve().name === 'Product')
|
||||||
|
const productModel = productModelResolver.resolve()
|
||||||
|
|
||||||
|
expect((new productModel()).custom_attribute).toEqual("test")
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
import glob from "glob"
|
||||||
|
import path from "path"
|
||||||
|
import { EntitySchema } from "typeorm"
|
||||||
|
|
||||||
|
import { formatRegistrationName } from "../../utils/format-registration-name"
|
||||||
|
import { ClassConstructor } from "../../types/global"
|
||||||
|
|
||||||
|
type GetModelExtensionMapParams = {
|
||||||
|
directory?: string
|
||||||
|
pathGlob?: string
|
||||||
|
config: Record<string, any>
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getModelExtensionsMap({
|
||||||
|
directory,
|
||||||
|
pathGlob,
|
||||||
|
config = {},
|
||||||
|
}: GetModelExtensionMapParams): Map<
|
||||||
|
string,
|
||||||
|
ClassConstructor<unknown> | EntitySchema
|
||||||
|
> {
|
||||||
|
const modelExtensionsMap = new Map<
|
||||||
|
string,
|
||||||
|
ClassConstructor<unknown> | EntitySchema
|
||||||
|
>()
|
||||||
|
const fullPathGlob =
|
||||||
|
directory && pathGlob ? path.join(directory, pathGlob) : null
|
||||||
|
|
||||||
|
const modelExtensions = fullPathGlob
|
||||||
|
? glob.sync(fullPathGlob, {
|
||||||
|
cwd: directory,
|
||||||
|
ignore: ["index.js", "index.js.map"],
|
||||||
|
})
|
||||||
|
: []
|
||||||
|
|
||||||
|
modelExtensions.forEach((modelExtensionPath) => {
|
||||||
|
const extendedModel = require(modelExtensionPath) as
|
||||||
|
| ClassConstructor<unknown>
|
||||||
|
| EntitySchema
|
||||||
|
| undefined
|
||||||
|
|
||||||
|
if (extendedModel) {
|
||||||
|
Object.entries(extendedModel).map(
|
||||||
|
([_key, val]: [string, ClassConstructor<unknown> | EntitySchema]) => {
|
||||||
|
if (typeof val === "function" || val instanceof EntitySchema) {
|
||||||
|
if (config.register) {
|
||||||
|
const name = formatRegistrationName(modelExtensionPath)
|
||||||
|
|
||||||
|
modelExtensionsMap.set(name, val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return modelExtensionsMap
|
||||||
|
}
|
||||||
@@ -67,7 +67,7 @@ export default async ({
|
|||||||
|
|
||||||
const modelsActivity = Logger.activity(`Initializing models${EOL}`)
|
const modelsActivity = Logger.activity(`Initializing models${EOL}`)
|
||||||
track("MODELS_INIT_STARTED")
|
track("MODELS_INIT_STARTED")
|
||||||
modelsLoader({ container })
|
modelsLoader({ container, rootDirectory })
|
||||||
const mAct = Logger.success(modelsActivity, "Models initialized") || {}
|
const mAct = Logger.success(modelsActivity, "Models initialized") || {}
|
||||||
track("MODELS_INIT_COMPLETED", { duration: mAct.duration })
|
track("MODELS_INIT_COMPLETED", { duration: mAct.duration })
|
||||||
|
|
||||||
|
|||||||
@@ -1,34 +1,74 @@
|
|||||||
import formatRegistrationName from "../utils/format-registration-name"
|
import {
|
||||||
|
formatRegistrationName,
|
||||||
|
formatRegistrationNameWithoutNamespace,
|
||||||
|
} from "../utils/format-registration-name"
|
||||||
|
import { getModelExtensionsMap } from "./helpers/get-model-extension-map"
|
||||||
import glob from "glob"
|
import glob from "glob"
|
||||||
import path from "path"
|
import path from "path"
|
||||||
import { ClassConstructor, MedusaContainer } from "../types/global"
|
import { ClassConstructor, MedusaContainer } from "../types/global"
|
||||||
import { EntitySchema } from "typeorm"
|
import { EntitySchema } from "typeorm"
|
||||||
import { asClass, asValue } from "awilix"
|
import { asClass, asValue } from "awilix"
|
||||||
|
|
||||||
|
type ModelLoaderParams = {
|
||||||
|
container: MedusaContainer
|
||||||
|
isTest?: boolean
|
||||||
|
rootDirectory?: string
|
||||||
|
corePathGlob?: string
|
||||||
|
coreTestPathGlob?: string
|
||||||
|
extensionPathGlob?: string
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* Registers all models in the model directory
|
* Registers all models in the model directory
|
||||||
*/
|
*/
|
||||||
export default (
|
export default (
|
||||||
{ container, isTest }: { container: MedusaContainer; isTest?: boolean },
|
{
|
||||||
|
container,
|
||||||
|
isTest,
|
||||||
|
rootDirectory,
|
||||||
|
corePathGlob = "../models/*.js",
|
||||||
|
coreTestPathGlob = "../models/*.ts",
|
||||||
|
extensionPathGlob = "dist/models/*.js",
|
||||||
|
}: ModelLoaderParams,
|
||||||
config = { register: true }
|
config = { register: true }
|
||||||
) => {
|
) => {
|
||||||
const corePath = isTest ? "../models/*.ts" : "../models/*.js"
|
const coreModelsGlob = isTest ? coreTestPathGlob : corePathGlob
|
||||||
const coreFull = path.join(__dirname, corePath)
|
const coreModelsFullGlob = path.join(__dirname, coreModelsGlob)
|
||||||
|
|
||||||
const models: (ClassConstructor<unknown> | EntitySchema)[] = []
|
const models: (ClassConstructor<unknown> | EntitySchema)[] = []
|
||||||
|
|
||||||
const core = glob.sync(coreFull, {
|
const coreModels = glob.sync(coreModelsFullGlob, {
|
||||||
cwd: __dirname,
|
cwd: __dirname,
|
||||||
ignore: ["index.js", "index.ts"],
|
ignore: ["index.js", "index.ts", "index.js.map"],
|
||||||
})
|
})
|
||||||
core.forEach((fn) => {
|
|
||||||
const loaded = require(fn) as ClassConstructor<unknown> | EntitySchema
|
const modelExtensionsMap = getModelExtensionsMap({
|
||||||
|
directory: rootDirectory,
|
||||||
|
pathGlob: extensionPathGlob,
|
||||||
|
config,
|
||||||
|
})
|
||||||
|
|
||||||
|
coreModels.forEach((modelPath) => {
|
||||||
|
const loaded = require(modelPath) as
|
||||||
|
| ClassConstructor<unknown>
|
||||||
|
| EntitySchema
|
||||||
|
|
||||||
if (loaded) {
|
if (loaded) {
|
||||||
Object.entries(loaded).map(
|
Object.entries(loaded).map(
|
||||||
([, val]: [string, ClassConstructor<unknown> | EntitySchema]) => {
|
([, val]: [string, ClassConstructor<unknown> | EntitySchema]) => {
|
||||||
if (typeof val === "function" || val instanceof EntitySchema) {
|
if (typeof val === "function" || val instanceof EntitySchema) {
|
||||||
if (config.register) {
|
if (config.register) {
|
||||||
const name = formatRegistrationName(fn)
|
const name = formatRegistrationName(modelPath)
|
||||||
|
const mappedExtensionModel = modelExtensionsMap.get(name)
|
||||||
|
|
||||||
|
// If an extension file is found, override it with that instead
|
||||||
|
if (mappedExtensionModel) {
|
||||||
|
const coreModel = require(modelPath)
|
||||||
|
const modelName =
|
||||||
|
formatRegistrationNameWithoutNamespace(modelPath)
|
||||||
|
|
||||||
|
coreModel[modelName] = mappedExtensionModel
|
||||||
|
val = mappedExtensionModel
|
||||||
|
}
|
||||||
|
|
||||||
container.register({
|
container.register({
|
||||||
[name]: asClass(val as ClassConstructor<unknown>),
|
[name]: asClass(val as ClassConstructor<unknown>),
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -30,7 +30,11 @@ import {
|
|||||||
Logger,
|
Logger,
|
||||||
MedusaContainer,
|
MedusaContainer,
|
||||||
} from "../types/global"
|
} from "../types/global"
|
||||||
import formatRegistrationName from "../utils/format-registration-name"
|
import {
|
||||||
|
formatRegistrationName,
|
||||||
|
formatRegistrationNameWithoutNamespace,
|
||||||
|
} from "../utils/format-registration-name"
|
||||||
|
import { getModelExtensionsMap } from "./helpers/get-model-extension-map"
|
||||||
import {
|
import {
|
||||||
registerPaymentProcessorFromClass,
|
registerPaymentProcessorFromClass,
|
||||||
registerPaymentServiceFromClass,
|
registerPaymentServiceFromClass,
|
||||||
@@ -95,7 +99,8 @@ export default async ({
|
|||||||
|
|
||||||
function getResolvedPlugins(
|
function getResolvedPlugins(
|
||||||
rootDirectory: string,
|
rootDirectory: string,
|
||||||
configModule: ConfigModule
|
configModule: ConfigModule,
|
||||||
|
extensionDirectoryPath: string = 'dist'
|
||||||
): undefined | PluginDetails[] {
|
): undefined | PluginDetails[] {
|
||||||
const { plugins } = configModule
|
const { plugins } = configModule
|
||||||
|
|
||||||
@@ -110,9 +115,10 @@ function getResolvedPlugins(
|
|||||||
return details
|
return details
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const extensionDirectory = path.join(rootDirectory, extensionDirectoryPath)
|
||||||
// Resolve user's project as a plugin for loading purposes
|
// Resolve user's project as a plugin for loading purposes
|
||||||
resolved.push({
|
resolved.push({
|
||||||
resolve: `${rootDirectory}/dist`,
|
resolve: extensionDirectory,
|
||||||
name: MEDUSA_PROJECT_NAME,
|
name: MEDUSA_PROJECT_NAME,
|
||||||
id: createPluginId(MEDUSA_PROJECT_NAME),
|
id: createPluginId(MEDUSA_PROJECT_NAME),
|
||||||
options: configModule,
|
options: configModule,
|
||||||
@@ -126,15 +132,29 @@ export async function registerPluginModels({
|
|||||||
rootDirectory,
|
rootDirectory,
|
||||||
container,
|
container,
|
||||||
configModule,
|
configModule,
|
||||||
|
extensionDirectoryPath = 'dist',
|
||||||
|
pathGlob = "/models/*.js"
|
||||||
}: {
|
}: {
|
||||||
rootDirectory: string
|
rootDirectory: string
|
||||||
container: MedusaContainer
|
container: MedusaContainer
|
||||||
configModule: ConfigModule
|
configModule: ConfigModule
|
||||||
|
extensionDirectoryPath?: string
|
||||||
|
pathGlob?: string
|
||||||
}): Promise<void> {
|
}): Promise<void> {
|
||||||
const resolved = getResolvedPlugins(rootDirectory, configModule) || []
|
const resolved = getResolvedPlugins(
|
||||||
|
rootDirectory,
|
||||||
|
configModule,
|
||||||
|
extensionDirectoryPath
|
||||||
|
) || []
|
||||||
|
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
resolved.map(async (pluginDetails) => {
|
resolved.map(async (pluginDetails) => {
|
||||||
registerModels(pluginDetails, container)
|
registerModels(
|
||||||
|
pluginDetails,
|
||||||
|
container,
|
||||||
|
rootDirectory,
|
||||||
|
pathGlob,
|
||||||
|
)
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -563,16 +583,45 @@ function registerRepositories(
|
|||||||
*/
|
*/
|
||||||
function registerModels(
|
function registerModels(
|
||||||
pluginDetails: PluginDetails,
|
pluginDetails: PluginDetails,
|
||||||
container: MedusaContainer
|
container: MedusaContainer,
|
||||||
|
rootDirectory: string,
|
||||||
|
pathGlob: string = "/models/*.js"
|
||||||
): void {
|
): void {
|
||||||
const files = glob.sync(`${pluginDetails.resolve}/models/*.js`, {})
|
const pluginFullPathGlob = path.join(pluginDetails.resolve, pathGlob)
|
||||||
files.forEach((fn) => {
|
|
||||||
const loaded = require(fn) as ClassConstructor<unknown> | EntitySchema
|
const modelExtensionsMap = getModelExtensionsMap({
|
||||||
|
directory: rootDirectory,
|
||||||
|
pathGlob: pathGlob,
|
||||||
|
config: { register: true },
|
||||||
|
})
|
||||||
|
|
||||||
|
const coreOrPluginModelsPath = glob.sync(
|
||||||
|
pluginFullPathGlob,
|
||||||
|
{ ignore: ["index.js", "index.js.map"] }
|
||||||
|
)
|
||||||
|
|
||||||
|
coreOrPluginModelsPath.forEach((coreOrPluginModelPath) => {
|
||||||
|
const loaded = require(coreOrPluginModelPath) as
|
||||||
|
| ClassConstructor<unknown>
|
||||||
|
| EntitySchema
|
||||||
|
|
||||||
Object.entries(loaded).map(
|
Object.entries(loaded).map(
|
||||||
([, val]: [string, ClassConstructor<unknown> | EntitySchema]) => {
|
([, val]: [string, ClassConstructor<unknown> | EntitySchema]) => {
|
||||||
if (typeof val === "function" || val instanceof EntitySchema) {
|
if (typeof val === "function" || val instanceof EntitySchema) {
|
||||||
const name = formatRegistrationName(fn)
|
const name = formatRegistrationName(coreOrPluginModelPath)
|
||||||
|
const mappedExtensionModel = modelExtensionsMap.get(name)
|
||||||
|
|
||||||
|
// If an extension file is found, override it with that instead
|
||||||
|
if (mappedExtensionModel) {
|
||||||
|
const coreOrPluginModel = require(coreOrPluginModelPath)
|
||||||
|
const modelName = formatRegistrationNameWithoutNamespace(
|
||||||
|
coreOrPluginModelPath
|
||||||
|
)
|
||||||
|
|
||||||
|
coreOrPluginModel[modelName] = mappedExtensionModel
|
||||||
|
val = mappedExtensionModel
|
||||||
|
}
|
||||||
|
|
||||||
container.register({
|
container.register({
|
||||||
[name]: asValue(val),
|
[name]: asValue(val),
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
import path from "path"
|
import path from "path"
|
||||||
import formatRegistrationName from "../format-registration-name"
|
import {
|
||||||
|
formatRegistrationName,
|
||||||
|
formatRegistrationNameWithoutNamespace,
|
||||||
|
} from "../format-registration-name"
|
||||||
|
|
||||||
describe("formatRegistrationName", () => {
|
describe("formatRegistrationName", () => {
|
||||||
const tests = [
|
const tests = [
|
||||||
@@ -32,3 +35,26 @@ describe("formatRegistrationName", () => {
|
|||||||
expect(res).toEqual(expected)
|
expect(res).toEqual(expected)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe("formatRegistrationNameWithoutNamespace", () => {
|
||||||
|
const tests = [
|
||||||
|
[["medusa-test-dir", "dist", "services", "my-test.js"], "myTest"],
|
||||||
|
[["medusa-test-dir", "dist", "services", "my.js"], "my"],
|
||||||
|
[["services", "my-quite-long-file.js"], "myQuiteLongFile"],
|
||||||
|
[["/", "Users", "seb", "com.medusa.js", "services", "dot.js"], "dot"],
|
||||||
|
[["/", "Users", "seb.rin", "com.medusa.js", "services", "dot.js"], "dot"],
|
||||||
|
[
|
||||||
|
["/", "Users", "seb.rin", "com.medusa.js", "repositories", "dot.js"],
|
||||||
|
"dot",
|
||||||
|
],
|
||||||
|
[["/", "Users", "seb.rin", "com.medusa.js", "models", "dot.js"], "dot"],
|
||||||
|
[["C:", "server", "services", "dot.js"], "dot"],
|
||||||
|
]
|
||||||
|
|
||||||
|
test.each(
|
||||||
|
tests.map(([pathParts, expected]) => [path.join(...pathParts), expected])
|
||||||
|
)("Service %s -> %s", (fn, expected) => {
|
||||||
|
const res = formatRegistrationNameWithoutNamespace(fn)
|
||||||
|
expect(res).toEqual(expected)
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
import { parse } from "path"
|
import { parse } from "path"
|
||||||
|
import { toCamelCase, upperCaseFirst } from "@medusajs/utils"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Formats a filename into the correct container resolution name.
|
* Formats a filename into the correct container resolution name.
|
||||||
@@ -7,41 +8,39 @@ import { parse } from "path"
|
|||||||
* @param path - the full path of the file
|
* @param path - the full path of the file
|
||||||
* @return the formatted name
|
* @return the formatted name
|
||||||
*/
|
*/
|
||||||
function formatRegistrationName(path: string): string {
|
export function formatRegistrationName(path: string): string {
|
||||||
const parsed = parse(path)
|
const parsed = parse(path)
|
||||||
const parsedDir = parse(parsed.dir)
|
const parsedDir = parse(parsed.dir)
|
||||||
|
|
||||||
const rawname = parsed.name
|
const rawname = parsed.name
|
||||||
let namespace = parsedDir.name
|
let directoryNamespace = parsedDir.name
|
||||||
if (namespace.startsWith("__")) {
|
|
||||||
|
if (directoryNamespace.startsWith("__")) {
|
||||||
const parsedCoreDir = parse(parsedDir.dir)
|
const parsedCoreDir = parse(parsedDir.dir)
|
||||||
namespace = parsedCoreDir.name
|
directoryNamespace = parsedCoreDir.name
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (namespace) {
|
switch (directoryNamespace) {
|
||||||
// We strip the last character when adding the type of registration
|
// We strip the last character when adding the type of registration
|
||||||
// this is a trick for plural "ies"
|
// this is a trick for plural "ies"
|
||||||
case "repositories":
|
case "repositories":
|
||||||
namespace = "repositorys"
|
directoryNamespace = "repositorys"
|
||||||
break
|
break
|
||||||
case "strategies":
|
case "strategies":
|
||||||
namespace = "strategys"
|
directoryNamespace = "strategys"
|
||||||
break
|
break
|
||||||
default:
|
default:
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
const upperNamespace =
|
const upperNamespace = upperCaseFirst(directoryNamespace.slice(0, -1))
|
||||||
namespace.charAt(0).toUpperCase() + namespace.slice(1, -1)
|
|
||||||
|
|
||||||
const parts = rawname.split("-").map((n, index) => {
|
return formatRegistrationNameWithoutNamespace(path) + upperNamespace
|
||||||
if (index !== 0) {
|
}
|
||||||
return n.charAt(0).toUpperCase() + n.slice(1)
|
|
||||||
}
|
|
||||||
return n
|
|
||||||
})
|
|
||||||
|
|
||||||
return parts.join("") + upperNamespace
|
export function formatRegistrationNameWithoutNamespace(path: string): string {
|
||||||
|
const parsed = parse(path)
|
||||||
|
|
||||||
|
return toCamelCase(parsed.name)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default formatRegistrationName
|
export default formatRegistrationName
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
import { toCamelCase } from "../to-camel-case"
|
||||||
|
|
||||||
|
describe("toCamelCase", function () {
|
||||||
|
it("should convert all cases to camel case", function () {
|
||||||
|
const expectations = [
|
||||||
|
{
|
||||||
|
input: "testing-camelize",
|
||||||
|
output: "testingCamelize",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: "testing-Camelize",
|
||||||
|
output: "testingCamelize",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: "TESTING-CAMELIZE",
|
||||||
|
output: "testingCamelize",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: "this_is-A-test",
|
||||||
|
output: "thisIsATest",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: "this_is-A-test ANOTHER",
|
||||||
|
output: "thisIsATestAnother",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: "testingAlreadyCamelized",
|
||||||
|
output: "testingAlreadyCamelized",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
expectations.forEach((expectation) => {
|
||||||
|
expect(toCamelCase(expectation.input)).toEqual(expectation.output)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
import { upperCaseFirst } from "../upper-case-first"
|
||||||
|
|
||||||
|
describe("upperCaseFirst", function () {
|
||||||
|
it("should convert first letter of the word to capital letter", function () {
|
||||||
|
const expectations = [
|
||||||
|
{
|
||||||
|
input: "testing capitalize",
|
||||||
|
output: "Testing capitalize",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: "testing",
|
||||||
|
output: "Testing",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: "Testing",
|
||||||
|
output: "Testing",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: "TESTING",
|
||||||
|
output: "TESTING",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: "t",
|
||||||
|
output: "T",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: "",
|
||||||
|
output: "",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
expectations.forEach((expectation) => {
|
||||||
|
expect(upperCaseFirst(expectation.input)).toEqual(expectation.output)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -8,12 +8,14 @@ export * from "./is-email"
|
|||||||
export * from "./is-object"
|
export * from "./is-object"
|
||||||
export * from "./is-string"
|
export * from "./is-string"
|
||||||
export * from "./lower-case-first"
|
export * from "./lower-case-first"
|
||||||
|
export * from "./upper-case-first"
|
||||||
export * from "./medusa-container"
|
export * from "./medusa-container"
|
||||||
export * from "./object-to-string-path"
|
export * from "./object-to-string-path"
|
||||||
export * from "./set-metadata"
|
export * from "./set-metadata"
|
||||||
export * from "./simple-hash"
|
export * from "./simple-hash"
|
||||||
export * from "./wrap-handler"
|
export * from "./wrap-handler"
|
||||||
export * from "./to-kebab-case"
|
export * from "./to-kebab-case"
|
||||||
|
export * from "./to-camel-case"
|
||||||
export * from "./stringify-circular"
|
export * from "./stringify-circular"
|
||||||
export * from "./build-query"
|
export * from "./build-query"
|
||||||
export * from "./handle-postgres-database-error"
|
export * from "./handle-postgres-database-error"
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
export function toCamelCase(str: string): string {
|
||||||
|
return /^([a-z]+)(([A-Z]([a-z]+))+)$/.test(str)
|
||||||
|
? str
|
||||||
|
: str
|
||||||
|
.toLowerCase()
|
||||||
|
.replace(/[^a-zA-Z0-9]+(.)/g, (m, chr) => chr.toUpperCase())
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
export function upperCaseFirst(str: string): string {
|
||||||
|
return str.charAt(0).toUpperCase() + str.slice(1)
|
||||||
|
}
|
||||||
Reference in new issue
Block a user