Adds publish/subscribe pattern to allow plugins and projects to hook into events (#55)

Also adds CLI to ease development.
This commit is contained in:
Sebastian Rindom authored and GitHub committed 2020-05-07 13:47:27 +02:00
1 parent 6a78df1ecd
commit 4c2aec2838
39 files changed
+6974 -444

No files matched your search

+9
View File
@@ -0,0 +1,9 @@
{
"plugins": ["@babel/plugin-proposal-class-properties"],
"presets": ["@babel/preset-env"],
"env": {
"test": {
"plugins": ["@babel/plugin-transform-runtime"]
}
}
}
+9
View File
@@ -0,0 +1,9 @@
{
"plugins": ["prettier"],
"extends": ["prettier"],
"rules": {
"prettier/prettier": "error",
"semi": "error",
"no-unused-expressions": "true"
}
}
+2
View File
@@ -0,0 +1,2 @@
dist/
node_modules/
+7
View File
@@ -0,0 +1,7 @@
{
"endOfLine": "lf",
"semi": false,
"singleQuote": false,
"tabWidth": 2,
"trailingComma": "es5"
}
+49
View File
@@ -0,0 +1,49 @@
{
"name": "medusa-cli",
"version": "0.1.0",
"description": "Command Line interface for Medusa Commerce",
"main": "dist/index.js",
"bin": {
"medusa": "dist/index.js"
},
"repository": {
"type": "git",
"url": "https://github.com/medusajs/medusa",
"directory": "packages/medusa-cli"
},
"scripts": {
"build": "babel src --out-dir dist/ --ignore **/__tests__",
"prepare": "cross-env NODE_ENV=production npm run build",
"watch": "babel -w src --out-dir dist/ --ignore **/__tests__"
},
"author": "Sebastian Rindom",
"license": "AGPL-3.0-or-later",
"devDependencies": {
"@babel/cli": "^7.7.5",
"@babel/core": "^7.7.5",
"@babel/plugin-proposal-class-properties": "^7.7.4",
"@babel/plugin-transform-classes": "^7.9.5",
"@babel/plugin-transform-runtime": "^7.7.6",
"@babel/preset-env": "^7.7.5",
"cross-env": "^5.2.1",
"eslint": "^6.8.0",
"jest": "^25.5.2",
"prettier": "^1.19.1"
},
"dependencies": {
"@babel/polyfill": "^7.8.7",
"@babel/runtime": "^7.9.6",
"@hapi/joi": "^16.1.8",
"chalk": "^4.0.0",
"clipboardy": "^2.3.0",
"core-js": "^3.6.5",
"fs-exists-cached": "^1.0.0",
"joi-objectid": "^3.0.1",
"meant": "^1.0.1",
"medusa-core-utils": "^0.1.27",
"regenerator-runtime": "^0.13.5",
"resolve-cwd": "^3.0.0",
"yargs": "^15.3.1"
},
"gitHead": "35e0930650d5f4aedf2610749cd131ae8b7e17cc"
}
@@ -0,0 +1,16 @@
const chalk = require(`chalk`)
const showSuccessMessage = () => {
console.log(chalk.green(`Success!\n`))
console.log(chalk.cyan(`Welcome to the Medusa CLI!`))
}
try {
// check if it's a global installation of medusa-cli
const npmArgs = JSON.parse(process.env[`npm_config_argv`])
if (npmArgs.cooked && npmArgs.cooked.includes(`--global`)) {
const createCli = require(`../dist/create-cli`)
showSuccessMessage()
createCli(`--help`)
}
} catch (e) {}
+238
View File
@@ -0,0 +1,238 @@
const path = require(`path`)
const resolveCwd = require(`resolve-cwd`)
const yargs = require(`yargs`)
const { getLocalMedusaVersion } = require(`./util/version`)
const { didYouMean } = require(`./did-you-mean`)
const envinfo = require(`envinfo`)
const existsSync = require(`fs-exists-cached`).sync
const clipboardy = require(`clipboardy`)
const handlerP = fn => (...args) => {
Promise.resolve(fn(...args)).then(
() => process.exit(0),
err => console.log(err)
)
}
function buildLocalCommands(cli, isLocalProject) {
const defaultHost = `localhost`
const defaultPort = `9000`
const directory = path.resolve(`.`)
const projectInfo = { directory }
const useYarn = existsSync(path.join(directory, `yarn.lock`))
if (isLocalProject) {
const json = require(path.join(directory, `package.json`))
projectInfo.sitePackageJson = json
}
function getLocalMedusaMajorVersion() {
let version = getLocalMedusaVersion()
if (version) {
version = Number(version.split(`.`)[0])
}
return version
}
function resolveLocalCommand(command) {
if (!isLocalProject) {
cli.showHelp()
}
try {
const cmdPath = resolveCwd.silent(
`@medusajs/medusa/dist/commands/${command}`
)
return require(cmdPath).default
} catch (err) {
cli.showHelp()
}
}
function getCommandHandler(command, handler) {
return argv => {
const localCmd = resolveLocalCommand(command)
const args = { ...argv, ...projectInfo, useYarn }
// report.verbose(`running command: ${command}`)
return handler ? handler(args, localCmd) : localCmd(args)
}
}
cli
.command({
command: `develop`,
desc: `Start development server. Watches file and rebuilds when something changes`,
builder: _ =>
_.option(`H`, {
alias: `host`,
type: `string`,
default: defaultHost,
describe: `Set host. Defaults to ${defaultHost}`,
}).option(`p`, {
alias: `port`,
type: `string`,
default: process.env.PORT || defaultPort,
describe: process.env.PORT
? `Set port. Defaults to ${process.env.PORT} (set by env.PORT) (otherwise defaults ${defaultPort})`
: `Set port. Defaults to ${defaultPort}`,
}),
handler: handlerP(
getCommandHandler(`develop`, (args, cmd) => {
process.env.NODE_ENV = process.env.NODE_ENV || `development`
cmd(args)
// Return an empty promise to prevent handlerP from exiting early.
// The development server shouldn't ever exit until the user directly
// kills it so this is fine.
return new Promise(resolve => {})
})
),
})
.command({
command: `start`,
desc: `Start development server.`,
builder: _ =>
_.option(`H`, {
alias: `host`,
type: `string`,
default: defaultHost,
describe: `Set host. Defaults to ${defaultHost}`,
}).option(`p`, {
alias: `port`,
type: `string`,
default: process.env.PORT || defaultPort,
describe: process.env.PORT
? `Set port. Defaults to ${process.env.PORT} (set by env.PORT) (otherwise defaults ${defaultPort})`
: `Set port. Defaults to ${defaultPort}`,
}),
handler: handlerP(
getCommandHandler(`start`, (args, cmd) => {
process.env.NODE_ENV = process.env.NODE_ENV || `development`
cmd(args)
// Return an empty promise to prevent handlerP from exiting early.
// The development server shouldn't ever exit until the user directly
// kills it so this is fine.
return new Promise(resolve => {})
})
),
})
.command({
command: `user`,
desc: `Create a user`,
builder: _ =>
_.option(`e`, {
alias: `email`,
type: `string`,
describe: `User's email.`,
}).option(`p`, {
alias: `password`,
type: `string`,
describe: `User's password.`,
}),
handler: handlerP(
getCommandHandler(`user`, (args, cmd) => {
cmd(args)
// Return an empty promise to prevent handlerP from exiting early.
// The development server shouldn't ever exit until the user directly
// kills it so this is fine.
return new Promise(resolve => {})
})
),
})
}
function isLocalMedusaProject() {
let inMedusaProject = false
try {
const { dependencies, devDependencies } = require(path.resolve(
`./package.json`
))
inMedusaProject =
(dependencies && dependencies["@medusajs/medusa"]) ||
(devDependencies && devDependencies["@medusajs/medusa"])
} catch (err) {
/* ignore */
}
return !!inMedusaProject
}
function getVersionInfo() {
const { version } = require(`../package.json`)
const isMedusaProject = isLocalMedusaProject()
if (isMedusaProject) {
let medusaVersion = getLocalMedusaVersion()
if (!medusaVersion) {
medusaVersion = `unknown`
}
return `Medusa CLI version: ${version}
Medusa version: ${medusaVersion}
Note: this is the Medusa version for the site at: ${process.cwd()}`
} else {
return `Medusa CLI version: ${version}`
}
}
module.exports = argv => {
const cli = yargs()
const isLocalProject = isLocalMedusaProject()
cli
.scriptName(`medusa`)
.usage(`Usage: $0 <command> [options]`)
.alias(`h`, `help`)
.alias(`v`, `version`)
.option(`verbose`, {
default: false,
type: `boolean`,
describe: `Turn on verbose output`,
global: true,
})
.option(`no-color`, {
alias: `no-colors`,
default: false,
type: `boolean`,
describe: `Turn off the color in output`,
global: true,
})
.option(`json`, {
describe: `Turn on the JSON logger`,
default: false,
type: `boolean`,
global: true,
})
buildLocalCommands(cli, isLocalProject)
try {
cli.version(
`version`,
`Show the version of the Medusa CLI and the Medusa package in the current project`,
getVersionInfo()
)
} catch (e) {
// ignore
}
return cli
.wrap(cli.terminalWidth())
.demandCommand(1, `Pass --help to see all available commands and options.`)
.strict()
.fail((msg, err, yargs) => {
const availableCommands = yargs.getCommands().map(commandDescription => {
const [command] = commandDescription
return command.split(` `)[0]
})
const arg = argv.slice(2)[0]
const suggestion = arg ? didYouMean(arg, availableCommands) : ``
cli.showHelp()
// report.log(suggestion)
// report.log(msg)
})
.parse(argv.slice(2))
}
+18
View File
@@ -0,0 +1,18 @@
import meant from "meant"
export function didYouMean(scmd, commands) {
const bestSimilarity = meant(scmd, commands).map(str => {
return ` ${str}`
})
if (bestSimilarity.length === 0) return ``
if (bestSimilarity.length === 1) {
return `\nDid you mean this?\n ${bestSimilarity[0]}\n`
} else {
return (
[`\nDid you mean one of these?`]
.concat(bestSimilarity.slice(0, 3))
.join(`\n`) + `\n`
)
}
}
+69
View File
@@ -0,0 +1,69 @@
#!/usr/bin/env node
import "core-js/stable"
import "regenerator-runtime/runtime"
import os from "os"
import semver from "semver"
import util from "util"
import createCli from "./create-cli"
// import report from "./reporter"
import pkg from "../package.json"
// import updateNotifier from "update-notifier"
// import { ensureWindowsDriveLetterIsUppercase } from "./util/ensure-windows-drive-letter-is-uppercase"
const useJsonLogger = process.argv.slice(2).some(arg => arg.includes(`json`))
if (useJsonLogger) {
process.env.GATSBY_LOGGER = `json`
}
// Ensure stable runs on Windows when started from different shells (i.e. c:\dir vs C:\dir)
if (os.platform() === `win32`) {
// ensureWindowsDriveLetterIsUppercase()
}
// Check if update is available
// updateNotifier({ pkg }).notify({ isGlobal: true })
const MIN_NODE_VERSION = `10.13.0`
// const NEXT_MIN_NODE_VERSION = `10.13.0`
if (!semver.satisfies(process.version, `>=${MIN_NODE_VERSION}`)) {
//report.panic(
// report.stripIndent(`
// Gatsby requires Node.js ${MIN_NODE_VERSION} or higher (you have ${process.version}).
// Upgrade Node to the latest stable release: https://gatsby.dev/upgrading-node-js
// `)
//)
}
// if (!semver.satisfies(process.version, `>=${NEXT_MIN_NODE_VERSION}`)) {
// report.warn(
// report.stripIndent(`
// Node.js ${process.version} has reached End of Life status on 31 December, 2019.
// Gatsby will only actively support ${NEXT_MIN_NODE_VERSION} or higher and drop support for Node 8 soon.
// Please upgrade Node.js to a currently active LTS release: https://gatsby.dev/upgrading-node-js
// `)
// )
// }
process.on(`unhandledRejection`, reason => {
// This will exit the process in newer Node anyway so lets be consistent
// across versions and crash
// reason can be anything, it can be a message, an object, ANYTHING!
// we convert it to an error object so we don't crash on structured error validation
if (!(reason instanceof Error)) {
reason = new Error(util.format(reason))
}
console.log(reason)
// report.panic(`UNHANDLED REJECTION`, reason as Error)
})
process.on(`uncaughtException`, error => {
console.log(error)
// report.panic(`UNHANDLED EXCEPTION`, error)
})
createCli(process.argv)
+6
View File
@@ -0,0 +1,6 @@
import { getMedusaVersion } from "medusa-core-utils"
export const getLocalMedusaVersion = () => {
const version = getMedusaVersion()
return version
}
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,14 @@
import path from "path"
export const getMedusaVersion = () => {
try {
return require(path.join(
process.cwd(),
`node_modules`,
`@medusajs/medusa`,
`package.json`
)).version
} catch (e) {
return ``
}
}
+12
View File
@@ -846,6 +846,13 @@
levenary "^1.1.0"
semver "^5.5.0"
"@babel/runtime@^7.9.6":
version "7.9.6"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.9.6.tgz#a9102eb5cadedf3f31d08a9ecf294af7827ea29f"
integrity sha512-64AF1xY3OAkFHqOb9s4jpgk1Mm5vDZ4L3acHvAml+53nO1XbXLuDodsVpO4OIUsmemlUHMxNdYMNJmsvOwLrvQ==
dependencies:
regenerator-runtime "^0.13.4"
"@babel/template@^7.3.3", "@babel/template@^7.7.4", "@babel/template@^7.8.6":
version "7.8.6"
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.8.6.tgz#86b22af15f828dfb086474f964dcc3e39c43ce2b"
@@ -3996,6 +4003,11 @@ regenerate@^1.4.0:
resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.4.0.tgz#4a856ec4b56e4077c557589cae85e7a4c8869a11"
integrity sha512-1G6jJVDWrt0rK99kBjvEtziZNCICAuvIPkSiUFIQxVP06RCVpq3dmDo2oi6ABpYaDYaTRr67BEhL8r1wgEZZKg==
regenerator-runtime@^0.13.4:
version "0.13.5"
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.5.tgz#d878a1d094b4306d10b9096484b33ebd55e26697"
integrity sha512-ZS5w8CpKFinUzOwW3c83oPeVXoNsrLsaCoLtJvAClH135j/R77RuymhiSErhm2lKcwSCIpmvIWSbDkIfAqKQlA==
regenerator-transform@^0.14.0:
version "0.14.1"
resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.14.1.tgz#3b2fce4e1ab7732c08f665dfdb314749c7ddd2fb"
+5 -3
View File
@@ -19,8 +19,8 @@
"devDependencies": {
"@babel/cli": "^7.7.5",
"@babel/core": "^7.7.5",
"@babel/plugin-transform-classes": "^7.9.5",
"@babel/plugin-proposal-class-properties": "^7.7.4",
"@babel/plugin-transform-classes": "^7.9.5",
"@babel/plugin-transform-instanceof": "^7.8.3",
"@babel/plugin-transform-runtime": "^7.7.6",
"@babel/preset-env": "^7.7.5",
@@ -30,9 +30,11 @@
"jest": "^25.5.2",
"prettier": "^1.19.1"
},
"peerDependencies": {
"mongoose": "5.x"
},
"dependencies": {
"medusa-core-utils": "^0.3.0",
"mongoose": "^5.8.0"
"medusa-core-utils": "^0.3.0"
},
"gitHead": "35e0930650d5f4aedf2610749cd131ae8b7e17cc"
}
+4 -3
View File
@@ -8,7 +8,7 @@ import mongoose from "mongoose"
class BaseModel {
constructor() {
/** @const the underlying mongoose model used for queries */
this.mongooseModel_ = this.createMongooseModel_(this.schema)
this.mongooseModel_ = this.createMongooseModel_()
}
/**
@@ -72,8 +72,9 @@ class BaseModel {
* @param options {?object=} mongoose options
* @return {object} mongoose result
*/
updateOne(query, update, options) {
return this.mongooseModel_.updateOne(query, update, options)
updateOne(query, update, options = {}) {
options.new = true
return this.mongooseModel_.findOneAndUpdate(query, update, options)
}
/**
+9
View File
@@ -0,0 +1,9 @@
{
"plugins": ["prettier"],
"extends": ["prettier"],
"rules": {
"prettier/prettier": "error",
"semi": "error",
"no-unused-expressions": "true"
}
}
@@ -0,0 +1,7 @@
{
"endOfLine": "lf",
"semi": false,
"singleQuote": false,
"tabWidth": 2,
"trailingComma": "es5"
}
+2 -3
View File
@@ -15,6 +15,8 @@
"@babel/core": "^7.7.5",
"@babel/plugin-proposal-class-properties": "^7.7.4",
"@babel/plugin-transform-runtime": "^7.7.6",
"@babel/plugin-transform-classes": "^7.9.5",
"@babel/plugin-transform-instanceof": "^7.8.3",
"@babel/preset-env": "^7.7.5",
"@babel/runtime": "^7.9.6",
"client-sessions": "^0.8.0",
@@ -28,9 +30,6 @@
"watch": "babel -w src --out-dir . --ignore **/__tests__"
},
"dependencies": {
"@babel/plugin-transform-classes": "^7.9.5",
"@babel/plugin-transform-instanceof": "^7.8.3",
"@babel/runtime": "^7.7.6",
"express": "^4.17.1",
"medusa-core-utils": "^0.3.0",
"medusa-interfaces": "^0.3.0"
@@ -0,0 +1,12 @@
class CartSubscriber {
constructor({ cartService, eventBusService }) {
this.cartService_ = cartService
this.eventBus_ = eventBusService
this.eventBus_.subscribe("cart.created", (data) => {
console.log(data)
})
}
}
export default CartSubscriber
+12
View File
@@ -861,6 +861,13 @@
levenary "^1.1.0"
semver "^5.5.0"
"@babel/runtime@^7.9.6":
version "7.9.6"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.9.6.tgz#a9102eb5cadedf3f31d08a9ecf294af7827ea29f"
integrity sha512-64AF1xY3OAkFHqOb9s4jpgk1Mm5vDZ4L3acHvAml+53nO1XbXLuDodsVpO4OIUsmemlUHMxNdYMNJmsvOwLrvQ==
dependencies:
regenerator-runtime "^0.13.4"
"@babel/template@^7.3.3", "@babel/template@^7.7.4", "@babel/template@^7.8.6":
version "7.8.6"
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.8.6.tgz#86b22af15f828dfb086474f964dcc3e39c43ce2b"
@@ -4064,6 +4071,11 @@ regenerate@^1.4.0:
resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.4.0.tgz#4a856ec4b56e4077c557589cae85e7a4c8869a11"
integrity sha512-1G6jJVDWrt0rK99kBjvEtziZNCICAuvIPkSiUFIQxVP06RCVpq3dmDo2oi6ABpYaDYaTRr67BEhL8r1wgEZZKg==
regenerator-runtime@^0.13.4:
version "0.13.5"
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.5.tgz#d878a1d094b4306d10b9096484b33ebd55e26697"
integrity sha512-ZS5w8CpKFinUzOwW3c83oPeVXoNsrLsaCoLtJvAClH135j/R77RuymhiSErhm2lKcwSCIpmvIWSbDkIfAqKQlA==
regenerator-transform@^0.14.0:
version "0.14.1"
resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.14.1.tgz#3b2fce4e1ab7732c08f665dfdb314749c7ddd2fb"
+7 -4
View File
@@ -38,6 +38,12 @@
"serve": "node dist/app.js",
"test": "jest"
},
"peerDependencies": {
"medusa-interfaces": "^0.3.0"
},
"peerDependencies": {
"mongoose": "5.x"
},
"dependencies": {
"@babel/plugin-transform-classes": "^7.9.5",
"@hapi/joi": "^16.1.8",
@@ -47,7 +53,6 @@
"bull": "^3.12.1",
"client-sessions": "^0.8.0",
"cookie-parser": "^1.4.4",
"core-js": "^3.4.8",
"cors": "^2.8.5",
"dotenv": "^8.2.0",
"express": "^4.17.1",
@@ -57,15 +62,13 @@
"joi-objectid": "^3.0.1",
"jsonwebtoken": "^8.5.1",
"medusa-core-utils": "^0.3.0",
"medusa-interfaces": "^0.3.0",
"medusa-interfaces": "^0.1.27",
"medusa-test-utils": "^0.3.0",
"mongoose": "^5.8.0",
"morgan": "^1.9.1",
"passport": "^0.4.0",
"passport-http-bearer": "^1.0.1",
"passport-jwt": "^4.0.0",
"passport-local": "^1.0.0",
"regenerator-runtime": "^0.13.3",
"winston": "^3.2.1"
},
"gitHead": "35e0930650d5f4aedf2610749cd131ae8b7e17cc"
+45
View File
@@ -0,0 +1,45 @@
import { spawn, execSync } from "child_process"
import mongoose from "mongoose"
import chokidar from "chokidar"
import express from "express"
import path from "path"
import loaders from "../loaders"
import Logger from "../loaders/logger"
export default async function({ port, directory }) {
const args = process.argv
args.shift()
args.shift()
args.shift()
execSync("babel src -d dist", {
cwd: directory,
stdio: ["ignore", process.stdout, process.stderr],
})
let child = spawn("medusa", [`start`, ...args], {
cwd: directory,
env: process.env,
stdio: ["pipe", process.stdout, process.stderr],
})
chokidar.watch(`${directory}/src`).on("change", file => {
const f = file.split("src")[1]
Logger.info(`${f} changed: restarting...`)
child.kill("SIGINT")
execSync(`babel src -d dist`, {
cwd: directory,
stdio: ["pipe", process.stdout, process.stderr],
})
Logger.info("Rebuilt")
child = spawn("medusa", [`start`, ...args], {
cwd: directory,
env: process.env,
stdio: ["pipe", process.stdout, process.stderr],
})
})
}
+28
View File
@@ -0,0 +1,28 @@
import "core-js/stable"
import "regenerator-runtime/runtime"
import mongoose from "mongoose"
import chokidar from "chokidar"
import express from "express"
import cwdResolve from "resolve-cwd"
import loaders from "../loaders"
import Logger from "../loaders/logger"
export default async function({ port, directory }) {
async function start() {
const app = express()
const { dbConnection } = await loaders({ directory, expressApp: app })
const server = app.listen(port, err => {
if (err) {
return
}
Logger.info(`Server is ready on port: ${port}!`)
})
return { dbConnection, server }
}
let { dbConnection, server } = await start()
}
+23
View File
@@ -0,0 +1,23 @@
import "core-js/stable"
import "regenerator-runtime/runtime"
import mongoose from "mongoose"
import chokidar from "chokidar"
import express from "express"
import cwdResolve from "resolve-cwd"
import loaders from "../loaders"
import Logger from "../loaders/logger"
export default async function({ directory, email, password }) {
const app = express()
const { container, dbConnection } = await loaders({
directory,
expressApp: app,
})
const userService = container.resolve("userService")
const user = await userService.create({ email }, password)
process.exit()
}
+5 -3
View File
@@ -8,7 +8,7 @@ import passportLoader from "./passport"
import pluginsLoader from "./plugins"
import Logger from "./logger"
export default async ({ expressApp }) => {
export default async ({ directory: rootDirectory, expressApp }) => {
const container = createContainer()
container.registerAdd = function(name, registration) {
let storeKey = name + "_STORE"
@@ -36,7 +36,7 @@ export default async ({ expressApp }) => {
await servicesLoader({ container })
Logger.info("Services initialized")
await mongooseLoader({ container })
const dbConnection = await mongooseLoader({ container })
Logger.info("MongoDB Intialized")
await expressLoader({ app: expressApp })
@@ -51,11 +51,13 @@ export default async ({ expressApp }) => {
next()
})
await pluginsLoader({ container, app: expressApp })
await pluginsLoader({ container, rootDirectory, app: expressApp })
Logger.info("Plugins Intialized")
await apiLoader({ container, app: expressApp })
Logger.info("API initialized")
return { container, dbConnection, app: expressApp }
}
function asArray(resolvers) {
+11 -9
View File
@@ -3,17 +3,19 @@ import config from "../config"
export default async ({ container }) => {
const logger = container.resolve("logger")
await mongoose
.connect(config.databaseURL, {
useNewUrlParser: true,
useCreateIndex: true,
useUnifiedTopology: true,
})
.catch(err => {
logger.error(err)
})
mongoose.connection.on("error", err => {
logger.error(err)
})
return mongoose
.connect(config.databaseURL, {
useNewUrlParser: true,
useCreateIndex: true,
useUnifiedTopology: true,
useFindAndModify: false,
})
.catch(err => {
logger.error(err)
})
}
+29 -3
View File
@@ -15,9 +15,9 @@ import { sync as existsSync } from "fs-exists-cached"
/**
* Registers all services in the services directory
*/
export default ({ container, app }) => {
export default ({ rootDirectory, container, app }) => {
const { configModule, configFilePath } = getConfigFile(
process.cwd(),
rootDirectory,
`medusa-config`
)
@@ -39,7 +39,7 @@ export default ({ container, app }) => {
})
resolved.push({
resolve: process.cwd(),
resolve: `${rootDirectory}/dist`,
name: `project-plugin`,
id: createPluginId(`project-plugin`),
options: {},
@@ -51,6 +51,7 @@ export default ({ container, app }) => {
registerServices(pluginDetails, container)
registerMedusaApi(pluginDetails, container)
registerApi(pluginDetails, app)
registerSubscribers(pluginDetails, container)
})
}
@@ -156,6 +157,31 @@ function registerServices(pluginDetails, container) {
})
}
/**
* Registers a plugin's models at the right location in our container. Models
* must inherit from BaseModel. Models are registered directly in the container.
* Names are camelCase formatted and namespaced by the folder i.e:
* models/example-person -> examplePersonModel
* @param {object} pluginDetails - the plugin details including plugin options,
* version, id, resolved path, etc. See resolvePlugin
* @param {object} container - the container where the services will be
* registered
* @return {void}
*/
function registerSubscribers(pluginDetails, container) {
const files = glob.sync(`${pluginDetails.resolve}/subscribers/*.js`, {})
files.forEach(fn => {
const loaded = require(fn).default
const name = formatRegistrationName(fn)
container.build(
asFunction(
cradle => new loaded(cradle, pluginDetails.options)
).singleton()
)
})
}
/**
* Registers a plugin's models at the right location in our container. Models
* must inherit from BaseModel. Models are registered directly in the container.
+2 -2
View File
@@ -312,9 +312,9 @@ export const carts = {
}
export const CartModelMock = {
create: jest.fn().mockReturnValue(Promise.resolve()),
create: jest.fn().mockImplementation(data => Promise.resolve(data)),
updateOne: jest.fn().mockImplementation((query, update) => {
return Promise.resolve()
return Promise.resolve(update)
}),
deleteOne: jest.fn().mockReturnValue(Promise.resolve()),
findOne: jest.fn().mockImplementation(query => {
+3 -3
View File
@@ -15,15 +15,15 @@ class CartModel extends BaseModel {
static schema = {
email: { type: String },
billing_address: { type: AddressSchema, default: {} },
shipping_address: { type: AddressSchema, default: {} },
billing_address: { type: AddressSchema },
shipping_address: { type: AddressSchema },
items: { type: [LineItemSchema], default: [] },
region_id: { type: String, required: true },
discounts: { type: [DiscountModel.schema], default: [] },
customer_id: { type: String, default: "" },
payment_sessions: { type: [PaymentMethodSchema], default: [] },
shipping_options: { type: [ShippingMethodSchema], default: [] },
payment_method: { type: PaymentMethodSchema, default: {} },
payment_method: { type: PaymentMethodSchema },
shipping_methods: { type: [ShippingMethodSchema], default: [] },
metadata: { type: mongoose.Schema.Types.Mixed, default: {} },
}
@@ -0,0 +1,10 @@
export const EventBusServiceMock = {
emit: jest.fn(),
subscribe: jest.fn(),
}
const mock = jest.fn().mockImplementation(() => {
return EventBusServiceMock
})
export default mock
@@ -7,6 +7,7 @@ import {
} from "../__mocks__/payment-provider"
import { ProductVariantServiceMock } from "../__mocks__/product-variant"
import { RegionServiceMock } from "../__mocks__/region"
import { EventBusServiceMock } from "../__mocks__/event-bus"
import { ShippingOptionServiceMock } from "../__mocks__/shipping-option"
import { ShippingProfileServiceMock } from "../__mocks__/shipping-profile"
import { CartModelMock, carts } from "../../models/__mocks__/cart"
@@ -40,6 +41,7 @@ describe("CartService", () => {
describe("setMetadata", () => {
const cartService = new CartService({
cartModel: CartModelMock,
eventBusService: EventBusServiceMock,
})
beforeEach(() => {
@@ -50,6 +52,12 @@ describe("CartService", () => {
const id = mongoose.Types.ObjectId()
await cartService.setMetadata(`${id}`, "metadata", "testMetadata")
expect(EventBusServiceMock.emit).toHaveBeenCalledTimes(1)
expect(EventBusServiceMock.emit).toHaveBeenCalledWith(
"cart.updated",
expect.any(Object)
)
expect(CartModelMock.updateOne).toBeCalledTimes(1)
expect(CartModelMock.updateOne).toBeCalledWith(
{ _id: `${id}` },
@@ -74,6 +82,7 @@ describe("CartService", () => {
const cartService = new CartService({
cartModel: CartModelMock,
regionService: RegionServiceMock,
eventBusService: EventBusServiceMock,
})
beforeEach(() => {
@@ -85,6 +94,12 @@ describe("CartService", () => {
region_id: IdMap.getId("testRegion"),
})
expect(EventBusServiceMock.emit).toHaveBeenCalledTimes(1)
expect(EventBusServiceMock.emit).toHaveBeenCalledWith(
"cart.created",
expect.any(Object)
)
expect(CartModelMock.create).toHaveBeenCalledTimes(1)
expect(CartModelMock.create).toHaveBeenCalledWith({
region_id: IdMap.getId("testRegion"),
@@ -97,6 +112,7 @@ describe("CartService", () => {
cartModel: CartModelMock,
productVariantService: ProductVariantServiceMock,
lineItemService: LineItemServiceMock,
eventBusService: EventBusServiceMock,
})
beforeEach(() => {
@@ -123,6 +139,12 @@ describe("CartService", () => {
await cartService.addLineItem(IdMap.getId("emptyCart"), lineItem)
expect(EventBusServiceMock.emit).toHaveBeenCalledTimes(1)
expect(EventBusServiceMock.emit).toHaveBeenCalledWith(
"cart.updated",
expect.any(Object)
)
expect(CartModelMock.updateOne).toHaveBeenCalledTimes(1)
expect(CartModelMock.updateOne).toHaveBeenCalledWith(
{
@@ -280,6 +302,7 @@ describe("CartService", () => {
cartModel: CartModelMock,
productVariantService: ProductVariantServiceMock,
lineItemService: LineItemServiceMock,
eventBusService: EventBusServiceMock,
})
beforeEach(() => {
@@ -310,6 +333,12 @@ describe("CartService", () => {
lineItem
)
expect(EventBusServiceMock.emit).toHaveBeenCalledTimes(1)
expect(EventBusServiceMock.emit).toHaveBeenCalledWith(
"cart.updated",
expect.any(Object)
)
expect(CartModelMock.updateOne).toHaveBeenCalledTimes(1)
expect(CartModelMock.updateOne).toHaveBeenCalledWith(
{
@@ -357,6 +386,7 @@ describe("CartService", () => {
describe("updateEmail", () => {
const cartService = new CartService({
cartModel: CartModelMock,
eventBusService: EventBusServiceMock,
})
beforeEach(() => {
@@ -369,6 +399,12 @@ describe("CartService", () => {
"test@testdom.com"
)
expect(EventBusServiceMock.emit).toHaveBeenCalledTimes(1)
expect(EventBusServiceMock.emit).toHaveBeenCalledWith(
"cart.updated",
expect.any(Object)
)
expect(CartModelMock.updateOne).toHaveBeenCalledTimes(1)
expect(CartModelMock.updateOne).toHaveBeenCalledWith(
{
@@ -394,6 +430,7 @@ describe("CartService", () => {
describe("updateBillingAddress", () => {
const cartService = new CartService({
cartModel: CartModelMock,
eventBusService: EventBusServiceMock,
})
beforeEach(() => {
@@ -413,6 +450,12 @@ describe("CartService", () => {
await cartService.updateBillingAddress(IdMap.getId("emptyCart"), address)
expect(EventBusServiceMock.emit).toHaveBeenCalledTimes(1)
expect(EventBusServiceMock.emit).toHaveBeenCalledWith(
"cart.updated",
expect.any(Object)
)
expect(CartModelMock.updateOne).toHaveBeenCalledTimes(1)
expect(CartModelMock.updateOne).toHaveBeenCalledWith(
{
@@ -450,6 +493,7 @@ describe("CartService", () => {
describe("updateShippingAddress", () => {
const cartService = new CartService({
cartModel: CartModelMock,
eventBusService: EventBusServiceMock,
})
beforeEach(() => {
@@ -469,6 +513,12 @@ describe("CartService", () => {
await cartService.updateShippingAddress(IdMap.getId("emptyCart"), address)
expect(EventBusServiceMock.emit).toHaveBeenCalledTimes(1)
expect(EventBusServiceMock.emit).toHaveBeenCalledWith(
"cart.updated",
expect.any(Object)
)
expect(CartModelMock.updateOne).toHaveBeenCalledTimes(1)
expect(CartModelMock.updateOne).toHaveBeenCalledWith(
{
@@ -508,6 +558,7 @@ describe("CartService", () => {
cartModel: CartModelMock,
regionService: RegionServiceMock,
productVariantService: ProductVariantServiceMock,
eventBusService: EventBusServiceMock,
})
beforeEach(() => {
@@ -520,6 +571,12 @@ describe("CartService", () => {
IdMap.getId("region-us")
)
expect(EventBusServiceMock.emit).toHaveBeenCalledTimes(1)
expect(EventBusServiceMock.emit).toHaveBeenCalledWith(
"cart.updated",
expect.any(Object)
)
expect(CartModelMock.updateOne).toHaveBeenCalledTimes(1)
expect(CartModelMock.updateOne).toHaveBeenCalledWith(
{
@@ -645,6 +702,7 @@ describe("CartService", () => {
cartModel: CartModelMock,
regionService: RegionServiceMock,
paymentProviderService: PaymentProviderServiceMock,
eventBusService: EventBusServiceMock,
})
beforeEach(() => {
@@ -664,6 +722,12 @@ describe("CartService", () => {
paymentMethod
)
expect(EventBusServiceMock.emit).toHaveBeenCalledTimes(1)
expect(EventBusServiceMock.emit).toHaveBeenCalledWith(
"cart.updated",
expect.any(Object)
)
expect(RegionServiceMock.retrieve).toHaveBeenCalledTimes(1)
expect(RegionServiceMock.retrieve).toHaveBeenCalledWith(
IdMap.getId("testRegion")
@@ -787,6 +851,7 @@ describe("CartService", () => {
cartModel: CartModelMock,
regionService: RegionServiceMock,
paymentProviderService: PaymentProviderServiceMock,
eventBusService: EventBusServiceMock,
})
beforeEach(() => {
@@ -796,6 +861,12 @@ describe("CartService", () => {
it("initializes payment sessions for each of the providers", async () => {
await cartService.setPaymentSessions(IdMap.getId("cartWithLine"))
expect(EventBusServiceMock.emit).toHaveBeenCalledTimes(1)
expect(EventBusServiceMock.emit).toHaveBeenCalledWith(
"cart.updated",
expect.any(Object)
)
expect(PaymentProviderServiceMock.createSession).toHaveBeenCalledTimes(2)
expect(PaymentProviderServiceMock.createSession).toHaveBeenCalledWith(
"default_provider",
@@ -938,6 +1009,7 @@ describe("CartService", () => {
describe("retrievePaymentSession", () => {
const cartService = new CartService({
cartModel: CartModelMock,
eventBusService: EventBusServiceMock,
})
let res
@@ -999,6 +1071,7 @@ describe("CartService", () => {
cartModel: CartModelMock,
shippingProfileService: ShippingProfileServiceMock,
shippingOptionService: ShippingOptionServiceMock,
eventBusService: EventBusServiceMock,
})
describe("successfully adds the shipping method", () => {
@@ -1043,6 +1116,12 @@ describe("CartService", () => {
})
it("updates cart", () => {
expect(EventBusServiceMock.emit).toHaveBeenCalledTimes(1)
expect(EventBusServiceMock.emit).toHaveBeenCalledWith(
"cart.updated",
expect.any(Object)
)
expect(CartModelMock.updateOne).toHaveBeenCalledTimes(1)
expect(CartModelMock.updateOne).toHaveBeenCalledWith(
{
@@ -1171,6 +1250,7 @@ describe("CartService", () => {
const cartService = new CartService({
cartModel: CartModelMock,
discountService: DiscountServiceMock,
eventBusService: EventBusServiceMock,
})
beforeEach(async () => {
jest.clearAllMocks()
@@ -1183,6 +1263,12 @@ describe("CartService", () => {
_id: IdMap.getId("fr-cart"),
})
expect(EventBusServiceMock.emit).toHaveBeenCalledTimes(1)
expect(EventBusServiceMock.emit).toHaveBeenCalledWith(
"cart.updated",
expect.any(Object)
)
expect(DiscountServiceMock.retrieveByCode).toHaveBeenCalledTimes(1)
expect(DiscountServiceMock.retrieveByCode).toHaveBeenCalledWith("10%OFF")
+209 -111
View File
@@ -7,6 +7,11 @@ import { BaseService } from "medusa-interfaces"
* @implements BaseService
*/
class CartService extends BaseService {
static Events = {
CREATED: "cart.created",
UPDATED: "cart.updated",
}
constructor({
cartModel,
eventBusService,
@@ -199,6 +204,10 @@ class CartService extends BaseService {
.create({
region_id: region._id,
})
.then(result => {
this.eventBus_.emit(CartService.Events.CREATED, result)
return result
})
.catch(err => {
throw new MedusaError(MedusaError.Types.DB_ERROR, err.message)
})
@@ -246,17 +255,23 @@ class CartService extends BaseService {
)
}
return this.cartModel_.updateOne(
{
_id: cartId,
"items._id": currentItem._id,
},
{
$set: {
"items.$.quantity": newQuantity,
return this.cartModel_
.updateOne(
{
_id: cartId,
"items._id": currentItem._id,
},
}
)
{
$set: {
"items.$.quantity": newQuantity,
},
}
)
.then(result => {
// Notify subscribers
this.eventBus_.emit(CartService.Events.UPDATED, result)
return result
})
}
// Confirm inventory
@@ -273,14 +288,20 @@ class CartService extends BaseService {
}
// The line we are adding doesn't already exist so it is safe to push
return this.cartModel_.updateOne(
{
_id: cartId,
},
{
$push: { items: validatedLineItem },
}
)
return this.cartModel_
.updateOne(
{
_id: cartId,
},
{
$push: { items: validatedLineItem },
}
)
.then(result => {
// Notify subscribers
this.eventBus_.emit(CartService.Events.UPDATED, result)
return result
})
}
/**
@@ -325,17 +346,23 @@ class CartService extends BaseService {
}
// Update the line item
return this.cartModel_.updateOne(
{
_id: cartId,
"items._id": lineItemId,
},
{
$set: {
"items.$": validatedLineItem,
return this.cartModel_
.updateOne(
{
_id: cartId,
"items._id": lineItemId,
},
}
)
{
$set: {
"items.$": validatedLineItem,
},
}
)
.then(result => {
// Notify subscribers
this.eventBus_.emit(CartService.Events.UPDATED, result)
return result
})
}
/**
@@ -357,14 +384,20 @@ class CartService extends BaseService {
)
}
return this.cartModel_.updateOne(
{
_id: cart._id,
},
{
$set: { email: value },
}
)
return this.cartModel_
.updateOne(
{
_id: cart._id,
},
{
$set: { email: value },
}
)
.then(result => {
// Notify subscribers
this.eventBus_.emit(CartService.Events.UPDATED, result)
return result
})
}
/**
@@ -383,14 +416,20 @@ class CartService extends BaseService {
)
}
return this.cartModel_.updateOne(
{
_id: cart._id,
},
{
$set: { billing_address: value },
}
)
return this.cartModel_
.updateOne(
{
_id: cart._id,
},
{
$set: { billing_address: value },
}
)
.then(result => {
// Notify subscribers
this.eventBus_.emit(CartService.Events.UPDATED, result)
return result
})
}
/**
@@ -409,14 +448,20 @@ class CartService extends BaseService {
)
}
return this.cartModel_.updateOne(
{
_id: cartId,
},
{
$set: { shipping_address: value },
}
)
return this.cartModel_
.updateOne(
{
_id: cartId,
},
{
$set: { shipping_address: value },
}
)
.then(result => {
// Notify subscribers
this.eventBus_.emit(CartService.Events.UPDATED, result)
return result
})
}
/**
* Updates the cart's discounts.
@@ -455,49 +500,73 @@ class CartService extends BaseService {
shippingDisc.length === 0 &&
discount.discount_rule.type === "free_shipping"
) {
return this.cartModel_.updateOne(
{
_id: cart._id,
},
{
$push: { discounts: discount },
}
)
return this.cartModel_
.updateOne(
{
_id: cart._id,
},
{
$push: { discounts: discount },
}
)
.then(result => {
// Notify subscribers
this.eventBus_.emit(CartService.Events.UPDATED, result)
return result
})
} else if (
shippingDisc.length > 0 &&
discount.discount_rule.type === "free_shipping"
) {
return this.cartModel_.updateOne(
{
_id: cart._id,
},
{
$pull: { discounts: { _id: shippingDisc[0]._id } },
$push: { discounts: discount },
}
)
return this.cartModel_
.updateOne(
{
_id: cart._id,
},
{
$pull: { discounts: { _id: shippingDisc[0]._id } },
$push: { discounts: discount },
}
)
.then(result => {
// Notify subscribers
this.eventBus_.emit(CartService.Events.UPDATED, result)
return result
})
}
// replace the current discount if there, else add the new one
if (otherDisc.length === 0) {
return this.cartModel_.updateOne(
{
_id: cart._id,
},
{
$push: { discounts: discount },
}
)
return this.cartModel_
.updateOne(
{
_id: cart._id,
},
{
$push: { discounts: discount },
}
)
.then(result => {
// Notify subscribers
this.eventBus_.emit(CartService.Events.UPDATED, result)
return result
})
} else {
return this.cartModel_.updateOne(
{
_id: cart._id,
},
{
$pull: { discounts: { _id: otherDisc[0]._id } },
$push: { discounts: discount },
}
)
return this.cartModel_
.updateOne(
{
_id: cart._id,
},
{
$pull: { discounts: { _id: otherDisc[0]._id } },
$push: { discounts: discount },
}
)
.then(result => {
// Notify subscribers
this.eventBus_.emit(CartService.Events.UPDATED, result)
return result
})
}
}
@@ -571,14 +640,20 @@ class CartService extends BaseService {
}
// At this point we can register the payment method.
return this.cartModel_.updateOne(
{
_id: cart._id,
},
{
$set: { payment_method: paymentMethod },
}
)
return this.cartModel_
.updateOne(
{
_id: cart._id,
},
{
$set: { payment_method: paymentMethod },
}
)
.then(result => {
// Notify subscribers
this.eventBus_.emit(CartService.Events.UPDATED, result)
return result
})
}
/**
@@ -627,14 +702,20 @@ class CartService extends BaseService {
// Update the payment sessions with the concatenated array of updated and
// newly created payment sessions
return this.cartModel_.updateOne(
{
_id: cart._id,
},
{
$set: { payment_sessions: sessions.concat(newSessions) },
}
)
return this.cartModel_
.updateOne(
{
_id: cart._id,
},
{
$set: { payment_sessions: sessions.concat(newSessions) },
}
)
.then(result => {
// Notify subscribers
this.eventBus_.emit(CartService.Events.UPDATED, result)
return result
})
}
/**
@@ -693,14 +774,20 @@ class CartService extends BaseService {
newMethods.push(option)
}
return this.cartModel_.updateOne(
{
_id: cart._id,
},
{
$set: { shipping_methods: newMethods },
}
)
return this.cartModel_
.updateOne(
{
_id: cart._id,
},
{
$set: { shipping_methods: newMethods },
}
)
.then(result => {
// Notify subscribers
this.eventBus_.emit(CartService.Events.UPDATED, result)
return result
})
}
/**
@@ -763,7 +850,13 @@ class CartService extends BaseService {
update.payment_method = undefined
}
return this.cartModel_.updateOne({ _id: cart._id }, { $set: update })
return this.cartModel_
.updateOne({ _id: cart._id }, { $set: update })
.then(result => {
// Notify subscribers
this.eventBus_.emit(CartService.Events.UPDATED, result)
return result
})
}
/**
@@ -788,6 +881,11 @@ class CartService extends BaseService {
const keyPath = `metadata.${key}`
return this.cartModel_
.updateOne({ _id: validatedId }, { $set: { [keyPath]: value } })
.then(result => {
// Notify subscribers
this.eventBus_.emit(CartService.Events.UPDATED, result)
return result
})
.catch(err => {
throw new MedusaError(MedusaError.Types.DB_ERROR, err.message)
})
+2 -1
View File
@@ -59,7 +59,8 @@ class EventBusService {
*
* @returns {Promise} resolves to the results of the subscriber calls.
*/
async worker_({ eventName, data }) {
worker_ = job => {
const { eventName, data } = job.data
const observers = this.observers_[eventName] || []
this.logger_.info(
`Processing ${eventName} which has ${observers.length} subscribers`
+8 -1
View File
@@ -2156,7 +2156,7 @@ core-js-compat@^3.4.7:
browserslist "^4.8.0"
semver "^6.3.0"
core-js@^3.2.1, core-js@^3.4.8:
core-js@^3.2.1:
version "3.4.8"
resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.4.8.tgz#e0fc0c61f2ef90cbc10c531dbffaa46dfb7152dd"
integrity sha512-b+BBmCZmVgho8KnBUOXpvlqEMguko+0P+kXCwD4vIprsXC6ht1qgPxtb1OK6XgSlrySF71wkwBQ0Hv695bk9gQ==
@@ -4522,6 +4522,13 @@ media-typer@0.3.0:
resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748"
integrity sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=
medusa-interfaces@^0.1.27:
version "0.1.27"
resolved "https://registry.yarnpkg.com/medusa-interfaces/-/medusa-interfaces-0.1.27.tgz#e77f9a9f82a7118eac8b35c1498ef8a5cec78898"
integrity sha512-FVRyD85aBCPon+itf0cgTc1/lQXxHcMXGWGF8fIMzcoVj8xqSjculwZmY4GH4EtKhv3X3+DGe0BsIMbiW31unA==
dependencies:
mongoose "^5.8.0"
memory-pager@^1.0.2:
version "1.5.0"
resolved "https://registry.yarnpkg.com/memory-pager/-/memory-pager-1.5.0.tgz#d8751655d22d384682741c972f2c3d6dfa3e66b5"