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
2020-05-07 13:47:27 +02:00
committed by GitHub
parent 6a78df1ecd
commit 4c2aec2838
39 changed files with 6974 additions and 444 deletions
+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
}